Commit 343a730a04880cdb46f5c7b00baf681d78c11ff9

Authored by Philip Top
Committed by Henry Schreiner
1 parent c1799d2c

Add needs to subcommand (#317)

* add a needs method to the app/subcommand

* add some needs subcommand tests

* add a few more subcommand tests for needs and alias

* fix shadow warnings

* add some tests of the error pathways and fix a few anomalous conditions on the Option excludes function

* add needs and alias functions in the readme

* add some tests of ignore_case and underscore with the alias operations

* add a few more test cases for needs option groups

* add callback tests with needs and add a few comments in the readme

* update formatting

* add error checks on the aliases and restrictions on valid names for subcommands and aliases

* add checks for matching subcommands and improve error return values to include the offending name

* add some tests of the alias errors

* add some more tests to check subcommand name matching during addition

* add some additional tests and remove a redundant chunk of codes

* add some more checks of subcommand name overlap in option_groups

* allow disabled subcommand to bypass name matching check
README.md
... ... @@ -414,21 +414,21 @@ After specifying a set of options, you can also specify "filter" functions of th
414 414 Here are some examples
415 415 of `IsMember`:
416 416  
417   - - `CLI::IsMember({"choice1", "choice2"})`: Select from exact match to choices.
418   - - `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`: Match things like `Choice_1`, too.
419   - - `CLI::IsMember(std::set<int>({2,3,4}))`: Most containers and types work; you just need `std::begin`, `std::end`, and `::value_type`.
420   - - `CLI::IsMember(std::map<std::string, TYPE>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched value with the matched key. The value member of the map is not used in `IsMember`, so it can be any type.
421   - - `auto p = std::make_shared<std::vector<std::string>>(std::initializer_list<std::string>("one", "two")); CLI::IsMember(p)`: You can modify `p` later.
422   -- ๐Ÿ†• The `Transformer` and `CheckedTransformer` Validators transform one value into another. Any container or copyable pointer (including `std::shared_ptr`) to a container that generates pairs of values can be passed to these `Validator's`; the container just needs to be iterable and have a `::value_type` that consists of pairs. The key type should be convertible from a string, and the value type should be convertible to a string You can use an initializer list directly if you like. If you need to modify the map later, the pointer form lets you do that; the description message will correctly refer to the current version of the map. `Transformer` does not do any checking so values not in the map are ignored. `CheckedTransformer` takes an extra step of verifying that the value is either one of the map key values, in which case it is transformed, or one of the expected output values, and if not will generate a `ValidationError`. A Transformer placed using `check` will not do anything.
  417 +- `CLI::IsMember({"choice1", "choice2"})`: Select from exact match to choices.
  418 +- `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`: Match things like `Choice_1`, too.
  419 +- `CLI::IsMember(std::set<int>({2,3,4}))`: Most containers and types work; you just need `std::begin`, `std::end`, and `::value_type`.
  420 +- `CLI::IsMember(std::map<std::string, TYPE>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched value with the matched key. The value member of the map is not used in `IsMember`, so it can be any type.
  421 +- `auto p = std::make_shared<std::vector<std::string>>(std::initializer_list<std::string>("one", "two")); CLI::IsMember(p)`: You can modify `p` later.
  422 +- ๐Ÿ†• The `Transformer` and `CheckedTransformer` Validators transform one value into another. Any container or copyable pointer (including `std::shared_ptr`) to a container that generates pairs of values can be passed to these `Validator's`; the container just needs to be iterable and have a `::value_type` that consists of pairs. The key type should be convertible from a string, and the value type should be convertible to a string You can use an initializer list directly if you like. If you need to modify the map later, the pointer form lets you do that; the description message will correctly refer to the current version of the map. `Transformer` does not do any checking so values not in the map are ignored. `CheckedTransformer` takes an extra step of verifying that the value is either one of the map key values, in which case it is transformed, or one of the expected output values, and if not will generate a `ValidationError`. A Transformer placed using `check` will not do anything.
423 423 After specifying a map of options, you can also specify "filter" just like in `CLI::IsMember`.
424 424 Here are some examples (`Transformer` and `CheckedTransformer` are interchangeable in the examples)
425 425 of `Transformer`:
426 426  
427   - - `CLI::Transformer({{"key1", "map1"},{"key2","map2"}})`: Select from key values and produce map values.
  427 +- `CLI::Transformer({{"key1", "map1"},{"key2","map2"}})`: Select from key values and produce map values.
428 428  
429   - - `CLI::Transformer(std::map<std::string,int>({"two",2},{"three",3},{"four",4}}))`: most maplike containers work, the `::value_type` needs to produce a pair of some kind.
430   - - `CLI::CheckedTransformer(std::map<std::string, int>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched key with the value. `CheckedTransformer` also requires that the value either match one of the keys or match one of known outputs.
431   - - `auto p = std::make_shared<CLI::TransformPairs<std::string>>(std::initializer_list<std::pair<std::string,std::string>>({"key1", "map1"},{"key2","map2"})); CLI::Transformer(p)`: You can modify `p` later. `TransformPairs<T>` is an alias for `std::vector<std::pair<<std::string,T>>`
  429 +- `CLI::Transformer(std::map<std::string,int>({"two",2},{"three",3},{"four",4}}))`: most maplike containers work, the `::value_type` needs to produce a pair of some kind.
  430 +- `CLI::CheckedTransformer(std::map<std::string, int>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched key with the value. `CheckedTransformer` also requires that the value either match one of the keys or match one of known outputs.
  431 +- `auto p = std::make_shared<CLI::TransformPairs<std::string>>(std::initializer_list<std::pair<std::string,std::string>>({"key1", "map1"},{"key2","map2"})); CLI::Transformer(p)`: You can modify `p` later. `TransformPairs<T>` is an alias for `std::vector<std::pair<<std::string,T>>`
432 432  
433 433 NOTES: If the container used in `IsMember`, `Transformer`, or `CheckedTransformer` has a `find` function like `std::unordered_map` or `std::map` then that function is used to do the searching. If it does not have a `find` function a linear search is performed. If there are filters present, the fast search is performed first, and if that fails a linear search with the filters on the key values is performed.
434 434  
... ... @@ -535,6 +535,7 @@ There are several options that are supported on the main app and subcommands and
535 535 - `.enabled_by_default()`: ๐Ÿ†• Specify that at the start of each parse the subcommand/option_group should be enabled. This is useful for allowing some Subcommands to disable others.
536 536 - `.validate_positionals()`: ๐Ÿ†• Specify that positionals should pass validation before matching. Validation is specified through `transform`, `check`, and `each` for an option. If an argument fails validation it is not an error and matching proceeds to the next available positional or extra arguments.
537 537 - `.excludes(option_or_subcommand)`: ๐Ÿ†• If given an option pointer or pointer to another subcommand, these subcommands cannot be given together. In the case of options, if the option is passed the subcommand cannot be used and will generate an error.
  538 +- `.needs(option_or_subcommand)`: ๐Ÿšง If given an option pointer or pointer to another subcommand, the subcommands will require the given option to have been given before this subcommand is validated which occurs prior to execution of any callback or after parsing is completed.
538 539 - `.require_option()`: ๐Ÿ†• Require 1 or more options or option groups be used.
539 540 - `.require_option(N)`: ๐Ÿ†• Require `N` options or option groups, if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more.
540 541 - `.require_option(min, max)`: ๐Ÿ†• Explicitly set min and max allowed options or option groups. Setting `max` to 0 implies unlimited options.
... ... @@ -555,6 +556,7 @@ There are several options that are supported on the main app and subcommands and
555 556 - `.formatter(fmt)`: Set a formatter, with signature `std::string(const App*, std::string, AppFormatMode)`. See Formatting for more details.
556 557 - `.description(str)`: Set/change the description.
557 558 - `.get_description()`: Access the description.
  559 +- `.alias(str)`:๐Ÿšง set an alias for the subcommand, this allows subcommands to be called by more than one name.
558 560 - `.parsed()`: True if this subcommand was given on the command line.
559 561 - `.count()`: Returns the number of times the subcommand was called.
560 562 - `.count(option_name)`: Returns the number of times a particular option was called.
... ...
include/CLI/App.hpp
... ... @@ -159,6 +159,14 @@ class App {
159 159 /// not be
160 160 std::set<Option *> exclude_options_;
161 161  
  162 + /// this is a list of subcommands or option groups that are required by this one, the list is not mutual, the
  163 + /// listed subcommands do not require this one
  164 + std::set<App *> need_subcommands_;
  165 +
  166 + /// This is a list of options which are required by this app, the list is not mutual, listed options do not need the
  167 + /// subcommand not be
  168 + std::set<Option *> need_options_;
  169 +
162 170 ///@}
163 171 /// @name Subcommands
164 172 ///@{
... ... @@ -213,6 +221,9 @@ class App {
213 221 /// The group membership INHERITABLE
214 222 std::string group_{"Subcommands"};
215 223  
  224 + /// Alias names for the subcommand
  225 + std::vector<std::string> aliases_;
  226 +
216 227 ///@}
217 228 /// @name Config
218 229 ///@{
... ... @@ -315,11 +326,42 @@ class App {
315 326  
316 327 /// Set a name for the app (empty will use parser to set the name)
317 328 App *name(std::string app_name = "") {
318   - name_ = app_name;
  329 +
  330 + if(parent_ != nullptr) {
  331 + auto oname = name_;
  332 + name_ = app_name;
  333 + auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent());
  334 + if(!res.empty()) {
  335 + name_ = oname;
  336 + throw(OptionAlreadyAdded(app_name + " conflicts with existing subcommand names"));
  337 + }
  338 + } else {
  339 + name_ = app_name;
  340 + }
319 341 has_automatic_name_ = false;
320 342 return this;
321 343 }
322 344  
  345 + /// Set an alias for the app
  346 + App *alias(std::string app_name) {
  347 + if(!detail::valid_name_string(app_name)) {
  348 + throw(IncorrectConstruction("alias is not a valid name string"));
  349 + }
  350 +
  351 + if(parent_ != nullptr) {
  352 + aliases_.push_back(app_name);
  353 + auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent());
  354 + if(!res.empty()) {
  355 + aliases_.pop_back();
  356 + throw(OptionAlreadyAdded("alias already matches an existing subcommand: " + app_name));
  357 + }
  358 + } else {
  359 + aliases_.push_back(app_name);
  360 + }
  361 +
  362 + return this;
  363 + }
  364 +
323 365 /// Remove the error when extras are left over on the command line.
324 366 App *allow_extras(bool allow = true) {
325 367 allow_extras_ = allow;
... ... @@ -386,13 +428,16 @@ class App {
386 428  
387 429 /// Ignore case. Subcommands inherit value.
388 430 App *ignore_case(bool value = true) {
389   - ignore_case_ = value;
390   - if(parent_ != nullptr && !name_.empty()) {
391   - for(const auto &subc : parent_->subcommands_) {
392   - if(subc.get() != this && (this->check_name(subc->name_) || subc->check_name(this->name_)))
393   - throw OptionAlreadyAdded(subc->name_);
  431 + if(value && !ignore_case_) {
  432 + ignore_case_ = true;
  433 + auto *p = (parent_ != nullptr) ? _get_fallthrough_parent() : this;
  434 + auto &match = _compare_subcommand_names(*this, *p);
  435 + if(!match.empty()) {
  436 + ignore_case_ = false; // we are throwing so need to be exception invariant
  437 + throw OptionAlreadyAdded("ignore case would cause subcommand name conflicts: " + match);
394 438 }
395 439 }
  440 + ignore_case_ = value;
396 441 return this;
397 442 }
398 443  
... ... @@ -411,13 +456,16 @@ class App {
411 456  
412 457 /// Ignore underscore. Subcommands inherit value.
413 458 App *ignore_underscore(bool value = true) {
414   - ignore_underscore_ = value;
415   - if(parent_ != nullptr && !name_.empty()) {
416   - for(const auto &subc : parent_->subcommands_) {
417   - if(subc.get() != this && (this->check_name(subc->name_) || subc->check_name(this->name_)))
418   - throw OptionAlreadyAdded(subc->name_);
  459 + if(value && !ignore_underscore_) {
  460 + ignore_underscore_ = true;
  461 + auto *p = (parent_ != nullptr) ? _get_fallthrough_parent() : this;
  462 + auto &match = _compare_subcommand_names(*this, *p);
  463 + if(!match.empty()) {
  464 + ignore_underscore_ = false;
  465 + throw OptionAlreadyAdded("ignore underscore would cause subcommand name conflicts: " + match);
419 466 }
420 467 }
  468 + ignore_underscore_ = value;
421 469 return this;
422 470 }
423 471  
... ... @@ -493,7 +541,17 @@ class App {
493 541  
494 542 return option.get();
495 543 }
496   - throw OptionAlreadyAdded(myopt.get_name());
  544 + // we know something matches now find what it is so we can produce more error information
  545 + for(auto &opt : options_) {
  546 + auto &matchname = opt->matching_name(myopt);
  547 + if(!matchname.empty()) {
  548 + throw(OptionAlreadyAdded("added option matched existing option name: " + matchname));
  549 + }
  550 + }
  551 + // this line should not be reached the above loop should trigger the throw
  552 + // LCOV_EXCL_START
  553 + throw(OptionAlreadyAdded("added option matched existing option name"));
  554 + // LCOV_EXCL_END
497 555 }
498 556  
499 557 /// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`)
... ... @@ -1033,6 +1091,9 @@ class App {
1033 1091  
1034 1092 /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag
1035 1093 App *add_subcommand(std::string subcommand_name = "", std::string subcommand_description = "") {
  1094 + if(!subcommand_name.empty() && !detail::valid_name_string(subcommand_name)) {
  1095 + throw IncorrectConstruction("subcommand name is not valid");
  1096 + }
1036 1097 CLI::App_p subcom = std::shared_ptr<App>(new App(std::move(subcommand_description), subcommand_name, this));
1037 1098 return add_subcommand(std::move(subcom));
1038 1099 }
... ... @@ -1041,10 +1102,10 @@ class App {
1041 1102 App *add_subcommand(CLI::App_p subcom) {
1042 1103 if(!subcom)
1043 1104 throw IncorrectConstruction("passed App is not valid");
1044   - if(!subcom->name_.empty()) {
1045   - for(const auto &subc : subcommands_)
1046   - if(subc->check_name(subcom->name_) || subcom->check_name(subc->name_))
1047   - throw OptionAlreadyAdded(subc->name_);
  1105 + auto ckapp = (name_.empty() && parent_ != nullptr) ? _get_fallthrough_parent() : this;
  1106 + auto &mstrg = _compare_subcommand_names(*subcom, *ckapp);
  1107 + if(!mstrg.empty()) {
  1108 + throw(OptionAlreadyAdded("subcommand name or alias matches existing subcommand: " + mstrg));
1048 1109 }
1049 1110 subcom->parent_ = this;
1050 1111 subcommands_.push_back(std::move(subcom));
... ... @@ -1056,6 +1117,7 @@ class App {
1056 1117 // Make sure no links exist
1057 1118 for(App_p &sub : subcommands_) {
1058 1119 sub->remove_excludes(subcom);
  1120 + sub->remove_needs(subcom);
1059 1121 }
1060 1122  
1061 1123 auto iterator = std::find_if(
... ... @@ -1440,9 +1502,12 @@ class App {
1440 1502  
1441 1503 /// Sets excluded subcommands for the subcommand
1442 1504 App *excludes(App *app) {
1443   - if((app == this) || (app == nullptr)) {
  1505 + if(app == nullptr) {
1444 1506 throw OptionNotFound("nullptr passed");
1445 1507 }
  1508 + if(app == this) {
  1509 + throw OptionNotFound("cannot self reference in needs");
  1510 + }
1446 1511 auto res = exclude_subcommands_.insert(app);
1447 1512 // subcommand exclusion should be symmetric
1448 1513 if(res.second) {
... ... @@ -1451,6 +1516,25 @@ class App {
1451 1516 return this;
1452 1517 }
1453 1518  
  1519 + App *needs(Option *opt) {
  1520 + if(opt == nullptr) {
  1521 + throw OptionNotFound("nullptr passed");
  1522 + }
  1523 + need_options_.insert(opt);
  1524 + return this;
  1525 + }
  1526 +
  1527 + App *needs(App *app) {
  1528 + if(app == nullptr) {
  1529 + throw OptionNotFound("nullptr passed");
  1530 + }
  1531 + if(app == this) {
  1532 + throw OptionNotFound("cannot self reference in needs");
  1533 + }
  1534 + need_subcommands_.insert(app);
  1535 + return this;
  1536 + }
  1537 +
1454 1538 /// Removes an option from the excludes list of this subcommand
1455 1539 bool remove_excludes(Option *opt) {
1456 1540 auto iterator = std::find(std::begin(exclude_options_), std::end(exclude_options_), opt);
... ... @@ -1461,7 +1545,7 @@ class App {
1461 1545 return true;
1462 1546 }
1463 1547  
1464   - /// Removes a subcommand from this excludes list of this subcommand
  1548 + /// Removes a subcommand from the excludes list of this subcommand
1465 1549 bool remove_excludes(App *app) {
1466 1550 auto iterator = std::find(std::begin(exclude_subcommands_), std::end(exclude_subcommands_), app);
1467 1551 if(iterator == std::end(exclude_subcommands_)) {
... ... @@ -1473,6 +1557,26 @@ class App {
1473 1557 return true;
1474 1558 }
1475 1559  
  1560 + /// Removes an option from the needs list of this subcommand
  1561 + bool remove_needs(Option *opt) {
  1562 + auto iterator = std::find(std::begin(need_options_), std::end(need_options_), opt);
  1563 + if(iterator == std::end(need_options_)) {
  1564 + return false;
  1565 + }
  1566 + need_options_.erase(iterator);
  1567 + return true;
  1568 + }
  1569 +
  1570 + /// Removes a subcommand from the needs list of this subcommand
  1571 + bool remove_needs(App *app) {
  1572 + auto iterator = std::find(std::begin(need_subcommands_), std::end(need_subcommands_), app);
  1573 + if(iterator == std::end(need_subcommands_)) {
  1574 + return false;
  1575 + }
  1576 + need_subcommands_.erase(iterator);
  1577 + return true;
  1578 + }
  1579 +
1476 1580 ///@}
1477 1581 /// @name Help
1478 1582 ///@{
... ... @@ -1688,7 +1792,16 @@ class App {
1688 1792 const App *get_parent() const { return parent_; }
1689 1793  
1690 1794 /// Get the name of the current app
1691   - std::string get_name() const { return name_; }
  1795 + const std::string &get_name() const { return name_; }
  1796 +
  1797 + /// Get the aliases of the current app
  1798 + const std::vector<std::string> &get_aliases() const { return aliases_; }
  1799 +
  1800 + /// clear all the aliases of the current App
  1801 + App *clear_aliases() {
  1802 + aliases_.clear();
  1803 + return this;
  1804 + }
1692 1805  
1693 1806 /// Get a display name for an app
1694 1807 std::string get_display_name() const { return (!name_.empty()) ? name_ : "[Option Group: " + get_group() + "]"; }
... ... @@ -1705,7 +1818,21 @@ class App {
1705 1818 name_to_check = detail::to_lower(name_to_check);
1706 1819 }
1707 1820  
1708   - return local_name == name_to_check;
  1821 + if(local_name == name_to_check) {
  1822 + return true;
  1823 + }
  1824 + for(auto les : aliases_) {
  1825 + if(ignore_underscore_) {
  1826 + les = detail::remove_underscore(les);
  1827 + }
  1828 + if(ignore_case_) {
  1829 + les = detail::to_lower(les);
  1830 + }
  1831 + if(les == name_to_check) {
  1832 + return true;
  1833 + }
  1834 + }
  1835 + return false;
1709 1836 }
1710 1837  
1711 1838 /// Get the groups available directly from this option (in order)
... ... @@ -2026,6 +2153,30 @@ class App {
2026 2153 // if we are excluded but didn't receive anything, just return
2027 2154 return;
2028 2155 }
  2156 +
  2157 + // check excludes
  2158 + bool missing_needed{false};
  2159 + std::string missing_need;
  2160 + for(auto &opt : need_options_) {
  2161 + if(opt->count() == 0) {
  2162 + missing_needed = true;
  2163 + missing_need = opt->get_name();
  2164 + }
  2165 + }
  2166 + for(auto &subc : need_subcommands_) {
  2167 + if(subc->count_all() == 0) {
  2168 + missing_needed = true;
  2169 + missing_need = subc->get_display_name();
  2170 + }
  2171 + }
  2172 + if(missing_needed) {
  2173 + if(count_all() > 0) {
  2174 + throw RequiresError(get_display_name(), missing_need);
  2175 + }
  2176 + // if we missing something but didn't have any options, just return
  2177 + return;
  2178 + }
  2179 +
2029 2180 size_t used_options = 0;
2030 2181 for(const Option_p &opt : options_) {
2031 2182  
... ... @@ -2287,7 +2438,7 @@ class App {
2287 2438 break;
2288 2439 case detail::Classifier::NONE:
2289 2440 // Probably a positional or something for a parent (sub)command
2290   - retval = _parse_positional(args);
  2441 + retval = _parse_positional(args, false);
2291 2442 if(retval && positionals_at_end_) {
2292 2443 positional_only = true;
2293 2444 }
... ... @@ -2327,8 +2478,9 @@ class App {
2327 2478 }
2328 2479  
2329 2480 /// Parse a positional, go up the tree to check
  2481 + /// @param haltOnSubcommand if set to true the operation will not process subcommands merely return false
2330 2482 /// Return true if the positional was used false otherwise
2331   - bool _parse_positional(std::vector<std::string> &args) {
  2483 + bool _parse_positional(std::vector<std::string> &args, bool haltOnSubcommand) {
2332 2484  
2333 2485 const std::string &positional = args.back();
2334 2486  
... ... @@ -2377,7 +2529,7 @@ class App {
2377 2529  
2378 2530 for(auto &subc : subcommands_) {
2379 2531 if((subc->name_.empty()) && (!subc->disabled_)) {
2380   - if(subc->_parse_positional(args)) {
  2532 + if(subc->_parse_positional(args, false)) {
2381 2533 if(!subc->pre_parse_called_) {
2382 2534 subc->_trigger_pre_parse(args.size());
2383 2535 }
... ... @@ -2387,11 +2539,14 @@ class App {
2387 2539 }
2388 2540 // let the parent deal with it if possible
2389 2541 if(parent_ != nullptr && fallthrough_)
2390   - return _get_fallthrough_parent()->_parse_positional(args);
  2542 + return _get_fallthrough_parent()->_parse_positional(args, static_cast<bool>(parse_complete_callback_));
2391 2543  
2392 2544 /// Try to find a local subcommand that is repeated
2393 2545 auto com = _find_subcommand(args.back(), true, false);
2394 2546 if(com != nullptr && (require_subcommand_max_ == 0 || require_subcommand_max_ > parsed_subcommands_.size())) {
  2547 + if(haltOnSubcommand) {
  2548 + return false;
  2549 + }
2395 2550 args.pop_back();
2396 2551 com->_parse(args);
2397 2552 return true;
... ... @@ -2436,7 +2591,8 @@ class App {
2436 2591 if(subc != nullptr) {
2437 2592 return subc;
2438 2593 }
2439   - } else if(com->check_name(subc_name)) {
  2594 + }
  2595 + if(com->check_name(subc_name)) {
2440 2596 if((!*com) || !ignore_used)
2441 2597 return com.get();
2442 2598 }
... ... @@ -2450,7 +2606,7 @@ class App {
2450 2606 /// return true if the subcommand was processed false otherwise
2451 2607 bool _parse_subcommand(std::vector<std::string> &args) {
2452 2608 if(_count_remaining_positionals(/* required */ true) > 0) {
2453   - _parse_positional(args);
  2609 + _parse_positional(args, false);
2454 2610 return true;
2455 2611 }
2456 2612 auto com = _find_subcommand(args.back(), true, true);
... ... @@ -2645,6 +2801,56 @@ class App {
2645 2801 return fallthrough_parent;
2646 2802 }
2647 2803  
  2804 + /// Helper function to run through all possible comparisons of subcommand names to check there is no overlap
  2805 + const std::string &_compare_subcommand_names(const App &subcom, const App &base) const {
  2806 + static const std::string estring;
  2807 + if(subcom.disabled_) {
  2808 + return estring;
  2809 + }
  2810 + for(auto &subc : base.subcommands_) {
  2811 + if(subc.get() != &subcom) {
  2812 + if(subc->disabled_) {
  2813 + continue;
  2814 + }
  2815 + if(!subcom.get_name().empty()) {
  2816 + if(subc->check_name(subcom.get_name())) {
  2817 + return subcom.get_name();
  2818 + }
  2819 + }
  2820 + if(!subc->get_name().empty()) {
  2821 + if(subcom.check_name(subc->get_name())) {
  2822 + return subc->get_name();
  2823 + }
  2824 + }
  2825 + for(const auto &les : subcom.aliases_) {
  2826 + if(subc->check_name(les)) {
  2827 + return les;
  2828 + }
  2829 + }
  2830 + // this loop is needed in case of ignore_underscore or ignore_case on one but not the other
  2831 + for(const auto &les : subc->aliases_) {
  2832 + if(subcom.check_name(les)) {
  2833 + return les;
  2834 + }
  2835 + }
  2836 + // if the subcommand is an option group we need to check deeper
  2837 + if(subc->get_name().empty()) {
  2838 + auto &cmpres = _compare_subcommand_names(subcom, *subc);
  2839 + if(!cmpres.empty()) {
  2840 + return cmpres;
  2841 + }
  2842 + }
  2843 + // if the test subcommand is an option group we need to check deeper
  2844 + if(subcom.get_name().empty()) {
  2845 + auto &cmpres = _compare_subcommand_names(*subc, subcom);
  2846 + if(!cmpres.empty()) {
  2847 + return cmpres;
  2848 + }
  2849 + }
  2850 + }
  2851 + }
  2852 + return estring;
  2853 + }
2648 2854 /// Helper function to place extra values in the most appropriate position
2649 2855 void _move_to_missing(detail::Classifier val_type, const std::string &val) {
2650 2856 if(allow_extras_ || subcommands_.empty()) {
... ... @@ -2696,10 +2902,10 @@ class App {
2696 2902 app->options_.push_back(std::move(*iterator));
2697 2903 options_.erase(iterator);
2698 2904 } else {
2699   - throw OptionAlreadyAdded(opt->get_name());
  2905 + throw OptionAlreadyAdded("option was not located: " + opt->get_name());
2700 2906 }
2701 2907 } else {
2702   - throw OptionNotFound("could not locate the given App");
  2908 + throw OptionNotFound("could not locate the given Option");
2703 2909 }
2704 2910 }
2705 2911 }; // namespace CLI
... ...
include/CLI/Option.hpp
... ... @@ -421,18 +421,19 @@ class Option : public OptionBase&lt;Option&gt; {
421 421  
422 422 /// Sets required options
423 423 Option *needs(Option *opt) {
424   - auto tup = needs_.insert(opt);
425   - if(!tup.second)
426   - throw OptionAlreadyAdded::Requires(get_name(), opt->get_name());
  424 + if(opt != this) {
  425 + needs_.insert(opt);
  426 + }
427 427 return this;
428 428 }
429 429  
430 430 /// Can find a string if needed
431 431 template <typename T = App> Option *needs(std::string opt_name) {
432   - for(const Option_p &opt : dynamic_cast<T *>(parent_)->options_)
433   - if(opt.get() != this && opt->check_name(opt_name))
434   - return needs(opt.get());
435   - throw IncorrectConstruction::MissingOption(opt_name);
  432 + auto opt = dynamic_cast<T *>(parent_)->get_option_no_throw(opt_name);
  433 + if(opt == nullptr) {
  434 + throw IncorrectConstruction::MissingOption(opt_name);
  435 + }
  436 + return needs(opt);
436 437 }
437 438  
438 439 /// Any number supported, any mix of string and Opt
... ... @@ -454,6 +455,9 @@ class Option : public OptionBase&lt;Option&gt; {
454 455  
455 456 /// Sets excluded options
456 457 Option *excludes(Option *opt) {
  458 + if(opt == this) {
  459 + throw(IncorrectConstruction("and option cannot exclude itself"));
  460 + }
457 461 excludes_.insert(opt);
458 462  
459 463 // Help text should be symmetric - excluding a should exclude b
... ... @@ -467,10 +471,11 @@ class Option : public OptionBase&lt;Option&gt; {
467 471  
468 472 /// Can find a string if needed
469 473 template <typename T = App> Option *excludes(std::string opt_name) {
470   - for(const Option_p &opt : dynamic_cast<T *>(parent_)->options_)
471   - if(opt.get() != this && opt->check_name(opt_name))
472   - return excludes(opt.get());
473   - throw IncorrectConstruction::MissingOption(opt_name);
  474 + auto opt = dynamic_cast<T *>(parent_)->get_option_no_throw(opt_name);
  475 + if(opt == nullptr) {
  476 + throw IncorrectConstruction::MissingOption(opt_name);
  477 + }
  478 + return excludes(opt);
474 479 }
475 480  
476 481 /// Any number supported, any mix of string and Opt
... ... @@ -501,13 +506,22 @@ class Option : public OptionBase&lt;Option&gt; {
501 506 /// The template hides the fact that we don't have the definition of App yet.
502 507 /// You are never expected to add an argument to the template here.
503 508 template <typename T = App> Option *ignore_case(bool value = true) {
504   - ignore_case_ = value;
505   - auto *parent = dynamic_cast<T *>(parent_);
506   -
507   - for(const Option_p &opt : parent->options_)
508   - if(opt.get() != this && *opt == *this)
509   - throw OptionAlreadyAdded(opt->get_name(true, true));
510   -
  509 + if(!ignore_case_ && value) {
  510 + ignore_case_ = value;
  511 + auto *parent = dynamic_cast<T *>(parent_);
  512 + for(const Option_p &opt : parent->options_) {
  513 + if(opt.get() == this) {
  514 + continue;
  515 + }
  516 + auto &omatch = opt->matching_name(*this);
  517 + if(!omatch.empty()) {
  518 + ignore_case_ = false;
  519 + throw OptionAlreadyAdded("adding ignore case caused a name conflict with " + omatch);
  520 + }
  521 + }
  522 + } else {
  523 + ignore_case_ = value;
  524 + }
511 525 return this;
512 526 }
513 527  
... ... @@ -516,12 +530,23 @@ class Option : public OptionBase&lt;Option&gt; {
516 530 /// The template hides the fact that we don't have the definition of App yet.
517 531 /// You are never expected to add an argument to the template here.
518 532 template <typename T = App> Option *ignore_underscore(bool value = true) {
519   - ignore_underscore_ = value;
520   - auto *parent = dynamic_cast<T *>(parent_);
521   - for(const Option_p &opt : parent->options_)
522   - if(opt.get() != this && *opt == *this)
523   - throw OptionAlreadyAdded(opt->get_name(true, true));
524 533  
  534 + if(!ignore_underscore_ && value) {
  535 + ignore_underscore_ = value;
  536 + auto *parent = dynamic_cast<T *>(parent_);
  537 + for(const Option_p &opt : parent->options_) {
  538 + if(opt.get() == this) {
  539 + continue;
  540 + }
  541 + auto &omatch = opt->matching_name(*this);
  542 + if(!omatch.empty()) {
  543 + ignore_underscore_ = false;
  544 + throw OptionAlreadyAdded("adding ignore underscore caused a name conflict with " + omatch);
  545 + }
  546 + }
  547 + } else {
  548 + ignore_underscore_ = value;
  549 + }
525 550 return this;
526 551 }
527 552  
... ... @@ -746,26 +771,29 @@ class Option : public OptionBase&lt;Option&gt; {
746 771 throw ConversionError(get_name(), results_);
747 772 }
748 773  
749   - /// If options share any of the same names, they are equal (not counting positional)
750   - bool operator==(const Option &other) const {
  774 + /// If options share any of the same names, find it
  775 + const std::string &matching_name(const Option &other) const {
  776 + static const std::string estring;
751 777 for(const std::string &sname : snames_)
752 778 if(other.check_sname(sname))
753   - return true;
  779 + return sname;
754 780 for(const std::string &lname : lnames_)
755 781 if(other.check_lname(lname))
756   - return true;
  782 + return lname;
757 783  
758 784 if(ignore_case_ ||
759 785 ignore_underscore_) { // We need to do the inverse, in case we are ignore_case or ignore underscore
760 786 for(const std::string &sname : other.snames_)
761 787 if(check_sname(sname))
762   - return true;
  788 + return sname;
763 789 for(const std::string &lname : other.lnames_)
764 790 if(check_lname(lname))
765   - return true;
  791 + return lname;
766 792 }
767   - return false;
  793 + return estring;
768 794 }
  795 + /// If options share any of the same names, they are equal (not counting positional)
  796 + bool operator==(const Option &other) const { return !matching_name(other).empty(); }
769 797  
770 798 /// Check a name. Requires "-" or "--" for short / long, supports positional name
771 799 bool check_name(std::string name) const {
... ...
tests/AppTest.cpp
... ... @@ -1792,6 +1792,8 @@ TEST_F(TApp, NeedsFlags) {
1792 1792  
1793 1793 args = {"--both"};
1794 1794 EXPECT_THROW(run(), CLI::RequiresError);
  1795 +
  1796 + EXPECT_NO_THROW(opt->needs(opt));
1795 1797 }
1796 1798  
1797 1799 TEST_F(TApp, ExcludesFlags) {
... ... @@ -1811,6 +1813,8 @@ TEST_F(TApp, ExcludesFlags) {
1811 1813  
1812 1814 args = {"--string", "--nostr"};
1813 1815 EXPECT_THROW(run(), CLI::ExcludesError);
  1816 +
  1817 + EXPECT_THROW(opt->excludes(opt), CLI::IncorrectConstruction);
1814 1818 }
1815 1819  
1816 1820 TEST_F(TApp, ExcludesMixedFlags) {
... ...
tests/CreationTest.cpp
... ... @@ -227,14 +227,16 @@ TEST_F(TApp, IncorrectConstructionDuplicateNeeds) {
227 227 auto cat = app.add_flag("--cat");
228 228 auto other = app.add_flag("--other");
229 229 ASSERT_NO_THROW(cat->needs(other));
230   - EXPECT_THROW(cat->needs(other), CLI::OptionAlreadyAdded);
  230 + // duplicated needs is redundant but not an error
  231 + EXPECT_NO_THROW(cat->needs(other));
231 232 }
232 233  
233 234 TEST_F(TApp, IncorrectConstructionDuplicateNeedsTxt) {
234 235 auto cat = app.add_flag("--cat");
235 236 app.add_flag("--other");
236 237 ASSERT_NO_THROW(cat->needs("--other"));
237   - EXPECT_THROW(cat->needs("--other"), CLI::OptionAlreadyAdded);
  238 + // duplicate needs is redundant but not an error
  239 + EXPECT_NO_THROW(cat->needs("--other"));
238 240 }
239 241  
240 242 // Now allowed
... ...
tests/OptionGroupTest.cpp
... ... @@ -438,6 +438,52 @@ TEST_F(ManyGroups, ExcludesGroup) {
438 438 EXPECT_FALSE(g1->remove_excludes(g2));
439 439 }
440 440  
  441 +TEST_F(ManyGroups, NeedsGroup) {
  442 + remove_required();
  443 + // all groups needed if g1 is used
  444 + g1->needs(g2);
  445 + g1->needs(g3);
  446 + args = {"--name1", "test"};
  447 + EXPECT_THROW(run(), CLI::RequiresError);
  448 + // other groups should run fine
  449 + args = {"--name2", "test2"};
  450 +
  451 + run();
  452 + // all three groups should be fine
  453 + args = {"--name1", "test", "--name2", "test2", "--name3", "test3"};
  454 +
  455 + EXPECT_NO_THROW(run());
  456 +}
  457 +
  458 +// test adding an option group with existing subcommands to an app
  459 +TEST_F(TApp, ExistingSubcommandMatch) {
  460 + auto sshared = std::make_shared<CLI::Option_group>("documenting the subcommand", "sub1g", nullptr);
  461 + auto s1 = sshared->add_subcommand("sub1");
  462 + auto o1 = sshared->add_option_group("opt1");
  463 + o1->add_subcommand("sub3")->alias("sub4");
  464 +
  465 + app.add_subcommand("sub1");
  466 +
  467 + try {
  468 + app.add_subcommand(sshared);
  469 + // this should throw the next line should never be reached
  470 + EXPECT_FALSE(true);
  471 + } catch(const CLI::OptionAlreadyAdded &oaa) {
  472 + EXPECT_THAT(oaa.what(), HasSubstr("sub1"));
  473 + }
  474 + sshared->remove_subcommand(s1);
  475 +
  476 + app.add_subcommand("sub3");
  477 + // now check that the subsubcommand overlaps
  478 + try {
  479 + app.add_subcommand(sshared);
  480 + // this should throw the next line should never be reached
  481 + EXPECT_FALSE(true);
  482 + } catch(const CLI::OptionAlreadyAdded &oaa) {
  483 + EXPECT_THAT(oaa.what(), HasSubstr("sub3"));
  484 + }
  485 +}
  486 +
441 487 TEST_F(ManyGroups, SingleGroupError) {
442 488 // only 1 group can be used
443 489 main->require_option(1);
... ... @@ -503,7 +549,7 @@ TEST_F(ManyGroups, RequiredFirst) {
503 549 }
504 550  
505 551 TEST_F(ManyGroups, DisableFirst) {
506   - // only 1 group can be used
  552 + // only 1 group can be used if remove_required not used
507 553 remove_required();
508 554 g1->disabled();
509 555  
... ... @@ -521,12 +567,15 @@ TEST_F(ManyGroups, DisableFirst) {
521 567 }
522 568  
523 569 TEST_F(ManyGroups, SameSubcommand) {
524   - // only 1 group can be used
  570 + // only 1 group can be used if remove_required not used
525 571 remove_required();
526   - auto sub1 = g1->add_subcommand("sub1");
527   - auto sub2 = g2->add_subcommand("sub1");
  572 + auto sub1 = g1->add_subcommand("sub1")->disabled();
  573 + auto sub2 = g2->add_subcommand("sub1")->disabled();
528 574 auto sub3 = g3->add_subcommand("sub1");
529   -
  575 + // so when the subcommands are disabled they can have the same name
  576 + sub1->disabled(false);
  577 + sub2->disabled(false);
  578 + // if they are reenabled they are not checked for overlap on enabling so they can have the same name
530 579 args = {"sub1", "sub1", "sub1"};
531 580  
532 581 run();
... ... @@ -534,7 +583,6 @@ TEST_F(ManyGroups, SameSubcommand) {
534 583 EXPECT_TRUE(*sub1);
535 584 EXPECT_TRUE(*sub2);
536 585 EXPECT_TRUE(*sub3);
537   - /// This should be made to work at some point
538 586 auto subs = app.get_subcommands();
539 587 EXPECT_EQ(subs.size(), 3u);
540 588 EXPECT_EQ(subs[0], sub1);
... ... @@ -556,7 +604,7 @@ TEST_F(ManyGroups, SameSubcommand) {
556 604 EXPECT_EQ(subs[2], sub3);
557 605 }
558 606 TEST_F(ManyGroups, CallbackOrder) {
559   - // only 1 group can be used
  607 + // only 1 group can be used if remove_required not used
560 608 remove_required();
561 609 std::vector<int> callback_order;
562 610 g1->callback([&callback_order]() { callback_order.push_back(1); });
... ... @@ -582,6 +630,7 @@ TEST_F(ManyGroups, CallbackOrder) {
582 630  
583 631 // Test the fallthrough for extra arguments
584 632 TEST_F(ManyGroups, ExtrasFallDown) {
  633 + // only 1 group can be used if remove_required not used
585 634 remove_required();
586 635  
587 636 args = {"--test1", "--flag", "extra"};
... ...
tests/SubcommandTest.cpp
... ... @@ -93,6 +93,17 @@ TEST_F(TApp, MultiSubFallthrough) {
93 93 EXPECT_THROW(app.got_subcommand("sub3"), CLI::OptionNotFound);
94 94 }
95 95  
  96 +TEST_F(TApp, CrazyNameSubcommand) {
  97 + auto sub1 = app.add_subcommand("sub1");
  98 + // name can be set to whatever
  99 + EXPECT_NO_THROW(sub1->name("crazy name with spaces"));
  100 + args = {"crazy name with spaces"};
  101 + run();
  102 +
  103 + EXPECT_TRUE(app.got_subcommand("crazy name with spaces"));
  104 + EXPECT_EQ(sub1->count(), 1u);
  105 +}
  106 +
96 107 TEST_F(TApp, RequiredAndSubcoms) { // #23
97 108  
98 109 std::string baz;
... ... @@ -272,6 +283,46 @@ TEST_F(TApp, CallbackOrder) {
272 283 EXPECT_EQ(cb[6], "c2");
273 284 EXPECT_EQ(cb[7], "ac2");
274 285 }
  286 +
  287 +TEST_F(TApp, CallbackOrder2) {
  288 +
  289 + std::vector<std::string> cb;
  290 + app.add_subcommand("sub1")->parse_complete_callback([&cb]() { cb.push_back("sub1"); });
  291 + app.add_subcommand("sub2")->parse_complete_callback([&cb]() { cb.push_back("sub2"); });
  292 + app.add_subcommand("sub3")->parse_complete_callback([&cb]() { cb.push_back("sub3"); });
  293 +
  294 + args = {"sub1", "sub2", "sub3", "sub1", "sub1", "sub2", "sub1"};
  295 + run();
  296 + EXPECT_EQ(cb.size(), 7u);
  297 + EXPECT_EQ(cb[0], "sub1");
  298 + EXPECT_EQ(cb[1], "sub2");
  299 + EXPECT_EQ(cb[2], "sub3");
  300 + EXPECT_EQ(cb[3], "sub1");
  301 + EXPECT_EQ(cb[4], "sub1");
  302 + EXPECT_EQ(cb[5], "sub2");
  303 + EXPECT_EQ(cb[6], "sub1");
  304 +}
  305 +
  306 +TEST_F(TApp, CallbackOrder2_withFallthrough) {
  307 +
  308 + std::vector<std::string> cb;
  309 +
  310 + app.add_subcommand("sub1")->parse_complete_callback([&cb]() { cb.push_back("sub1"); })->fallthrough();
  311 + app.add_subcommand("sub2")->parse_complete_callback([&cb]() { cb.push_back("sub2"); });
  312 + app.add_subcommand("sub3")->parse_complete_callback([&cb]() { cb.push_back("sub3"); });
  313 +
  314 + args = {"sub1", "sub2", "sub3", "sub1", "sub1", "sub2", "sub1"};
  315 + run();
  316 + EXPECT_EQ(cb.size(), 7u);
  317 + EXPECT_EQ(cb[0], "sub1");
  318 + EXPECT_EQ(cb[1], "sub2");
  319 + EXPECT_EQ(cb[2], "sub3");
  320 + EXPECT_EQ(cb[3], "sub1");
  321 + EXPECT_EQ(cb[4], "sub1");
  322 + EXPECT_EQ(cb[5], "sub2");
  323 + EXPECT_EQ(cb[6], "sub1");
  324 +}
  325 +
275 326 TEST_F(TApp, RuntimeErrorInCallback) {
276 327 auto sub1 = app.add_subcommand("sub1");
277 328 sub1->callback([]() { throw CLI::RuntimeError(); });
... ... @@ -420,7 +471,7 @@ TEST_F(TApp, Nameless4LayerDeep) {
420 471 }
421 472  
422 473 /// Put subcommands in some crazy pattern and make everything still works
423   -TEST_F(TApp, Nameless4LayerDeepMulit) {
  474 +TEST_F(TApp, Nameless4LayerDeepMulti) {
424 475  
425 476 auto sub1 = app.add_subcommand();
426 477 auto sub2 = app.add_subcommand();
... ... @@ -1248,6 +1299,93 @@ TEST_F(ManySubcommands, SubcommandOptionExclusion) {
1248 1299 }
1249 1300 }
1250 1301  
  1302 +TEST_F(ManySubcommands, SubcommandNeeds) {
  1303 +
  1304 + sub1->needs(sub2);
  1305 + args = {"sub1", "sub2"};
  1306 + EXPECT_NO_THROW(run());
  1307 +
  1308 + args = {"sub2"};
  1309 + EXPECT_NO_THROW(run());
  1310 +
  1311 + args = {"sub1"};
  1312 + EXPECT_THROW(run(), CLI::RequiresError);
  1313 +
  1314 + sub1->needs(sub3);
  1315 + args = {"sub1", "sub2", "sub3"};
  1316 + EXPECT_NO_THROW(run());
  1317 +
  1318 + args = {"sub1", "sub2", "sub4"};
  1319 + EXPECT_THROW(run(), CLI::RequiresError);
  1320 +
  1321 + args = {"sub1", "sub2", "sub4"};
  1322 + sub1->remove_needs(sub3);
  1323 + EXPECT_NO_THROW(run());
  1324 +}
  1325 +
  1326 +TEST_F(ManySubcommands, SubcommandNeedsOptions) {
  1327 +
  1328 + auto opt = app.add_flag("--subactive");
  1329 + sub1->needs(opt);
  1330 + sub1->fallthrough();
  1331 + args = {"sub1", "--subactive"};
  1332 + EXPECT_NO_THROW(run());
  1333 +
  1334 + args = {"sub1"};
  1335 + EXPECT_THROW(run(), CLI::RequiresError);
  1336 +
  1337 + args = {"--subactive"};
  1338 + EXPECT_NO_THROW(run());
  1339 +
  1340 + auto opt2 = app.add_flag("--subactive2");
  1341 +
  1342 + sub1->needs(opt2);
  1343 + args = {"sub1", "--subactive"};
  1344 + EXPECT_THROW(run(), CLI::RequiresError);
  1345 +
  1346 + args = {"--subactive", "--subactive2", "sub1"};
  1347 + EXPECT_NO_THROW(run());
  1348 +
  1349 + sub1->remove_needs(opt2);
  1350 + args = {"sub1", "--subactive"};
  1351 + EXPECT_NO_THROW(run());
  1352 +}
  1353 +
  1354 +TEST_F(ManySubcommands, SubcommandNeedsOptionsCallbackOrdering) {
  1355 + int count = 0;
  1356 + auto opt = app.add_flag("--subactive");
  1357 + app.add_flag("--flag1");
  1358 + sub1->needs(opt);
  1359 + sub1->fallthrough();
  1360 + sub1->parse_complete_callback([&count]() { ++count; });
  1361 + args = {"sub1", "--flag1", "sub1", "--subactive"};
  1362 + EXPECT_THROW(run(), CLI::RequiresError);
  1363 + // the subcommand has to pass validation by the first callback
  1364 + sub1->immediate_callback(false);
  1365 + // now since the callback executes after
  1366 +
  1367 + EXPECT_NO_THROW(run());
  1368 + EXPECT_EQ(count, 1);
  1369 + sub1->immediate_callback();
  1370 + args = {"--subactive", "sub1"};
  1371 + // now the required is processed first
  1372 + EXPECT_NO_THROW(run());
  1373 +}
  1374 +
  1375 +TEST_F(ManySubcommands, SubcommandNeedsFail) {
  1376 +
  1377 + auto opt = app.add_flag("--subactive");
  1378 + auto opt2 = app.add_flag("--dummy");
  1379 + sub1->needs(opt);
  1380 + EXPECT_THROW(sub1->needs((CLI::Option *)nullptr), CLI::OptionNotFound);
  1381 + EXPECT_THROW(sub1->needs((CLI::App *)nullptr), CLI::OptionNotFound);
  1382 + EXPECT_THROW(sub1->needs(sub1), CLI::OptionNotFound);
  1383 +
  1384 + EXPECT_TRUE(sub1->remove_needs(opt));
  1385 + EXPECT_FALSE(sub1->remove_needs(opt2));
  1386 + EXPECT_FALSE(sub1->remove_needs(sub1));
  1387 +}
  1388 +
1251 1389 TEST_F(ManySubcommands, SubcommandRequired) {
1252 1390  
1253 1391 sub1->required();
... ... @@ -1377,6 +1515,176 @@ TEST_F(TApp, UnnamedSubNoExtras) {
1377 1515 EXPECT_EQ(sub->remaining_size(), 0u);
1378 1516 }
1379 1517  
  1518 +TEST_F(TApp, SubcommandAlias) {
  1519 + double val;
  1520 + auto sub = app.add_subcommand("sub1");
  1521 + sub->alias("sub2");
  1522 + sub->alias("sub3");
  1523 + sub->add_option("-v,--value", val);
  1524 + args = {"sub1", "-v", "-3"};
  1525 + run();
  1526 + EXPECT_EQ(val, -3.0);
  1527 +
  1528 + args = {"sub2", "--value", "-5"};
  1529 + run();
  1530 + EXPECT_EQ(val, -5.0);
  1531 +
  1532 + args = {"sub3", "-v", "7"};
  1533 + run();
  1534 + EXPECT_EQ(val, 7);
  1535 +
  1536 + auto &al = sub->get_aliases();
  1537 + ASSERT_GE(al.size(), 2U);
  1538 +
  1539 + EXPECT_EQ(al[0], "sub2");
  1540 + EXPECT_EQ(al[1], "sub3");
  1541 +
  1542 + sub->clear_aliases();
  1543 + EXPECT_TRUE(al.empty());
  1544 +}
  1545 +
  1546 +TEST_F(TApp, SubcommandAliasIgnoreCaseUnderscore) {
  1547 + double val;
  1548 + auto sub = app.add_subcommand("sub1");
  1549 + sub->alias("sub2");
  1550 + sub->alias("sub3");
  1551 + sub->ignore_case();
  1552 + sub->add_option("-v,--value", val);
  1553 + args = {"sub1", "-v", "-3"};
  1554 + run();
  1555 + EXPECT_EQ(val, -3.0);
  1556 +
  1557 + args = {"SUB2", "--value", "-5"};
  1558 + run();
  1559 + EXPECT_EQ(val, -5.0);
  1560 +
  1561 + args = {"sUb3", "-v", "7"};
  1562 + run();
  1563 + EXPECT_EQ(val, 7);
  1564 + sub->ignore_underscore();
  1565 + args = {"sub_1", "-v", "-3"};
  1566 + run();
  1567 + EXPECT_EQ(val, -3.0);
  1568 +
  1569 + args = {"SUB_2", "--value", "-5"};
  1570 + run();
  1571 + EXPECT_EQ(val, -5.0);
  1572 +
  1573 + args = {"sUb_3", "-v", "7"};
  1574 + run();
  1575 + EXPECT_EQ(val, 7);
  1576 +
  1577 + sub->ignore_case(false);
  1578 + args = {"sub_1", "-v", "-3"};
  1579 + run();
  1580 + EXPECT_EQ(val, -3.0);
  1581 +
  1582 + args = {"SUB_2", "--value", "-5"};
  1583 + EXPECT_THROW(run(), CLI::ExtrasError);
  1584 +
  1585 + args = {"sUb_3", "-v", "7"};
  1586 + EXPECT_THROW(run(), CLI::ExtrasError);
  1587 +}
  1588 +
  1589 +TEST_F(TApp, OptionGroupAlias) {
  1590 + double val;
  1591 + auto sub = app.add_option_group("sub1");
  1592 + sub->alias("sub2");
  1593 + sub->alias("sub3");
  1594 + sub->add_option("-v,--value", val);
  1595 + args = {"sub1", "-v", "-3"};
  1596 + EXPECT_THROW(run(), CLI::ExtrasError);
  1597 +
  1598 + args = {"sub2", "--value", "-5"};
  1599 + run();
  1600 + EXPECT_EQ(val, -5.0);
  1601 +
  1602 + args = {"sub3", "-v", "7"};
  1603 + run();
  1604 + EXPECT_EQ(val, 7);
  1605 +
  1606 + args = {"-v", "-3"};
  1607 + run();
  1608 + EXPECT_EQ(val, -3);
  1609 +}
  1610 +
  1611 +TEST_F(TApp, AliasErrors) {
  1612 + auto sub1 = app.add_subcommand("sub1");
  1613 + auto sub2 = app.add_subcommand("sub2");
  1614 +
  1615 + EXPECT_THROW(sub2->alias("this is a not a valid alias"), CLI::IncorrectConstruction);
  1616 + EXPECT_THROW(sub2->alias("-alias"), CLI::IncorrectConstruction);
  1617 + EXPECT_THROW(sub2->alias("alia$"), CLI::IncorrectConstruction);
  1618 +
  1619 + EXPECT_THROW(app.add_subcommand("--bad_subcommand_name", "documenting the bad subcommand"),
  1620 + CLI::IncorrectConstruction);
  1621 +
  1622 + EXPECT_THROW(app.add_subcommand("documenting a subcommand", "sub3"), CLI::IncorrectConstruction);
  1623 + // cannot alias to an existing subcommand
  1624 + EXPECT_THROW(sub2->alias("sub1"), CLI::OptionAlreadyAdded);
  1625 + EXPECT_THROW(sub1->alias("sub2"), CLI::OptionAlreadyAdded);
  1626 + // aliasing to an existing name should be allowed
  1627 + EXPECT_NO_THROW(sub1->alias(sub1->get_name()));
  1628 +
  1629 + sub1->alias("les1")->alias("les2")->alias("les_3");
  1630 + sub2->alias("s2les1")->alias("s2les2")->alias("s2les3");
  1631 +
  1632 + EXPECT_THROW(sub2->alias("les2"), CLI::OptionAlreadyAdded);
  1633 + EXPECT_THROW(sub1->alias("s2les2"), CLI::OptionAlreadyAdded);
  1634 +
  1635 + EXPECT_THROW(sub2->name("sub1"), CLI::OptionAlreadyAdded);
  1636 + sub2->ignore_underscore();
  1637 + EXPECT_THROW(sub2->alias("les3"), CLI::OptionAlreadyAdded);
  1638 +}
  1639 +// test adding a subcommand via the pointer
  1640 +TEST_F(TApp, ExistingSubcommandMatch) {
  1641 + auto sshared = std::make_shared<CLI::App>("documenting the subcommand", "sub1");
  1642 + sshared->alias("sub2")->alias("sub3");
  1643 +
  1644 + EXPECT_EQ(sshared->get_name(), "sub1");
  1645 + app.add_subcommand("sub1");
  1646 +
  1647 + try {
  1648 + app.add_subcommand(sshared);
  1649 + // this should throw the next line should never be reached
  1650 + EXPECT_FALSE(true);
  1651 + } catch(const CLI::OptionAlreadyAdded &oaa) {
  1652 + EXPECT_THAT(oaa.what(), HasSubstr("sub1"));
  1653 + }
  1654 + sshared->name("osub");
  1655 + app.add_subcommand("sub2");
  1656 + // now check that the aliases don't overlap
  1657 + try {
  1658 + app.add_subcommand(sshared);
  1659 + // this should throw the next line should never be reached
  1660 + EXPECT_FALSE(true);
  1661 + } catch(const CLI::OptionAlreadyAdded &oaa) {
  1662 + EXPECT_THAT(oaa.what(), HasSubstr("sub2"));
  1663 + }
  1664 + // now check that disabled subcommands can be added regardless of name
  1665 + sshared->name("sub1");
  1666 + sshared->disabled();
  1667 + EXPECT_NO_THROW(app.add_subcommand(sshared));
  1668 +}
  1669 +
  1670 +TEST_F(TApp, AliasErrorsInOptionGroup) {
  1671 + auto sub1 = app.add_subcommand("sub1");
  1672 + auto g2 = app.add_option_group("g1");
  1673 + auto sub2 = g2->add_subcommand("sub2");
  1674 +
  1675 + // cannot alias to an existing subcommand even if it is in an option group
  1676 + EXPECT_THROW(sub2->alias("sub1"), CLI::OptionAlreadyAdded);
  1677 + EXPECT_THROW(sub1->alias("sub2"), CLI::OptionAlreadyAdded);
  1678 +
  1679 + sub1->alias("les1")->alias("les2")->alias("les3");
  1680 + sub2->alias("s2les1")->alias("s2les2")->alias("s2les3");
  1681 +
  1682 + EXPECT_THROW(sub2->alias("les2"), CLI::OptionAlreadyAdded);
  1683 + EXPECT_THROW(sub1->alias("s2les2"), CLI::OptionAlreadyAdded);
  1684 +
  1685 + EXPECT_THROW(sub2->name("sub1"), CLI::OptionAlreadyAdded);
  1686 +}
  1687 +
1380 1688 TEST(SharedSubTests, SharedSubcommand) {
1381 1689 double val, val2, val3, val4;
1382 1690 CLI::App app1{"test program1"};
... ...