Commit fd313fcadd110bbb1877679b7f68cabb2a6807b1

Authored by Henry Fredrick Schreiner
1 parent 537aa3aa

Adding ability to “leave out” options (optional). Rename RequierdError to ExtrasError

include/CLI/App.hpp
... ... @@ -25,7 +25,9 @@
25 25  
26 26 namespace CLI {
27 27  
  28 +namespace detail {
28 29 enum class Classifer {NONE, POSITIONAL_MARK, SHORT, LONG, SUBCOMMAND};
  30 +}
29 31  
30 32 class App;
31 33  
... ... @@ -42,8 +44,11 @@ protected:
42 44 std::string name;
43 45 std::string prog_description;
44 46 std::vector<Option_p> options;
45   - std::vector<std::string> missing_options;
46   - std::deque<std::string> positionals;
  47 +
  48 + /// Pair of classifer, string for missing options. (extra detail is removed on returning from parse)
  49 + std::vector<std::pair<detail::Classifer, std::string>> missing;
  50 + bool no_extras {true};
  51 +
47 52 std::vector<App_p> subcommands;
48 53 bool parsed {false};
49 54 App* subcommand {nullptr};
... ... @@ -115,6 +120,7 @@ public:
115 120 App* add_subcommand(std::string name_, std::string description="", bool help=true) {
116 121 subcommands.emplace_back(new App(description, help));
117 122 subcommands.back()->name = name_;
  123 + subcommands.back()->allow_extras();
118 124 return subcommands.back().get();
119 125 }
120 126  
... ... @@ -337,19 +343,26 @@ public:
337 343  
338 344 /// Parses the command line - throws errors
339 345 /// This must be called after the options are in but before the rest of the program.
340   - void parse(int argc, char **argv) {
  346 + std::vector<std::string> parse(int argc, char **argv) {
341 347 progname = argv[0];
342 348 std::vector<std::string> args;
343 349 for(int i=argc-1; i>0; i--)
344 350 args.push_back(argv[i]);
345   - parse(args);
  351 + return parse(args);
  352 +
346 353 }
347 354  
348   - /// The real work is done here. Expects a reversed vector
349   - void parse(std::vector<std::string> &args) {
  355 + /// The real work is done here. Expects a reversed vector.
  356 + /// Changes the vector to the remaining options.
  357 + std::vector<std::string>& parse(std::vector<std::string> &args) {
350 358 return _parse(args);
351 359 }
352 360  
  361 + /// Remove the error when extras are left over on the command line.
  362 + void allow_extras (bool allow=true) {
  363 + no_extras = !allow;
  364 + }
  365 +
353 366  
354 367 /// Print a nice error message and return the exit code
355 368 int exit(const Error& e) const {
... ... @@ -478,25 +491,25 @@ protected:
478 491 }
479 492  
480 493 /// Selects a Classifer enum based on the type of the current argument
481   - Classifer _recognize(std::string current) const {
  494 + detail::Classifer _recognize(std::string current) const {
482 495 std::string dummy1, dummy2;
483 496  
484 497 if(current == "--")
485   - return Classifer::POSITIONAL_MARK;
  498 + return detail::Classifer::POSITIONAL_MARK;
486 499 for(const App_p &com : subcommands) {
487 500 if(com->name == current)
488   - return Classifer::SUBCOMMAND;
  501 + return detail::Classifer::SUBCOMMAND;
489 502 }
490 503 if(detail::split_long(current, dummy1, dummy2))
491   - return Classifer::LONG;
  504 + return detail::Classifer::LONG;
492 505 if(detail::split_short(current, dummy1, dummy2))
493   - return Classifer::SHORT;
494   - return Classifer::NONE;
  506 + return detail::Classifer::SHORT;
  507 + return detail::Classifer::NONE;
495 508 }
496 509  
497 510  
498 511 /// Internal parse function
499   - void _parse(std::vector<std::string> &args) {
  512 + std::vector<std::string>& _parse(std::vector<std::string> &args) {
500 513 parsed = true;
501 514  
502 515 bool positional_only = false;
... ... @@ -504,23 +517,24 @@ protected:
504 517 while(args.size()>0) {
505 518  
506 519  
507   - Classifer classifer = positional_only ? Classifer::NONE : _recognize(args.back());
  520 + detail::Classifer classifer = positional_only ? detail::Classifer::NONE : _recognize(args.back());
508 521 switch(classifer) {
509   - case Classifer::POSITIONAL_MARK:
  522 + case detail::Classifer::POSITIONAL_MARK:
  523 + missing.emplace_back(classifer, args.back());
510 524 args.pop_back();
511 525 positional_only = true;
512 526 break;
513   - case Classifer::SUBCOMMAND:
  527 + case detail::Classifer::SUBCOMMAND:
514 528 _parse_subcommand(args);
515 529 break;
516   - case Classifer::LONG:
  530 + case detail::Classifer::LONG:
517 531 _parse_long(args);
518 532 break;
519   - case Classifer::SHORT:
  533 + case detail::Classifer::SHORT:
520 534 _parse_short(args);
521 535 break;
522   - case Classifer::NONE:
523   - positionals.push_back(args.back());
  536 + case detail::Classifer::NONE:
  537 + missing.emplace_back(classifer, args.back());
524 538 args.pop_back();
525 539 }
526 540 }
... ... @@ -531,11 +545,34 @@ protected:
531 545  
532 546  
533 547 // Collect positionals
534   - for(const Option_p& opt : options) {
535   - while (opt->get_positional() && opt->count() < opt->get_expected() && positionals.size() > 0) {
536   - opt->get_new();
537   - opt->add_result(0, positionals.front());
538   - positionals.pop_front();
  548 +
  549 + // Loop over all positionals
  550 + for(int i=0; i<missing.size(); i++) {
  551 +
  552 + // Skip non-positionals (speedup)
  553 + if(missing.at(i).first != detail::Classifer::NONE)
  554 + continue;
  555 +
  556 + // Loop over all options
  557 + for(const Option_p& opt : options) {
  558 +
  559 + // Eat options, one by one, until done
  560 + while ( opt->get_positional()
  561 + && opt->count() < opt->get_expected()
  562 + && i < missing.size()
  563 + ) {
  564 +
  565 + // Skip options, only eat positionals
  566 + if(missing.at(i).first != detail::Classifer::NONE) {
  567 + i++;
  568 + continue;
  569 + }
  570 +
  571 + opt->get_new();
  572 + opt->add_result(0, missing.at(i).second);
  573 + missing.erase(missing.begin() + i); // Remove option that was eaten
  574 + // Don't need to remove 1 from i since this while loop keeps reading i
  575 + }
539 576 }
540 577 }
541 578  
... ... @@ -591,11 +628,22 @@ protected:
591 628 if(required_subcommand && subcommand == nullptr)
592 629 throw RequiredError("Subcommand required");
593 630  
594   - if(positionals.size()>0)
595   - throw PositionalError("[" + detail::join(positionals) + "]");
  631 + // Convert missing (pairs) to extras (string only)
  632 + args.resize(missing.size());
  633 + std::transform(std::begin(missing), std::end(missing), std::begin(args),
  634 + [](const std::pair<detail::Classifer, std::string>& val){return val.second;});
  635 + std::reverse(std::begin(args), std::end(args));
  636 +
  637 + size_t num_left_over = std::count_if(std::begin(missing), std::end(missing),
  638 + [](std::pair<detail::Classifer, std::string>& val){return val.first != detail::Classifer::POSITIONAL_MARK;});
  639 +
  640 + if(num_left_over>0 && no_extras)
  641 + throw ExtrasError("[" + detail::join(args, " ") + "]");
596 642  
597 643 pre_callback();
598 644 run_callback();
  645 +
  646 + return args;
599 647 }
600 648  
601 649  
... ... @@ -624,7 +672,7 @@ protected:
624 672 auto op_ptr = std::find_if(std::begin(options), std::end(options), [name_](const Option_p &opt){return opt->check_sname(name_);});
625 673  
626 674 if(op_ptr == std::end(options)) {
627   - missing_options.push_back("-" + name_);
  675 + missing.emplace_back(detail::Classifer::SHORT, "-" + name_);
628 676 return;
629 677 }
630 678  
... ... @@ -645,7 +693,7 @@ protected:
645 693  
646 694  
647 695 if(num == -1) {
648   - while(args.size()>0 && _recognize(args.back()) == Classifer::NONE) {
  696 + while(args.size()>0 && _recognize(args.back()) == detail::Classifer::NONE) {
649 697 op->add_result(vnum, args.back());
650 698 args.pop_back();
651 699 }
... ... @@ -675,7 +723,7 @@ protected:
675 723 auto op_ptr = std::find_if(std::begin(options), std::end(options), [name_](const Option_p &v){return v->check_lname(name_);});
676 724  
677 725 if(op_ptr == std::end(options)) {
678   - missing_options.push_back("--" + name_);
  726 + missing.emplace_back(detail::Classifer::LONG, "--" + name_);
679 727 return;
680 728 }
681 729  
... ... @@ -699,7 +747,7 @@ protected:
699 747 }
700 748  
701 749 if(num == -1) {
702   - while(args.size() > 0 && _recognize(args.back()) == Classifer::NONE) {
  750 + while(args.size() > 0 && _recognize(args.back()) == detail::Classifer::NONE) {
703 751 op->add_result(vnum, args.back());
704 752 args.pop_back();
705 753 }
... ...
include/CLI/Error.hpp
... ... @@ -95,9 +95,9 @@ struct ExcludesError : public ParseError {
95 95 ExcludesError(std::string name, std::string subname) : ParseError("ExcludesError", name + " excludes " + subname, 14) {}
96 96 };
97 97  
98   -/// Thrown when too many positionals are found
99   -struct PositionalError : public ParseError {
100   - PositionalError(std::string name) : ParseError("PositionalError", name, 6) {}
  98 +/// Thrown when too many positionals or options are found
  99 +struct ExtrasError : public ParseError {
  100 + ExtrasError(std::string name) : ParseError("ExtrasError", name, 6) {}
101 101 };
102 102  
103 103 /// This is just a safety check to verify selection and parsing match
... ...
tests/SimpleTest.cpp
... ... @@ -18,7 +18,7 @@ TEST(Basic, Empty) {
18 18 {
19 19 CLI::App app;
20 20 input_t spare = {"spare"};
21   - EXPECT_THROW(app.parse(spare), CLI::PositionalError);
  21 + EXPECT_THROW(app.parse(spare), CLI::ExtrasError);
22 22 }
23 23 {
24 24 CLI::App app;
... ...
tests/SubcommandTest.cpp
... ... @@ -89,13 +89,13 @@ TEST_F(SubcommandProgram, Working) {
89 89 TEST_F(SubcommandProgram, Spare) {
90 90 args = {"extra", "-d", "start", "-ffilename"};
91 91  
92   - EXPECT_THROW(run(), CLI::PositionalError);
  92 + EXPECT_THROW(run(), CLI::ExtrasError);
93 93 }
94 94  
95 95 TEST_F(SubcommandProgram, SpareSub) {
96 96 args = {"-d", "start", "spare", "-ffilename"};
97 97  
98   - EXPECT_THROW(run(), CLI::PositionalError);
  98 + EXPECT_THROW(run(), CLI::ExtrasError);
99 99 }
100 100  
101 101  
... ...