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 }