Download Reference Manual
The Developer's Library for D
About Wiki Forums Source Search Contact

Ticket #748: Arguments.d

File Arguments.d, 18.7 kB (added by darrylb, 1 year ago)

Updated Arguments module (adds .parse method)

Line 
1 /++
2     copyright:  Copyright (c) 2007 Darryl Bleau. All rights reserved.
3     license:    BSD style.
4     version:    Oct2007
5     author:     Darryl B, Jeff D
6     
7     History:
8     ---
9     Date     Who           What
10     Sep2006  Darryl Bleau  Original C module
11     Sep2007  Jeff Davey    Ported to D, added comments.
12     Oct2007  Darryl Bleau  Added validation delegates/functions, additional comments.
13     ---
14
15     This module is used to parse arguments, and give easy access to them.
16     The command line arguments into an array of found arguments and their respective parameters (if any).
17     Arguments can be short (-), long (--), or implicit. Arguments can optionally be passed parameters. For example, this module
18     parses command lines such as: "-a -b -c --long --argument=parameter"
19     
20     Example:
21     ---
22     char[][] arguments = [ "programname", "-a:on", "-abc:on", "--this=good", "-x:on" ];
23     Arguments args = new Arguments(arguments);
24     if (args)
25     {
26         args.addValidation("b", true, false);
27         args.addValidation("c", true, true);
28         args.addValidation("x", false, true);
29         args.addValidation("this", false, true);
30         args.addValidation("this", (char[] a) { (return a.length < 5); });
31         try
32         {
33             args.validate();
34             return Test.Status.Success;
35         }
36         catch (ArgumentException ex)
37         {
38             messages ~= Stdout.layout.convert("{}: {} - {}", ex.name, ex.msg, ex.reason == ArgumentException.ExceptionReason.INVALID_PARAMETER ? "invalid parameter" : ex.reason == ArgumentException.ExceptionReason.MISSING_PARAMETER ? "missing parameter" : "missing argument");
39         }
40     }
41     ---
42     
43     Syntax:
44     ---
45     Short Argument - -[x][:=]?[ parameter]...
46     Long Argument - --[long][:=]?[ parameter]...
47     Implicit Arguments - [implicitName]... Multiple implicit arguments are allowed.
48     ---
49
50     Usage:
51
52     Short options can be grouped in a single dash. The following are equivalent.
53     ---
54     - "myprogram -a -b -c"
55     - "myprogram -abc"
56     ---
57
58     Arguments can be passed with space, '=', or ':'. The following are equivalent.
59     ---
60     - "myprogram -c arg"
61     - "myprogram -c=arg"
62     - "myprogram -c:arg"
63     ---
64
65     As are these.
66     ---
67     - "myprogram --long arg"
68     - "myprogram --long=arg"
69     - "myprogram --long:arg"
70     ---
71
72     Arguments can contain either '=' or ':', but not both. For example. the following results in the argument 'long' being set to 'arg=other' and 'arg:other', respectively.
73     ---
74     - "myprogram --long:arg=other"
75     - "myprogram --long=arg:other"
76     ---
77
78     Blank dashes are ignored. The following are all equivalent.
79     ---
80     - "myprogram -c -- -a"
81     - "myprogram -c - -a"
82     - "myprogram - - - -a -- -c"
83     ---
84
85     In the absence of implicit arguments, short options can be infered when they come first. Given no implicit arguments, the following are equivalent.
86     ---
87     - "myprogram abc"
88     - "myprogram -abc"
89     - "myprogram -a -b -c"
90     ---
91
92     Short options are case sensitive, while long options are not. The following are equivalent.
93     ---
94     - "myprogram -a -A -LONG"
95     - "myprogram -a -A -Long"
96     - "myprogram -a -A -long"
97     ---
98
99     In the event of multiple definitions of an argument, any parameters given will be concatenated. The following are equivalent.
100     ---
101     - "myprogram -a one two three"
102     - "myprogram -a one -a two -a three"
103     - "myprogram -a:one two -a=three"
104     ---
105         
106     Multiple parameters can be iterated through using .getArray("argument"), otherwise, access using index[] results in all parameters concatenated with a space " ".
107     For example, given:
108     ---
109     - "myprogram -collect one two three '4 5 6'"
110     ---
111     args["collect"] will return "one two three 4 5 6", while args.getArray("collect") will return a char[][] array ["one", "two", "three", "4 5 6"].
112
113     Implicit arguments can be defined by passing in an implicit arguments array, which may look something like: ["first", "second"].
114     Given implicit arguments, any non-argument command line parameters will be automatically assigned to the implicit arguments,
115     in the order they were given.
116     For example, given the implicit arguments ["first", "second"] and command line:
117     ---
118     - "myprogram hello there bob"
119     ---
120     The argument 'first' will be assigned 'hello', and the argument 'second' will be assigned both 'there' and 'bob'.
121
122     Any intervening arguments will end the assignment. For example, given:
123     ---
124     - "myprogram hello there bob -a how are you"
125     ---
126     'first' is assigned 'hello', 'second' is assigned 'there' and 'bob', and 'a' is assigned 'how', 'are', and 'you'.
127     
128     Implicit arguments also allows programs to support non-option arguments, given implicit arguments ["actions"], and a command line such as:
129     ---
130     - "myprogram mop sweep get_coffee -time:now"
131     ---
132     args.getArray("actions") will contain ["mop", "sweep", "get_coffee"], and args["time"] will contain "now".
133 ++/
134 module tetra.app.Arguments;
135
136 import tango.text.Util;
137 import tango.math.Math;
138 import tango.io.Stdout;
139
140 /++
141     This exception is thrown during argument validation.
142 ++/
143 public class ArgumentException : Exception
144 {
145     /++
146         The reason the exception was thrown
147     ++/
148     enum ExceptionReason
149     {
150         /++ An invalid parameter was passed ++/
151         INVALID_PARAMETER,
152         /++ A parameter wasn't passed to an argument when it was expected ++/
153         MISSING_PARAMETER,
154         /++ An argument was missing ++/
155         MISSING_ARGUMENT
156     }
157
158     private char[] _name;
159     private char[] _parameter;
160     private ExceptionReason _reason;
161
162     /++ The name of the specific argument ++/
163     char[] name() { return _name; }
164     /++ The parameter to the argument ++/
165     char[] parameter() { return _parameter; }
166     /++ the enum to the reason it failed ++/
167     ExceptionReason reason() {  return _reason; }
168
169     this(char[] msg, char[] name, char[] parameter, ExceptionReason reason)
170     {
171         _name = name;
172         _parameter = parameter;
173         _reason = reason;
174         super(msg);
175     }
176 }
177
178 /++
179     The arguments class is used to parse arguments
180 ++/
181 public class Arguments
182 {
183     /// Function used to validate multiple parameters at once.
184     alias bool function(char[][] params, inout char[] invalidParam) validationFunctionMulti;
185     /// Delegate used to validate multiple parameters at once.
186     alias bool delegate(char[][] params, inout char[] invalidParam) validationDelegateMulti;
187     /// Function used to validate single parameters at a time.
188     alias bool function(char[] param) validationFunction;
189     /// Delegate used to validate single parameters at a time.
190     alias bool delegate(char[] param) validationDelegate;
191    
192     private char[][][char[]] _args;
193     private char[] _program;
194     private struct validation
195     {
196         validationFunction[] validF;
197         validationDelegate[] validD;
198         validationFunctionMulti[] validFM;
199         validationDelegateMulti[] validDM;
200         bool required;
201         bool paramRequired;
202     }
203     private validation[char[]] _validations;
204
205     private char[] _parseLongArgument(char[] arg)
206     {
207         char[] rtn;
208
209         if (arg)
210         {
211             int equalDelim = locate(arg, '=');
212             int colonDelim = locate(arg, ':');
213             int paramPos = ((equalDelim != arg.length) && (colonDelim != arg.length)) ? min(equalDelim, colonDelim) : (equalDelim != arg.length) ? equalDelim : colonDelim;
214             if (paramPos != arg.length)
215             {
216                 char[] argName = arg[0 .. paramPos];
217                 char[] value = arg[(paramPos + 1) .. arg.length];
218                 _setArg(argName, value);
219                 rtn = argName;
220             }
221             else
222             {
223                 _setArg(arg, null);
224                 rtn = arg;
225             }
226         }
227         return rtn;
228     }
229
230     private void _setArg(char[] arg, char[] value)
231     {
232         if (arg)
233         {
234             if ((arg in _args) || (value !is null))
235                 _args[arg] ~= value;
236             else
237                 _args[arg] = null;
238         }
239     }
240
241     private char[] _parseShortArgument(char[] arg)
242     {
243         char[] rtn;
244
245         if (arg)
246         {
247             char[] argName;
248             uint i;
249             for (i = 0; i < arg.length; i++)
250             {
251                 if (arg[i] != '=' && arg[i] != ':')
252                 {
253                     argName = arg[i .. i + 1];
254                     _setArg(argName, null);
255                 }
256                 else if (((arg.length) - i) > 1)
257                 {
258                     _setArg(argName, arg[i+1 .. arg.length]);
259                     break;
260                 }
261             }
262             rtn = argName;
263         }
264         return rtn;     
265     }
266
267     /++
268         Print out the arguments passed
269     ++/
270     public void print()
271     {
272         foreach (char[] key, char[][] values; _args)
273             foreach(char[] value; values)
274                 Stdout(key)("-")(value).newline;
275     }
276
277     /++
278         Gives back an array of parameters for a specific argument
279         This is to cover something like: param1 "parm with space" param2
280
281         Params:
282             key = the argument name
283     ++/
284     char[][] getArray(char[] key)
285     {
286         char[][] rtn = null;
287         if (key && (key in _args))
288             rtn = _args[key];
289         return rtn;
290     }
291
292     /++
293         Allows external argument assignment, works the same as command line
294         in that it appends to any values already assigned to the given key.
295
296         Params:
297             value = assigned value
298             key = key to assign to
299     ++/
300     void opIndexAssign(char[] value, char[] key)
301     {
302         _setArg(key, value);
303     }
304
305     /++
306         Allows removal of keys from the arguments. Useful if you want to replace
307         values for a given key rather than to append to them.
308
309         Params:
310             key = key to remove values from.
311     ++/
312     void remove(char[] key)
313     {
314         _args[key] = null;
315     }
316    
317     /++
318         Directly access an argument's parameters via opIndex operator
319     ++/
320     char[] opIndex(char[] key)
321     {
322         char[] rtn = null; 
323         if (key && (key in _args))
324             rtn = join(_args[key], " ");
325         return rtn;
326     }
327
328     /++
329         Operator is used to check if the argument exists
330     ++/
331     bool opIn_r(char[] key)
332     {
333         bool rtn = false;
334         if (key)
335             rtn = (key in _args) != null;
336         return rtn;
337     }
338
339     /++
340         Adds a validation to the arguments
341
342         Params:
343             argument = the argument name
344             required = specifies if this argument is required
345             paramRequired = specifies if this argument requires a parameter
346     ++/
347     void addValidation(char[] argument, bool required, bool paramRequired)
348     {
349         if (argument)
350         {
351             validation* val = _getValidation(argument);
352             if (val !is null)
353             {
354                 val.required = required;
355                 val.paramRequired = paramRequired;
356             }
357         }
358     }
359     /++
360         Adds a validation to the arguments
361
362         Params:
363             argument = the argument name
364             validF = a validation function for single parameters
365     ++/
366     void addValidation(char[] argument, validationFunction validF)
367     {
368         if (argument && validF)
369         {
370             validation* val = _getValidation(argument);
371             if (val !is null)
372                 val.validF ~= validF;
373         }
374     }
375     /++
376         Adds a validation to the arguments
377
378         Params:
379             argument = the argument name
380             validD = a validation delegate for single parameters
381     ++/
382     void addValidation(char[] argument, validationDelegate validD)
383     {
384         if (argument && validD)
385         {
386             validation* val = _getValidation(argument);
387             if (val !is null)
388                 val.validD ~= validD;
389         }
390     }
391     /++
392         Adds a validation to the arguments
393
394         Params:
395             argument = the argument name
396             validF = a validation function for multiple parameters
397     ++/
398     void addValidation(char[] argument, validationFunctionMulti validFM)
399     {
400         if (argument && validFM)
401         {
402             validation* val = _getValidation(argument);
403             if (val !is null)
404                 val.validFM ~= validFM;
405         }
406     }
407     /++
408         Adds a validation to the arguments
409
410         Params:
411             argument = the argument name
412             validD = a validation delegate for multiple parameters
413     ++/
414     void addValidation(char[] argument, validationDelegateMulti validDM)
415     {
416         if (argument && validDM)
417         {
418             validation* val = _getValidation(argument);
419             if (val !is null)
420                 val.validDM ~= validDM;
421         }
422     }   
423     private validation* _getValidation(char[] argument)
424     {
425         validation* rtn = null;
426         if (!(argument in _validations))
427         {
428             validation newValidation;
429             _validations[argument] = newValidation;
430         }
431         if (argument in _validations)
432             rtn = &(_validations[argument]);
433         return rtn;
434     }
435    
436     /++
437         Validates the parsed arguments.
438
439         Throws ArgumentException if it finds something wrong.
440     ++/
441     void validate()
442     {
443         foreach(char[] argument, validation val; _validations)
444         {
445             if (val.required && !(argument in _args))
446                 throw new ArgumentException("Argument required.", argument, null, ArgumentException.ExceptionReason.MISSING_ARGUMENT);
447             if (val.paramRequired && (argument in _args) && (_args[argument].length == 0))
448                 throw new ArgumentException("Parameter required.", argument, null, ArgumentException.ExceptionReason.MISSING_PARAMETER);
449             if ((argument in _args) && (_args[argument].length > 0))
450             {
451                 char[] invalidParameter = null;
452                 foreach(validationFunctionMulti validFM; val.validFM)
453                     if (!validFM(_args[argument], invalidParameter))
454                         break;
455                 if (invalidParameter is null)
456                 {
457                     foreach(validationDelegateMulti validDM; val.validDM)
458                         if (!validDM(_args[argument], invalidParameter))
459                             break;
460                     if (invalidParameter is null)
461                     {
462                         foreach(char[] arg; _args[argument])
463                         {
464                             foreach(validationFunction validF; val.validF)
465                             {
466                                 if (!validF(arg))
467                                 {
468                                     invalidParameter = arg;
469                                     break;
470                                 }
471                             }
472                             if (invalidParameter is null)
473                             {
474                                 foreach(validationDelegate validD; val.validD)
475                                 {
476                                     if (!validD(arg))
477                                     {
478                                         invalidParameter = arg;
479                                         break;
480                                     }
481                                 }
482                             }
483                         }
484                     }
485                 }
486                 if (invalidParameter !is null)
487                     throw new ArgumentException("Invalid parameter.", argument, invalidParameter, ArgumentException.ExceptionReason.INVALID_PARAMETER);
488             }
489         }
490     }
491
492     void parse(char[][] arguments, char[][] implicitArgs, char[][][] aliases)
493     {
494         char[] lastArgumentSet;
495         uint currentImplicitArg = 0;
496         for (uint i = 1; i < arguments.length; i++)
497         {
498             char[] currentArgument = arguments[i];
499             if (currentArgument)
500             {
501                 if (currentArgument[0] == '-')
502                 {
503                     if (currentArgument.length > 1)
504                     {
505                         if (currentArgument[1] == '-')
506                         {
507                             if (currentArgument.length > 2)
508                                 lastArgumentSet = _parseLongArgument(currentArgument[2 .. currentArgument.length]); // long argument
509                         }
510                         else
511                             lastArgumentSet = _parseShortArgument(currentArgument[1 .. currentArgument.length]); // short argument
512                     }
513                 }
514                 else
515                 {
516                     char[] argName;
517                     // implicit argument / previously set argument
518                     if (implicitArgs && (currentImplicitArg < implicitArgs.length))
519                         lastArgumentSet = argName = implicitArgs[currentImplicitArg++];
520                     else
521                         argName = lastArgumentSet;
522
523                     if (argName)
524                         _setArg(argName, currentArgument);
525                     else
526                         lastArgumentSet = _parseShortArgument(currentArgument);
527                 }
528             }
529         }
530
531         if (aliases)
532         {
533             for (uint i = 0; i < aliases.length; i++)
534             {
535                 bool foundOne = false;
536                 char[][] currentValues;
537                 for (uint j = 0; j < aliases[i].length; j++)
538                 {
539                     if (aliases[i][j] in _args)
540                     {
541                         foundOne = true;
542                         currentValues ~= _args[aliases[i][j]];
543                     }
544                 }
545
546                 if (foundOne)
547                 {
548                     for (uint j = 0; j < aliases[i].length; j++)
549                         _args[aliases[i][j]] = currentValues;
550                 }
551             }
552         }
553     }
554
555     /++
556         Constructor that supports all features
557
558         Params:
559             arguments = the list of arguments (usually from main)
560             implicitArgs = assigns values using these keys in order from the arguments array.
561             aliases = aliases specific arguments to each other to concat parameters. looks like aliases[0] = [ "alias1", "alias2", "alias3" ]; (which groups all these arguments together)
562     ++/
563     this(char[][] arguments, char[][] implicitArgs, char[][][] aliases)
564     {
565         _program = arguments[0];
566         this.parse(arguments, implicitArgs, aliases);
567     }
568
569     /++
570         Basic constructor which only deals with arguments
571
572         Params:
573             arguments = array usually from main()
574     ++/
575     this(char[][] arguments)
576     {
577         this(arguments, null, null);
578     }
579
580
581     /++
582         This constructor allows implicitArgs to be set as well
583
584         Params:
585             arguments = array usually from main()
586             implicitArgs = the implicit arguments
587     ++/
588     this(char[][] arguments, char[][] implicitArgs)
589     {
590         this(arguments, implicitArgs, null);
591     }
592
593     /++
594         This constructor allows aliases
595
596         Params:
597             arguments = array usually from main
598             aliases = the array of arguments to alias
599     ++/
600     this(char[][] arguments, char[][][] aliases)
601     {
602         this(arguments, null, aliases);
603     }
604 }
605
606 version(Test)
607 {
608     import tetra.util.Test;
609    
610     unittest
611     {   
612         Test.Status parseTest(inout char[][] messages)
613         {   
614             char[][] arguments = [ "ignoreprogramname", "--accumulate", "one", "-x", "on", "--accumulate:two", "-y", "off", "--accumulate=three", "-abc" ];
615             Arguments args = new Arguments(arguments);
616             if (args)
617             {
618                 if (!("ignoreprogramname" in args))
619                 {
620                     if (args["accumulate"] == "one two three")
621                     {
622                         if (args["x"] == "on")
623                         {
624                             if (("a" in args) && ("b" in args) && ("c" in args))
625                                 return Test.Status.Success;     
626                         }
627                     }
628                 }
629             }
630             return Test.Status.Failure;
631         }
632
633         Test.Status implicitParseTest(inout char[][] messages)
634         {
635             char[][] arguments = ["ignoreprogramname", "-r", "zero", "one two three", "four five", "-s", "six"];
636             char[][] implicitArgs = [ "first", "second" ];
637             Arguments args = new Arguments(arguments, implicitArgs);
638             if (args)
639             {
640                 if (!("ignoreprogramname" in args))
641                 {
642                     if (("r" in args) && !args["r"])
643                     {
644                         if (args["first"] == "zero")
645                         {
646                             if (args["second"] == "one two three four five")
647                             {
648                                 if (args.getArray("second") == ["one two three", "four five"])
649                                 {
650                                     if (args["s"