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 }