1 /*******************************************************************************
2 
3     Application extension to log or output the version information.
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.VersionArgsExt;
17 
18 
19 
20 
21 public import ocean.util.app.ext.VersionInfo;
22 
23 import ocean.io.device.File;
24 import ocean.util.app.model.IApplicationExtension;
25 import ocean.util.app.ext.model.IArgumentsExtExtension;
26 import ocean.util.app.ext.model.ILogExtExtension;
27 import ocean.util.app.ext.LogExt;
28 import ocean.util.app.ext.ConfigExt;
29 import ocean.util.app.ext.ReopenableFilesExt;
30 import ocean.util.app.ext.UnixSocketExt;
31 import ocean.util.app.Application;
32 
33 import ocean.text.Arguments;
34 import ocean.text.Util;
35 import ocean.util.config.ConfigParser;
36 import ocean.io.Stdout;
37 import ocean.core.Array: startsWith, map;
38 
39 import ocean.meta.types.Qualifiers;
40 import ocean.core.Verify;
41 import ocean.util.log.Logger;
42 import ocean.util.log.AppendFile;
43 import ocean.util.log.LayoutDate;
44 import ocean.core.array.Mutation /* : moveToEnd, sort */;
45 
46 
47 
48 /*******************************************************************************
49 
50     Application extension to log or output the version information.
51 
52     This extension is an ArgumentsExt and a LogExt extension, being optional for
53     both (but makes no sense unless it's registered at least as one of them).
54 
55     If it's registered as an ArgumentsExt, it adds the option --version to print
56     the version information and exit. (Note that the actual handling of the
57     --version command line option is performed by ArgumentsExt.)
58 
59     If it's registered as a LogExt, it will log the version information using
60     the logger with the name of this module.
61 
62 *******************************************************************************/
63 
64 class VersionArgsExt : IApplicationExtension, IArgumentsExtExtension,
65         ILogExtExtension
66 {
67     import Version = ocean.application.components.Version;
68 
69     /***************************************************************************
70 
71         Version information.
72 
73     ***************************************************************************/
74 
75     public VersionInfo ver;
76 
77 
78     /***************************************************************************
79 
80         True if a default logger for the version should be added.
81 
82     ***************************************************************************/
83 
84     public bool default_logging;
85 
86 
87     /***************************************************************************
88 
89         Default name of the file to log when using the default logger.
90 
91     ***************************************************************************/
92 
93     public istring default_file;
94 
95 
96     /***************************************************************************
97 
98         Logger to use to log the version information.
99 
100     ***************************************************************************/
101 
102     public Logger ver_log;
103 
104 
105     /**************************************************************************
106 
107         The application's name.
108 
109     ***************************************************************************/
110 
111     private istring app_name;
112 
113 
114     /***************************************************************************
115 
116         Constructor.
117 
118         Params:
119             ver = version information.
120             default_logging = true if a default logger for the version should be
121                               added
122             default_file = default name of the file to log when using the
123                            default logger
124 
125     ***************************************************************************/
126 
127     this ( VersionInfo ver, bool default_logging = true,
128             istring default_file = "log/version.log" )
129     {
130         this.ver = ver;
131         this.default_logging = default_logging;
132         this.default_file = default_file;
133         this.ver_log = Log.lookup("ocean.util.app.ext.VersionArgsExt");
134     }
135 
136     /***************************************************************************
137 
138       Extension order. This extension uses 100_000 because it should be
139         called very late.
140 
141         Returns:
142             the extension order
143 
144     ***************************************************************************/
145 
146     public override int order ( )
147     {
148         return 100_000;
149     }
150 
151 
152     /***************************************************************************
153 
154         Adds the command line option --version.
155 
156         Params:
157             app = the application instance
158             args = command-line arguments instance
159 
160     ***************************************************************************/
161 
162     public override void setupArgs ( IApplication app, Arguments args )
163     {
164         args("version").params(0)
165             .help("show version information and exit");
166         args("build-info").params(0)
167             .help("show detailed build information and exit");
168     }
169 
170 
171     /***************************************************************************
172 
173         Checks whether the --version flag is present and, if it is, prints the
174         app version and exits without further arguments validation.
175 
176         Params:
177             app = application instance
178             args = command line arguments instance
179 
180     ***************************************************************************/
181 
182     public override void preValidateArgs ( IApplication app, Arguments args )
183     {
184         istring output;
185 
186         if (args.exists("build-info"))
187             output = Version.getBuildInfoString(app.name, this.ver);
188         else if (args.exists("version"))
189             output = Version.getVersionString(app.name, this.ver);
190 
191         if (output.length)
192         {
193             version (unittest) { } // suppress console output in unittests
194             else
195                 Stdout.formatln("{}", output);
196             app.exit(0);
197         }
198     }
199 
200     /***************************************************************************
201 
202         Registers this extension with the unix socket extension and activates the
203         handling of the specified unix socket command, which will print the application
204         version (as shown by `--version`) to the socket when called.
205 
206         Params:
207             app = the application instance
208             unix_socket_ext = UnixSocketExt instance to register with
209             reopen_command = command to trigger displaying of the version
210 
211     ***************************************************************************/
212 
213     public void setupUnixSocketHandler ( IApplication app,
214             UnixSocketExt unix_socket_ext,
215             istring version_command = "show_version" )
216     {
217         verify(unix_socket_ext !is null);
218 
219         this.app_name = idup(app.name);
220         unix_socket_ext.addHandler(version_command,
221             &this.showVersionHandler);
222     }
223 
224 
225     /****************************************************************************
226 
227         Print the version to the sink delegate. Used as a callback from the
228         Unix socket
229 
230         Params:
231             args = list of arguments received from the socket - ignored
232             send_response = delegate to send the response to the client
233 
234     *****************************************************************************/
235 
236     private void showVersionHandler ( cstring[] args,
237             scope void delegate ( cstring response ) send_response )
238     {
239         send_response(Version.getVersionString(this.app_name, this.ver));
240         send_response("\n");
241     }
242 
243 
244     /***************************************************************************
245 
246         Add the default logger if default_logging is true.
247 
248         If the configuration variable is present, it will override the current
249         default_logging value. If the value does not exist in the config file,
250         the value set in the ctor will be used.
251 
252         Note that the logger is explicitly set to output all levels, to avoid
253         the situation where the root logger is configured to not output level
254         'info'.
255 
256         Params:
257             app = the application instance
258             config = the configuration instance
259             loose_config_parsing = if true, configuration files will be parsed
260                                    in a more relaxed way
261             use_insert_appender = true if the InsertConsole appender should be
262                                   used (needed when using the AppStatus module)
263 
264     ***************************************************************************/
265 
266     public override void postConfigureLoggers ( IApplication app, ConfigParser config,
267             bool loose_config_parsing, bool use_insert_appender )
268     {
269         this.ver_log.level = this.ver_log.Level.Info;
270 
271         this.default_logging = config.get("VERSION", "default_version_log",
272                 this.default_logging);
273 
274         if (this.default_logging)
275         {
276             auto appender = new AppendFile(this.default_file, new LayoutDate);
277 
278             if ( auto reopenable_files_ext =
279                 (cast(Application)app).getExtension!(ReopenableFilesExt) )
280             {
281                 reopenable_files_ext.register(appender.file());
282             }
283 
284             this.ver_log.add(appender);
285         }
286     }
287 
288 
289     /***************************************************************************
290 
291         Print the version information to the log if the ConfigExt and LogExt are
292         present.
293 
294         Params:
295             app = the application instance
296             args = command-line arguments
297 
298     ***************************************************************************/
299 
300     public override void preRun ( IApplication app, istring[] args )
301     {
302         auto conf_ext = (cast(Application)app).getExtension!(ConfigExt)();
303         if (conf_ext is null)
304         {
305             return;
306         }
307 
308         auto log_ext = conf_ext.getExtension!(LogExt)();
309         if (log_ext is null)
310         {
311             return;
312         }
313 
314         this.ver_log.info(Version.getBuildInfoString(app.name, this.ver, true));
315     }
316 
317 
318     /***************************************************************************
319 
320         Unused IApplicationExtension methods.
321 
322         We just need to provide an "empty" implementation to satisfy the
323         interface.
324 
325     ***************************************************************************/
326 
327     public override void postRun ( IApplication app, istring[] args, int status )
328     {
329         // Unused
330     }
331 
332     public override void atExit ( IApplication app, istring[] args, int status,
333                          ExitException exception )
334     {
335         // Unused
336     }
337 
338     public override ExitException onExitException ( IApplication app,
339                                            istring[] args,
340                                            ExitException exception )
341     {
342         // Unused
343         return exception;
344     }
345 
346 
347     /***************************************************************************
348 
349         Unused IArgumentsExtension methods.
350 
351         We just need to provide an "empty" implementation to satisfy the
352         interface.
353 
354         Params:
355             app = the application instance
356             args = command-line arguments instance
357 
358     ***************************************************************************/
359 
360     public override cstring validateArgs ( IApplication app, Arguments args )
361     {
362         // Unused
363         return null;
364     }
365 
366     public override void processArgs ( IApplication app, Arguments args )
367     {
368         // Unused
369     }
370 
371 
372     /***************************************************************************
373 
374         Unused ILogExtExtension methods.
375 
376         We just need to provide an "empty" implementation to satisfy the
377         interface.
378 
379         Params:
380             app = the application instance
381             config = the configuration instance
382             loose_config_parsing = if true, configuration files will be parsed
383                                    in a more relaxed way
384             use_insert_appender = true if the InsertConsole appender should be
385                                   used (needed when using the AppStatus module)
386 
387     ***************************************************************************/
388 
389     public override void preConfigureLoggers ( IApplication app, ConfigParser config,
390             bool loose_config_parsing, bool use_insert_appender )
391     {
392         // Unused
393     }
394 
395 }
396 
397 version (unittest)
398 {
399     import ocean.core.Test;
400     import ocean.util.app.ext.ArgumentsExt;
401 }
402 
403 /*******************************************************************************
404 
405     Test that --version succeeds when there are unprovided required arguments.
406 
407 *******************************************************************************/
408 
409 unittest
410 {
411     class MyApp : Application, IArgumentsExtExtension
412     {
413         this ( )
414         {
415             super("test_app", "desc");
416 
417             auto args_ext = new ArgumentsExt("test", "just a test");
418             args_ext.registerExtension(this);
419             this.registerExtension(args_ext);
420 
421             auto ver_ext = new VersionArgsExt(VersionInfo.init);
422             args_ext.registerExtension(ver_ext);
423             this.registerExtension(ver_ext);
424         }
425 
426         override protected int run ( istring[] ) { return 10; }
427 
428         override public void setupArgs ( IApplication, Arguments args )
429         {
430             args("important").params(1).required;
431         }
432 
433         override public void preValidateArgs ( IApplication, Arguments ) { }
434 
435         override public cstring validateArgs ( IApplication, Arguments )
436             { return null; }
437 
438         override public void processArgs ( IApplication, Arguments ) { }
439 
440         // ArgumentsExt.preRun() calls exit(2) if the args parsing fails,
441         // VersionArgsExt.displayVersion() should call exit(0) before that
442         // happens.
443         override public void exit ( int status, istring msg = null )
444         {
445             test!("==")(status, 0);
446 
447             super.exit(status, msg);
448         }
449     }
450 
451     auto app = new MyApp;
452     app.main(["app_name", "--version"]);
453 }