1 /*******************************************************************************
2 3 Application extension to parse command line arguments.
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 moduleocean.util.app.ext.ArgumentsExt;
17 18 19 20 21 importocean.util.app.model.ExtensibleClassMixin;
22 importocean.util.app.model.IApplicationExtension;
23 importocean.util.app.ext.model.IArgumentsExtExtension;
24 25 importocean.text.Arguments;
26 importocean.io.Stdout : Stdout, Stderr;
27 28 importocean.transition;
29 importocean.io.stream.Format : FormatOutput;
30 31 32 33 /*******************************************************************************
34 35 Application extension to parse command line arguments.
36 37 This extension is an extension itself, providing new hooks via
38 IArgumentsExtExtension.
39 40 By default it adds a --help command line argument to show a help message.
41 42 *******************************************************************************/43 44 classArgumentsExt : IApplicationExtension45 {
46 47 /***************************************************************************
48 49 Adds a list of extensions (this.extensions) and methods to handle them.
50 See ExtensibleClassMixin documentation for details.
51 52 ***************************************************************************/53 54 mixinExtensibleClassMixin!(IArgumentsExtExtension);
55 56 57 /***************************************************************************
58 59 Formatted output stream to use to print normal messages.
60 61 ***************************************************************************/62 63 protectedFormatOutputstdout;
64 65 66 /***************************************************************************
67 68 Formatted output stream to use to print error messages.
69 70 ***************************************************************************/71 72 protectedFormatOutputstderr;
73 74 75 /***************************************************************************
76 77 Command line arguments parser and storage.
78 79 ***************************************************************************/80 81 publicArgumentsargs;
82 83 84 /***************************************************************************
85 86 Constructor.
87 88 See ocean.text.Arguments for details on format of the parameters.
89 90 Params:
91 name = Name of the application (to show in the help message)
92 desc = Short description of what the program does (should be
93 one line only, preferably less than 80 characters)
94 usage = How the program is supposed to be invoked
95 help = Long description of what the program does and how to use it
96 stdout = Formatted output stream to use to print normal messages
97 stderr = Formatted output stream to use to print error messages
98 99 ***************************************************************************/100 101 publicthis ( istringname = null, istringdesc = null,
102 istringusage = null, istringhelp = null,
103 FormatOutputstdout = Stdout,
104 FormatOutputstderr = Stderr )
105 {
106 this.stdout = stdout;
107 this.stderr = stderr;
108 this.args = newArguments(name, desc, usage, help);
109 }
110 111 112 /***************************************************************************
113 114 Extension order. This extension uses -100_000 because it should be
115 called very early.
116 117 Returns:
118 the extension order
119 120 ***************************************************************************/121 122 publicoverrideintorder ( )
123 {
124 return -100_000;
125 }
126 127 128 /***************************************************************************
129 130 Setup, parse, validate and process command line args (Application hook).
131 132 This function does all the extension processing invoking all the
133 extension hooks. It also adds the --help option, which when present,
134 shows the help and exits the program.
135 136 If argument parsing or validation fails (including extensions
137 validation), it also prints an error message and exits. Note that if
138 argument parsing fails, validation is not performed.
139 140 Params:
141 app = the application instance
142 cl_args = command line arguments
143 144 ***************************************************************************/145 146 publicoverridevoidpreRun ( IApplicationapp, istring[] cl_args )
147 {
148 autoargs = this.args;
149 150 args("help").aliased('h').params(0)
151 .help("display this help message and exit");
152 153 foreach (ext; this.extensions)
154 {
155 ext.setupArgs(app, args);
156 }
157 158 cstring[] errors;
159 autoargs_ok = args.parse(cl_args[1 .. $]);
160 161 if ( args.exists("help") )
162 {
163 args.displayHelp(this.stdout);
164 app.exit(0);
165 }
166 167 foreach (ext; this.extensions)
168 {
169 ext.preValidateArgs(app, args);
170 }
171 172 if ( args_ok )
173 {
174 foreach (ext; this.extensions)
175 {
176 autoerror = ext.validateArgs(app, args);
177 if (error != "")
178 {
179 errors ~= error;
180 args_ok = false;
181 }
182 }
183 }
184 185 if (!args_ok)
186 {
187 autoocean_stderr = cast (typeof(Stderr)) this.stderr;
188 if (ocean_stderr !isnull)
189 ocean_stderr.red;
190 args.displayErrors(this.stderr);
191 foreach (error; errors)
192 {
193 this.stderr.format("{}", error).newline;
194 }
195 if (ocean_stderr !isnull)
196 ocean_stderr.default_colour;
197 this.stderr.formatln("\nType {} -h for help", app.name);
198 app.exit(2);
199 }
200 201 foreach (ext; this.extensions)
202 {
203 ext.processArgs(app, args);
204 }
205 }
206 207 208 /***************************************************************************
209 210 Unused IApplicationExtension methods.
211 212 We just need to provide an "empty" implementation to satisfy the
213 interface.
214 215 ***************************************************************************/216 217 publicoverridevoidpostRun ( IApplicationapp, istring[] args, intstatus )
218 {
219 // Unused220 }
221 222 publicoverridevoidatExit ( IApplicationapp, istring[] args, intstatus,
223 ExitExceptionexception )
224 {
225 // Unused226 }
227 228 publicoverrideExitExceptiononExitException ( IApplicationapp,
229 istring[] args, ExitExceptionexception )
230 {
231 // Unused232 returnexception;
233 }
234 235 }
236 237 238 239 /*******************************************************************************
240 241 Tests
242 243 *******************************************************************************/244 245 version (UnitTest)
246 {
247 importocean.core.Test;
248 importocean.util.app.Application;
249 importocean.io.device.MemoryDevice;
250 importocean.io.stream.Text : TextOutput;
251 importocean.core.Array : find;
252 253 classApp : Application254 {
255 this ( ) { super("app", "A test application"); }
256 protectedoverrideintrun ( istring[] args ) { return10; }
257 }
258 }
259 260 261 /*******************************************************************************
262 263 Test --help can be used even when required arguments are not specified
264 265 *******************************************************************************/266 267 unittest268 {
269 270 autostdout_dev = newMemoryDevice;
271 autostdout = newTextOutput(stdout_dev);
272 273 autostderr_dev = newMemoryDevice;
274 autostderr = newTextOutput(stderr_dev);
275 276 istringusage_text = "test: usage";
277 istringhelp_text = "test: help";
278 autoarg = newArgumentsExt("test-name", "test-desc", usage_text, help_text,
279 stdout, stderr);
280 arg.args("--required").params(1).required;
281 282 autoapp = newApp;
283 284 try285 {
286 arg.preRun(app, ["./app", "--help"]);
287 test(false, "An ExitException should have been thrown");
288 }
289 catch (ExitExceptione)
290 {
291 // Status should be 0 (success)292 test!("==")(e.status, 0);
293 // No errors should be printed294 test!("==")(stderr_dev.bufferSize, 0);
295 // Help should be printed to stdout296 autos = cast(mstring) stdout_dev.peek();
297 test(s.length > 0,
298 "Stdout should have some help message but it's empty");
299 test(s.find(arg.args.short_desc) < s.length,
300 "No application description found in help message:\n" ~ s);
301 test(s.find(usage_text) < s.length,
302 "No usage text found in help message:\n" ~ s);
303 test(s.find(help_text) < s.length,
304 "No help text found in help message:\n" ~ s);
305 test(s.find("--help"[]) < s.length,
306 "--help should be found in help message:\n" ~ s);
307 }
308 }