1 /++
2 $(H2 The Option Type for Cmdline)
3 
4 This modlue mainly has `Option` Type.
5 We can set the inner value by manly way.
6 And if the `Option` value is valid, then we
7 can initialize it and get the inner value.
8 
9 Authors: 笑愚(xiaoyu)
10 +/
11 module cmdline.option;
12 
13 import std.string;
14 import std.regex;
15 import std.meta;
16 import std.traits;
17 import std.conv;
18 import std.process;
19 import std.range.primitives;
20 import std.algorithm;
21 import std.array;
22 
23 import mir.algebraic;
24 
25 import cmdline.error;
26 import cmdline.pattern;
27 
28 // the result type after parsing the flags.
29 package struct OptionFlags {
30     // short flag
31     // Examples: `-f`, `-s`, `-x`
32     string shortFlag = "";
33     // long flag
34     // Examples: `--flag`, `--mixin-flag`, `--no-flag`
35     string longFlag = "";
36     // value flag
37     // Examples: `<required>`, `[optional]`, `<variadic...>`
38     string valueFlag = "";
39 }
40 
41 /// a sequenece of inner option base type
42 alias OptionBaseValue = AliasSeq!(string, int, double, bool);
43 /// a sequenece of inner option array type
44 alias OptionArrayValue = AliasSeq!(string[], int[], double[]);
45 /// a sequenece of inner option type, equals to the union of `OptionBaseValue` and `OptionArrayValue`
46 alias OptionValueSeq = AliasSeq!(OptionBaseValue, OptionArrayValue);
47 
48 /// a nullable variant which may contain one of type in `OptionValueSeq`
49 alias OptionNullable = Nullable!OptionValueSeq;
50 /// a no-nullable variant which may contain one of type in `OptionValueSeq`
51 alias OptionVariant = Variant!OptionValueSeq;
52 
53 /// the source of the final option value gotten
54 enum Source {
55     /// from client terminal
56     Cli,
57     /// from env
58     Env,
59     /// from config file
60     Config,
61     /// from impled value by other options
62     Imply,
63     /// from the value that is set by user using `defaultVal` 
64     Default,
65     /// from the value that is set by user using `preset`
66     Preset,
67     /// default value
68     None
69 }
70 
71 /// the callback for parsing the `string` value to the target type
72 alias ParseArgFn(Target) = Target function(string str);
73 /// furhter callback after using `ParseArgFn`
74 alias ProcessArgFn(Target) = Target function(Target value);
75 /// the callback for recursingly parsing the multiple values to only one value with same type, using in `VariadicOption` 
76 alias ProcessReduceFn(Target) = Target function(Target cur, Target prev);
77 
78 /// a trait func for checking whether a type is base inner option value (`OptionBaseValue`)
79 template isBaseOptionValueType(T) {
80     enum bool isBaseOptionValueType = isBoolean!T || allSameType!(T, int) ||
81         allSameType!(T, double) || allSameType!(T, string);
82 }
83 /// a trait func for checking whether a type is option inner value (`OptionValueSeq`)
84 template isOptionValueType(T) {
85     static if (isDynamicArray!T && !allSameType!(T, string)) {
86         enum bool isOptionValueType = !is(ElementType!T == bool) && isBaseOptionValueType!(
87                 ElementType!T);
88     }
89     else {
90         enum bool isOptionValueType = isBaseOptionValueType!T;
91     }
92 }
93 
94 unittest {
95     static assert(isBaseOptionValueType!int);
96     static assert(isBaseOptionValueType!double);
97     static assert(isBaseOptionValueType!bool);
98     static assert(isBaseOptionValueType!string);
99 
100     static assert(isOptionValueType!int);
101     static assert(isOptionValueType!double);
102     static assert(isOptionValueType!bool);
103     static assert(isOptionValueType!string);
104 
105     static assert(isOptionValueType!(int[]));
106     static assert(isOptionValueType!(double[]));
107     static assert(isOptionValueType!(string[]));
108 }
109 
110 unittest {
111     alias test_bool = visit!((bool v) => true, (v) => false);
112     OptionVariant ov = 12;
113     OptionNullable on = ov;
114     assert(!test_bool(on));
115 }
116 
117 /** 
118 the option type.
119 store the value that command line's options input.
120 we can get the inner value after it is initialized.
121  */
122 class Option {
123 package:
124     string _description;
125     string defaultValueDescription;
126     bool mandatory;
127 
128     string flags;
129 
130     bool required;
131     bool optional;
132     string shortFlag;
133     string longFlag;
134 
135     string valueName;
136 
137     bool variadic;
138 
139     bool hidden;
140 
141     string[] conflictsWith;
142     string envKey;
143     ImplyOptionMap implyMap;
144 
145     OptionNullable innerImplyData;
146 
147     bool found;
148     bool settled;
149 
150     Source source;
151 
152     alias Self = typeof(this);
153     alias ImplyOptionMap = OptionVariant[string];
154 
155     bool isValueData;
156     bool innerBoolData;
157 
158     bool _isMerge;
159 
160     this(string flags, string description) {
161         this.flags = flags;
162         this._description = description;
163         this.mandatory = false;
164         this.defaultValueDescription = "";
165         auto opt = splitOptionFlags(flags);
166         this.shortFlag = opt.shortFlag;
167         this.longFlag = opt.longFlag;
168         if (longFlag.length == 0) {
169             error("the long flag must be specified");
170         }
171         if (!matchFirst(this.longFlag, PTN_NEGATE).empty) {
172             error("the negate flag cannot be specified by `new Option`");
173         }
174         this.variadic = (opt.valueFlag == "" || opt.valueFlag[$ - 2] != '.') ? false : true;
175         this.valueName = opt.valueFlag == "" ? "" : this.variadic ? opt.valueFlag[1 .. $ - 4].idup
176             : opt.valueFlag[1 .. $ - 1].idup;
177         if (this.valueName == "") {
178             this.required = this.optional = false;
179         }
180         else {
181             this.required = opt.valueFlag[0] == '<' ? true : false;
182             this.optional = opt.valueFlag[0] == '[' ? true : false;
183         }
184         this.hidden = false;
185         this.conflictsWith = [];
186         this.implyMap = null;
187         this.envKey = "";
188         this.found = false;
189         this.settled = false;
190         this.source = Source.None;
191         this.innerImplyData = null;
192         this.isValueData = false;
193         this.innerBoolData = false;
194         _isMerge = true;
195     }
196 
197 public:
198     /// get the description, and the output starts with `description: `
199     string description() const {
200         return "description: " ~ this._description;
201     }
202 
203     /// set the description
204     Self description(string desc) {
205         this._description = desc;
206         return this;
207     }
208 
209     /// test whether the other `Option` variable's flag same in some parts
210     bool matchFlag(in Option other) const {
211         return this.longFlag == other.longFlag ||
212             (this.shortFlag.empty ? false : this.shortFlag == other.shortFlag);
213     }
214 
215     /// test whether the other `NegateOption` variable's flag same in some parts
216     bool matchFlag(in NegateOption other) const {
217         auto short_flag = this.shortFlag;
218         auto nshort_flag = other.shortFlag;
219         return short_flag.empty ? false : short_flag == nshort_flag;
220     }
221 
222     /// specify the name of the option that conflicts with this option
223     /// Params:
224     ///   name = the name of the option that conflicts with this option
225     /// Returns: `Self` for chain call
226     Self conflicts(string name) {
227         this.conflictsWith ~= name;
228         return this;
229     }
230 
231     /// specify the name of the options that conflicts with this option
232     /// Params:
233     ///   names = the names of the option that conflicts with this option
234     /// Returns: `Self` for chain call
235     Self conflicts(const string[] names) {
236         this.conflictsWith ~= names;
237         return this;
238     }
239 
240     /// imply the value of other options' value of `true`
241     /// Params:
242     ///   names = the options' names
243     /// Returns: `Self` for chain call
244     Self implies(string[] names) {
245         if (!names.length) {
246             error("the length of implies key cannot be zero");
247         }
248         foreach (name; names) {
249             bool signal = false;
250             foreach (k; implyMap.byKey) {
251                 if (name.length < k.length && name == k[0 .. name.length]) {
252                     signal = true;
253                     break;
254                 }
255             }
256             if (signal)
257                 error(format!"the implies key must be unique, here are keys: `%s`, key: `%s`"(
258                         implyMap.byKey.to!string, name));
259             implyMap[name ~ ":" ~ bool.stringof] = true;
260         }
261         return this;
262     }
263 
264     /// imply the value of other option's value of `T`, `T` must satisfy `isOptionValueType`
265     /// Params:
266     ///   key = the name of option
267     ///   value = the value imply for
268     /// Returns: `Self` for chain call
269     Self implies(T)(string key, T value) if (isOptionValueType!T) {
270         bool signal = false;
271         foreach (k; implyMap.byKey) {
272             if (key.length < k.length && key == k[0 .. key.length]) {
273                 signal = true;
274                 break;
275             }
276         }
277         if (signal)
278             error(format!"the implies key must be unique, here are keys: `%s`, key: `%s`"(
279                     implyMap.byKey.to!string, key));
280         implyMap[key ~ ":" ~ T.stringof] = value;
281         return this;
282     }
283 
284     /// set the env variable's key so that the option can set its value from `env`
285     /// Params:
286     ///   name = the env variable's key
287     /// Returns: `Self` for chain call
288     Self env(string name) {
289         this.envKey = name;
290         return this;
291     }
292 
293     /// set whether the option is mandatory
294     /// Params:
295     ///   mandatory = whether the option is mandatory
296     /// Returns: `Self` for chain call
297     Self makeMandatory(bool mandatory = true) {
298         this.mandatory = mandatory;
299         return this;
300     }
301 
302     /// set whether the option is hidden out of help command
303     /// Params:
304     ///   hide = whether the option is hidden out of help command
305     /// Returns: `Self` for chain call
306     Self hideHelp(bool hide = true) {
307         this.hidden = hide;
308         return this;
309     }
310 
311     /// get the name
312     @property
313     string name() const {
314         return this.longFlag[2 .. $].idup;
315     }
316 
317     /// get the attribute name with camel-like
318     @property
319     string attrName() const {
320         return _camelCase(this.name);
321     }
322 
323     /// get the raw env variable in `string` type acccording to `this.envKey`
324     /// which is set by `this.env`
325     package
326     @property
327     string envStr() const {
328         auto raw = environment.get(this.envKey);
329         return raw;
330     }
331 
332     /// test whether a string is this option's long or short flag 
333     bool isFlag(string flag) const {
334         return !flag.empty && (this.shortFlag == flag || this.longFlag == flag);
335     }
336 
337     /// test whether is a bool option
338     @property
339     bool isBoolean() const {
340         return this.valueName.length == 0;
341     }
342 
343     /// test whether is optional option
344     @property
345     bool isOptional() const {
346         return (!this.required && this.optional);
347     }
348 
349     /// Test is required option
350     @property
351     bool isRequired() const {
352         return (!this.optional && this.required);
353     }
354 
355     /// Test is allowed to merge variadic final value from different source, default `true`
356     bool isMerge() const {
357         return this._isMerge;
358     }
359 
360     /// whether is allowed to merge variadic final value from different source, default `true`
361     Self merge(bool allow = true) {
362         this._isMerge = allow;
363         return this;
364     }
365 
366     /// set the imply value as `true`, which is used for
367     /// inernal impletation and is not recommended for use in you project
368     /// Returns: `Self` for chain call
369     Self implyVal() {
370         // throw new OptionMemberFnCallError;
371         this.innerImplyData = true;
372         return this;
373     }
374 
375     /// set the option value from `en`
376     Self envVal() {
377         // throw new OptionMemberFnCallError;
378         return this;
379     }
380 
381     /// set the preset value as `true`
382     Self preset() {
383         // throw new OptionMemberFnCallError;
384         return this;
385     }
386 
387     /// set the value from client shell
388     /// Params:
389     ///   value = the first input value, and this func will call inner parsing callback to transform `string` type
390     ///           to the target type that `Self` required
391     ///   rest = the rest of input value
392     /// Returns: `Self`` for chain call
393     Self cliVal(string value, string[] rest...) {
394         // throw new OptionMemberFnCallError;
395         return this;
396     }
397 
398     /// test whether the argument is valid so that you can safely get the inner value
399     /// after the return value is `true`
400     @property
401     abstract bool isValid() const;
402 
403     /// get the innner value and is recommended to be used after calling `this.initialize()`
404     /// Returns: the variant of final value
405     @property
406     abstract OptionVariant get() const;
407 
408     /// initialize the final value. if `this.isValid` is `false`, then would throw error
409     /// Returns: `Self`` for chain call
410     abstract Self initialize();
411 
412     /// set the imply value, which is used for
413     /// inernal impletation and is not recommended for use in you project
414     /// Returns: `Self` for chain call
415     abstract Self implyVal(OptionVariant value);
416 
417     /// set the choices of argument inner type
418     ///Params: values = the sequence of choices value
419     Self choices(T)(T[] values) {
420         auto is_variadic = this.variadic;
421         if (is_variadic) {
422             auto derived = cast(VariadicOption!T) this;
423             if (!derived) {
424                 error(format!"the element type of the inner value of option `%s` is not `%s` in `Option.choices`"(
425                         this.flags,
426                         T.stringof
427                 ));
428             }
429             return derived.choices(values);
430         }
431         else if (!this.isBoolean) {
432             auto derived = cast(ValueOption!T) this;
433             if (!derived) {
434                 error(format!"the type of the inner value of option `%s` is not `%s` in `Option.choices"(
435                         this.flags,
436                         T.stringof
437                 ));
438             }
439             return derived.choices(values);
440         }
441         else {
442             error(format!"connnot use `Option.choices` for option `%s` is bool option"(this.flags));
443         }
444         return this;
445     }
446 
447     /// set the choices of argument inner type
448     /** 
449      * 
450      * Params:
451      *   value = a choice value 
452      *   rest = the rest choice values
453      * Returns: `Self` for chain call
454      */
455     Self choices(T)(T value, T[] rest...) {
456         auto tmp = rest ~ value;
457         return choices(tmp);
458     }
459 
460     /// set the range of option inner value when the option innner value type is `int` or `double`
461     /// Params:
462     ///   min = the minimum
463     ///   max = the maximum
464     /// Returns: `Self` for chain call
465     Self rangeOf(T)(T min, T max) if (is(T == int) || is(T == double)) {
466         auto is_variadic = this.variadic;
467         if (is_variadic) {
468             auto derived = cast(VariadicOption!T) this;
469             if (!derived) {
470                 error(format!"the element type of the inner value of option `%s` is not `%s` in `Option.rangeOf`"(
471                         this.flags,
472                         T.stringof
473                 ));
474             }
475             return derived.rangeOf(min, max);
476         }
477         else {
478             auto derived = cast(ValueOption!T) this;
479             if (!derived) {
480                 error(format!"the type of the inner value of option `%s` is not `%s` in `Option.rangeOf"(
481                         this.flags,
482                         T.stringof
483                 ));
484             }
485             return derived.rangeOf(min, max);
486         }
487     }
488 
489     /// set the default value `true`, only for `BooleanOption`.
490     /// Returns: `Self` for chain call
491     Self defaultVal() {
492         auto derived = cast(BoolOption) this;
493         if (!derived) {
494             error(format!"connot cast the option `%s` to bool option using `Option.default()`"(
495                     this.flags));
496         }
497         derived.defaultVal(true);
498         return this;
499     }
500 
501     /// set the default value
502     /// Params:
503     ///   value = the value to be set as default value, `T` must satisfy `isBaseOptionValueType`
504     /// Returns: `Self`` for chain call
505     Self defaultVal(T)(T value) if (isBaseOptionValueType!T) {
506         static if (is(T == bool)) {
507             auto derived = cast(BoolOption) this;
508         }
509         else {
510             auto derived = cast(ValueOption!T) this;
511         }
512         if (!derived) {
513             error(
514                 format!"the value type is `%s` while the option `%s` inner type is not the type or related array type"(
515                     T.stringof, this.flags));
516         }
517         return derived.defaultVal(value);
518     }
519 
520     /// set the default value
521     /// Params:
522     ///   value = the first value to be set as default value, usually as the first element of default array value 
523     ///   rest = the rest values to be set as default value, `T` must satisfy `isBaseOptionValueType` and not `bool`
524     /// Returns: `Self`` for chain call
525     Self defaultVal(T)(T value, T[] rest...)
526             if (isBaseOptionValueType!T && !is(T == bool)) {
527         auto derived = cast(VariadicOption!T) this;
528         if (!derived) {
529             error(
530                 format!"the value type is `%s` while the option `%s` inner type is not the type or related array type"(
531                     T.stringof, this.flags));
532         }
533         return derived.defaultVal(value, rest);
534     }
535 
536     /// set the default value
537     /// Params:
538     ///   values = the value to be set as default value, usually as the default array value,
539     ///            `T` must satisfy `isBaseOptionValueType` and not `bool`
540     /// Returns: `Self`` for chain call
541     Self defaultVal(T)(in T[] values) if (isBaseOptionValueType!T && !is(T == bool)) {
542         if (!values.length) {
543             error("the values length cannot be zero using `Self defaultVal(T)(in T[] values)`");
544         }
545         return defaultVal(values[0], cast(T[]) values[1 .. $]);
546     }
547 
548 package:
549     Self configVal(T)(T value) if (isBaseOptionValueType!T) {
550         static if (is(T == bool)) {
551             auto derived = cast(BoolOption) this;
552         }
553         else {
554             auto derived = cast(ValueOption!T) this;
555         }
556         if (!derived) {
557             parsingError(
558                 format!"the value type is `%s` while the option `%s` inner type is not the type or related array type"(
559                     this.flags,
560                     T.stringof));
561         }
562         return derived.configVal(value);
563     }
564 
565     Self configVal(T)(T value, T[] rest...)
566             if (isBaseOptionValueType!T && !is(T == bool)) {
567         auto derived = cast(VariadicOption!T) this;
568         if (!derived) {
569             parsingError(
570                 format!"the value type is `%s` while the option `%s` inner type is not the type or related array type"(
571                     this.flags,
572                     T.stringof));
573         }
574         return derived.configVal(value, rest);
575     }
576 
577     Self configVal(T)(in T[] values) if (isBaseOptionValueType!T && !is(T == bool)) {
578         if (!values.length) {
579             parsingError(format!"the values length cannot be zero in option `%s`"(this.flags));
580         }
581         return configVal(values[0], cast(T[]) values[1 .. $]);
582     }
583 
584     // Self implyVal(OptionVariant value) {
585     //     this.innerImplyData = value;
586     //     return this;
587     // }
588 
589     Self implyVal(T)(T value) if (isBaseOptionValueType!T) {
590         this.innerImplyData = value;
591         return this;
592     }
593 
594     Self implyVal(T)(T value, T[] rest...)
595             if (isBaseOptionValueType!T && !is(T == bool)) {
596         this.innerImplyData = [value] ~ rest;
597         return this;
598     }
599 
600     Self implyVal(T)(T[] values) if (isBaseOptionValueType!T && !is(T == bool)) {
601         if (!values.length) {
602             parsingError(format!"the values length cannot be zero in option `%s`"(this.flags));
603         }
604         return implyVal(values[0], values[1 .. $]);
605     }
606 
607     // Self implyVal(T)(T value) if (isBaseOptionValueType!T) {
608     //     static if (is(T == bool)) {
609     //         auto derived = cast(BoolOption) this;
610     //     }
611     //     else {
612     //         auto derived = cast(ValueOption!T) this;
613     //     }
614     //     return derived.implyVal(value);
615     // }
616 
617     // Self implyVal(T)(T value, T[] rest...)
618     //         if (isBaseOptionValueType!T && !is(T == bool)) {
619     //     auto derived = cast(VariadicOption!T) this;
620     //     return derived.implyVal(value, rest);
621     // }
622 
623     // Self implyVal(T)(in T[] values) if (isBaseOptionValueType!T && !is(T == bool)) {
624     //     assert(values.length > 0);
625     //     return implyVal(values[0], cast(T[]) values[1 .. $]);
626     // }
627 public:
628 
629     /// preset the value used for value option
630     /// Params:
631     ///   value = the value to be preset
632     /// Returns: `Self` for chain call
633     Self preset(T)(T value) if (isBaseOptionValueType!T && !is(T == bool)) {
634         auto derived = cast(ValueOption!T) this;
635         if (!derived) {
636             error(
637                 format!"the value type is `%s` while the option `%s` inner type is not the type or related array type"(
638                     T.stringof, this.flags));
639         }
640         return derived.preset(value);
641     }
642 
643     /// preset the value used for variadic option
644     /// Params:
645     ///   value = the first value to be preset as an element of array inner value
646     ///   rest = the rest value to be preset
647     /// Returns: `Self` for chain call
648     Self preset(T)(T value, T[] rest...)
649             if (isBaseOptionValueType!T && !is(T == bool)) {
650         auto derived = cast(VariadicOption!T) this;
651         if (!derived) {
652             error(
653                 format!"the value type is `%s` while the option `%s` inner type is not the type or related array type"(
654                     T.stringof, this.flags));
655         }
656         return derived.preset(value, rest);
657     }
658 
659     /// preset the value used for variadic option
660     /// Params:
661     ///   values = the first value to be preset
662     /// Returns: `Self` for chain call
663     Self preset(T)(in T[] values) if (isBaseOptionValueType!T && !is(T == bool)) {
664         if (!values.length) {
665             error("the values length cannot be zero using `Self preset(T)(in T[] values)`");
666         }
667         return preset(values[0], cast(T[]) values[1 .. $]);
668     }
669 
670     /// get inner value in the specified type, `T` usually is among `OptionValueSeq`
671     /// Returns: the result value
672     T get(T)() const if (isBaseOptionValueType!T && !is(T == bool)) {
673         assert(isValueData);
674         auto derived_1 = cast(ValueOption!T) this;
675         auto derived_2 = cast(VariadicOption!T) this;
676         if (derived_1) {
677             return derived_1.get!T;
678         }
679         if (derived_2) {
680             return derived_2.get!T;
681         }
682         error(format!"connot get the value of type `%s` from option `%s`"(
683                 T.stringof,
684                 this.flags
685         ));
686         return T.init;
687     }
688 
689     /// get inner value in the specified type, `T` usually is among `OptionValueSeq`
690     /// Returns: the result value
691     T get(T : bool)() const {
692         assert(!isValueData);
693         if (isValueData) {
694             error(format!"connot get the value of type `%s` from option `%s`"(
695                     T.stringof,
696                     this.flags
697             ));
698         }
699         return this.innerBoolData;
700     }
701 
702     /// get inner value in the specified type, `T` usually is among `OptionValueSeq`
703     /// Returns: the result value
704     T get(T)() const
705     if (!is(ElementType!T == void) && isBaseOptionValueType!(ElementType!T)) {
706         alias Ele = ElementType!T;
707         static assert(!is(Ele == bool));
708         assert(isValueData);
709         auto derived = cast(VariadicOption!Ele) this;
710         if (!derived)
711             error(format!"connot get the value of type `%s` from option `%s`"(
712                     Ele.stringof,
713                     this.flags
714             ));
715         return derived.get!T;
716     }
717 
718     /// set the parsing function using for transforming from `string` to `T`
719     /// alias fn = `T fn(string v)`, where `T` is the inner value type 
720     /// Returns: `Self` for chain call
721     Self parser(alias fn)() {
722         alias T = typeof({ string v; return fn(v); }());
723         static assert(isBaseOptionValueType!T && !is(T == bool));
724         auto derived_1 = cast(ValueOption!T) this;
725         auto derived_2 = cast(VariadicOption!T) this;
726 
727         if (derived_1) {
728             derived_1.parseFn = fn;
729             return derived_1;
730         }
731         if (derived_2) {
732             derived_2.parseFn = fn;
733             return derived_2;
734         }
735         error(format!"connot set the parser fn `%s` to option `%s`"(
736                 typeof(fn)
737                 .stringof,
738                 this.flags
739         ));
740         return this;
741     }
742 
743     /// set the process function for processing the value parsed by innner parsing function
744     /// alias fn = `T fn(T v)`, where `T` is the inner value type
745     /// Returns: `Self` for chain call
746     Self processor(alias fn)() {
747         alias return_t = ReturnType!fn;
748         alias param_t = Parameters!fn;
749         static assert(param_t.length == 1 && is(return_t == param_t[0]));
750         static assert(isBaseOptionValueType!return_t && !is(return_t == bool));
751         alias T = return_t;
752         auto derived_1 = cast(ValueOption!T) this;
753         auto derived_2 = cast(VariadicOption!T) this;
754         if (derived_1) {
755             derived_1.processFn = fn;
756             return derived_1;
757         }
758         if (derived_2) {
759             derived_2.processFn = fn;
760             return derived_2;
761         }
762         error(format!"connot set the processor fn `%s` to option `%s`"(
763                 typeof(fn)
764                 .stringof,
765                 this.flags
766         ));
767         return this;
768     }
769 
770     /// set the reduce process function for reducely processing the value parsed by innner parsing function or process function
771     /// mainly used in variadic option
772     /// alias fn = `T fn(T v, T t)`, where `T` is the inner value type
773     /// Returns: `Self` for chain call
774     Self processReducer(alias fn)() {
775         alias return_t = ReturnType!fn;
776         alias param_t = Parameters!fn;
777         static assert(allSameType!(return_t, param_t) && param_t.length == 2);
778         alias T = return_t;
779         static assert(isBaseOptionValueType!T && !is(T == bool));
780         auto derived = cast(VariadicOption!T) this;
781         if (!derived) {
782             error(format!"connot set the process reducer fn `%s` to option `%s`"(
783                     typeof(fn)
784                     .stringof,
785                     this.flags
786             ));
787         }
788         derived.processReduceFn = fn;
789         return derived;
790     }
791 
792     /// get the type in `string` type, used for help command and help option
793     /// start with `type: `
794     string typeStr() const {
795         return "";
796     }
797 
798     /// get the default value in `string` type, start with `default: `
799     string defaultValStr() const {
800         return "";
801     }
802 
803     /// get the preset value in `string` type, start with `preset: `
804     string presetStr() const {
805         return "";
806     }
807 
808     /// get the env variable's key
809     string envValStr() const {
810         if (this.envKey == "")
811             return this.envKey;
812         return "env: " ~ this.envKey;
813     }
814 
815     /// get the imply map in`string` type, start with `imply `
816     string implyOptStr() const {
817         if (!implyMap)
818             return "";
819         auto str = "imply { ";
820         foreach (key, val; implyMap) {
821             auto captures = matchFirst(key, PTN_IMPLYMAPKEY);
822             auto name = captures[1];
823             auto is_variadic = captures[4] == "[]";
824             auto type_str = captures[2];
825             assert(name != "");
826             string value;
827             if (!is_variadic) {
828                 if (type_str == "bool")
829                     value = val.get!bool
830                         .to!string;
831                 if (type_str == "string")
832                     value = val.get!string;
833                 if (type_str == "int")
834                     value = val.get!int
835                         .to!string;
836                 if (type_str == "double")
837                     value = val.get!double
838                         .to!string;
839             }
840             else {
841                 if (type_str == "string[]")
842                     value = val.get!(string[])
843                         .to!string;
844                 if (type_str == "int[]")
845                     value = val.get!(int[])
846                         .to!string;
847                 if (type_str == "double[]")
848                     value = val.get!(double[])
849                         .to!string;
850             }
851             str ~= (name ~ ": " ~ value ~ ", ");
852         }
853         str ~= "}";
854         return str;
855     }
856 
857     /// get the list of confilt option in `string` type, start with `conflict with `
858     string conflictOptStr() const {
859         if (this.conflictsWith.empty)
860             return "";
861         auto str = "conflict with [ ";
862         conflictsWith.each!((name) { str ~= (name ~ ", "); });
863         return str ~ "]";
864     }
865 
866     /// get the choices in `string` type, start with `choices: `
867     string choicesStr() const {
868         return "";
869     }
870 
871     /// get the range in `string` type, start with `range: `
872     string rangeOfStr() const {
873         return "";
874     }
875 }
876 
877 /// create `bool` option
878 /// Params:
879 ///   flags = the flag like `-f, --flag`, `--flag`, must include long flag
880 ///   desc = the description of option
881 /// Returns: a `bool` option
882 Option createOption(string flags, string desc = "") {
883     return createOption!bool(flags, desc);
884 }
885 
886 /// create `bool` option
887 Option createOption(T : bool)(string flags, string desc = "") {
888     auto opt = splitOptionFlags(flags);
889     bool is_bool = opt.valueFlag == "";
890     if (!is_bool) {
891         error("the value flag must not exist using `createOption!bool`");
892     }
893     return new BoolOption(flags, desc);
894 }
895 
896 /// create value/variadic option, whose inner type or inner value's element type
897 /// `T` must satisfy `isBaseOptionValueType` and not `bool`
898 /// Params:
899 ///   flags = the flag like `-f, --flag <name>`, `--flag [name...]`
900 ///   desc = the description of option
901 /// Returns: a value/variadic option
902 Option createOption(T)(string flags, string desc = "")
903         if (!is(T == bool) && isBaseOptionValueType!T) {
904     auto opt = splitOptionFlags(flags);
905     bool is_bool = opt.valueFlag == "";
906     bool is_variadic = (is_bool || opt.valueFlag[$ - 2] != '.') ? false : true;
907     if (is_bool) {
908         error("the value flag must exist using `createOption!T`, while `T` is not bool");
909     }
910     if (is_variadic) {
911         return new VariadicOption!T(flags, desc);
912     }
913     else {
914         return new ValueOption!T(flags, desc);
915     }
916 }
917 
918 /// create varidic option, whose inner values's element type `T` must satisfy `isBaseOptionValueType` and not `bool`
919 /// Params:
920 ///   flags = the flag like `-f, --flag [name...]`, `--flag <name...>`
921 ///   desc = the description of option
922 /// Returns: a variadic option
923 Option createOption(T : U[], U)(string flags, string desc = "")
924         if (!is(U == bool) && isBaseOptionValueType!U) {
925     return createOption!U(flags, desc);
926 }
927 
928 NegateOption createNOption(string flags, string desc = "") {
929     return new NegateOption(flags, desc);
930 }
931 
932 unittest {
933     Option[] opts = [
934         new BoolOption("-m, --mixed", ""),
935         new ValueOption!int("-m, --mixed [dig]", ""),
936         new VariadicOption!int("-m, --mixed <dig...>", "")
937     ];
938 
939     Option opt_1 = opts[0];
940     Option opt_2 = opts[1];
941     Option opt_3 = opts[2];
942 
943     opt_1.defaultVal().implyVal(false);
944     opt_2.defaultVal().parser!((string v) => v.to!(int)).cliVal("123");
945     opt_3.rangeOf(11, 150);
946     opt_3.choices(12, 13, 14, 15, 123);
947     opt_3.defaultVal([123]);
948     opt_3.parser!((string v) => v.to!(int));
949     opt_3.processor!((int a) => a + 1);
950     opt_3.cliVal("12", "13", "14");
951 
952     opt_1.initialize();
953     opt_2.found = true;
954     opt_2.initialize();
955     opt_3.found = true;
956     opt_3.initialize();
957 
958     assert(opt_1.get!bool == false);
959     assert(opt_2.get!int == 123);
960     assert(opt_3.get!(int[]) == [13, 14, 15]);
961 }
962 
963 unittest {
964     Option[] opts = [
965         createOption!bool("-m, --mixed").defaultVal.implyVal(false),
966         createOption!int("-m, --mixed [dig]", "")
967             .defaultVal.parser!((string v) => v.to!(int)).cliVal("123"),
968         createOption!int("-m, --mixed <dig...>", "").defaultVal([123])
969             .parser!((string v) => v.to!(int))
970             .processor!((int a) => a + 1)
971             .cliVal("12", "13", "14")
972     ];
973     opts[1].found = opts[2].found = true;
974     opts.each!(v => v.initialize);
975 
976     assert(opts[0].get!bool == false);
977     assert(opts[1].get!int == 123);
978     assert(opts[2].get!(int[]) == [13, 14, 15]);
979 }
980 
981 package class BoolOption : Option {
982     // Nullable!bool implyArg;
983     Nullable!bool configArg;
984     Nullable!bool defaultArg;
985 
986     this(string flags, string description) {
987         super(flags, description);
988         if (!this.isBoolean || this.variadic) {
989             error(
990                 "the value flag must not exist and the flag cannot contain `...` using `new BoolOption`");
991         }
992         // this.implyArg = null;
993         this.configArg = null;
994         this.defaultArg = null;
995     }
996 
997     alias Self = typeof(this);
998 
999     Self defaultVal(bool value) {
1000         this.defaultArg = value;
1001         return this;
1002     }
1003 
1004     Self configVal(bool value = true) {
1005         this.configArg = value;
1006         return this;
1007     }
1008 
1009     override Self implyVal(OptionVariant value) {
1010         alias test_bool = visit!((bool v) => true, v => false);
1011         if (!test_bool(value))
1012             parsingError(format!"the imply value must be a bool value in option %s"(this.flags));
1013         this.innerImplyData = value;
1014         return this;
1015     }
1016 
1017     // Self implyVal(bool value) {
1018     //     this.implyArg = value;
1019     //     return this;
1020     // }
1021 
1022     // override Self implyVal() {
1023     //     this.implyArg = true;
1024     //     return this;
1025     // }
1026 
1027     @property
1028     override bool isValid() const {
1029         return this.found || !this.configArg.isNull ||
1030             !this.defaultArg.isNull || !this.innerImplyData.isNull;
1031     }
1032 
1033     override Self initialize() {
1034         if (this.settled)
1035             return this;
1036         if (!this.isValid) {
1037             parsingError(format!"the option `%s` must valid before initializing"(this.name));
1038         }
1039         this.settled = true;
1040         if (this.found) {
1041             this.innerBoolData = (true);
1042             this.source = Source.Cli;
1043             return this;
1044         }
1045         // if (!this.implyArg.isNull) {
1046         //     this.innerBoolData = this.implyArg.get;
1047         //     this.source = Source.Imply;
1048         //     return this;
1049         // }
1050         if (!this.configArg.isNull) {
1051             this.innerBoolData = this.configArg.get;
1052             this.source = Source.Config;
1053             return this;
1054         }
1055         if (!this.innerImplyData.isNull) {
1056             this.innerBoolData = this.innerImplyData.get!bool;
1057             this.source = Source.Imply;
1058             return this;
1059         }
1060         if (!this.defaultArg.isNull) {
1061             this.innerBoolData = this.defaultArg.get;
1062             this.source = Source.Default;
1063             return this;
1064         }
1065         return this;
1066     }
1067 
1068     @property
1069     override OptionVariant get() const {
1070         assert(this.settled);
1071         return OptionVariant(this.innerBoolData);
1072     }
1073 
1074     @property
1075     bool get(T : bool)() const {
1076         assert(this.settled);
1077         return this.innerBoolData;
1078     }
1079 
1080     override string typeStr() const {
1081         return "type: " ~ "bool";
1082     }
1083 
1084     override string defaultValStr() const {
1085         if (defaultArg.isNull)
1086             return "";
1087         else
1088             return "default: " ~ defaultArg.get!bool
1089                 .to!string;
1090     }
1091 }
1092 
1093 // unittest {
1094 //     auto bopt = new BoolOption("-m, --mixed", "").implyVal(false).configVal.defaultVal;
1095 //     bopt.initialize;
1096 //     bool value = bopt.get!bool;
1097 //     assert(!value);
1098 // }
1099 
1100 package class ValueOption(T) : Option {
1101     static assert(isBaseOptionValueType!T && !is(T == bool));
1102 
1103     Nullable!T cliArg;
1104     Nullable!T envArg;
1105     // Nullable!(T, bool) implyArg;
1106     Nullable!(T) configArg;
1107     Nullable!(T) defaultArg;
1108 
1109     Nullable!(T, bool) presetArg;
1110 
1111     T innerValueData;
1112     // bool innerBoolData;
1113 
1114     // bool isValueData;
1115 
1116     ParseArgFn!T parseFn;
1117     ProcessArgFn!T processFn;
1118 
1119     T[] argChoices;
1120 
1121     static if (is(T == int) || is(T == double)) {
1122         T _min = int.min;
1123         T _max = int.max;
1124     }
1125 
1126     this(string flags, string description) {
1127         super(flags, description);
1128         if (this.isBoolean || this.variadic) {
1129             error(
1130                 "the value flag must exist and the flag cannot contain `...` using `new ValueOption!T`");
1131         }
1132         this.cliArg = null;
1133         this.envArg = null;
1134         // this.implyArg = null;
1135         this.configArg = null;
1136         this.defaultArg = null;
1137         this.innerBoolData = false;
1138         innerValueData = T.init;
1139         this.argChoices = [];
1140         this.isValueData = true;
1141         this.parseFn = (string v) => to!T(v);
1142         this.processFn = v => v;
1143         if (isRequired)
1144             this.presetArg = null;
1145         else if (isOptional)
1146             this.presetArg = true;
1147         else
1148             throw new CMDLineError;
1149     }
1150 
1151     alias Self = typeof(this);
1152 
1153     Self choices(T[] values...) {
1154         foreach (index_i, i; values) {
1155             foreach (j; values[index_i + 1 .. $]) {
1156                 if (i == j) {
1157                     error(format!"the element value of choices can not be equal in option `%s`, the values is: `%s`"(
1158                             this.flags,
1159                             values.to!string));
1160                 }
1161             }
1162         }
1163         static if (is(T == int) || is(T == double)) {
1164             if (values.any!(val => val < this._min || val > this._max)) {
1165                 error(format!"the element value of choices cannot be out of %s in option `%s`, the values is: `%s`"(
1166                         this.rangeOfStr(), this.flags, values.to!string
1167                 ));
1168             }
1169         }
1170         this.argChoices = values;
1171         return this;
1172     }
1173 
1174     void _checkVal(in T value) const {
1175         if (!this.argChoices.empty) {
1176             if (!this.argChoices.count(value)) {
1177                 parsingError(format!"the value cannot be out of %s in option `%s`, the value is: `%s`"(
1178                         this.choicesStr(),
1179                         this.flags,
1180                         value.to!string
1181                 ));
1182             }
1183         }
1184         static if (is(T == int) || is(T == double)) {
1185             if (value < this._min || value > this._max) {
1186                 parsingError(format!"the value cannot be out of %s in option `%s`, the value is: `%s`"(
1187                         this.rangeOfStr(),
1188                         this.flags,
1189                         value.to!string
1190                 ));
1191             }
1192         }
1193     }
1194 
1195     static if (is(T == int) || is(T == double)) {
1196         Self rangeOf(T min, T max) {
1197             assert(max > min);
1198             this._min = min;
1199             this._max = max;
1200             return this;
1201         }
1202 
1203         Self choices(string[] values...) {
1204             try {
1205                 auto fn = this.parseFn;
1206                 auto arr = values.map!fn.array;
1207                 return this.choices(arr);
1208             }
1209             catch (ConvException e) {
1210                 error(format!"on option `%s` cannot convert the input `%s` to type `%s`"(
1211                         this.name,
1212                         values.to!string,
1213                         T.stringof
1214                 ));
1215             }
1216             return this;
1217         }
1218 
1219         override string rangeOfStr() const {
1220             if (_min == int.min && _max == int.max)
1221                 return "";
1222             return "range: " ~ _min.to!string ~ " ~ " ~ _max.to!string;
1223         }
1224     }
1225 
1226     Self defaultVal(T value) {
1227         _checkVal(value);
1228         this.defaultArg = value;
1229         return this;
1230     }
1231 
1232     // override Self defaultVal() {
1233     //     if (!isOptional) {
1234     //         error("the option must be optional using `Self defaultVal()`");
1235     //     }
1236     //     this.defaultArg = true;
1237     //     return this;
1238     // }
1239 
1240     Self configVal(T value) {
1241         _checkVal(value);
1242         this.configArg = value;
1243         return this;
1244     }
1245 
1246     // override Self configVal() {
1247     //     if (!isOptional) {
1248     //         parsingError("the option must be optional using `Self configVal()`");
1249     //     }
1250     //     this.configArg = true;
1251     //     return this;
1252     // }
1253 
1254     override Self implyVal(OptionVariant value) {
1255         alias test_t = visit!((T v) => true, v => false);
1256         if (!test_t(value)) {
1257             parsingError(format!"the value type must be %s in option `%s`"(T.stringof, this.flags));
1258         }
1259         _checkVal(value.get!T);
1260         this.innerImplyData = value;
1261         return this;
1262     }
1263 
1264     // Self implyVal(T value) {
1265     //     _checkVal(value);
1266     //     this.implyArg = value;
1267     //     return this;
1268     // }
1269 
1270     // override Self implyVal() {
1271     //     assert(isOptional);
1272     //     this.implyArg = true;
1273     //     return this;
1274     // }
1275 
1276     override Self cliVal(string value, string[] rest...) {
1277         assert(rest.length == 0);
1278         try {
1279             auto tmp = this.parseFn(value);
1280             _checkVal(tmp);
1281             this.cliArg = tmp;
1282         }
1283         catch (ConvException e) {
1284             parsingError(format!"on option `%s` cannot convert the input `%s` to type `%s`"(
1285                     this.name,
1286                     value,
1287                     T.stringof
1288             ));
1289         }
1290         return this;
1291     }
1292 
1293     override Self envVal() {
1294         if (this.envStr.empty)
1295             return this;
1296         try {
1297             auto tmp = this.parseFn(this.envStr);
1298             _checkVal(tmp);
1299             this.envArg = tmp;
1300         }
1301         catch (ConvException e) {
1302             parsingError(format!"on option `%s` cannot convert the input `%s` to type `%s`"(
1303                     this.name,
1304                     this.envStr,
1305                     T.stringof
1306             ));
1307         }
1308 
1309         return this;
1310     }
1311 
1312     Self preset(T value) {
1313         if (!isOptional) {
1314             error("the option must be optional using `Self preset()`");
1315         }
1316         _checkVal(value);
1317         this.presetArg = value;
1318         return this;
1319     }
1320 
1321     override Self preset() {
1322         if (!isOptional) {
1323             error("the option must be optional using `Self preset()`");
1324         }
1325         this.presetArg = true;
1326         return this;
1327     }
1328 
1329     @property
1330     override bool isValid() const {
1331         return this.found ? (!this.presetArg.isNull || !this.cliArg.isNull) : (!this.envArg.isNull
1332                 || !this.configArg.isNull || !this.defaultArg.isNull || !this.innerImplyData.isNull);
1333     }
1334 
1335     override Self initialize() {
1336         if (this.settled)
1337             return this;
1338         if (!this.isValid) {
1339             parsingError(format!"the option `%s` must valid before initializing"(this.name));
1340         }
1341         this.settled = true;
1342         alias test_bool = visit!((bool v) => true, (v) => false);
1343         alias test_t = visit!((T v) => true, (v) => false);
1344         if (this.found) {
1345             if (!this.cliArg.isNull) {
1346                 this.innerValueData = this.cliArg.get!T;
1347                 this.source = Source.Cli;
1348                 return this;
1349             }
1350             if (test_bool(this.presetArg)) {
1351                 this.isValueData = false;
1352                 this.innerBoolData = this.presetArg.get!bool;
1353             }
1354             if (test_t(this.presetArg)) {
1355                 this.innerValueData = this.presetArg.get!T;
1356             }
1357             this.source = Source.Preset;
1358             return this;
1359         }
1360         if (!this.envArg.isNull) {
1361             this.innerValueData = this.envArg.get!T;
1362             this.source = Source.Env;
1363             return this;
1364         }
1365         // if (!this.implyArg.isNull) {
1366         //     if (test_bool(this.implyArg)) {
1367         //         this.isValueData = false;
1368         //         this.innerBoolData = this.implyArg.get!bool;
1369         //     }
1370         //     if (test_t(this.implyArg))
1371         //         this.innerValueData = this.implyArg.get!T;
1372         //     this.source = Source.Imply;
1373         //     return this;
1374         // }
1375         if (!this.configArg.isNull) {
1376             this.innerValueData = this.configArg.get!T;
1377             this.source = Source.Config;
1378             return this;
1379         }
1380         if (!this.innerImplyData.isNull) {
1381             this.innerValueData = this.innerImplyData.get!T;
1382             this, source = Source.Imply;
1383             return this;
1384         }
1385         if (!this.defaultArg.isNull) {
1386             this.innerValueData = this.defaultArg.get!T;
1387             this.source = Source.Default;
1388             return this;
1389         }
1390         return this;
1391     }
1392 
1393     @property
1394     override OptionVariant get() const {
1395         assert(this.settled);
1396         return isValueData ? OptionVariant(this.get!T) : OptionVariant(this.get!bool);
1397     }
1398 
1399     @property
1400     bool get(U : bool)() const {
1401         assert(this.settled);
1402         assert(!this.isValueData);
1403         return this.innerBoolData;
1404     }
1405 
1406     @property
1407     T get(U : T)() const {
1408         assert(this.settled);
1409         assert(this.isValueData);
1410         auto fn = this.processFn;
1411         T tmp = fn(this.innerValueData);
1412         return tmp;
1413     }
1414 
1415     override string typeStr() const {
1416         return "type: " ~ (this.isOptional ? T.stringof ~ "|true" : T.stringof);
1417     }
1418 
1419     override string defaultValStr() const {
1420         if (defaultArg.isNull)
1421             return "";
1422         return "default: " ~ defaultArg.get!T
1423             .to!string;
1424     }
1425 
1426     override string presetStr() const {
1427         if (presetArg.isNull)
1428             return "";
1429         alias test_bool = visit!((bool v) => true, (const T v) => false);
1430         alias test_t = visit!((const T v) => true, (bool v) => false);
1431         if (test_bool(presetArg))
1432             return "preset: " ~ presetArg.get!bool
1433                 .to!string;
1434         if (test_t(presetArg))
1435             return "preset: " ~ presetArg.get!T
1436                 .to!string;
1437         throw new CMDLineError;
1438     }
1439 
1440     override string choicesStr() const {
1441         if (argChoices.empty)
1442             return "";
1443         return "choices " ~ argChoices.to!string;
1444     }
1445 }
1446 
1447 unittest {
1448     auto vopt = new ValueOption!int("-m, --mixed [raw]", "");
1449     vopt.defaultVal(123);
1450     vopt.preset(125);
1451     vopt.found = true;
1452     vopt.initialize;
1453     assert(vopt.get == 125);
1454 }
1455 
1456 package class VariadicOption(T) : Option {
1457     static assert(isBaseOptionValueType!T && !is(T == bool));
1458 
1459     Nullable!(T[]) cliArg;
1460     Nullable!(T[]) envArg;
1461     // Nullable!(T[], bool) implyArg;
1462     Nullable!(T[]) configArg;
1463     Nullable!(T[]) defaultArg;
1464 
1465     Nullable!(T[], bool) presetArg;
1466 
1467     T[] innerValueData;
1468     // bool innerBoolData;
1469 
1470     // bool isValueData;
1471 
1472     ParseArgFn!T parseFn;
1473     ProcessArgFn!T processFn;
1474 
1475     T[] argChoices;
1476 
1477     ProcessReduceFn!T processReduceFn;
1478 
1479     static if (is(T == int) || is(T == double)) {
1480         T _min = int.min;
1481         T _max = int.max;
1482     }
1483 
1484     this(string flags, string description) {
1485         super(flags, description);
1486         if (this.isBoolean || !this.variadic) {
1487             error(
1488                 "the value flag must exist and the flag must contain `...` using `new VariadicOption!T`");
1489         }
1490         this.cliArg = null;
1491         this.envArg = null;
1492         // this.implyArg = null;
1493         this.configArg = null;
1494         this.defaultArg = null;
1495         this.innerBoolData = false;
1496         this.innerValueData = [];
1497         this.isValueData = true;
1498         this.argChoices = [];
1499         this.parseFn = (string v) => to!T(v);
1500         this.processFn = v => v;
1501         this.processReduceFn = null;
1502         if (isRequired)
1503             this.presetArg = null;
1504         else if (isOptional)
1505             this.presetArg = true;
1506         else
1507             throw new CMDLineError;
1508     }
1509 
1510     alias Self = typeof(this);
1511 
1512     Self choices(T[] values...) {
1513         foreach (index_i, i; values) {
1514             foreach (j; values[index_i + 1 .. $]) {
1515                 if (i == j) {
1516                     error(format!"the element value of choices can not be equal in option `%s`, the values is: `%s`"(
1517                             this.flags,
1518                             values.to!string));
1519                 }
1520             }
1521         }
1522         static if (is(T == int) || is(T == double)) {
1523             if (values.any!(val => val < this._min || val > this._max)) {
1524                 error(format!"the element value of choices cannot be out of %s in option `%s`, the values is: `%s`"(
1525                         this.rangeOfStr(), this.flags, values.to!string
1526                 ));
1527             }
1528         }
1529         this.argChoices = values;
1530         return this;
1531     }
1532 
1533     void _checkVal_impl(in T value) const {
1534         if (!this.argChoices.empty) {
1535             if (!this.argChoices.count(value)) {
1536                 parsingError(format!"the value cannot be out of %s in option `%s`, the value is: `%s`"(
1537                         this.choicesStr(),
1538                         this.flags,
1539                         value.to!string
1540                 ));
1541             }
1542         }
1543         static if (is(T == int) || is(T == double)) {
1544             if (value < this._min || value > this._max) {
1545                 parsingError(format!"the value cannot be out of %s in option `%s`, the value is: `%s`"(
1546                         this.rangeOfStr(),
1547                         this.flags,
1548                         value.to!string
1549                 ));
1550             }
1551         }
1552     }
1553 
1554     void _checkVal(T value, T[] rest...) const {
1555         _checkVal_impl(value);
1556         foreach (T val; rest) {
1557             _checkVal_impl(val);
1558         }
1559     }
1560 
1561     void _checkVal(T[] values) const {
1562         assert(values.length > 0);
1563         foreach (T val; values) {
1564             _checkVal_impl(val);
1565         }
1566     }
1567 
1568     static if (is(T == int) || is(T == double)) {
1569         Self rangeOf(T min, T max) {
1570             assert(max > min);
1571             this._min = min;
1572             this._max = max;
1573             return this;
1574         }
1575 
1576         Self choices(string[] values...) {
1577             try {
1578                 auto fn = this.parseFn;
1579                 auto arr = values.map!fn.array;
1580                 return this.choices(arr);
1581             }
1582             catch (ConvException e) {
1583                 error(format!"on option `%s` cannot convert the input `%s` to type `%s`"(
1584                         this.name,
1585                         values.to!string,
1586                         T.stringof
1587                 ));
1588             }
1589             return this;
1590         }
1591 
1592         override string rangeOfStr() const {
1593             if (_min == int.min && _max == int.max)
1594                 return "";
1595             return "range: " ~ _min.to!string ~ " ~ " ~ _max.to!string;
1596         }
1597     }
1598 
1599     Self defaultVal(T value, T[] rest...) {
1600         auto tmp = [value] ~ rest;
1601         _checkVal(tmp);
1602         this.defaultArg = tmp;
1603         return this;
1604     }
1605 
1606     // override Self defaultVal() {
1607     //     if (!isOptional) {
1608     //         error("the option must be optional using `Self defaultVal()`");
1609     //     }
1610     //     this.defaultArg = true;
1611     //     return this;
1612     // }
1613 
1614     Self configVal(T value, T[] rest...) {
1615         auto tmp = [value] ~ rest;
1616         _checkVal(tmp);
1617         this.configArg = tmp;
1618         return this;
1619     }
1620 
1621     // override Self configVal() {
1622     //     if (!isOptional) {
1623     //         parsingError("the option must be optional using `Self configVal()`");
1624     //     }
1625     //     this.configArg = true;
1626     //     return this;
1627     // }
1628 
1629     override Self implyVal(OptionVariant value) {
1630         alias test_t = visit!((T[] v) => true, (v) => false);
1631         if (!test_t(value)) {
1632             parsingError(format!"the value type must be %s in option `%s`"((T[])
1633                     .stringof, this.flags));
1634         }
1635         _checkVal(value.get!(T[]));
1636         this.innerImplyData = value;
1637         return this;
1638     }
1639 
1640     // Self implyVal(T value, T[] rest...) {
1641     //     auto tmp = [value] ~ rest;
1642     //     _checkVal(tmp);
1643     //     this.implyArg = tmp;
1644     //     return this;
1645     // }
1646 
1647     // override Self implyVal() {
1648     //     assert(isOptional);
1649     //     this.implyArg = true;
1650     //     return this;
1651     // }
1652 
1653     override Self cliVal(string value, string[] rest...) {
1654         try {
1655             string[] tmp = [value] ~ rest;
1656             auto fn = parseFn;
1657             auto xtmp = tmp.map!(fn).array;
1658             _checkVal(xtmp);
1659             if (this._isMerge) {
1660                 cliArg = (cliArg.isNull ? [] : cliArg.get!(T[])) ~ xtmp;
1661             }
1662             else
1663                 this.cliArg = xtmp;
1664         }
1665         catch (ConvException e) {
1666             parsingError(format!"on option `%s` cannot convert the input `%s` to type `%s`"(
1667                     this.name,
1668                     ([value] ~ rest).to!string,
1669                     T.stringof
1670             ));
1671         }
1672         return this;
1673     }
1674 
1675     override Self envVal() {
1676         if (this.envStr.empty)
1677             return this;
1678         try {
1679             string[] str_arr = split(this.envStr, regex(`;`)).filter!(v => v != "").array;
1680             auto fn = parseFn;
1681             auto tmp = str_arr.map!(fn).array;
1682             _checkVal(tmp);
1683             this.envArg = tmp;
1684         }
1685         catch (ConvException e) {
1686             parsingError(format!"on option `%s` cannot convert the input `%s` to type `%s`"(
1687                     this.name,
1688                     this.envStr,
1689                     T.stringof
1690             ));
1691         }
1692         return this;
1693     }
1694 
1695     Self preset(T value, T[] rest...) {
1696         if (!isOptional) {
1697             error("the option must be optional using `Self preset()`");
1698         }
1699         auto tmp = [value] ~ rest;
1700         _checkVal(tmp);
1701         this.presetArg = tmp;
1702         return this;
1703     }
1704 
1705     override Self preset() {
1706         if (!isOptional) {
1707             error("the option must be optional using `Self preset()`");
1708         }
1709         this.presetArg = true;
1710         return this;
1711     }
1712 
1713     @property
1714     override bool isValid() const {
1715         return this.found ? (!this.presetArg.isNull || !this.cliArg.isNull) : (!this.envArg.isNull || !this
1716                 .configArg.isNull || !this.defaultArg.isNull || !this.innerImplyData.isNull);
1717     }
1718 
1719     override Self initialize() {
1720         if (this.settled)
1721             return this;
1722         if (!this.isValid) {
1723             parsingError(format!"the option `%s` must valid before initializing"(this.name));
1724         }
1725         this.settled = true;
1726         alias test_bool = visit!((bool v) => true, (v) => false);
1727         alias test_t = visit!((T[] v) => true, (v) => false);
1728         this.innerValueData = [];
1729         if (this.found) {
1730             if (!this.cliArg.isNull) {
1731                 this.innerValueData = this.cliArg.get!(T[]);
1732                 this.source = Source.Cli;
1733                 if (_isMerge)
1734                     goto _env_ini_;
1735                 return this;
1736             }
1737             if (test_bool(this.presetArg)) {
1738                 this.isValueData = false;
1739                 this.innerBoolData = this.presetArg.get!bool;
1740             }
1741             if (test_t(this.presetArg)) {
1742                 this.innerValueData = this.presetArg.get!(T[]);
1743             }
1744             this.source = Source.Preset;
1745             return this;
1746         }
1747     _env_ini_:
1748         if (!this.envArg.isNull) {
1749             if (this._isMerge) {
1750                 this.innerValueData ~= this.envArg.get!(T[]);
1751                 this.source = cast(int) this.source < cast(int) Source.Env ?
1752                     this.source : Source.Env;
1753                 goto _config_ini_;
1754             }
1755             this.innerValueData = this.envArg.get!(T[]);
1756             this.source = Source.Env;
1757             return this;
1758         }
1759     _config_ini_:
1760         if (!this.configArg.isNull) {
1761             if (this._isMerge) {
1762                 this.innerValueData ~= this.configArg.get!(T[]);
1763                 this.source = cast(int) this.source < cast(int) Source.Config ?
1764                     this.source : Source.Config;
1765                 goto _imply_ini_;
1766             }
1767             this.innerValueData = this.configArg.get!(T[]);
1768             this.source = Source.Config;
1769             return this;
1770         }
1771     _imply_ini_:
1772         if (!this.innerImplyData.isNull) {
1773             if (this._isMerge) {
1774                 this.innerValueData ~= this.innerImplyData.get!(T[]);
1775                 this.source = cast(int) this.source < cast(int) Source.Imply ?
1776                     this.source : Source.Imply;
1777                 goto _default_ini_;
1778             }
1779             this.innerValueData = this.innerImplyData.get!(T[]);
1780             this.source = Source.Imply;
1781             return this;
1782         }
1783     _default_ini_:
1784         if (!this.defaultArg.isNull) {
1785             if (this._isMerge) {
1786                 this.innerValueData ~= this.defaultArg.get!(T[]);
1787                 this.source = cast(int) this.source < cast(int) Source.Default ?
1788                     this.source : Source.Default;
1789                 return this;
1790             }
1791             this.innerValueData = this.defaultArg.get!(T[]);
1792             this.source = Source.Default;
1793             return this;
1794         }
1795         return this;
1796     }
1797 
1798     @property
1799     override OptionVariant get() const {
1800         assert(this.settled);
1801         return isValueData ? OptionVariant(this.get!(T[])) : OptionVariant(this.get!bool);
1802     }
1803 
1804     @property
1805     bool get(U : bool)() const {
1806         assert(this.settled);
1807         assert(!this.isValueData);
1808         return this.innerBoolData;
1809     }
1810 
1811     @property
1812     T[] get(U : T[])() const {
1813         assert(this.settled);
1814         assert(this.isValueData);
1815         auto fn = this.processFn;
1816         auto tmp = this.innerValueData.map!fn.array;
1817         return tmp;
1818     }
1819 
1820     @property
1821     T get(U : T)() const {
1822         assert(this.settled);
1823         assert(this.isValueData);
1824         auto process_fn = this.processFn;
1825         auto reduce_fn = this.processReduceFn;
1826         if (!reduce_fn) {
1827             error(format!"connot get `%s` value from option `%s`"(
1828                     U.stringof,
1829                     this.flags
1830             ));
1831         }
1832         auto tmp = this.innerValueData
1833             .map!process_fn
1834             .reduce!reduce_fn;
1835         return tmp;
1836     }
1837 
1838     override string typeStr() const {
1839         return "type: " ~ (isOptional ? T.stringof ~ "[]|true" : T.stringof ~ "[]");
1840     }
1841 
1842     override string defaultValStr() const {
1843         if (defaultArg.isNull)
1844             return "";
1845         return "default: " ~ defaultArg.get!(T[])
1846             .to!string;
1847     }
1848 
1849     override string presetStr() const {
1850         if (presetArg.isNull)
1851             return "";
1852         alias test_bool = visit!((const T[] v) => false, (bool v) => true);
1853         alias test_t = visit!((const T[] v) => true, (bool v) => false);
1854         // pragma(msg, typeof(test_bool).stringof);
1855         if (test_bool(presetArg))
1856             return "preset: " ~ presetArg.get!bool
1857                 .to!string;
1858         if (test_t(presetArg))
1859             return "preset: " ~ presetArg.get!(T[])
1860                 .to!string;
1861         throw new CMDLineError;
1862     }
1863 
1864     override string choicesStr() const {
1865         if (argChoices.empty)
1866             return "";
1867         return "choices " ~ argChoices.to!string;
1868     }
1869 }
1870 
1871 unittest {
1872     auto vopt = new VariadicOption!int("-n, --number [num...]", "");
1873     vopt.defaultVal(1, 2, 3, 4, 5, 6, 7);
1874     vopt.processReduceFn = (int a, int b) => a + b;
1875     vopt.initialize;
1876     assert(vopt.get!int == 28);
1877 }
1878 
1879 /++ 
1880 the negate option like `--no-flag`, which is controller option that doesn't contains inner value.
1881 +/
1882 class NegateOption {
1883 package:
1884     string shortFlag;
1885     string longFlag;
1886     string flags;
1887     string description;
1888 
1889     bool hidden;
1890 
1891     this(string flags, string description) {
1892         this.flags = flags;
1893         this.description = description;
1894         this.hidden = false;
1895         auto opt = splitOptionFlags(flags);
1896         this.shortFlag = opt.shortFlag;
1897         this.longFlag = opt.longFlag;
1898         if ((matchFirst(this.longFlag, PTN_NEGATE)).empty) {
1899             error("the long flag must star with `--no-` using `new NegateOption`");
1900         }
1901     }
1902 
1903 public:
1904     /// test whether the other `NegateOption` variable's flag same in some parts
1905     bool matchFlag(in NegateOption other) const {
1906         return this.longFlag == other.longFlag ||
1907             (this.shortFlag.empty ? false : this.shortFlag == other.shortFlag);
1908     }
1909 
1910     /// test whether the other `Option` variable's flag same in some parts
1911     bool matchFlag(in Option other) const {
1912         auto nshort_flag = this.shortFlag;
1913         auto short_flag = other.shortFlag;
1914         return short_flag.empty ? false : short_flag == nshort_flag;
1915     }
1916 
1917     /// get the name of option
1918     @property
1919     string name() const {
1920         return this.longFlag[5 .. $].idup;
1921     }
1922 
1923     /// get the attribute name of option in camel-like, which is gennerated from `this.name`
1924     @property
1925     string attrName() const {
1926         return _camelCase(this.name);
1927     }
1928 
1929     /// test whether a string matches this option's long/short flag
1930     bool isFlag(string flag) const {
1931         return !flag.empty && (this.shortFlag == flag || this.longFlag == flag);
1932     }
1933 }
1934 
1935 /// creae a negate option
1936 /// Params:
1937 ///   flags = the flag like `-F, --no-flag`
1938 ///   desc = the description of option
1939 /// Returns: a negate option
1940 NegateOption createNegateOption(string flags, string desc = "") {
1941     return new NegateOption(flags, desc);
1942 }
1943 
1944 unittest {
1945     import std.stdio;
1946 
1947     auto nopt = new NegateOption("-P, --no-print-warning", "");
1948     scope (exit) {
1949         writeln(nopt.name, " ", nopt.attrName);
1950         writeln(nopt.shortFlag);
1951         writeln(nopt.longFlag);
1952     }
1953     assert(nopt.name == "print-warning" && nopt.attrName == "printWarning");
1954 }
1955 
1956 private string _camelReducer(string str, string word = "") {
1957     return str ~ cast(char) word[0].toUpper ~ cast(string) word[1 .. $];
1958 }
1959 
1960 package string _camelCase(string str) {
1961     import std.algorithm : reduce;
1962 
1963     return str.split("-").reduce!(_camelReducer);
1964 }
1965 
1966 unittest {
1967     template TestCamelCase(string input, string expected) {
1968         bool flag = expected == _camelCase(input);
1969     }
1970 
1971     assert(TestCamelCase!("value-flag", "valueFlag").flag);
1972     assert(TestCamelCase!("s-s-s-s", "sSSS").flag);
1973     assert(TestCamelCase!("Val-cc", "ValCc").flag);
1974 }
1975 
1976 package OptionFlags splitOptionFlags(string flags) {
1977     string short_flag = "", long_flag = "", value_flag = "";
1978     string[] flag_arr = flags.split(PTN_SP);
1979     if (flag_arr.length > 3)
1980         error(format!"error type of flag `%s`"(flags));
1981     foreach (const ref string flag; flag_arr) {
1982         if (!matchAll(flag, PTN_SHORT).empty) {
1983             short_flag = flag;
1984             continue;
1985         }
1986         if (!matchAll(flag, PTN_LONG).empty) {
1987             long_flag = flag;
1988             continue;
1989         }
1990         if (!matchAll(flag, PTN_VALUE).empty) {
1991             value_flag = flag;
1992             continue;
1993         }
1994     }
1995     return OptionFlags(short_flag, long_flag, value_flag);
1996 }
1997 
1998 package bool testType(T)(in OptionNullable value) {
1999     if (is(T == typeof(null)) && value.isNull)
2000         return true;
2001     if (!is(T == typeof(null)) && value.isNull)
2002         return false;
2003     alias test_t = visit!((const T v) => true, v => false);
2004     return test_t(value);
2005 }
2006 
2007 private void error(string msg = "", string code = "option.error") {
2008     throw new CMDLineError(msg, 1, code);
2009 }
2010 
2011 private void parsingError(string msg = "", string code = "option.error") {
2012     throw new InvalidOptionError(msg, code);
2013 }
2014 
2015 unittest {
2016     import std.stdio : stderr;
2017 
2018     mixin template TestFlags(string text) {
2019         string flags = text;
2020         auto opt = splitOptionFlags(flags);
2021         auto shortFlag = opt.shortFlag;
2022         auto longFlag = opt.longFlag;
2023         auto valueFlag = opt.valueFlag;
2024     }
2025 
2026     {
2027         mixin TestFlags!"-m, --mixed <value>";
2028         scope (failure)
2029             stderr.writeln(opt);
2030         assert(shortFlag == "-m" && longFlag == "--mixed" && valueFlag == "<value>");
2031     }
2032     {
2033         mixin TestFlags!"-m [value]";
2034         scope (failure)
2035             stderr.writeln(opt);
2036         assert(shortFlag == "-m" && longFlag == "" && valueFlag == "[value]");
2037     }
2038     {
2039         mixin TestFlags!"-m";
2040         scope (failure)
2041             stderr.writeln(opt);
2042         assert(shortFlag == "-m" && longFlag == "" && valueFlag == "");
2043     }
2044     {
2045         mixin TestFlags!"--mixed";
2046         scope (failure)
2047             stderr.writeln(opt);
2048         assert(longFlag == "--mixed" && shortFlag == "" && valueFlag == "");
2049     }
2050     {
2051         mixin TestFlags!"--mixed [value]";
2052         scope (failure)
2053             stderr.writeln(opt);
2054         assert(longFlag == "--mixed" && shortFlag == "" && valueFlag == "[value]");
2055     }
2056     {
2057         mixin TestFlags!"--mixed-flag <value>";
2058         scope (failure)
2059             stderr.writeln(opt);
2060         assert(longFlag == "--mixed-flag" && shortFlag == "" && valueFlag == "<value>");
2061     }
2062     {
2063         mixin TestFlags!"--mixed-flag-xx | -m [value-flag]";
2064         scope (failure)
2065             stderr.writeln(opt);
2066         assert(longFlag == "--mixed-flag-xx" && shortFlag == "-m" && valueFlag == "[value-flag]");
2067     }
2068     {
2069         mixin TestFlags!"--mixed-flag-xx | -m [value-flag...]";
2070         scope (failure)
2071             stderr.writeln(opt);
2072         assert(longFlag == "--mixed-flag-xx" && shortFlag == "-m" && valueFlag == "[value-flag...]");
2073     }
2074 }