comparison tango/tango/util/Arguments.d @ 132:1700239cab2e trunk

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