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

Ticket #748: Arguments.3.d

File Arguments.3.d, 35.8 kB (added by darrylb, 8 months ago)

Arguments, -opIn, +contains, sed "s/\t/ /g"

Line 
1 /*******************************************************************************
2     copyright:  Copyright (c) 2008 Darryl Bleau. All rights reserved.
3     license:    BSD style: $(LICENSE)
4     version:    Feb2008
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     Feb2008  Darryl Bleau  Entirely rewritten, addressing issues brought up with initial design.
14     ---
15 *******************************************************************************/
16
17 /*******************************************************************************
18 Arguments is a module for parsing argument strings, such as command line arguments passed to main().
19 It is an extrememly flexible module, able to accomodate a wide variety of desired parsing behavior.
20 Arguments follows a declarative paradigm, requiring the programmer to tell it some basic information about the arguments and any possible parameters.
21 However, it also will parse a given string using a default set of actions that covers most basic use cases.
22 *******************************************************************************/
23
24 module tango.util.Arguments;
25
26 import tango.text.Util;
27
28 /*******************************************************************************
29     The Arguments class is used to parse a given argument string, and encapsulate all found arguments and parameters. For example, this module parses command line strings such as: "-a -b -c --long --argument=parameter".
30
31     Arguments can be short or long, and can optionally be passed parameters. Parameters can also be passed implicitly (that is, not belonging to a particular argument).
32
33     Example:
34     ---
35     char[][] cmdl = ["programname", "-z", "--lights:off", "-zz"];
36     Arguments args = new Arguments(cmdl[1..$]);
37     if ((args.contains("lights")) && (args["lights"].length))
38     {
39         if (args["lights"] == "off")
40             lights.enabled = false; // turn the lights off
41         sleep(args.count("z")); // sleep for the indicated period
42     }
43     ---
44     
45     Terminology:
46     ---
47     - Short Argument: Single character arguments, potentially grouped under a single prefix. (-a, -abc)
48     - Long Argument: Multiple character arguments, always contained under their own prefix. (--file, --help)
49     - Parameter: A parameter to a particular argument, identified by a parameter delimiter or whitespace with no prefix (--arg=parameter, -arg parameter)
50     - Implicit Parameter: A given parameter that doesn't match to a particular argument, accessible using the null index (args[null]). (file1.txt file2.txt)
51     ---
52
53     Behavior:
54
55     Default:
56
57     Arguments defaults to a basic parse behavior which provides for handling of most common argument strings. You can invoke this default parse behavior by simply passing the argument string to the class constructor.
58
59     Short Arguments can be grouped under a single prefix. The following are equivalent.
60     ---
61     - "-a -b -c"
62     - "-abc"
63     ---
64
65     Argument Parameters can be identified via being space-separated from their argument, or via either a '=' or a ':'. The following are equivalent.
66     ---
67     - "-c arg"
68     - "-c=arg"
69     - "-c:arg"
70     ---
71
72     Only the first found parameter will be skipped over as a delimiter. The following are equivalent.
73     ---
74     - "-c =blah"
75     - "-c==blah"
76     - "-c:=blah"
77     ---
78
79     Empty prefixes are ignored. The following are equivalent.
80     ---
81     - "-c -- -a"
82     - "-c - -a"
83     - "- - - - -a -- -c"
84     ---
85
86     Multiple parameters to a particular argument are appended to the array for that argument. The following are equivalent.
87     ---
88     - "-a one two three"
89     - "-a one -a two -a three"
90     - "-a:one two -a=three"
91     ---
92
93     Implicit parameters are assigned to the [null] index of the class.
94     ---
95     - "file1 file2 file3"
96     ---
97
98     Defined:
99
100     Accepted arguments and the particular behavior to apply to them can be defined via the .define method. .define returns a Arguments.Definition, which can then be chained with additional methods for further definition.
101     The prefixes for both short and long arguments can be overwritten or appended to, as can the parameter delimiters, by setting the .prefixLong, .prefixShort, and/or delimiters arrays as appropriate.
102     
103     Null prefixes can be used to specify 'word sentence' behavior.
104     ---
105     args.prefixLong = [null];
106     ---
107
108     Will parse each of the items as if they were Arguments prefixed with "--" in the default behavior.
109     ---
110     myProgram one two three
111     ---
112
113     Null delimiters can be used to specify 'arguments smushed up with parameters' behavior.
114     ---
115     args.define("f").parameters.delimiters([null]);
116     ---
117
118     Will parse the following as args["f"] = ["file"].
119     ---
120     myProgram -ffile
121     ---
122
123     You can also define argument aliases.
124     ---
125     args.define("a").aliases(["b"]);
126     ---
127
128     Given that, the following would be equivalent.
129     ---
130     - "-a"
131     - "-b"
132     ---
133
134     You can also specify that an argument has default parameters.
135     ---
136     args.define("x").defaults(["hello"]);
137     ---
138
139     In which case, the following would be equivalent.
140     ---
141     - "-a -x hello"
142     - "-a"
143     ---
144
145     Finally, you may also specify limiting behavior, that a particular argument is required, conflicts with another argument, requires another argument, that a callback should be called when encountering the argument, or that a particular validation routine should be called on all defined parameters for an argument.
146     ---
147     args.define("x").required;
148     args.define("x").conflicts("b");
149     args.define("x").requires("a");
150     args.define("x").callback( ... );
151     args.define("x").validation( ... );
152     ---
153
154     Note that requires and conflicts are order-sensitive, if you want, for example, "a" and "b" to be mutually exclusive, you would need to define that explicitly.
155     ---
156     args.define("a").conflicts("b");
157     args.define("b").conflicts("a");
158     ---
159
160     Samples:
161
162     Sample definitions (tar-like).
163     ---
164     args.prefixShort = [null];
165     args.define("f").parameters;
166     args.define("v");
167     args.define("x");
168     args.define("z");
169     args.parse(["zxfv", "file1.tar", "file2.tar"]);
170     ---
171
172     Sample definitions (dsss-like).
173     ---
174     args.prefixLong = [null];
175     args.define("net").parameters(0,2);
176     args.parse(["net", "install", "tango"]);
177     ---
178
179     Sample definitions (ls-like).
180     ---
181     args.define("a");
182     args.define("l");
183     args.parse(["-al", "blah.txt"]);
184     args.parse(["blah.txt", "-al"]);
185     ---
186
187     Sample definitions (complex).
188     ---
189     args.define("x").defaults(["on"]).aliases(["X"]).conflicts("a").requires("b").required;
190     args.define("C").callback(delegate void(char[] n, char[] p){ ... });
191     args.define("V").validation(delegate bool(char[][] p, out char[] ip){ ... });
192     ---
193     
194     Ideas:
195     Some thoughts about future progression of the module.
196     ---
197     -Forcing arguments to lowercase. (define("x").lowercase, -X gives "x" in args)
198     -User defined callback when encountering an undefined argument.
199     -Standard help text generation
200     ---
201 *******************************************************************************/
202
203 public class Arguments
204 {
205     /// All Arguments exceptions are derived from this
206     class ParseException : Exception
207     {
208         private char[] _argument;
209         /// The name of the particular argument
210         char[] argument() { return _argument; }
211         this(char[] msg, char[] argument)
212         {
213             _argument = argument;
214             super(msg);
215         }
216     }
217
218     /// Thrown when a parameter for a given argument is determined to be invalid
219     class InvalidParameterException : ParseException
220     {
221         private char[] _parameter;
222         /// The invalid parameter
223         char[] parameter() { return _parameter; }
224         this(char[] msg, char[] argument, char[] parameter)
225         {
226             _parameter = parameter;
227             super(msg, argument);
228         }
229     }
230
231     /// Thrown when the minimum defined number of parameters for a given argument are not discovered
232     class InsufficientParameterException : ParseException
233     {
234         private int _count;
235         private int _expected;
236         /// The discovered parameter count
237         int count() { return _count; }
238         /// The expected parameter count
239         int expected() { return _expected; }
240         this(char[] msg, char[] argument, int count, int expected)
241         {
242             _count = count;
243             _expected = expected;
244             super(msg, argument);
245         }
246     }
247
248     /// Thrown when a previously discovered argument was defined to conflict with this one
249     class ConflictingArgumentException : ParseException
250     {
251         private char[] _conflict;
252         /// The name of the conflicting argument
253         char[] conflict() { return _conflict; }
254         this(char[] msg, char[] argument, char[] conflict)
255         {
256             _conflict = conflict;
257             super(msg, argument);
258         }
259     }
260
261     /// Thrown when an argument requires a previously defined argument which was not discovered
262     class MissingPrerequisiteException : ParseException
263     {
264         private char[] _requirement;
265         /// The name of the missing prerequisite argument
266         char[] requirement() { return _requirement; }
267         this(char[] msg, char[] argument, char[] requirement)
268         {
269             _requirement = requirement;
270             super(msg, argument);
271         }
272     }
273
274     /// Thrown when an argument is defined as being required but is not discovered
275     class MissingArgumentException : ParseException
276     {
277         this(char[] msg, char[] argument)
278         {
279             super(msg, argument);
280         }
281     }
282
283     private char[][][char[]] _args;
284     private char[][char[]] _aliases;
285     private int[char[]] _count;
286
287     private class Parameters
288     {
289         char[][] opIndex(char[] key)
290         {
291             char[][] rtn = null;
292             if (key in _args)
293                 rtn = _args[key];
294             return rtn;
295         }
296     }
297     /// Provides char[][] access of all discovered parameters for a particular argument (via .parameters["argName"])
298     Parameters parameters;
299
300     /// Delegate intended to be called back when the defined argument is discovered.
301     alias void delegate(char[] name, char[] parameter) argumentCallback;
302     /// Delegate to be used to perform validation on all parameters given for the defined argument.
303     alias bool delegate(char[][] parameters, out char[] invalidParameter) argumentValidation;
304
305     /// This class represents the definition of one particular argument.
306     class Definition
307     {
308         private char[] _name;
309         /// The name of the particular argument.
310         char[] name() { return _name; }
311         private int _parameterMin;
312         /// The defined minumum number of parameters this argument requires.
313         int parameterMin() { return _parameterMin; }
314         private int _parameterMax;
315         /// The defined maximum number of parameters this argument will consume.
316         int parameterMax() { return _parameterMax; }
317         private bool _isRequired;
318         /// Whether this argument is required.
319         bool isRequired() { return _isRequired; }
320         private char[][] _definedDelimiters;
321         /// A set of defined parameter delimiters for this argument.
322         char[][] definedDelimiters() { return _definedDelimiters; }
323         private char[][] _definedAliases;
324         /// A set of defined aliases for this argument.
325         char[][] definedAliases() { return _definedAliases; }
326         private char[][] _definedConflicts;
327         /// A set of defined conflicting arguments to this argument. Note that this is order-sensitive.
328         char[][] definedConflicts() { return _definedConflicts; }
329         private char[][] _definedPrerequisites;
330         /// A set of defined required arguments to this argument. Note that they are also order-sensitive.
331         char[][] definedPrerequisites() { return _definedPrerequisites; }
332         private argumentCallback[] _callbacks;
333         /// A set of defined callback functions to be called when this argument is discovered.
334         argumentCallback[] callbacks() { return _callbacks; }
335         private argumentValidation[] _validations;
336         /// A set of defined validation functions to be called on the parameters for this argument.
337         argumentValidation[] validations() { return _validations; }
338         private char[][] _defaultParameters;
339         /// A set of default parameters that will represent this argument if it is not discovered during parsing.
340         char[][] defaultParameters() { return _defaultParameters; }
341
342         /// Defines both a minumum and maximum number of parameters that this argument will take. Set max to -1 for unlimited.
343         Definition parameters(int min, int max)
344         {
345             this._parameterMin = min;
346             this._parameterMax = max;
347             return this;
348         }
349
350         /// Defines a definite number of parameters that this argument must take, implies both min and max of the given requirement.
351         Definition parameters(int req)
352         {
353             this._parameterMin = req;
354             this._parameterMax = req;
355             return this;
356         }
357
358         // Defines that this argument will consume any and all parameters following it that don't belong to another argument.
359         Definition parameters()
360         {
361             this._parameterMin = 0;
362             this._parameterMax = -1;
363             return this;
364         }
365
366         /// Sets the default parameters that will be assigned to this argument if it is not discovered during parsing.
367         Definition defaults(char[][] defaultParameters)
368         {
369             this._defaultParameters = defaultParameters;
370             return this;
371         }
372
373         /// Sets the set of valid delimiters for this argument. Set to [null] for '-ffile' behavior. Supercedes any defined Arguments.delimiters, for this argument only.
374         Definition delimiters(char[][] delimiters)
375         {
376             this._definedDelimiters ~= delimiters;
377             return this;
378         }
379
380         /// Defines that this argument must be discovered during parsing.
381         Definition required()
382         {
383             this._isRequired = true;
384             return this;
385         }
386
387         /// Defines a set of aliases which will also correspond to this argument. Note that only the root defined argument is accessible via the Arguments index[] following parse.
388         Definition aliases(char[][] aliases)
389         {
390             this._definedAliases ~= aliases;
391             foreach(char[] thisAlias; aliases)
392                 _aliases[thisAlias] = this.name;
393             return this;
394         }
395
396         /// Defines a set of arguments which, if found before this one is discovered, will conflict with this one. Note that for mutually exclusive conflicts, you need to declare both directions.
397         Definition conflicts(char[][] conflictingArguments)
398         {
399             this._definedConflicts ~= conflictingArguments;
400             return this;
401         }
402
403         /// Defines a set of arguments which must be discovered before this one is.
404         Definition prerequisites(char[][] requiredArguments)
405         {
406             this._definedPrerequisites ~= requiredArguments;
407             return this;
408         }
409
410         /// Defines a callback function that will be called when this argument is discovered.
411         Definition callback(argumentCallback cb)
412         {
413             this._callbacks ~= cb;
414             return this;
415         }
416
417         /// Defines a validation function which will be called on any and all found parameters for this argument.
418         Definition validation(argumentValidation av)
419         {
420             this._validations ~= av;
421             return this;
422         }
423
424         /*******************************************************************************
425         Constructor
426         Params:
427             name = name of this argument.
428         *******************************************************************************/
429         this(char[] name)
430         {
431             this._name = name;
432         }
433     }
434     /// A set of defined argument Definitions, indexed by their respective name.
435     Definition[char[]] definitions;
436
437     /// The set of prefixes which define a short argument.
438     char[][] prefixShort = ["-"];
439     /// The set of prefixes which define a long argument.
440     char[][] prefixLong = ["--"];
441     /// The set of delimiters which identify a parameter to an argument.
442     char[][] delimiters = [":", "="];
443
444     private char[][]* _prefixCompare(char[] candidate, char[][][] prefixes, out char[]* prefix)
445     { // returns the prefix array that contains the longest prefix match for our candidate, and sets out var to the matching prefix.
446         char[][]* rtn = null;
447         char[]* matchingPrefix = null;
448         for (uint p = 0; p < prefixes.length; p++)
449         {
450             for (uint i = 0; i < prefixes[p].length; i++)
451             {
452                 if (candidate.length >= prefixes[p][i].length)
453                 {
454                     if (prefixes[p][i] == candidate[0..(prefixes[p][i].length)])
455                     {
456                         if ((matchingPrefix is null) || (prefixes[p][i].length > (*matchingPrefix).length))
457                         {
458                             matchingPrefix = &prefixes[p][i];
459                             rtn = &prefixes[p];
460                             break;
461                         }
462                     }
463                 }
464             }
465         }
466         prefix = matchingPrefix;
467         return rtn;
468     }
469
470     private void _addArgument(char[] argumentName, char[][]* seenArguments, int* unsatisfiedParameters)
471     { // adds the argument, increments the unsatisfied count if required, and calls any configured callbacks for this argument.
472         char[] thisArgument;
473         if (argumentName in _aliases)
474             thisArgument = _aliases[argumentName];
475         else
476             thisArgument = argumentName.dup;
477         this[thisArgument] = null;
478         _count[thisArgument] = (thisArgument in _count) ? _count[thisArgument] + 1 : 1;
479         *seenArguments ~= thisArgument;
480
481         auto argDefinition = (thisArgument in definitions);
482         if (argDefinition !is null)
483         {
484             if (*unsatisfiedParameters != -1)
485             {
486                 int parameterCount;
487                 char[][] theseParameters = *(thisArgument in _args);
488                 if (theseParameters !is null)
489                     parameterCount = theseParameters.length;
490
491                 if (argDefinition.parameterMax == -1)
492                     *unsatisfiedParameters = -1;
493                 else if (parameterCount < argDefinition.parameterMax)
494                     *unsatisfiedParameters += (argDefinition.parameterMax - parameterCount);
495             }
496             foreach (cb; argDefinition.callbacks)
497                 cb(thisArgument, null);
498             if (argDefinition.definedConflicts.length)
499             {
500                 foreach(char[] conflict; argDefinition.definedConflicts)
501                 {
502                     if (conflict in _args)
503                         throw new ConflictingArgumentException("Argument conflicts with previously a discovered argument.", thisArgument, conflict);
504                 }
505             }
506             if (argDefinition.definedPrerequisites.length)
507             {
508                 foreach(char[] requirement; argDefinition.definedPrerequisites)
509                 {
510                     if (!(requirement in _args))
511                         throw new MissingPrerequisiteException("Argument requires a prerequisite argument which was not discovered.", thisArgument, requirement);
512                 }
513             }
514         }
515     }
516
517     private void _addParameter(char[] parameter, char[][]* seenArguments, int* unsatisfiedParameters)
518     { // adds this parameter to the appropriate argument, and also calls any configured callbacks.
519         char[] argumentName;
520         if (definitions.length == 0)
521         {
522             if ((*seenArguments).length)
523                 argumentName = (*seenArguments)[$-1];
524         }
525         else
526         {
527             for (uint p = seenArguments.length; p > 0; p--)
528             {
529                 auto argDefinition = ((*seenArguments)[p-1] in definitions);
530                 if (argDefinition !is null)
531                 {
532                     int parameterCount;
533                     char[][] theseParameters = *((*seenArguments)[p-1] in _args);
534                     if (theseParameters !is null)
535                         parameterCount = theseParameters.length;
536                        
537                     if ((argDefinition.parameterMax == -1) || (parameterCount < argDefinition.parameterMax))
538                     {
539                         argumentName = (*seenArguments)[p-1];
540                         if (*unsatisfiedParameters != -1)
541                             (*unsatisfiedParameters)--;
542                     }
543                     foreach(cb; argDefinition.callbacks)
544                         cb((*seenArguments)[p-1], parameter);
545                 }
546                 else
547                     argumentName = (*seenArguments)[p-1];
548                 if (argumentName !is null)
549                     break;
550             }
551         }
552         this[argumentName] = parameter.dup;
553     }
554
555     private int _locateDelimiter(char[] argString, out char[]* delimiter)
556     { // finds the delimiter closest to the start of the string, returns parameter start index
557         char[][] thisDelimiters;
558         char[] overrideArgument;
559         for (uint i = 1; i <= argString.length; i++)
560         {
561             auto thisDefinition = (argString[0..i] in definitions);
562             if (thisDefinition && thisDefinition.definedDelimiters.length)
563             {
564                 overrideArgument = argString[0..i];
565                 thisDelimiters = thisDefinition.definedDelimiters;
566                 break;
567             }
568         }
569         if (thisDelimiters is null)
570             thisDelimiters = delimiters;
571            
572         int rtn = argString.length;
573         for (uint i = 0; i < thisDelimiters.length; i++)
574         {
575             if (thisDelimiters[i] !is null)
576             {
577                 int loc = locatePattern(argString, thisDelimiters[i]);
578                 if (loc < rtn)
579                 {
580                     rtn = loc;
581                     delimiter = &thisDelimiters[i];
582                 }
583             }
584             else
585             {
586                 if (overrideArgument !is null)
587                 {
588                     rtn = overrideArgument.length;
589                     delimiter = &thisDelimiters[i];
590                 }
591                 else
592                 {
593                     for (uint j = 1; j <= argString.length; j++)
594                     {
595                         if (argString[0..j] in definitions)
596                         {
597                             rtn = j;
598                             delimiter = &thisDelimiters[i];
599                         }
600                     }
601                 }
602             }
603         }
604         return rtn;
605     }
606
607     /*******************************************************************************
608     Parse the passed command line string according to the defined behavior.
609     Params:
610         cmdl: command line string.
611     *******************************************************************************/
612     void parse(char[][] cmdl)
613     { // performs the magic, and also processes any argument validation for defined arguments.
614         char[][] seenArguments;
615         int unsatisfiedParameters;
616         for (uint i = 0; i < cmdl.length; i++)
617         {
618             char[]* prefix;
619             char[][]* prefixMatch = _prefixCompare(cmdl[i], [prefixShort, prefixLong], prefix);
620             if (((prefixMatch !is null) && (*prefixMatch !is null)) && ((*prefix !is null) || !unsatisfiedParameters))
621             {
622                 if (cmdl[i].length > (*prefix).length)
623                 {
624                     char[]* delimiter;
625                     int parameterStart = (_locateDelimiter(cmdl[i][(*prefix).length..$], delimiter) + (*prefix).length);
626                     if (parameterStart != (*prefix).length)
627                     {
628                         if (*prefixMatch is prefixShort)
629                         {
630                             for (uint p = (*prefix).length; p < parameterStart; p++)
631                                 _addArgument(cmdl[i][p..p+1], &seenArguments, &unsatisfiedParameters);
632                         }
633                         else if (*prefixMatch is prefixLong)
634                             _addArgument(cmdl[i][(*prefix).length..parameterStart], &seenArguments, &unsatisfiedParameters);
635                     }
636                   &n