1 /** 2 * D Documentation Generator 3 * Copyright: © 2014 Economic Modeling Specialists, Intl., Ferdinand Majerech 4 * Authors: Ferdinand Majerech 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt Boost License 1.0) 6 */ 7 8 9 /// Config loading and writing. 10 module config; 11 12 import std.algorithm; 13 import std.array; 14 import std.conv: to; 15 import std.stdio; 16 import std.string; 17 18 /** Stores configuration data loaded from command-line or config files. 19 * 20 * Note that multiple calls to loadCLI/loadConfigFile are supported; data loaded with 21 * earlier calls is overwritten by later calls (e.g. command-line overriding config file), 22 * except arrays like macroFileNames/excludes/sourcePaths: successive calls always add to 23 * these arrays instead of overwriting them, so e.g. extra modules can be excluded with 24 * command-line. 25 */ 26 struct Config 27 { 28 /** */ bool doHelp; 29 /** */ bool doGenerateConfig; 30 /** */ string doGenerateCSSPath; 31 /** */ string[] macroFileNames; 32 /** */ string indexFileName; 33 /** */ string[] tocAdditionalFileNames; 34 /** */ string[] tocAdditionalStrings; 35 /** */ string cssFileName; 36 /** */ string outputDirectory = "./doc"; 37 /** */ string format = "html-aggregated"; 38 /** */ string projectName; 39 /** */ bool noMarkdown; 40 /** */ string projectVersion; 41 /** */ uint maxFileSizeK = 16_384; 42 /** */ uint maxModuleListLength = 256; 43 /// Names of packages and modules to exclude from generated documentation. 44 /** */ string[] excludes; 45 /** */ string[] sourcePaths; 46 47 /// Loaded from macroFileNames + default macros; not set on the command-line. 48 string[string] macros; 49 50 /** Load config options from CLI arguments. 51 * 52 * Params: 53 * 54 * cliArgs = Command-line args. 55 */ 56 void loadCLI(string[] cliArgs) 57 { 58 import std.getopt : getopt, GetoptResult, config, GetOptException; 59 60 // If the user requests a config file, we must look for that option first 61 // and process it before other options so the config file doesn't override 62 // CLI options (it would override them if loaded after processing the CLI 63 // options). 64 string configFile; 65 string[] newMacroFiles; 66 string[] newExcludes; 67 try 68 { 69 // -h/--help will not pass through due to the autogenerated help option 70 const GetoptResult firstResult = getopt(cliArgs, config.caseSensitive, 71 config.passThrough, "config|F", &configFile); 72 doHelp = firstResult.helpWanted; 73 if (configFile.length) 74 loadConfigFile(configFile, true); 75 76 getopt(cliArgs, config.caseSensitive, 77 "css|c", &cssFileName, 78 "generate-css|C", &doGenerateCSSPath, 79 "exclude|e", &newExcludes, 80 "format|f", &format, 81 "generate-cfg|g", &doGenerateConfig, 82 "index|i", &indexFileName, 83 "macros|m", &newMacroFiles, 84 "max-file-size|M", &maxFileSizeK, 85 "output-directory|o", &outputDirectory, 86 "project-name|p", &projectName, 87 "project-version|n", &projectVersion, 88 "no-markdown|D", &noMarkdown, 89 "toc-additional|t", &tocAdditionalFileNames, 90 "toc-additional-direct|T", &tocAdditionalStrings, 91 "max-module-list-length|l", &maxModuleListLength 92 ); 93 } 94 catch (GetOptException e) 95 { 96 writeln("Failed to parse command-line arguments: ", e.msg); 97 writeln("Maybe try 'hmod -h' for help information?"); 98 return; 99 } 100 101 macroFileNames ~= newMacroFiles; 102 excludes ~= newExcludes; 103 sourcePaths ~= cliArgs[1 .. $]; 104 } 105 106 /** Load specified config file and add loaded data to the configuration. 107 * 108 * Params: 109 * 110 * fileName = Name of the config file. 111 * requestedByUser = If true, this is not the default config file and has been 112 * explicitly requested by the user, i.e. we have to inform the 113 * user if the file was not found. 114 * 115 */ 116 void loadConfigFile(string fileName, bool requestedByUser = false) 117 { 118 import std.file: exists, isFile; 119 import std.typecons: tuple; 120 121 if (!fileName.exists || !fileName.isFile) 122 { 123 if (requestedByUser) 124 { 125 writefln("Config file '%s' not found", fileName); 126 } 127 return; 128 } 129 130 writefln("Loading config file '%s'", fileName); 131 try 132 { 133 File(fileName).byLine 134 .map!(l => l.until!(c => ";#".canFind(c))) 135 .map!array 136 .map!strip 137 .filter!(s => !s.empty && s.canFind("=")) 138 .map!(l => l.findSplit("=")) 139 .map!(p => tuple(p[0].strip.to!string, p[2].strip.to!string)) 140 .filter!(p => !p[0].empty) 141 .each!(a => processConfigValue(a[0], a[1])); 142 } 143 catch(Exception e) 144 { 145 writefln("Failed to parse config file '%s': %s", fileName, e.msg); 146 } 147 } 148 149 private: 150 151 void processConfigValue(string key, string value) 152 { 153 // ensures something like "macros = " won't add an empty string value 154 void add(ref string[] array, string value) 155 { 156 if (value.length) { array ~= value; } 157 } 158 159 switch(key) 160 { 161 case "help": doHelp = value.to!bool; break; 162 case "generate-cfg": doGenerateConfig = value.to!bool; break; 163 case "generate-css": doGenerateCSSPath = value; break; 164 case "macros": add(macroFileNames, value); break; 165 case "max-file-size": maxFileSizeK = value.to!uint; break; 166 case "max-module-list-length":maxModuleListLength = value.to!uint; break; 167 case "project-name": projectName = value; break; 168 case "project-version": projectVersion = value; break; 169 case "no-markdown": noMarkdown = value.to!bool; break; 170 case "index": indexFileName = value; break; 171 case "toc-additional": 172 if (value.length) { tocAdditionalFileNames ~= value; } break; 173 case "toc-additional-direct": 174 if (value.length) { tocAdditionalStrings ~= value; } break; 175 case "css": cssFileName = value; break; 176 case "output-directory": outputDirectory = value; break; 177 case "exclude": add(excludes, value); break; 178 case "config": if (value) loadConfigFile(value, true); break; 179 case "source": add(sourcePaths, value); break; 180 default: writefln("Unknown key in config file: '%s'", key); 181 } 182 } 183 } 184 185 immutable string helpString = import("help"); 186 immutable string defaultConfigString = import("hmod.cfg");