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