Commit 68e86a8085ecbbc39dba8d3b15e50cd622a92c36
1 parent
324a9c73
Adding better vector check
Showing
2 changed files
with
105 additions
and
33 deletions
include/CLI.hpp
| ... | ... | @@ -63,6 +63,17 @@ using std::enable_if_t; |
| 63 | 63 | #endif |
| 64 | 64 | // If your compiler supports C++14, you can use that definition instead |
| 65 | 65 | |
| 66 | +template <typename T> | |
| 67 | +struct is_vector { | |
| 68 | + static const bool value = false; | |
| 69 | +}; | |
| 70 | + | |
| 71 | + | |
| 72 | +template<class T, class A> | |
| 73 | +struct is_vector<std::vector<T, A> > { | |
| 74 | + static bool const value = true; | |
| 75 | +}; | |
| 76 | + | |
| 66 | 77 | struct Combiner { |
| 67 | 78 | int num; |
| 68 | 79 | bool positional; |
| ... | ... | @@ -366,7 +377,20 @@ bool lexical_cast(std::string input, T& output) { |
| 366 | 377 | |
| 367 | 378 | // String and similar |
| 368 | 379 | template<typename T, |
| 369 | -enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value, detail::enabler> = dummy> | |
| 380 | +enable_if_t<is_vector<T>::value, detail::enabler> = dummy> | |
| 381 | +bool lexical_cast(std::string input, T& output) { | |
| 382 | + logit("vector lexical cast: " + input); | |
| 383 | + if(output.size() == input.size()) | |
| 384 | + output.resize(input.size()); | |
| 385 | + for(size_t i=0; i<input.size(); i++) | |
| 386 | + output[i] = input[i]; | |
| 387 | + return true; | |
| 388 | +} | |
| 389 | + | |
| 390 | +// String and similar | |
| 391 | +template<typename T, | |
| 392 | +enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !is_vector<T>::value | |
| 393 | +, detail::enabler> = dummy> | |
| 370 | 394 | bool lexical_cast(std::string input, T& output) { |
| 371 | 395 | logit("Direct lexical cast: " + input); |
| 372 | 396 | output = input; |
| ... | ... | @@ -483,7 +507,7 @@ public: |
| 483 | 507 | } |
| 484 | 508 | |
| 485 | 509 | /// Add option for string |
| 486 | - template<typename T, enable_if_t<!std::is_array<T>::value, detail::enabler> = dummy> | |
| 510 | + template<typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = dummy> | |
| 487 | 511 | Option* add_option( |
| 488 | 512 | std::string name, ///< The name, long,short |
| 489 | 513 | T &variable, ///< The variable to set |
| ... | ... | @@ -598,13 +622,16 @@ public: |
| 598 | 622 | |
| 599 | 623 | /// Prototype for new output style |
| 600 | 624 | template<typename T = std::string, |
| 601 | - enable_if_t<!std::is_array<T>::value, detail::enabler> = dummy> | |
| 625 | + enable_if_t<!is_vector<T>::value, detail::enabler> = dummy> | |
| 602 | 626 | Value<T> make_option( |
| 603 | 627 | std::string name, ///< The name, short,long |
| 604 | 628 | std::string discription="", |
| 605 | 629 | Combiner opts=VALIDATORS |
| 606 | 630 | ) { |
| 607 | 631 | |
| 632 | + if(opts.num!=1) | |
| 633 | + throw IncorrectConstruction("Must have ARGS(1)."); | |
| 634 | + | |
| 608 | 635 | Value<T> out(name); |
| 609 | 636 | std::shared_ptr<std::unique_ptr<T>> ptr = out.value; |
| 610 | 637 | |
| ... | ... | @@ -623,8 +650,8 @@ public: |
| 623 | 650 | } |
| 624 | 651 | |
| 625 | 652 | /// Prototype for new output style with default |
| 626 | - template<typename T = std::string, | |
| 627 | - enable_if_t<!std::is_array<T>::value, detail::enabler> = dummy> | |
| 653 | + template<typename T, | |
| 654 | + enable_if_t<!is_vector<T>::value, detail::enabler> = dummy> | |
| 628 | 655 | Value<T> make_option( |
| 629 | 656 | std::string name, ///< The name, short,long |
| 630 | 657 | const T& default_value, |
| ... | ... | @@ -632,6 +659,9 @@ public: |
| 632 | 659 | Combiner opts=VALIDATORS |
| 633 | 660 | ) { |
| 634 | 661 | |
| 662 | + if(opts.num!=1) | |
| 663 | + throw IncorrectConstruction("Must have ARGS(1)."); | |
| 664 | + | |
| 635 | 665 | Value<T> out(name); |
| 636 | 666 | std::shared_ptr<std::unique_ptr<T>> ptr = out.value; |
| 637 | 667 | ptr->reset(new T(default_value)); // resets the internal ptr |
| ... | ... | @@ -650,9 +680,10 @@ public: |
| 650 | 680 | return out; |
| 651 | 681 | } |
| 652 | 682 | |
| 653 | - /// Prototype for new output style | |
| 654 | - template<typename T> | |
| 655 | - Value<std::vector<T>> make_option( | |
| 683 | + /// Prototype for new output style, vector | |
| 684 | + template<typename T, | |
| 685 | + enable_if_t<is_vector<T>::value, detail::enabler> = dummy> | |
| 686 | + Value<T> make_option( | |
| 656 | 687 | std::string name, ///< The name, short,long |
| 657 | 688 | std::string discription="", |
| 658 | 689 | Combiner opts=VALIDATORS |
| ... | ... | @@ -661,11 +692,11 @@ public: |
| 661 | 692 | if(opts.num==0) |
| 662 | 693 | throw IncorrectConstruction("Must have ARGS or be a vector."); |
| 663 | 694 | |
| 664 | - Value<std::vector<T>> out(name); | |
| 665 | - std::shared_ptr<std::unique_ptr<std::vector<T> >> ptr = out.value; | |
| 695 | + Value<T> out(name); | |
| 696 | + std::shared_ptr<std::unique_ptr<T>> ptr = out.value; | |
| 666 | 697 | |
| 667 | 698 | CLI::callback_t fun = [ptr](CLI::results_t res){ |
| 668 | - ptr->reset(new std::vector<T>()); // resets the internal ptr | |
| 699 | + ptr->reset(new T()); // resets the internal ptr | |
| 669 | 700 | bool retval = true; |
| 670 | 701 | for(const auto &a : res) |
| 671 | 702 | for(const auto &b : a) { |
| ... | ... | @@ -841,23 +872,17 @@ public: |
| 841 | 872 | |
| 842 | 873 | |
| 843 | 874 | if(num == -1) { |
| 844 | - std::string current = args.back(); | |
| 845 | - while(_recognize(current) == Classifer::NONE) { | |
| 846 | - std::string current = args.back(); | |
| 875 | + while(args.size()>0 && _recognize(args.back()) == Classifer::NONE) { | |
| 876 | + op->add_result(vnum, args.back()); | |
| 847 | 877 | args.pop_back(); |
| 848 | - op->add_result(vnum,current); | |
| 849 | - if(args.size()==0) | |
| 850 | - return; | |
| 851 | 878 | |
| 852 | 879 | } |
| 853 | - } else while(num>0) { | |
| 880 | + } else while(num>0 && args.size() > 0) { | |
| 854 | 881 | num--; |
| 855 | 882 | std::string current = args.back(); |
| 856 | 883 | logit("Adding: "+current); |
| 857 | 884 | args.pop_back(); |
| 858 | 885 | op->add_result(vnum,current); |
| 859 | - if(args.size()==0) | |
| 860 | - return; | |
| 861 | 886 | } |
| 862 | 887 | |
| 863 | 888 | if(rest != "") { |
| ... | ... | @@ -916,22 +941,14 @@ public: |
| 916 | 941 | } |
| 917 | 942 | |
| 918 | 943 | if(num == -1) { |
| 919 | - std::string current = args.back(); | |
| 920 | - while(_recognize(current) == Classifer::NONE) { | |
| 921 | - std::string current = args.back(); | |
| 944 | + while(args.size() > 0 && _recognize(args.back()) == Classifer::NONE) { | |
| 945 | + op->add_result(vnum, args.back()); | |
| 922 | 946 | args.pop_back(); |
| 923 | - op->add_result(vnum,current); | |
| 924 | - if(args.size()==0) | |
| 925 | - return; | |
| 926 | - | |
| 927 | 947 | } |
| 928 | - } else while(num>0) { | |
| 948 | + } else while(num>0 && args.size()>0) { | |
| 929 | 949 | num--; |
| 930 | - std::string current = args.back(); | |
| 950 | + op->add_result(vnum,args.back()); | |
| 931 | 951 | args.pop_back(); |
| 932 | - op->add_result(vnum,current); | |
| 933 | - if(args.size()==0) | |
| 934 | - return; | |
| 935 | 952 | } |
| 936 | 953 | return; |
| 937 | 954 | } | ... | ... |
tests/CLITest.cpp
| ... | ... | @@ -382,9 +382,64 @@ TEST_F(TAppValue, OneString) { |
| 382 | 382 | |
| 383 | 383 | } |
| 384 | 384 | |
| 385 | +TEST_F(TAppValue, SeveralInts) { | |
| 386 | + auto value = app.make_option<int>("first"); | |
| 387 | + CLI::Value<int> value2 = app.make_option<int>("s"); | |
| 388 | + int v; | |
| 389 | + args = {"--first", "12", "-s", "19"}; | |
| 390 | + EXPECT_FALSE((bool) value); | |
| 391 | + EXPECT_FALSE((bool) value2); | |
| 392 | + | |
| 393 | + EXPECT_THROW(v = *value, CLI::EmptyError); | |
| 394 | + //EXPECT_THROW(v = str, CLI::EmptyError); | |
| 395 | + EXPECT_NO_THROW(run()); | |
| 396 | + EXPECT_TRUE((bool) value); | |
| 397 | + EXPECT_NO_THROW(v = *value); | |
| 398 | + EXPECT_NO_THROW(v = value); | |
| 399 | + | |
| 400 | + EXPECT_EQ(1, app.count("s")); | |
| 401 | + EXPECT_EQ(1, app.count("first")); | |
| 402 | + EXPECT_EQ(*value, 12); | |
| 403 | + EXPECT_EQ(*value2, 19); | |
| 404 | + | |
| 405 | +} | |
| 406 | + | |
| 407 | +TEST_F(TAppValue, Vector) { | |
| 408 | + auto value = app.make_option<std::vector<int>>("first", "", CLI::ARGS); | |
| 409 | + auto value2 = app.make_option<std::vector<std::string>>("second", "", CLI::ARGS); | |
| 410 | + | |
| 411 | + std::vector<int> i; | |
| 412 | + std::vector<std::string> s; | |
| 413 | + | |
| 414 | + args = {"--first", "12", "3", "9", "--second", "thing", "try"}; | |
| 415 | + | |
| 416 | + EXPECT_FALSE((bool) value); | |
| 417 | + EXPECT_FALSE((bool) value2); | |
| 418 | + | |
| 419 | + EXPECT_THROW(i = *value, CLI::EmptyError); | |
| 420 | + EXPECT_THROW(s = *value2, CLI::EmptyError); | |
| 421 | + | |
| 422 | + EXPECT_NO_THROW(run()); | |
| 423 | + | |
| 424 | + EXPECT_TRUE((bool) value); | |
| 425 | + EXPECT_TRUE((bool) value2); | |
| 426 | + | |
| 427 | + EXPECT_NO_THROW(i = *value); | |
| 428 | + //EXPECT_NO_THROW(i = value); | |
| 429 | + | |
| 430 | + EXPECT_NO_THROW(s = *value2); | |
| 431 | + //EXPECT_NO_THROW(s = value2); | |
| 432 | + | |
| 433 | + EXPECT_EQ(3, app.count("first")); | |
| 434 | + EXPECT_EQ(2, app.count("second")); | |
| 435 | + | |
| 436 | + EXPECT_EQ(*value, std::vector<int>({12,3,9})); | |
| 437 | + EXPECT_EQ(*value2, std::vector<std::string>({"thing", "try"})); | |
| 438 | + | |
| 439 | +} | |
| 440 | + | |
| 385 | 441 | // TODO: Maybe add function to call on subcommand parse? Stashed. |
| 386 | 442 | // TODO: Check help output |
| 387 | 443 | // TODO: Add default/type info to help |
| 388 | 444 | // TODO: Add set checking |
| 389 | 445 | // TODO: Try all of the options together |
| 390 | -// TODO: Add make_option alternative with type? Cancelled for now | ... | ... |