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