1 /++
2 $(H2 The Argument Type for Cmdline)
3 
4 This modlue mainly has `Argument` Type.
5 We can set the inner value by manly way.
6 And if the `Argument` value is valid, then we
7 can initialize it and get the inner value.
8 
9 Authors: 笑愚(xiaoyu)
10 +/
11 module cmdline.argument;
12 
13 import std.conv;
14 import std.range.primitives;
15 import std.algorithm;
16 import std.array;
17 import std.format;
18 
19 import mir.algebraic;
20 
21 import cmdline.error;
22 import cmdline.option;
23 
24 /// same as `OptionBaseValue`
25 alias ArgBaseValue = OptionBaseValue;
26 /// same as `OptionArrayValue`
27 alias ArgArrayValue = OptionArrayValue;
28 /// same as `OptionValueSeq`
29 alias ArgValueSeq = OptionValueSeq;
30 /// same as `OptionNullable`
31 alias ArgNullable = Nullable!ArgValueSeq;
32 /// same as `OptionVariant`
33 alias ArgVariant = Variant!ArgValueSeq;
34 
35 /// test whether the type`T` is base innner argument value type
36 alias isBaseArgValueType(T) = isBaseOptionValueType!T;
37 /// test whether the type`T` is innner argument value type
38 alias isArgValueType(T) = isOptionValueType!T;
39 /// same as `cmdline.error.OptionMemberFnCallError`
40 alias ArgMemberFnCallError = cmdline.error.OptionMemberFnCallError;
41 
42 /** 
43 the argument type.
44 store the value that command line's arguments input.
45 we can get the inner value after it is initialized.
46  */
47 class Argument {
48 package:
49     string _description;
50     string defaultDescription;
51     bool required;
52     bool variadic;
53 
54     string _name;
55 
56     bool settled;
57     Source source;
58 
59     this(string flag, string description) {
60         this._description = description;
61         this.defaultDescription = "";
62         switch (flag[0]) {
63         case '<':
64             this.required = true;
65             this._name = flag[1 .. $ - 1].idup;
66             break;
67         case '[':
68             this.required = false;
69             this._name = flag[1 .. $ - 1].idup;
70             break;
71         default:
72             this.required = true;
73             this._name = flag.idup;
74             break;
75         }
76         auto has_3_dots = this._name[$ - 3 .. $] == "...";
77         if (this._name.length > 3 && has_3_dots) {
78             this.variadic = true;
79             this._name = this._name[0 .. $ - 3].idup;
80         }
81         this.settled = false;
82         this.source = Source.None;
83     }
84 
85     alias Self = typeof(this);
86 
87 public:
88     /// get the description, and the output starts with `description: `
89     string description() const {
90         return "description: " ~ this._description;
91     }
92     /// set the description
93     Self description(string desc) {
94         this._description = desc;
95         return this;
96     }
97     /// get the name
98     @property
99     string name() const {
100         return this._name.idup;
101     }
102     /// get the attribute name with camel-like
103     @property
104     string attrName() const {
105         return _camelCase(this._name);
106     }
107     /// test whether the argument are required
108     @property
109     bool isRequired() const {
110         return this.required;
111     }
112     ///  test whether the argument are optional
113     @property
114     bool isOptional() const {
115         return !this.required;
116     }
117     /// set the choices of argument inner type
118     ///Params: values = the sequence of choices value
119     Self choices(T)(T[] values) if (isBaseArgValueType!T) {
120         auto is_variadic = this.variadic;
121         if (!is_variadic) {
122             auto derived = cast(ValueArgument!T) this;
123             return derived.choices(values);
124         }
125         else {
126             auto derived = cast(ValueArgument!(T[])) this;
127             return derived.choices(values);
128         }
129     }
130     /// set the choices of argument inner type
131     /** 
132      * 
133      * Params:
134      *   value = a choice value 
135      *   rest = the rest choice values
136      * Returns: `Self` for chain call
137      */
138     Self choices(T)(T value, T[] rest...) if (isBaseArgValueType!T) {
139         auto tmp = rest ~ value;
140         return choices(tmp);
141     }
142     /// set the range of argument inner value when the argument innner value type is `int` or `double`
143     /// Params:
144     ///   min = the minimum
145     ///   max = the maximum
146     /// Returns: `Self` for chain call
147     Self rangeOf(T)(T min, T max) if (is(T == int) || is(T == double)) {
148         auto is_variadic = this.variadic;
149         if (!is_variadic) {
150             auto derived = cast(ValueArgument!T) this;
151             return derived.rangeOf(min, max);
152         }
153         else {
154             auto derived = cast(ValueArgument!(T[])) this;
155             return derived.rangeOf(min, max);
156         }
157     }
158     /// set the default value as `true`
159     /// Returns: `Self` for chain call
160     Self defaultVal() {
161         throw new ArgMemberFnCallError;
162     }
163     /// set the config value as `true`, which is used for
164     /// inernal impletation and is not recommended for use in you project
165     /// Returns: `Self` for chain call
166     Self configVal() {
167         throw new ArgMemberFnCallError;
168     }
169     /// test whether the argument is valid so that you can safely get the inner value
170     /// after the return value is `true`
171     abstract bool isValid() const;
172 
173     /// set the value from client shell
174     /// Params:
175     ///   value = the first input value, and this func will call inner parsing callback to transform `string` type
176     ///           to the target type that `Self` required
177     ///   rest = the rest of input value
178     /// Returns: `Self`` for chain call
179     abstract Self cliVal(string value, string[] rest...);
180     /// initialize the final value. if `this.isValid` is `false`, then would throw error
181     /// Returns: `Self`` for chain call
182     abstract Self initialize();
183     /// get the innner value and is recommended to be used after calling `this.initialize()`
184     /// Returns: the variant of final value
185     abstract ArgVariant get() const;
186 
187     /// set the default value
188     /// Params:
189     ///   value = the value to be set as default value, `T` must satisfy `isBaseArgValueType`
190     /// Returns: `Self`` for chain call
191     Self defaultVal(T)(T value) if (isBaseArgValueType!T) {
192         auto derived = cast(ValueArgument!T) this;
193         if (!derived) {
194             error(format!"the value type is `%s` while the argument `%s` inner type is not the type or related array type"(
195                     T.stringof, this._name));
196         }
197         return derived.defaultVal(value);
198     }
199 
200     /// set the default value
201     /// Params:
202     ///   value = the first value to be set as default value, usually as the first element of default array value 
203     ///   rest = the rest values to be set as default value, `T` must satisfy `isBaseArgValueType` and not `bool`
204     /// Returns: `Self`` for chain call
205     Self defaultVal(T)(T value, T[] rest...)
206             if (isBaseArgValueType!T && !is(T == bool)) {
207         auto derived = cast(ValueArgument!(T[])) this;
208         if (!derived) {
209             error(format!"the value type is `%s` while the argument `%s` inner type is not the type or related array type"(
210                     T.stringof, this._name));
211         }
212         return derived.defaultVal(value, rest);
213     }
214 
215     /// set the default value
216     /// Params:
217     ///   values = the value to be set as default value, usually as the default array value,
218     ///            `T` must satisfy `isBaseArgValueType` and not `bool`
219     /// Returns: `Self`` for chain call
220     Self defaultVal(T)(in T[] values) if (isBaseArgValueType!T && !is(T == bool)) {
221         if (values.length == 0) {
222             error(format!"the default value's num of argument `%s` cannot be zero"(this._name));
223         }
224         return defaultVal(values[0], cast(T[]) values[1 .. $]);
225     }
226 
227 package:
228     Self configVal(T)(T value) if (isBaseArgValueType!T) {
229         auto derived = cast(ValueArgument!T) this;
230         if (!derived) {
231             parsingError(
232                 format!"the value type is `%s` while argument the inner type is not the type or related array type in argument `%s`"(
233                     T.stringof, this._name));
234         }
235         return derived.configVal(value);
236     }
237 
238     Self configVal(T)(T value, T[] rest...)
239             if (isBaseArgValueType!T && !is(T == bool)) {
240         auto derived = cast(ValueArgument!(T[])) this;
241         if (!derived) {
242             parsingError(
243                 format!"the value type is `%s` while argument the inner type is not the type or related array type in argument `%s`"(
244                     T.stringof, this._name));
245         }
246         return derived.configVal(value, rest);
247     }
248 
249     Self configVal(T)(in T[] values) if (isBaseArgValueType!T && !is(T == bool)) {
250         if (values.length == 0) {
251             error(format!"the default value's num of argument `%s` cannot be zero"(this._name));
252         }
253         return configVal(values[0], cast(T[]) values[1 .. $]);
254     }
255 
256 public:
257     /// get inner value in the specified type, `T` usually is among `ArgValueSeq`
258     /// Returns: the result value
259     T get(T)() const {
260         return this.get.get!T;
261     }
262     /// get the human readable name, start with `<` representing required argument,
263     /// `[` representing optional argument and `...` is variadic argument
264     /// Returns: the result value
265     @property
266     string readableArgName() const {
267         auto nameOutput = this.name ~ (this.variadic ? "..." : "");
268         return this.isRequired
269             ? "<" ~ nameOutput ~ ">" : "[" ~ nameOutput ~ "]";
270     }
271     /// get the type in `string` type, used for help command and help option
272     /// start with `type: `
273     string typeStr() const {
274         return "";
275     }
276     /// get the default value in `string` type, start with `default: `
277     string defaultValStr() const {
278         return "";
279     }
280     /// get the choices in `string` type, start with `choices: `
281     string choicesStr() const {
282         return "";
283     }
284     /// get the range in `string` type, start with `range: `
285     string rangeOfStr() const {
286         return "";
287     }
288 }
289 
290 /// create `bool` argument
291 /// Params:
292 ///   flag = the flag like `<name>`, `[name]`, `name`
293 ///   desc = the description of argument
294 /// Returns: a `bool` argument
295 Argument createArgument(string flag, string desc = "") {
296     return createArgument!bool(flag, desc);
297 }
298 
299 /// create `bool` argument
300 Argument createArgument(T : bool)(string flag, string desc = "") {
301     bool is_variadic = flag[$ - 2] == '.';
302     if (is_variadic) {
303         error(format!"the flag `%s` cannot contain `...` in creating bool argument"(flag));
304     }
305     return new ValueArgument!bool(flag, desc);
306 }
307 
308 /// create `T` or `T[]` argument, which `T` must not `bool`
309 /// if flag is like `<name...>` then the argument's innner type is `T[]` 
310 /// Params:
311 ///   flag = the flag like `<name>`, `[name]`, `name`, `<name...>`
312 ///   desc = the description of argument
313 /// Returns: a value/variadic argument
314 Argument createArgument(T)(string flag, string desc = "")
315         if (!is(T == bool) && isBaseArgValueType!T) {
316     bool is_variadic = flag[$ - 2] == '.';
317     if (is_variadic) {
318         return new ValueArgument!(T[])(flag, desc);
319     }
320     else {
321         return new ValueArgument!T(flag, desc);
322     }
323 }
324 
325 /// create a argument, whose inner type is array type of `T` and `T` is not `bool`
326 /// Params:
327 ///   flag = the flag like `<name...>`, `[name...]`, `name...`
328 ///   desc = the description of argument
329 /// Returns: a variadic argument
330 Argument createArgument(T : U[], U)(string flag, string desc = "")
331         if (!is(U == bool) && isBaseArgValueType!U) {
332     bool is_variadic = flag[$ - 2] == '.';
333     if (!is_variadic) {
334         error(format!"the flag `%s` must contain `...` in creating variadic option"(flag));
335     }
336     return new ValueArgument!T(flag, desc);
337 }
338 
339 unittest {
340     Argument[] args = [
341         createArgument!int("<int>", ""),
342         createArgument!string("[str]", ""),
343         createArgument!int("<int>", ""),
344         createArgument!(double[])("<int...>", "")
345     ];
346     import std.array;
347     import std.algorithm;
348 
349     each!((Argument v) => v.cliVal("12").initialize)(args);
350 
351     assert(args[0].get!int == 12);
352     assert(args[1].get!string == "12");
353     assert(args[2].get!int == 12);
354     assert(args[3].get!(double[]) == [12f]);
355 }
356 
357 unittest {
358     import std.stdio;
359 
360     Argument arg1 = createArgument!int("<age>");
361     Argument arg2 = createArgument!double("<tall>");
362     Argument arg3 = createArgument!string("<family...>");
363 
364     arg1.rangeOf(18, 65).defaultVal(20).cliVal("24").initialize;
365     arg2.rangeOf(1.0, 2.0).choices(1.55, 1.66, 1.77).defaultVal(1.77).initialize;
366     arg3.choices("father", "mother", "brother", "sister", "son")
367         .cliVal("son", "brother").initialize;
368 
369     assert(arg1.get == 24);
370     assert(arg2.get == 1.77);
371     assert(arg3.get == ["son", "brother"]);
372 
373     writeln(arg1.typeStr);
374     writeln(arg1.defaultValStr);
375     writeln(arg1.rangeOfStr);
376 
377     writeln(arg2.typeStr);
378     writeln(arg2.defaultValStr);
379     writeln(arg2.rangeOfStr);
380     writeln(arg2.choicesStr);
381 
382     writeln(arg3.typeStr);
383     writeln(arg3.choicesStr);
384 }
385 
386 package class ValueArgument(T) : Argument {
387     static assert(isArgValueType!T);
388     Nullable!T cliArg;
389     Nullable!T configArg;
390     Nullable!T defaultArg;
391 
392     T innerData;
393 
394     static if (isBaseArgValueType!T) {
395         T[] argChoices;
396     }
397     else {
398         T argChoices;
399     }
400 
401     static if (is(T == int) || is(T == double)) {
402         T _min = int.min;
403         T _max = int.max;
404     }
405     else static if (is(T == int[]) || is(T == double[])) {
406         ElementType!T _min = int.min;
407         ElementType!T _max = int.max;
408     }
409 
410     this(string flag, string description) {
411         super(flag, description);
412         this.cliArg = null;
413         this.configArg = null;
414         this.defaultArg = null;
415         this.innerData = T.init;
416         this.argChoices = [];
417     }
418 
419     alias Self = typeof(this);
420 
421     Self choices(U)(U[] values...) if (isBaseArgValueType!T && is(U == T)) {
422         foreach (index_i, i; values) {
423             foreach (j; values[index_i + 1 .. $]) {
424                 if (i == j) {
425                     error(format!"the element value of choices can not be equal in argument `%s`, the values is: `%s`"(
426                             this._name,
427                             values.to!string));
428                 }
429             }
430         }
431         static if (is(T == int) || is(T == double)) {
432             if (values.any!(val => val < this._min || val > this._max)) {
433                 error(format!"the element value of choices cannot be out of %s in argument `%s`, the values is: `%s`"(
434                         this.rangeOfStr(), this._name, values.to!string
435                 ));
436             }
437         }
438         this.argChoices = values;
439         return this;
440     }
441 
442     Self choices(U)(U[] values...) if (is(U == ElementType!T) && !is(T == string)) {
443         foreach (index_i, i; values) {
444             foreach (j; values[index_i + 1 .. $]) {
445                 if (i == j) {
446                     error(format!"the element value of choices can not be equal in argument `%s`, the values is: `%s`"(
447                             this._name,
448                             values.to!string));
449                 }
450             }
451         }
452         static if (is(T == int[]) || is(T == double[])) {
453             if (values.any!(val => val < this._min || val > this._max)) {
454                 error(format!"the element value of choices cannot be out of %s in argument `%s`, the values is: `%s`"(
455                         this.rangeOfStr(), this._name, values.to!string
456                 ));
457             }
458         }
459         this.argChoices = values;
460         return this;
461     }
462 
463     void _checkVal(U)(in U value) const if (isBaseArgValueType!T && is(U == T)) {
464         if (!this.argChoices.empty) {
465             if (!this.argChoices.count(value)) {
466                 parsingError(format!"the value cannot be out of %s for argument `%s`, the value is: `%s`"(
467                         this.choicesStr(),
468                         this._name,
469                         value.to!string
470                 ));
471             }
472         }
473         static if (is(T == int) || is(T == double)) {
474             if (value < this._min || value > this._max) {
475                 parsingError(format!"the value cannot be out of %s for argument `%s`, the value is: `%s`"(
476                         this.rangeOfStr(),
477                         this._name,
478                         value.to!string
479                 ));
480             }
481         }
482     }
483 
484     void _checkVal(U)(in U value) const
485     if (is(U == ElementType!T) && !is(T == string)) {
486         if (!this.argChoices.empty) {
487             if (!this.argChoices.count(value)) {
488                 parsingError(format!"the value cannot be out of %s for argument `%s`, the value is: `%s`"(
489                         this.choicesStr(),
490                         this._name,
491                         value.to!string
492                 ));
493             }
494         }
495         static if (is(T == int[]) || is(T == double[])) {
496             if (value < this._min || value > this._max) {
497                 parsingError(format!"the value cannot be out of %s for argument `%s`, the value is: `%s`"(
498                         this.rangeOfStr(),
499                         this._name,
500                         value.to!string
501                 ));
502             }
503         }
504     }
505 
506     void _checkValSeq(U)(U[] values...) const
507     if (is(U == ElementType!T) && !is(T == string)) {
508         assert(values.length);
509         foreach (val; values) {
510             _checkVal(val);
511         }
512     }
513 
514     static if (is(T == int) || is(T == double) || is(T == int[]) || is(T == double[])) {
515         Self rangeOf(U)(U min, U max) {
516             assert(max > min);
517             this._min = min;
518             this._max = max;
519             return this;
520         }
521 
522         override string rangeOfStr() const {
523             if (_min == int.min && _max == int.max)
524                 return "";
525             return "range: " ~ _min.to!string ~ " ~ " ~ _max.to!string;
526         }
527     }
528 
529     Self defaultVal(T value) {
530         static if (isBaseArgValueType!T) {
531             _checkVal(value);
532         }
533         else {
534             _checkValSeq(value);
535         }
536         this.defaultArg = value;
537         return this;
538     }
539 
540     Self defaultVal(U)(U value, U[] rest...)
541             if (is(U == ElementType!T) && !is(T == string)) {
542         auto tmp = [value] ~ rest;
543         _checkValSeq(tmp);
544         this.defaultArg = tmp;
545     }
546 
547     Self configVal(T value) {
548         static if (isBaseArgValueType!T) {
549             _checkVal(value);
550         }
551         else {
552             _checkValSeq(value);
553         }
554         this.configArg = value;
555         return this;
556     }
557 
558     Self configVal(U)(U value, U[] rest...)
559             if (is(U == ElementType!T) && !is(T == string)) {
560         auto tmp = [value] ~ rest;
561         _checkValSeq(tmp);
562         this.configArg = tmp;
563         return this;
564     }
565 
566     static if (is(T == bool)) {
567         override Self defaultVal() {
568             static assert(is(T == bool));
569             this.defaultArg = true;
570             return this;
571         }
572 
573         override Self configVal() {
574             static assert(is(T == bool));
575             this.configArg = true;
576             return this;
577         }
578     }
579 
580     override Self cliVal(string value, string[] rest...) {
581         static if (isBaseArgValueType!T) {
582             assert(rest.empty);
583             try {
584                 auto tmp = value.to!T;
585                 _checkVal(tmp);
586                 this.cliArg = tmp;
587             }
588             catch (ConvException e) {
589                 parsingError(format!"on argument `%s` cannot convert the input `%s` to type `%s`"(
590                         this._name,
591                         value,
592                         T.stringof
593                 ));
594             }
595         }
596         else {
597             try {
598                 auto tmp = ([value] ~ rest).map!(to!(ElementType!T)).array;
599                 _checkValSeq(tmp);
600                 this.cliArg = tmp;
601             }
602             catch (ConvException e) {
603                 parsingError(format!"on argument `%s` cannot convert the input `%s` to type `%s`"(
604                         this._name,
605                         ([value] ~ rest).to!string,
606                         T.stringof
607                 ));
608             }
609         }
610         return this;
611     }
612 
613     @property
614     override bool isValid() const {
615         return !cliArg.isNull || !configArg.isNull || !defaultArg.isNull;
616     }
617 
618     override Self initialize() {
619         if (!this.isValid) {
620             parsingError(format!"the argument `%s` must valid before initializing"(this._name));
621         }
622         this.settled = true;
623         if (!cliArg.isNull) {
624             innerData = cliArg.get!T;
625             source = Source.Cli;
626             return this;
627         }
628         if (!configArg.isNull) {
629             innerData = configArg.get!T;
630             source = Source.Config;
631             return this;
632         }
633         if (!defaultArg.isNull) {
634             innerData = defaultArg.get!T;
635             source = Source.Default;
636             return this;
637         }
638         return this;
639     }
640 
641     @property
642     override ArgVariant get() const {
643         assert(this.settled);
644         T result;
645         static if (is(ElementType!T == void)) {
646             result = this.innerData;
647         }
648         else {
649             result = this.innerData.dup;
650         }
651         return ArgVariant(result);
652     }
653 
654     @property
655     T get(U : T)() const {
656         assert(this.settled);
657         return this.innerData;
658     }
659 
660     override string typeStr() const {
661         return "type: " ~ T.stringof;
662     }
663 
664     override string defaultValStr() const {
665         if (defaultArg.isNull)
666             return "";
667         return "default: " ~ defaultArg.get!T
668             .to!string;
669     }
670 
671     override string choicesStr() const {
672         if (argChoices.empty)
673             return "";
674         return "choices: " ~ argChoices.to!string;
675     }
676 }
677 
678 private void error(string msg = "", string code = "argument.error") {
679     throw new CMDLineError(msg, 1, code);
680 }
681 
682 private void parsingError(string msg = "", string code = "argument.error") {
683     throw new InvalidArgumentError(msg, code);
684 }