root/trunk/tango/scrapple/util/Arguments.d

Revision 32, 18.1 kB (checked in by flithm, 1 year ago)

Add Arguments.d

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 tango.scrapple.util.Arguments;
135
136 private import tango.text.Util;
137 private import tango.math.Math;
138 private 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         Directly access an argument's parameters via opIndex operator
294     ++/
295     char[] opIndex(char[] key)
296     {
297         char[] rtn = null; 
298         if (key && (key in _args))
299             rtn = join(_args[key], " ");
300         return rtn;
301     }
302
303     /++
304         Operator is used to check if the argument exists
305     ++/
306     bool opIn_r(char[] key)
307     {
308         bool rtn = false;
309         if (key)
310             rtn = (key in _args) != null;
311         return rtn;
312     }
313
314     /++
315         Adds a validation to the arguments
316
317         Params:
318             argument = the argument name
319             required = specifies if this argument is required
320             paramRequired = specifies if this argument requires a parameter
321     ++/
322     void addValidation(char[] argument, bool required, bool paramRequired)
323     {
324         if (argument)
325         {
326             validation* val = _getValidation(argument);
327             if (val !is null)
328             {
329                 val.required = required;
330                 val.paramRequired = paramRequired;
331             }
332         }
333     }
334     /++
335         Adds a validation to the arguments
336
337         Params:
338             argument = the argument name
339             validF = a validation function for single parameters
340     ++/
341     void addValidation(char[] argument, validationFunction validF)
342     {
343         if (argument && validF)
344         {
345             validation* val = _getValidation(argument);
346             if (val !is null)
347                 val.validF ~= validF;
348         }
349     }
350     /++
351         Adds a validation to the arguments
352
353         Params:
354             argument = the argument name
355             validD = a validation delegate for single parameters
356     ++/
357     void addValidation(char[] argument, validationDelegate validD)
358     {
359         if (argument && validD)
360         {
361             validation* val = _getValidation(argument);
362             if (val !is null)
363                 val.validD ~= validD;
364         }
365     }
366     /++
367         Adds a validation to the arguments
368
369         Params:
370             argument = the argument name
371             validF = a validation function for multiple parameters
372     ++/
373     void addValidation(char[] argument, validationFunctionMulti validFM)
374     {
375         if (argument && validFM)
376         {
377             validation* val = _getValidation(argument);
378             if (val !is null)
379                 val.validFM ~= validFM;
380         }
381     }
382     /++
383         Adds a validation to the arguments
384
385         Params:
386             argument = the argument name
387             validD = a validation delegate for multiple parameters
388     ++/
389     void addValidation(char[] argument, validationDelegateMulti validDM)
390     {
391         if (argument && validDM)
392         {
393             validation* val = _getValidation(argument);
394             if (val !is null)
395                 val.validDM ~= validDM;
396         }
397     }   
398     private validation* _getValidation(char[] argument)
399     {
400         validation* rtn = null;
401         if (!(argument in _validations))
402         {
403             validation newValidation;
404             _validations[argument] = newValidation;
405         }
406         if (argument in _validations)
407             rtn = &(_validations[argument]);
408         return rtn;
409     }
410    
411     /++
412         Validates the parsed arguments.
413
414         Throws ArgumentException if it finds something wrong.
415     ++/
416     void validate()
417     {
418         foreach(char[] argument, validation val; _validations)
419         {
420             if (val.required && !(argument in _args))
421                 throw new ArgumentException("Argument required.", argument, null, ArgumentException.ExceptionReason.MISSING_ARGUMENT);
422             if (val.paramRequired && (argument in _args) && (_args[argument].length == 0))
423                 throw new ArgumentException("Parameter required.", argument, null, ArgumentException.ExceptionReason.MISSING_PARAMETER);
424             if ((argument in _args) && (_args[argument].length > 0))
425             {
426                 char[] invalidParameter = null;
427                 foreach(validationFunctionMulti validFM; val.validFM)
428                     if (!validFM(_args[argument], invalidParameter))
429                         break;
430                 if (invalidParameter is null)
431                 {
432                     foreach(validationDelegateMulti validDM; val.validDM)
433                         if (!validDM(_args[argument], invalidParameter))
434                             break;
435                     if (invalidParameter is null)
436                     {
437                         foreach(char[] arg; _args[argument])
438                         {
439                             foreach(validationFunction validF; val.validF)
440                             {
441                                 if (!validF(arg))
442                                 {
443                                     invalidParameter = arg;
444                                     break;
445                                 }
446                             }
447                             if (invalidParameter is null)
448                             {
449                                 foreach(validationDelegate validD; val.validD)
450                                 {
451                                     if (!validD(arg))
452                                     {
453                                         invalidParameter = arg;
454                                         break;
455                                     }
456                                 }
457                             }
458                         }
459                     }
460                 }
461                 if (invalidParameter !is null)
462                     throw new ArgumentException("Invalid parameter.", argument, invalidParameter, ArgumentException.ExceptionReason.INVALID_PARAMETER);
463             }
464         }
465     }
466
467     /++
468         Constructor that supports all features
469
470         Params:
471             arguments = the list of arguments (usually from main)
472             implicitArgs = assigns values using these keys in order from the arguments array.
473             aliases = aliases specific arguments to each other to concat parameters. looks like aliases[0] = [ "alias1", "alias2", "alias3" ]; (which groups all these arguments together)
474     ++/
475     this(char[][] arguments, char[][] implicitArgs, char[][][] aliases)
476     {
477         char[] lastArgumentSet;
478         uint currentImplicitArg = 0;
479         _program = arguments[0];       
480         for (uint i = 1; i < arguments.length; i++)
481         {
482             char[] currentArgument = arguments[i];
483             if (currentArgument)
484             {
485                 if (currentArgument[0] == '-')
486                 {
487                     if (currentArgument.length > 1)
488                     {
489                         if (currentArgument[1] == '-')
490                         {
491                             if (currentArgument.length > 2)
492                                 lastArgumentSet = _parseLongArgument(currentArgument[2 .. currentArgument.length]); // long argument
493                         }
494                         else
495                             lastArgumentSet = _parseShortArgument(currentArgument[1 .. currentArgument.length]); // short argument
496                     }
497                 }
498                 else
499                 {
500                     char[] argName;
501                     // implicit argument / previously set argument
502                     if (implicitArgs && (currentImplicitArg < implicitArgs.length))
503                         lastArgumentSet = argName = implicitArgs[currentImplicitArg++];
504                     else
505                         argName = lastArgumentSet;
506
507                     if (argName)
508                         _setArg(argName, currentArgument);
509                     else
510                         lastArgumentSet = _parseShortArgument(currentArgument);
511                 }
512             }
513         }
514
515         if (aliases)
516         {
517             for (uint i = 0; i < aliases.length; i++)
518             {
519                 bool foundOne = false;
520                 char[][] currentValues;
521                 for (uint j = 0; j < aliases[i].length; j++)
522                 {
523                     if (aliases[i][j] in _args)
524                     {
525                         foundOne = true;
526                         currentValues ~= _args[aliases[i][j]];
527                     }
528                 }
529
530                 if (foundOne)
531                 {
532                     for (uint j = 0; j < aliases[i].length; j++)
533                         _args[aliases[i][j]] = currentValues;
534                 }
535             }
536         }
537     }
538
539     /++
540         Basic constructor which only deals with arguments
541
542         Params:
543             arguments = array usually from main()
544     ++/
545     this(char[][] arguments)
546     {
547         this(arguments, null, null);
548     }
549
550
551     /++
552         This constructor allows implicitArgs to be set as well
553
554         Params:
555             arguments = array usually from main()
556             implicitArgs = the implicit arguments
557     ++/
558     this(char[][] arguments, char[][] implicitArgs)
559     {
560         this(arguments, implicitArgs, null);
561     }
562
563     /++
564         This constructor allows aliases
565
566         Params:
567             arguments = array usually from main
568             aliases = the array of arguments to alias
569     ++/
570     this(char[][] arguments, char[][][] aliases)
571     {
572         this(arguments, null, aliases);
573     }
574 }
575
576 version(Test)
577 {
578     private import tetra.util.Test;
579    
580     unittest
581     {   
582         Test.Status parseTest(inout char[][] messages)
583         {   
584             char[][] arguments = [ "ignoreprogramname", "--accumulate", "one", "-x", "on", "--accumulate:two", "-y", "off", "--accumulate=three", "-abc" ];
585             Arguments args = new Arguments(arguments);
586             if (args)
587             {
588                 if (!("ignoreprogramname" in args))
589                 {
590                     if (args["accumulate"] == "one two three")
591                     {
592                         if (args["x"] == "on")
593                         {
594                             if (("a" in args) && ("b" in args) && ("c" in args))
595                                 return Test.Status.Success;     
596                         }
597                     }
598                 }
599             }
600             return Test.Status.Failure;
601         }
602
603         Test.Status implicitParseTest(inout char[][] messages)
604         {
605             char[][] arguments = ["ignoreprogramname", "-r", "zero", "one two three", "four five", "-s", "six"];
606             char[][] implicitArgs = [ "first", "second" ];
607             Arguments args = new Arguments(arguments, implicitArgs);
608             if (args)
609             {
610                 if (!("ignoreprogramname" in args))
611                 {
612                     if (("r" in args) && !args["r"])
613                     {
614                         if (args["first"] == "zero")
615                         {
616                             if (args["second"] == "one two three four five")
617                             {
618                                 if (args.getArray("second") == ["one two three", "four five"])
619                                 {
620                                     if (args["s"] == "six")
621                                         return Test.Status.Success;
622                                 }
623                             }
624                         }
625                     }
626                 }
627             }
628             return Test.Status.Failure;
629         }
630
631         Test.Status aliasParseTest(inout char[][] messages)
632         {
633             char[][] arguments = [ "ignoreprogramname", "abc", "-d", "-e=eee", "--eff=f" ];
634             char[][][4] aliases;
635             aliases[0] = [ "lettera", "a" ];
636             aliases[1] = [ "letterbc", "c", "b" ];
637</