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 }