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