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