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

Ticket #748: Arguments.2.d

File Arguments.2.d, 27.2 kB (added by larsivi, 1 year ago)

current revision for Tango

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