1 /******************************************************************************* 2 3 Application extension to parse configuration files. 4 5 Copyright: 6 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 7 All rights reserved. 8 9 License: 10 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 11 Alternatively, this file may be distributed under the terms of the Tango 12 3-Clause BSD License (see LICENSE_BSD.txt for details). 13 14 *******************************************************************************/ 15 16 module ocean.util.app.ext.ConfigExt; 17 18 import ocean.core.ExceptionDefinitions : IOException; 19 import ocean.core.Verify; 20 import ocean.meta.types.Qualifiers; 21 import ocean.text.Arguments; 22 import ocean.text.Util : join, locate, locatePrior, trim; 23 import ocean.util.app.Application; 24 import ocean.util.app.ext.ArgumentsExt; 25 import ocean.util.app.ext.model.IConfigExtExtension; 26 import ocean.util.app.ext.model.IArgumentsExtExtension; 27 import ocean.util.app.model.ExtensibleClassMixin; 28 import ocean.util.app.model.IApplicationExtension; 29 import ocean.util.config.ConfigParser; 30 31 /******************************************************************************* 32 33 Application extension to parse configuration files. 34 35 This extension is an extension itself, providing new hooks via 36 IConfigExtExtension. 37 38 It is also an extension for the ArgumentsExt extension, so if it is 39 registered as such, it will add the --config command line option to specify 40 the configuration file to read. If loose_config_parsing is false, it will 41 also add a --loose-config-parsing option to enable that feature. 42 43 *******************************************************************************/ 44 45 class ConfigExt : IApplicationExtension, IArgumentsExtExtension 46 { 47 import ConfigOverrides = ocean.application.components.ConfigOverrides; 48 49 /*************************************************************************** 50 51 Adds a list of extensions (this.extensions) and methods to handle them. 52 See ExtensibleClassMixin documentation for details. 53 54 ***************************************************************************/ 55 56 mixin ExtensibleClassMixin!(IConfigExtExtension); 57 58 59 /*************************************************************************** 60 61 Configuration parser to use. 62 63 ***************************************************************************/ 64 65 public ConfigParser config; 66 67 68 /*************************************************************************** 69 70 If true, configuration files will be parsed in a more relaxed way. 71 72 This might be overridden by command line arguments. 73 74 ***************************************************************************/ 75 76 public bool loose_config_parsing; 77 78 79 /*************************************************************************** 80 81 Default configuration files to parse. 82 83 ***************************************************************************/ 84 85 public string[] default_configs; 86 87 88 /*************************************************************************** 89 90 Constructor. 91 92 Params: 93 loose_config_parsing = if true, configuration files will be parsed 94 in a more relaxed way 95 default_configs = default configuration files to parse 96 config = configuration parser to use, instantiate one if null 97 is passed 98 99 ***************************************************************************/ 100 101 this ( bool loose_config_parsing = false, 102 string[] default_configs = [ "etc/config.ini" ], 103 ConfigParser config = null ) 104 { 105 this.loose_config_parsing = loose_config_parsing; 106 this.default_configs = default_configs; 107 if ( config is null ) 108 { 109 config = new ConfigParser; 110 } 111 this.config = config; 112 } 113 114 115 /*************************************************************************** 116 117 Extension order. This extension uses -10_000 because it should be 118 called pretty early, but after the ArgumentsExt extension. 119 120 Returns: 121 the extension order 122 123 ***************************************************************************/ 124 125 public override int order ( ) 126 { 127 return -10_000; 128 } 129 130 131 /*************************************************************************** 132 133 Setup command line arguments. 134 135 Adds the following additional command line arguments: 136 --config/-c 137 --loose-config-parsing (if needed) 138 --override-config/-O 139 140 Params: 141 app = the application instance 142 args = parsed command line arguments 143 144 ***************************************************************************/ 145 146 public override void setupArgs ( IApplication app, Arguments args ) 147 { 148 args("config").aliased('c').params(1).smush() 149 .help("use the given configuration file"); 150 151 foreach (conf; this.default_configs) 152 { 153 args("config").defaults(conf); 154 } 155 156 if (!this.loose_config_parsing) 157 { 158 args("loose-config-parsing").params(0) 159 .help("ignore unknown configuration parameters in config file"); 160 } 161 162 ConfigOverrides.setupArgs(args); 163 } 164 165 166 /*************************************************************************** 167 168 Process command line arguments (ArgumentsExt hook). 169 170 Overrides the loose_config_parsing variable if appropriate. 171 172 Params: 173 app = the application instance 174 args = parsed command line arguments 175 176 ***************************************************************************/ 177 178 public override void processArgs ( IApplication app, Arguments args ) 179 { 180 if (!this.loose_config_parsing) 181 { 182 this.loose_config_parsing = args("loose-config-parsing").set; 183 } 184 } 185 186 187 /*************************************************************************** 188 189 Do a simple validation over override-config arguments 190 191 Params: 192 app = the application instance 193 args = parsed command line arguments 194 195 Returns: 196 error text if any 197 198 ***************************************************************************/ 199 200 public override cstring validateArgs ( IApplication app, Arguments args ) 201 { 202 return ConfigOverrides.validateArgs(args); 203 } 204 205 206 /*************************************************************************** 207 208 Parse configuration files (Application hook). 209 210 This function do all the extension processing invoking all the 211 extensions hooks. 212 213 If configuration file parsing fails, it exits with status code 3 and 214 prints an appropriate error message. 215 216 Note: 217 This is not done in processArgs() method because it can be used 218 without being registered as a ArgumentsExt extension. 219 220 Params: 221 app = the application instance 222 cl_args = command line arguments 223 224 ***************************************************************************/ 225 226 public override void preRun ( IApplication app, string[] cl_args ) 227 { 228 foreach (ext; this.extensions) 229 { 230 ext.preParseConfig(app, this.config); 231 } 232 233 auto args_ext = (cast(Application)app).getExtension!(ArgumentsExt); 234 // If an ArgumentExt is present, `.assigned` returns the user's 235 // input or the default 236 auto config_files = (args_ext !is null) 237 ? args_ext.args("config").assigned.dup : this.default_configs; 238 239 foreach (e; this.extensions) 240 { 241 config_files = e.filterConfigFiles(app, this.config, config_files); 242 } 243 244 foreach (config_file; config_files) 245 { 246 try 247 { 248 this.config.parseFile(config_file, false); 249 } 250 catch (IOException e) 251 { 252 app.exit(3, "Error reading config file '" ~ config_file ~ 253 "': " ~ idup(e.message())); 254 } 255 } 256 257 if (args_ext !is null) 258 { 259 ConfigOverrides.handleArgs(args_ext.args, this.config); 260 } 261 262 foreach (ext; this.extensions) 263 { 264 ext.processConfig(app, this.config); 265 } 266 } 267 268 269 /*************************************************************************** 270 271 Unused IApplicationExtension method. 272 273 We just need to provide an "empty" implementation to satisfy the 274 interface. 275 276 ***************************************************************************/ 277 278 public override void postRun ( IApplication app, string[] args, int status ) 279 { 280 // Unused 281 } 282 283 /// ditto 284 public override void atExit ( IApplication app, string[] args, int status, 285 ExitException exception ) 286 { 287 // Unused 288 } 289 290 /// ditto 291 public override ExitException onExitException ( IApplication app, 292 string[] args, 293 ExitException exception ) 294 { 295 // Unused 296 return exception; 297 } 298 299 300 /*************************************************************************** 301 302 Unused IArgumentsExtExtension method. 303 304 We just need to provide an "empty" implementation to satisfy the 305 interface. 306 307 ***************************************************************************/ 308 309 public override void preValidateArgs ( IApplication app, Arguments args ) 310 { 311 // Unused 312 } 313 }