Commit fe7e84f29ad339928d417ecf5821f7d70e72f0ac

Authored by Philip Top
Committed by Henry Schreiner
1 parent be8a08f2

add some unit tests for the fallthrough_parent command

rework return values from _parse_* function to return true if the value was processed false otherwise, this simplified the logic and got rid of the pulling and clearing of the missing fields from option groups.

add TriggerOff and TriggerOn helper functions and some tests for them

add shapes example of multiple callbacks in order.

allow specification of callbacks that get executed immediately on completion of parsing of subcommand

add tests for enabled/disabled by default

add _get_fallthrough_parent.  To get the most appropriate parent to fallthrough to

add enabled and disabled by default functions

add positional_arity example

Add a pre_parse_callback_ for apps.  The Pre parse callback takes an argument for the number of remaining arguments left to process, and will execute prior to parsing for subcommands, and after the first option parse for option_groups.
README.md
... ... @@ -42,6 +42,7 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature
42 42 - [Subcommands](#subcommands)
43 43 - [Subcommand options](#subcommand-options)
44 44 - [Option groups](#option-groups) ๐Ÿšง
  45 + - [Callbacks](#callbacks)
45 46 - [Configuration file](#configuration-file)
46 47 - [Inheriting defaults](#inheriting-defaults)
47 48 - [Formatting](#formatting)
... ... @@ -235,7 +236,7 @@ Option_group *app.add_option_group(name,description); // ๐Ÿšง
235 236 -app.add_mutable_set_ignore_case_underscore(... // ๐Ÿ†• String only
236 237 ```
237 238  
238   -An option name must start with a alphabetic character, underscore, or a number. For long options, after the first character '.', and '-' are also valid. For the `add_flag*` functions '{' has special meaning. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on help line for its positional form. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option`.
  239 +An option name must start with a alphabetic character, underscore, or a number๐Ÿšง. For long options, after the first character '.', and '-' are also valid. For the `add_flag*` functions '{' has special meaning. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on help line for its positional form. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option`.
239 240  
240 241 The `add_option_function<type>(...` function will typically require the template parameter be given unless a `std::function` object with an exact match is passed. The type can be any type supported by the `add_option` function.
241 242  
... ... @@ -275,7 +276,7 @@ The add commands return a pointer to an internally stored `Option`. If you set t
275 276 Before parsing, you can set the following options:
276 277  
277 278 - `->required()`: The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works.
278   -- `->expected(N)`: Take `N` values instead of as many as possible, only for vector args. If negative, require at least `-N`; end with `--` or another recognized option.
  279 +- `->expected(N)`: Take `N` values instead of as many as possible, only for vector args. If negative, require at least `-N`; end with `--` or another recognized option or subcommand.
279 280 - `->type_name(typename)`: Set the name of an Option's type (`type_name_fn` allows a function instead)
280 281 - `->type_size(N)`: Set the intrinsic size of an option. The parser will require multiples of this number if negative.
281 282 - `->needs(opt)`: This option requires another option to also be present, opt is an `Option` pointer.
... ... @@ -353,7 +354,7 @@ These Validators can be used by simply passing the name into the `check` or `tra
353 354 ->check(CLI::Range(0,10));
354 355 ```
355 356  
356   -Validators can be merged using `&` and `|` and inverted using `!`
  357 +Validators can be merged using `&` and `|` and inverted using `!`๐Ÿšง
357 358 such as
358 359 ```cpp
359 360 ->check(CLI::Range(0,10)|CLI::Range(20,30));
... ... @@ -492,7 +493,9 @@ There are several options that are supported on the main app and subcommands and
492 493 - `.count(option_name)`: Returns the number of times a particular option was called
493 494 - `.count_all()`: ๐Ÿšง Returns the total number of arguments a particular subcommand processed, on the master App it returns the total number of processed commands
494 495 - `.name(name)`: Add or change the name.
495   -- `.callback(void() function)`: Set the callback that runs at the end of parsing. The options have already run at this point.
  496 +- `.callback(void() function)`: Set the callback that runs at the end of parsing. The options have already run at this point. See [Subcommand callbacks](#callbacks) for some additional details.
  497 +- `.immediate_callback()`: ๐Ÿšง Specify that the callback for a subcommand should run immediately on completion of a subcommand vs at the completion of all parsing if this option is not used.
  498 +- `.pre_parse_callback(void(size_t) function)`: ๐Ÿšง Set a callback that executes after the first argument of an application is processed. See [Subcommand callbacks](#callbacks) for some additional details.
496 499 - `.allow_extras()`: Do not throw an error if extra arguments are left over.
497 500 - `.positionals_at_end()`: ๐Ÿšง Specify that positional arguments occur as the last arguments and throw an error if an unexpected positional is encountered.
498 501 - `.prefix_command()`: Like `allow_extras`, but stop immediately on the first unrecognized item. It is ideal for allowing your app or subcommand to be a "prefix" to calling another app.
... ... @@ -505,9 +508,48 @@ There are several options that are supported on the main app and subcommands and
505 508  
506 509 > Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function.
507 510  
  511 +
  512 +#### Callbacks
  513 +A subcommand has two optional callbacks that are executed at different stages of processing. The `preparse_callback` ๐Ÿšง is executed once after the first argument of a subcommand or application is processed and gives an argument for the number of remaining arguments to process. For the main app the first argument is considered the program name, for subcommands the first argument is the subcommand name. For Option groups and nameless subcommands the first argument is after the first argument or subcommand is processed from that group.
  514 +The second callback is executed after parsing. Depending on the status of the `immediate_callback` flag ๐Ÿšง. This is either immediately after the parsing of the subcommand. Or if the flag is false, once after parsing of all arguments. If the immediate_callback is set then the callback can be executed multiple times. If the main app or subcommand has a config file, no data from the config file will be reflected in immediate_callback.
  515 +
  516 +For example say an application was set up like
  517 +```cpp
  518 +app.callback(ac);
  519 +sub1=app.add_subcommand("sub1")->callback(c1)->preparse_callback(pc1)->immediate_callback();
  520 +sub2=app.add_subcommand("sub2")->callback(c2)->preparse_callback(pc2);
  521 +app.preparse_callback( pa1);
  522 +
  523 +... A bunch of other options
  524 +
  525 +```
  526 +Then the command line is given as
  527 +
  528 +```
  529 +program --opt1 opt1_val sub1 --sub1opt --sub1optb val sub2 --sub2opt sub1 --sub1opt2 sub2 --sub2opt2 val
  530 +```
  531 +
  532 +* pa will be called prior to parsing any values with an argument of 14.
  533 +* pc1 will be called immediately after processing the sub1 command with a value of 10.
  534 +* c1 will be called when the `sub2` command is encountered
  535 +* pc2 will be called with value of 6 after the sub2 command is encountered.
  536 +* c1 will be called again after the second sub2 command is encountered
  537 +* ac will be called after completing the parse
  538 +* c2 will be called once after processing all arguments
  539 +
  540 +A subcommand is considered terminated when one of the following conditions are met.
  541 +1. There are no more arguments to process
  542 +2. Another subcommand is encountered that would not fit in an optional slot of the subcommand
  543 +3. The positional_mark(`--`) is encountered and there are no available positional slots in the subcommand.
  544 +4. The subcommand_terminator mark(`++`) is encountered
  545 +
  546 +If the `immediate_callback` flag is set then all contained options are processed and the callback is triggered. If a subcommand with an immediate_callback flag is called again, then the contained options are reset, and can be triggered again.
  547 +
  548 +
  549 +
508 550 #### Option groups ๐Ÿšง
509 551  
510   -The method
  552 +The subcommand method
511 553 ```cpp
512 554 .add_option_group(name,description)
513 555 ```
... ...
examples/CMakeLists.txt
... ... @@ -94,6 +94,28 @@ add_test(NAME option_groups_extra2 COMMAND option_groups --csv --address &quot;192.16
94 94 set_property(TEST option_groups_extra2 PROPERTY PASS_REGULAR_EXPRESSION
95 95 "at most 1")
96 96  
  97 +add_cli_exe(positional_arity positional_arity.cpp)
  98 +add_test(NAME positional_arity1 COMMAND positional_arity one )
  99 +set_property(TEST positional_arity1 PROPERTY PASS_REGULAR_EXPRESSION
  100 + "File 1 = one")
  101 +add_test(NAME positional_arity2 COMMAND positional_arity one two )
  102 +set_property(TEST positional_arity2 PROPERTY PASS_REGULAR_EXPRESSION
  103 + "File 1 = one"
  104 + "File 2 = two")
  105 +add_test(NAME positional_arity3 COMMAND positional_arity 1 2 one)
  106 +set_property(TEST positional_arity3 PROPERTY PASS_REGULAR_EXPRESSION
  107 + "File 1 = one")
  108 +add_test(NAME positional_arity_fail COMMAND positional_arity 1 one two)
  109 +set_property(TEST positional_arity_fail PROPERTY PASS_REGULAR_EXPRESSION
  110 + "Could not convert")
  111 +
  112 +add_cli_exe(shapes shapes.cpp)
  113 +add_test(NAME shapes_all COMMAND shapes circle 4.4 circle 10.7 rectangle 4 4 circle 2.3 triangle 4.5 ++ rectangle 2.1 ++ circle 234.675)
  114 +set_property(TEST shapes_all PROPERTY PASS_REGULAR_EXPRESSION
  115 + "circle2"
  116 + "circle4"
  117 + "rectangle2 with edges [2.1,2.1]"
  118 + "triangel1 with sides [4.5]")
97 119  
98 120 add_cli_exe(ranges ranges.cpp)
99 121 add_test(NAME ranges_range COMMAND ranges --range 1 2 3)
... ...
examples/positional_arity.cpp 0 โ†’ 100644
  1 +#include "CLI/CLI.hpp"
  2 +
  3 +int main(int argc, char **argv) {
  4 +
  5 + CLI::App app("test for positional arity");
  6 +
  7 + auto numbers = app.add_option_group("numbers", "specify key numbers");
  8 + auto files = app.add_option_group("files", "specify files");
  9 + int num1 = -1, num2 = -1;
  10 + numbers->add_option("num1", num1, "first number");
  11 + numbers->add_option("num2", num2, "second number");
  12 + std::string file1, file2;
  13 + files->add_option("file1", file1, "first file")->required();
  14 + files->add_option("file2", file2, "second file");
  15 + // set a pre parse callback that turns the numbers group on or off depending on the number of arguments
  16 + app.preparse_callback([numbers](size_t arity) {
  17 + if(arity <= 2) {
  18 + numbers->disabled();
  19 + } else {
  20 + numbers->disabled(false);
  21 + }
  22 + });
  23 +
  24 + CLI11_PARSE(app, argc, argv);
  25 +
  26 + if(num1 != -1)
  27 + std::cout << "Num1 = " << num1 << '\n';
  28 +
  29 + if(num2 != -1)
  30 + std::cout << "Num2 = " << num2 << '\n';
  31 +
  32 + std::cout << "File 1 = " << file1 << '\n';
  33 + if(!file2.empty()) {
  34 + std::cout << "File 2 = " << file2 << '\n';
  35 + }
  36 +
  37 + return 0;
  38 +}
... ...
examples/shapes.cpp 0 โ†’ 100644
  1 +#include "CLI/CLI.hpp"
  2 +
  3 +int main(int argc, char **argv) {
  4 +
  5 + CLI::App app("load shapes");
  6 +
  7 + app.set_help_all_flag("--help-all");
  8 + auto circle = app.add_subcommand("circle", "draw a circle")->immediate_callback();
  9 + double radius{0.0};
  10 + int circle_counter = 0;
  11 + circle->callback([&radius, &circle_counter] {
  12 + ++circle_counter;
  13 + std::cout << "circle" << circle_counter << " with radius " << radius << std::endl;
  14 + });
  15 +
  16 + circle->add_option("radius", radius, "the radius of the circle")->required();
  17 +
  18 + auto rect = app.add_subcommand("rectangle", "draw a rectangle")->immediate_callback();
  19 + double edge1{0.0};
  20 + double edge2{0.0};
  21 + int rect_counter = 0;
  22 + rect->callback([&edge1, &edge2, &rect_counter] {
  23 + ++rect_counter;
  24 + if(edge2 == 0) {
  25 + edge2 = edge1;
  26 + }
  27 + std::cout << "rectangle" << rect_counter << " with edges [" << edge1 << ',' << edge2 << "]" << std::endl;
  28 + edge2 = 0;
  29 + });
  30 +
  31 + rect->add_option("edge1", edge1, "the first edge length of the rectangle")->required();
  32 + rect->add_option("edge2", edge2, "the second edge length of the rectangle");
  33 +
  34 + auto tri = app.add_subcommand("triangle", "draw a rectangle")->immediate_callback();
  35 + std::vector<double> sides;
  36 + int tri_counter = 0;
  37 + tri->callback([&sides, &tri_counter] {
  38 + ++tri_counter;
  39 +
  40 + std::cout << "triangle" << tri_counter << " with sides [" << CLI::detail::join(sides) << "]" << std::endl;
  41 + });
  42 +
  43 + tri->add_option("sides", sides, "the side lengths of the triangle");
  44 +
  45 + CLI11_PARSE(app, argc, argv);
  46 +
  47 + return 0;
  48 +}
... ...
include/CLI/App.hpp
... ... @@ -38,7 +38,7 @@ namespace CLI {
38 38 #endif
39 39  
40 40 namespace detail {
41   -enum class Classifier { NONE, POSITIONAL_MARK, SHORT, LONG, WINDOWS, SUBCOMMAND };
  41 +enum class Classifier { NONE, POSITIONAL_MARK, SHORT, LONG, WINDOWS, SUBCOMMAND, SUBCOMMAND_TERMINATOR };
42 42 struct AppFriend;
43 43 } // namespace detail
44 44  
... ... @@ -90,6 +90,16 @@ class App {
90 90 /// If set to true the subcommand is disabled and cannot be used, ignored for main app
91 91 bool disabled_{false};
92 92  
  93 + /// Flag indicating that the pre_parse_callback has been triggered
  94 + bool pre_parse_called_{false};
  95 +
  96 + /// Flag indicating that the callback for the subcommand should be executed immediately on parse completion which is
  97 + /// before help or ini files are processed.
  98 + bool immediate_callback_{false};
  99 +
  100 + /// This is a function that runs prior to the start of parsing
  101 + std::function<void(size_t)> pre_parse_callback_;
  102 +
93 103 /// This is a function that runs when complete. Great for subcommands. Can throw.
94 104 std::function<void()> callback_;
95 105  
... ... @@ -173,6 +183,11 @@ class App {
173 183 /// specify that positional arguments come at the end of the argument sequence not inheritable
174 184 bool positionals_at_end_{false};
175 185  
  186 + /// If set to true the subcommand will start each parse disabled
  187 + bool disabled_by_default_{false};
  188 + /// If set to true the subcommand will be reenabled at the start of each parse
  189 + bool enabled_by_default_{false};
  190 +
176 191 /// A pointer to the parent if this is a subcommand
177 192 App *parent_{nullptr};
178 193  
... ... @@ -267,6 +282,13 @@ class App {
267 282 return this;
268 283 }
269 284  
  285 + /// Set a callback to execute prior to parsing.
  286 + ///
  287 + App *preparse_callback(std::function<void(size_t)> pp_callback) {
  288 + pre_parse_callback_ = std::move(pp_callback);
  289 + return this;
  290 + }
  291 +
270 292 /// Set a name for the app (empty will use parser to set the name)
271 293 App *name(std::string app_name = "") {
272 294 name_ = app_name;
... ... @@ -292,6 +314,25 @@ class App {
292 314 return this;
293 315 }
294 316  
  317 + /// Set the subcommand to be disabled by default, so on clear(), at the start of each parse it is disabled
  318 + App *disabled_by_default(bool disable = true) {
  319 + disabled_by_default_ = disable;
  320 + return this;
  321 + }
  322 +
  323 + /// Set the subcommand to be enabled by default, so on clear(), at the start of each parse it is enabled(not
  324 + /// disabled)
  325 + App *enabled_by_default(bool enable = true) {
  326 + enabled_by_default_ = enable;
  327 + return this;
  328 + }
  329 +
  330 + /// Set the subcommand callback to be executed immediately on subcommand completion
  331 + App *immediate_callback(bool immediate = true) {
  332 + immediate_callback_ = immediate;
  333 + return this;
  334 + }
  335 +
295 336 /// Remove the error when extras are left over on the command line.
296 337 /// Will also call App::allow_extras().
297 338 App *allow_config_extras(bool allow = true) {
... ... @@ -403,7 +444,7 @@ class App {
403 444 }
404 445  
405 446 /// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`)
406   - template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy>
  447 + template <typename T, enable_if_t<!is_vector<T>::value & !std::is_const<T>::value, detail::enabler> = detail::dummy>
407 448 Option *add_option(std::string option_name,
408 449 T &variable, ///< The variable to set
409 450 std::string option_description = "") {
... ... @@ -711,7 +752,7 @@ class App {
711 752 #ifdef CLI11_CPP14
712 753 /// Add option for callback (C++14 or better only)
713 754 Option *add_flag(std::string flag_name,
714   - std::function<void(int64_t)> function, ///< A function to call, void(int)
  755 + std::function<void(int64_t)> function, ///< A function to call, void(int64_t)
715 756 std::string flag_description = "") {
716 757 return add_flag_function(std::move(flag_name), std::move(function), std::move(flag_description));
717 758 }
... ... @@ -1195,13 +1236,15 @@ class App {
1195 1236 void clear() {
1196 1237  
1197 1238 parsed_ = 0;
  1239 + pre_parse_called_ = false;
  1240 +
1198 1241 missing_.clear();
1199 1242 parsed_subcommands_.clear();
1200 1243 for(const Option_p &opt : options_) {
1201 1244 opt->clear();
1202 1245 }
1203   - for(const App_p &app : subcommands_) {
1204   - app->clear();
  1246 + for(const App_p &subc : subcommands_) {
  1247 + subc->clear();
1205 1248 }
1206 1249 }
1207 1250  
... ... @@ -1575,6 +1618,15 @@ class App {
1575 1618 /// Get the status of disabled
1576 1619 bool get_disabled() const { return disabled_; }
1577 1620  
  1621 + /// Get the status of disabled
  1622 + bool get_immediate_callback() const { return immediate_callback_; }
  1623 +
  1624 + /// Get the status of disabled by default
  1625 + bool get_disabled_by_default() const { return disabled_by_default_; }
  1626 +
  1627 + /// Get the status of disabled by default
  1628 + bool get_enabled_by_default() const { return enabled_by_default_; }
  1629 +
1578 1630 /// Get the status of allow extras
1579 1631 bool get_allow_config_extras() const { return allow_config_extras_; }
1580 1632  
... ... @@ -1704,9 +1756,15 @@ class App {
1704 1756 }
1705 1757  
1706 1758 /// configure subcommands to enable parsing through the current object
1707   - /// set the correct fallthrough and prefix for nameless subcommands and
  1759 + /// set the correct fallthrough and prefix for nameless subcommands and manage the automatic enable or disable
1708 1760 /// makes sure parent is set correctly
1709 1761 void _configure() {
  1762 + if(disabled_by_default_) {
  1763 + disabled_ = true;
  1764 + }
  1765 + if(enabled_by_default_) {
  1766 + disabled_ = false;
  1767 + }
1710 1768 for(const App_p &app : subcommands_) {
1711 1769 if(app->has_automatic_name_) {
1712 1770 app->name_.clear();
... ... @@ -1723,34 +1781,35 @@ class App {
1723 1781 /// Internal function to run (App) callback, top down
1724 1782 void run_callback() {
1725 1783 pre_callback();
1726   - if(callback_)
  1784 + if(callback_ && (parsed_ > 0))
1727 1785 callback_();
1728 1786 for(App *subc : get_subcommands()) {
1729   - subc->run_callback();
  1787 + if((subc->get_name().empty()) || (!subc->immediate_callback_))
  1788 + subc->run_callback();
1730 1789 }
1731 1790 }
1732 1791  
1733 1792 /// Check to see if a subcommand is valid. Give up immediately if subcommand max has been reached.
1734   - bool _valid_subcommand(const std::string &current) const {
  1793 + bool _valid_subcommand(const std::string &current, bool ignore_used = true) const {
1735 1794 // Don't match if max has been reached - but still check parents
1736 1795 if(require_subcommand_max_ != 0 && parsed_subcommands_.size() >= require_subcommand_max_) {
1737   - return parent_ != nullptr && parent_->_valid_subcommand(current);
  1796 + return parent_ != nullptr && parent_->_valid_subcommand(current, ignore_used);
1738 1797 }
1739   - auto com = _find_subcommand(current, true, true);
  1798 + auto com = _find_subcommand(current, true, ignore_used);
1740 1799 if(com != nullptr) {
1741 1800 return true;
1742 1801 }
1743 1802 // Check parent if exists, else return false
1744   - return parent_ != nullptr && parent_->_valid_subcommand(current);
  1803 + return parent_ != nullptr && parent_->_valid_subcommand(current, ignore_used);
1745 1804 }
1746 1805  
1747 1806 /// Selects a Classifier enum based on the type of the current argument
1748   - detail::Classifier _recognize(const std::string &current) const {
  1807 + detail::Classifier _recognize(const std::string &current, bool ignore_used_subcommands = true) const {
1749 1808 std::string dummy1, dummy2;
1750 1809  
1751 1810 if(current == "--")
1752 1811 return detail::Classifier::POSITIONAL_MARK;
1753   - if(_valid_subcommand(current))
  1812 + if(_valid_subcommand(current, ignore_used_subcommands))
1754 1813 return detail::Classifier::SUBCOMMAND;
1755 1814 if(detail::split_long(current, dummy1, dummy2))
1756 1815 return detail::Classifier::LONG;
... ... @@ -1758,6 +1817,8 @@ class App {
1758 1817 return detail::Classifier::SHORT;
1759 1818 if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2)))
1760 1819 return detail::Classifier::WINDOWS;
  1820 + if((current == "++") && (!name_.empty()))
  1821 + return detail::Classifier::SUBCOMMAND_TERMINATOR;
1761 1822 return detail::Classifier::NONE;
1762 1823 }
1763 1824  
... ... @@ -1811,7 +1872,8 @@ class App {
1811 1872 }
1812 1873  
1813 1874 for(App_p &sub : subcommands_) {
1814   - sub->_process_env();
  1875 + if((sub->get_name().empty()) || (!sub->immediate_callback_))
  1876 + sub->_process_env();
1815 1877 }
1816 1878 }
1817 1879  
... ... @@ -1824,7 +1886,8 @@ class App {
1824 1886 }
1825 1887  
1826 1888 for(App_p &sub : subcommands_) {
1827   - sub->_process_callbacks();
  1889 + if((sub->get_name().empty()) || (!sub->immediate_callback_))
  1890 + sub->_process_callbacks();
1828 1891 }
1829 1892 }
1830 1893  
... ... @@ -1952,7 +2015,10 @@ class App {
1952 2015 }
1953 2016 }
1954 2017 }
1955   - sub->_process_requirements();
  2018 + if((sub->count() > 0) || (sub->name_.empty())) {
  2019 + sub->_process_requirements();
  2020 + }
  2021 +
1956 2022 if((sub->required_) && (sub->count_all() == 0)) {
1957 2023 throw(CLI::RequiredError(sub->get_display_name()));
1958 2024 }
... ... @@ -1996,10 +2062,13 @@ class App {
1996 2062 /// Internal parse function
1997 2063 void _parse(std::vector<std::string> &args) {
1998 2064 increment_parsed();
  2065 + _trigger_pre_parse(args.size());
1999 2066 bool positional_only = false;
2000 2067  
2001 2068 while(!args.empty()) {
2002   - _parse_single(args, positional_only);
  2069 + if(!_parse_single(args, positional_only)) {
  2070 + break;
  2071 + }
2003 2072 }
2004 2073  
2005 2074 if(parent_ == nullptr) {
... ... @@ -2010,6 +2079,12 @@ class App {
2010 2079  
2011 2080 // Convert missing (pairs) to extras (string only)
2012 2081 args = remaining(false);
  2082 + } else if(immediate_callback_) {
  2083 + _process_env();
  2084 + _process_callbacks();
  2085 + _process_help_flags();
  2086 + _process_requirements();
  2087 + run_callback();
2013 2088 }
2014 2089 }
2015 2090  
... ... @@ -2065,18 +2140,26 @@ class App {
2065 2140 }
2066 2141  
2067 2142 /// Parse "one" argument (some may eat more than one), delegate to parent if fails, add to missing if missing
2068   - /// from master
2069   - void _parse_single(std::vector<std::string> &args, bool &positional_only) {
2070   -
  2143 + /// from master return false if the parse has failed and needs to return to parent
  2144 + bool _parse_single(std::vector<std::string> &args, bool &positional_only) {
  2145 + bool retval = true;
2071 2146 detail::Classifier classifier = positional_only ? detail::Classifier::NONE : _recognize(args.back());
2072 2147 switch(classifier) {
2073 2148 case detail::Classifier::POSITIONAL_MARK:
2074   - missing_.emplace_back(classifier, args.back());
2075 2149 args.pop_back();
2076 2150 positional_only = true;
  2151 + if((!_has_remaining_positionals()) && (parent_ != nullptr)) {
  2152 + retval = false;
  2153 + } else {
  2154 + missing_.emplace_back(classifier, "--");
  2155 + }
  2156 + break;
  2157 + case detail::Classifier::SUBCOMMAND_TERMINATOR:
  2158 + args.pop_back();
  2159 + retval = false;
2077 2160 break;
2078 2161 case detail::Classifier::SUBCOMMAND:
2079   - _parse_subcommand(args);
  2162 + retval = _parse_subcommand(args);
2080 2163 break;
2081 2164 case detail::Classifier::LONG:
2082 2165 case detail::Classifier::SHORT:
... ... @@ -2086,11 +2169,12 @@ class App {
2086 2169 break;
2087 2170 case detail::Classifier::NONE:
2088 2171 // Probably a positional or something for a parent (sub)command
2089   - _parse_positional(args);
2090   - if(positionals_at_end_) {
  2172 + retval = _parse_positional(args);
  2173 + if(retval && positionals_at_end_) {
2091 2174 positional_only = true;
2092 2175 }
2093 2176 }
  2177 + return retval;
2094 2178 }
2095 2179  
2096 2180 /// Count the required remaining positional arguments
... ... @@ -2104,10 +2188,21 @@ class App {
2104 2188 return retval;
2105 2189 }
2106 2190  
  2191 + /// Count the required remaining positional arguments
  2192 + bool _has_remaining_positionals() const {
  2193 + for(const Option_p &opt : options_)
  2194 + if(opt->get_positional() &&
  2195 + ((opt->get_items_expected() < 0) || ((static_cast<int>(opt->count()) < opt->get_items_expected()))))
  2196 + return true;
  2197 +
  2198 + return false;
  2199 + }
  2200 +
2107 2201 /// Parse a positional, go up the tree to check
2108   - void _parse_positional(std::vector<std::string> &args) {
  2202 + /// Return true if the positional was used false otherwise
  2203 + bool _parse_positional(std::vector<std::string> &args) {
2109 2204  
2110   - std::string positional = args.back();
  2205 + const std::string &positional = args.back();
2111 2206 for(const Option_p &opt : options_) {
2112 2207 // Eat options, one by one, until done
2113 2208 if(opt->get_positional() &&
... ... @@ -2116,52 +2211,59 @@ class App {
2116 2211 opt->add_result(positional);
2117 2212 parse_order_.push_back(opt.get());
2118 2213 args.pop_back();
2119   - return;
  2214 + return true;
2120 2215 }
2121 2216 }
2122 2217  
2123 2218 for(auto &subc : subcommands_) {
2124 2219 if((subc->name_.empty()) && (!subc->disabled_)) {
2125   - subc->_parse_positional(args);
2126   - if(subc->missing_.empty()) { // check if it was used and is not in the missing category
2127   - return;
2128   - } else {
2129   - args.push_back(std::move(subc->missing_.front().second));
2130   - subc->missing_.clear();
  2220 + if(subc->_parse_positional(args)) {
  2221 + if(!subc->pre_parse_called_) {
  2222 + subc->_trigger_pre_parse(args.size());
  2223 + }
  2224 + return true;
2131 2225 }
2132 2226 }
2133 2227 }
2134   -
  2228 + /// let the parent deal with it if possible
2135 2229 if(parent_ != nullptr && fallthrough_)
2136   - return parent_->_parse_positional(args);
2137   - else {
2138   - /// now try one last gasp at subcommands that have been executed before, go to root app and try to find a
2139   - /// subcommand in a broader way
2140   - auto parent_app = this;
2141   - while(parent_app->parent_ != nullptr) {
2142   - parent_app = parent_app->parent_;
2143   - }
2144   - auto com = parent_app->_find_subcommand(args.back(), true, false);
2145   - if((com != nullptr) &&
2146   - ((com->parent_->require_subcommand_max_ == 0) ||
2147   - (com->parent_->require_subcommand_max_ > com->parent_->parsed_subcommands_.size()))) {
2148   - args.pop_back();
2149   - com->_parse(args);
2150   - } else {
2151   - if(positionals_at_end_) {
2152   - throw CLI::ExtrasError(args);
2153   - }
2154   - args.pop_back();
2155   - missing_.emplace_back(detail::Classifier::NONE, positional);
  2230 + return _get_fallthrough_parent()->_parse_positional(args);
2156 2231  
2157   - if(prefix_command_) {
2158   - while(!args.empty()) {
2159   - missing_.emplace_back(detail::Classifier::NONE, args.back());
2160   - args.pop_back();
2161   - }
2162   - }
  2232 + /// Try to find a local subcommand that is repeated
  2233 + auto com = _find_subcommand(args.back(), true, false);
  2234 + if((com != nullptr) &&
  2235 + ((require_subcommand_max_ == 0) || (require_subcommand_max_ > parsed_subcommands_.size()))) {
  2236 + args.pop_back();
  2237 + com->_parse(args);
  2238 + return true;
  2239 + }
  2240 + /// now try one last gasp at subcommands that have been executed before, go to root app and try to find a
  2241 + /// subcommand in a broader way, if one exists let the parent deal with it
  2242 + auto parent_app = (parent_ != nullptr) ? _get_fallthrough_parent() : this;
  2243 + com = parent_app->_find_subcommand(args.back(), true, false);
  2244 + if((com != nullptr) && ((com->parent_->require_subcommand_max_ == 0) ||
  2245 + (com->parent_->require_subcommand_max_ > com->parent_->parsed_subcommands_.size()))) {
  2246 + return false;
  2247 + }
  2248 +
  2249 + if(positionals_at_end_) {
  2250 + throw CLI::ExtrasError(args);
  2251 + }
  2252 + /// If this is an option group don't deal with it
  2253 + if((parent_ != nullptr) && (name_.empty())) {
  2254 + return false;
  2255 + }
  2256 + /// We are out of other options this goes to missing
  2257 + missing_.emplace_back(detail::Classifier::NONE, positional);
  2258 + args.pop_back();
  2259 + if(prefix_command_) {
  2260 + while(!args.empty()) {
  2261 + missing_.emplace_back(detail::Classifier::NONE, args.back());
  2262 + args.pop_back();
2163 2263 }
2164 2264 }
  2265 +
  2266 + return true;
2165 2267 }
2166 2268  
2167 2269 /// Locate a subcommand by name with two conditions, should disabled subcommands be ignored, and should used
... ... @@ -2186,27 +2288,34 @@ class App {
2186 2288 /// Parse a subcommand, modify args and continue
2187 2289 ///
2188 2290 /// Unlike the others, this one will always allow fallthrough
2189   - void _parse_subcommand(std::vector<std::string> &args) {
2190   - if(_count_remaining_positionals(/* required */ true) > 0)
2191   - return _parse_positional(args);
  2291 + /// return true if the subcommand was processed false otherwise
  2292 + bool _parse_subcommand(std::vector<std::string> &args) {
  2293 + if(_count_remaining_positionals(/* required */ true) > 0) {
  2294 + _parse_positional(args);
  2295 + return true;
  2296 + }
2192 2297 auto com = _find_subcommand(args.back(), true, true);
2193 2298 if(com != nullptr) {
2194 2299 args.pop_back();
2195   - if(std::find(std::begin(parsed_subcommands_), std::end(parsed_subcommands_), com) ==
2196   - std::end(parsed_subcommands_))
2197   - parsed_subcommands_.push_back(com);
  2300 + parsed_subcommands_.push_back(com);
2198 2301 com->_parse(args);
2199   - return;
  2302 + auto parent_app = com->parent_;
  2303 + while(parent_app != this) {
  2304 + parent_app->_trigger_pre_parse(args.size());
  2305 + parent_app->parsed_subcommands_.push_back(com);
  2306 + parent_app = parent_app->parent_;
  2307 + }
  2308 + return true;
2200 2309 }
2201 2310  
2202 2311 if(parent_ == nullptr)
2203 2312 throw HorribleError("Subcommand " + args.back() + " missing");
2204   -
2205   - return parent_->_parse_subcommand(args);
  2313 + return false;
2206 2314 }
2207 2315  
2208 2316 /// Parse a short (false) or long (true) argument, must be at the top of the list
2209   - void _parse_arg(std::vector<std::string> &args, detail::Classifier current_type) {
  2317 + /// return true if the argument was processed or false if nothing was done
  2318 + bool _parse_arg(std::vector<std::string> &args, detail::Classifier current_type) {
2210 2319  
2211 2320 std::string current = args.back();
2212 2321  
... ... @@ -2245,25 +2354,25 @@ class App {
2245 2354 if(op_ptr == std::end(options_)) {
2246 2355 for(auto &subc : subcommands_) {
2247 2356 if((subc->name_.empty()) && (!(subc->disabled_))) {
2248   - subc->_parse_arg(args, current_type);
2249   - if(subc->missing_.empty()) { // check if it was used and is not in the missing category
2250   - return;
2251   - } else {
2252   - // for unnamed subs they shouldn't trigger a missing argument
2253   - args.push_back(std::move(subc->missing_.front().second));
2254   - subc->missing_.clear();
  2357 + if(subc->_parse_arg(args, current_type)) {
  2358 + if(!subc->pre_parse_called_) {
  2359 + subc->_trigger_pre_parse(args.size());
  2360 + }
  2361 + return true;
2255 2362 }
2256 2363 }
2257 2364 }
2258 2365 // If a subcommand, try the master command
2259 2366 if(parent_ != nullptr && fallthrough_)
2260   - return parent_->_parse_arg(args, current_type);
2261   - // Otherwise, add to missing
2262   - else {
2263   - args.pop_back();
2264   - missing_.emplace_back(current_type, current);
2265   - return;
  2367 + return _get_fallthrough_parent()->_parse_arg(args, current_type);
  2368 + // don't capture missing if this is a nameless subcommand
  2369 + if((parent_ != nullptr) && (name_.empty())) {
  2370 + return false;
2266 2371 }
  2372 + // Otherwise, add to missing
  2373 + args.pop_back();
  2374 + missing_.emplace_back(current_type, current);
  2375 + return true;
2267 2376 }
2268 2377  
2269 2378 args.pop_back();
... ... @@ -2304,7 +2413,7 @@ class App {
2304 2413  
2305 2414 // Unlimited vector parser
2306 2415 if(num < 0) {
2307   - while(!args.empty() && _recognize(args.back()) == detail::Classifier::NONE) {
  2416 + while(!args.empty() && _recognize(args.back(), false) == detail::Classifier::NONE) {
2308 2417 if(collected >= -num) {
2309 2418 // We could break here for allow extras, but we don't
2310 2419  
... ... @@ -2340,6 +2449,38 @@ class App {
2340 2449 rest = "-" + rest;
2341 2450 args.push_back(rest);
2342 2451 }
  2452 + return true;
  2453 + }
  2454 +
  2455 + /// Trigger the pre_parse callback if needed
  2456 + void _trigger_pre_parse(size_t remaining_args) {
  2457 + if(!pre_parse_called_) {
  2458 + pre_parse_called_ = true;
  2459 + if(pre_parse_callback_) {
  2460 + pre_parse_callback_(remaining_args);
  2461 + }
  2462 + } else if(immediate_callback_) {
  2463 + if(!name_.empty()) {
  2464 + auto pcnt = parsed_;
  2465 + auto extras = std::move(missing_);
  2466 + clear();
  2467 + parsed_ = pcnt;
  2468 + pre_parse_called_ = true;
  2469 + missing_ = std::move(extras);
  2470 + }
  2471 + }
  2472 + }
  2473 +
  2474 + /// Get the appropriate parent to fallthrough to which is the first one that has a name or the main app
  2475 + App *_get_fallthrough_parent() {
  2476 + if(parent_ == nullptr) {
  2477 + throw(HorribleError("No Valid parent"));
  2478 + }
  2479 + auto fallthrough_parent = parent_;
  2480 + while((fallthrough_parent->parent_ != nullptr) && (fallthrough_parent->get_name().empty())) {
  2481 + fallthrough_parent = fallthrough_parent->parent_;
  2482 + }
  2483 + return fallthrough_parent;
2343 2484 }
2344 2485  
2345 2486 public:
... ... @@ -2390,6 +2531,7 @@ class Option_group : public App {
2390 2531 Option_group(std::string group_description, std::string group_name, App *parent)
2391 2532 : App(std::move(group_description), "", parent) {
2392 2533 group(group_name);
  2534 + // option groups should have automatic fallthrough
2393 2535 }
2394 2536 using App::add_option;
2395 2537 /// add an existing option to the Option_group
... ... @@ -2408,6 +2550,47 @@ class Option_group : public App {
2408 2550 add_options(args...);
2409 2551 }
2410 2552 };
  2553 +/// Helper function to enable one option group/subcommand when another is used
  2554 +inline void TriggerOn(App *trigger_app, App *app_to_enable) {
  2555 + app_to_enable->enabled_by_default(false);
  2556 + app_to_enable->disabled_by_default();
  2557 + trigger_app->preparse_callback([app_to_enable](size_t) { app_to_enable->disabled(false); });
  2558 +}
  2559 +
  2560 +/// Helper function to enable one option group/subcommand when another is used
  2561 +inline void TriggerOn(App *trigger_app, std::vector<App *> apps_to_enable) {
  2562 + for(auto &app : apps_to_enable) {
  2563 + app->enabled_by_default(false);
  2564 + app->disabled_by_default();
  2565 + }
  2566 +
  2567 + trigger_app->preparse_callback([apps_to_enable](size_t) {
  2568 + for(auto &app : apps_to_enable) {
  2569 + app->disabled(false);
  2570 + }
  2571 + });
  2572 +}
  2573 +
  2574 +/// Helper function to disable one option group/subcommand when another is used
  2575 +inline void TriggerOff(App *trigger_app, App *app_to_enable) {
  2576 + app_to_enable->disabled_by_default(false);
  2577 + app_to_enable->enabled_by_default();
  2578 + trigger_app->preparse_callback([app_to_enable](size_t) { app_to_enable->disabled(); });
  2579 +}
  2580 +
  2581 +/// Helper function to disable one option group/subcommand when another is used
  2582 +inline void TriggerOff(App *trigger_app, std::vector<App *> apps_to_enable) {
  2583 + for(auto &app : apps_to_enable) {
  2584 + app->disabled_by_default(false);
  2585 + app->enabled_by_default();
  2586 + }
  2587 +
  2588 + trigger_app->preparse_callback([apps_to_enable](size_t) {
  2589 + for(auto &app : apps_to_enable) {
  2590 + app->disabled();
  2591 + }
  2592 + });
  2593 +}
2411 2594  
2412 2595 namespace FailureMessage {
2413 2596  
... ... @@ -2456,6 +2639,8 @@ struct AppFriend {
2456 2639 typename std::result_of<decltype (&App::_parse_subcommand)(App, Args...)>::type {
2457 2640 return app->_parse_subcommand(std::forward<Args>(args)...);
2458 2641 }
  2642 + /// Wrap the fallthrough parent function to make sure that is working correctly
  2643 + static App *get_fallthrough_parent(App *app) { return app->_get_fallthrough_parent(); }
2459 2644 };
2460 2645 } // namespace detail
2461 2646  
... ...
include/CLI/Option.hpp
... ... @@ -76,7 +76,6 @@ template &lt;typename CRTP&gt; class OptionBase {
76 76 CRTP *group(std::string name) {
77 77 group_ = name;
78 78 return static_cast<CRTP *>(this);
79   - ;
80 79 }
81 80  
82 81 /// Set the option as required
... ...
tests/AppTest.cpp
... ... @@ -1169,9 +1169,9 @@ TEST_F(TApp, RequiredFlags) {
1169 1169  
1170 1170 TEST_F(TApp, CallbackFlags) {
1171 1171  
1172   - size_t value = 0;
  1172 + int64_t value = 0;
1173 1173  
1174   - auto func = [&value](size_t x) { value = x; };
  1174 + auto func = [&value](int64_t x) { value = x; };
1175 1175  
1176 1176 app.add_flag_function("-v", func);
1177 1177  
... ... @@ -1271,9 +1271,9 @@ TEST_F(TApp, CallbackFlagsFalseShortcut) {
1271 1271 #if __cplusplus >= 201402L || _MSC_VER >= 1900
1272 1272 TEST_F(TApp, CallbackFlagsAuto) {
1273 1273  
1274   - size_t value = 0;
  1274 + int64_t value = 0;
1275 1275  
1276   - auto func = [&value](size_t x) { value = x; };
  1276 + auto func = [&value](int64_t x) { value = x; };
1277 1277  
1278 1278 app.add_flag("-v", func);
1279 1279  
... ... @@ -1843,6 +1843,31 @@ TEST_F(TApp, CheckSubcomFail) {
1843 1843 EXPECT_THROW(CLI::detail::AppFriend::parse_subcommand(&app, args), CLI::HorribleError);
1844 1844 }
1845 1845  
  1846 +TEST_F(TApp, FallthroughParentFail) {
  1847 + EXPECT_THROW(CLI::detail::AppFriend::get_fallthrough_parent(&app), CLI::HorribleError);
  1848 +}
  1849 +
  1850 +TEST_F(TApp, FallthroughParents) {
  1851 + auto sub = app.add_subcommand("test");
  1852 + EXPECT_EQ(CLI::detail::AppFriend::get_fallthrough_parent(sub), &app);
  1853 +
  1854 + auto ssub = sub->add_subcommand("sub2");
  1855 + EXPECT_EQ(CLI::detail::AppFriend::get_fallthrough_parent(ssub), sub);
  1856 +
  1857 + auto og1 = app.add_option_group("g1");
  1858 + auto og2 = og1->add_option_group("g2");
  1859 + auto og3 = og2->add_option_group("g3");
  1860 + EXPECT_EQ(CLI::detail::AppFriend::get_fallthrough_parent(og3), &app);
  1861 +
  1862 + auto ogb1 = sub->add_option_group("g1");
  1863 + auto ogb2 = ogb1->add_option_group("g2");
  1864 + auto ogb3 = ogb2->add_option_group("g3");
  1865 + EXPECT_EQ(CLI::detail::AppFriend::get_fallthrough_parent(ogb3), sub);
  1866 +
  1867 + ogb2->name("groupb");
  1868 + EXPECT_EQ(CLI::detail::AppFriend::get_fallthrough_parent(ogb3), ogb2);
  1869 +}
  1870 +
1846 1871 TEST_F(TApp, OptionWithDefaults) {
1847 1872 int someint = 2;
1848 1873 app.add_option("-a", someint, "", true);
... ... @@ -1953,8 +1978,8 @@ TEST_F(TApp, EmptyOptionFail) {
1953 1978 }
1954 1979  
1955 1980 TEST_F(TApp, BeforeRequirements) {
1956   - app.add_flag_function("-a", [](size_t) { throw CLI::Success(); });
1957   - app.add_flag_function("-b", [](size_t) { throw CLI::CallForHelp(); });
  1981 + app.add_flag_function("-a", [](int64_t) { throw CLI::Success(); });
  1982 + app.add_flag_function("-b", [](int64_t) { throw CLI::CallForHelp(); });
1958 1983  
1959 1984 args = {"extra"};
1960 1985 EXPECT_THROW(run(), CLI::ExtrasError);
... ...
tests/OptionGroupTest.cpp
... ... @@ -517,12 +517,12 @@ TEST_F(ManyGroups, SameSubcommand) {
517 517 EXPECT_TRUE(*sub2);
518 518 EXPECT_TRUE(*sub3);
519 519 /// This should be made to work at some point
520   - /*auto subs = app.get_subcommands();
  520 + auto subs = app.get_subcommands();
521 521 EXPECT_EQ(subs.size(), 3u);
522 522 EXPECT_EQ(subs[0], sub1);
523 523 EXPECT_EQ(subs[1], sub2);
524 524 EXPECT_EQ(subs[2], sub3);
525   - */
  525 +
526 526 args = {"sub1", "sub1", "sub1", "sub1"};
527 527 // for the 4th and future ones they will route to the first one
528 528 run();
... ... @@ -531,9 +531,107 @@ TEST_F(ManyGroups, SameSubcommand) {
531 531 EXPECT_EQ(sub3->count(), 1u);
532 532  
533 533 // subs should remain the same since the duplicate would not be registered there
534   - /*subs = app.get_subcommands();
  534 + subs = app.get_subcommands();
535 535 EXPECT_EQ(subs.size(), 3u);
536 536 EXPECT_EQ(subs[0], sub1);
537 537 EXPECT_EQ(subs[1], sub2);
538   - EXPECT_EQ(subs[2], sub3);*/
  538 + EXPECT_EQ(subs[2], sub3);
  539 +}
  540 +
  541 +struct ManyGroupsPreTrigger : public ManyGroups {
  542 + size_t triggerMain, trigger1{87u}, trigger2{34u}, trigger3{27u};
  543 + ManyGroupsPreTrigger() {
  544 + remove_required();
  545 + app.preparse_callback([this](size_t count) { triggerMain = count; });
  546 +
  547 + g1->preparse_callback([this](size_t count) { trigger1 = count; });
  548 + g2->preparse_callback([this](size_t count) { trigger2 = count; });
  549 + g3->preparse_callback([this](size_t count) { trigger3 = count; });
  550 + }
  551 +};
  552 +
  553 +TEST_F(ManyGroupsPreTrigger, PreTriggerTestsOptions) {
  554 +
  555 + args = {"--name1", "test", "--name2", "test3"};
  556 + run();
  557 + EXPECT_EQ(triggerMain, 4u);
  558 + EXPECT_EQ(trigger1, 2u);
  559 + EXPECT_EQ(trigger2, 0u);
  560 + EXPECT_EQ(trigger3, 27u);
  561 +
  562 + args = {"--name1", "test"};
  563 + trigger2 = 34u;
  564 + run();
  565 + EXPECT_EQ(triggerMain, 2u);
  566 + EXPECT_EQ(trigger1, 0u);
  567 + EXPECT_EQ(trigger2, 34u);
  568 +
  569 + args = {};
  570 + run();
  571 + EXPECT_EQ(triggerMain, 0u);
  572 +
  573 + args = {"--name1", "test", "--val1", "45", "--name2", "test3", "--name3=test3", "--val2=37"};
  574 + run();
  575 + EXPECT_EQ(triggerMain, 8u);
  576 + EXPECT_EQ(trigger1, 6u);
  577 + EXPECT_EQ(trigger2, 2u);
  578 + EXPECT_EQ(trigger3, 1u);
  579 +}
  580 +
  581 +TEST_F(ManyGroupsPreTrigger, PreTriggerTestsPositionals) {
  582 + // only 1 group can be used
  583 + g1->add_option("pos1");
  584 + g2->add_option("pos2");
  585 + g3->add_option("pos3");
  586 +
  587 + args = {"pos1"};
  588 + run();
  589 + EXPECT_EQ(triggerMain, 1u);
  590 + EXPECT_EQ(trigger1, 0u);
  591 + EXPECT_EQ(trigger2, 34u);
  592 + EXPECT_EQ(trigger3, 27u);
  593 +
  594 + args = {"pos1", "pos2"};
  595 + run();
  596 + EXPECT_EQ(triggerMain, 2u);
  597 + EXPECT_EQ(trigger1, 1u);
  598 + EXPECT_EQ(trigger2, 0u);
  599 +
  600 + args = {"pos1", "pos2", "pos3"};
  601 + run();
  602 + EXPECT_EQ(triggerMain, 3u);
  603 + EXPECT_EQ(trigger1, 2u);
  604 + EXPECT_EQ(trigger2, 1u);
  605 + EXPECT_EQ(trigger3, 0u);
  606 +}
  607 +
  608 +TEST_F(ManyGroupsPreTrigger, PreTriggerTestsSubcommand) {
  609 +
  610 + auto sub1 = g1->add_subcommand("sub1")->fallthrough();
  611 + g2->add_subcommand("sub2")->fallthrough();
  612 + g3->add_subcommand("sub3")->fallthrough();
  613 +
  614 + size_t subtrigger;
  615 + sub1->preparse_callback([&subtrigger](size_t count) { subtrigger = count; });
  616 + args = {"sub1"};
  617 + run();
  618 + EXPECT_EQ(triggerMain, 1u);
  619 + EXPECT_EQ(trigger1, 0u);
  620 + EXPECT_EQ(trigger2, 34u);
  621 + EXPECT_EQ(trigger3, 27u);
  622 +
  623 + args = {"sub1", "sub2"};
  624 + run();
  625 + EXPECT_EQ(triggerMain, 2u);
  626 + EXPECT_EQ(subtrigger, 1u);
  627 + EXPECT_EQ(trigger1, 1u);
  628 + EXPECT_EQ(trigger2, 0u);
  629 +
  630 + args = {"sub2", "sub3", "--name1=test", "sub1"};
  631 + run();
  632 + EXPECT_EQ(triggerMain, 4u);
  633 + EXPECT_EQ(trigger1, 1u);
  634 + EXPECT_EQ(trigger2, 3u);
  635 + EXPECT_EQ(trigger3, 1u); // processes the first argument in group3 which includes the entire subcommand, which will
  636 + // go until the sub1 command is given
539 637 }
... ...
tests/SubcommandTest.cpp
... ... @@ -170,12 +170,50 @@ TEST_F(TApp, DuplicateSubcommands) {
170 170 args = {"foo", "foo"};
171 171 run();
172 172 EXPECT_TRUE(*foo);
173   - EXPECT_EQ(foo->count(), 2);
  173 + EXPECT_EQ(foo->count(), 2u);
174 174  
175 175 args = {"foo", "foo", "foo"};
176 176 run();
177 177 EXPECT_TRUE(*foo);
178   - EXPECT_EQ(foo->count(), 3);
  178 + EXPECT_EQ(foo->count(), 3u);
  179 +}
  180 +
  181 +TEST_F(TApp, DuplicateSubcommandCallbacks) {
  182 +
  183 + auto foo = app.add_subcommand("foo");
  184 + int count = 0;
  185 + foo->callback([&count]() { ++count; });
  186 + foo->immediate_callback();
  187 + EXPECT_TRUE(foo->get_immediate_callback());
  188 + args = {"foo", "foo"};
  189 + run();
  190 + EXPECT_EQ(count, 2);
  191 + count = 0;
  192 + args = {"foo", "foo", "foo"};
  193 + run();
  194 + EXPECT_EQ(count, 3);
  195 +}
  196 +
  197 +TEST_F(TApp, DuplicateSubcommandCallbacksValues) {
  198 +
  199 + auto foo = app.add_subcommand("foo");
  200 + int val;
  201 + foo->add_option("--val", val);
  202 + std::vector<int> vals;
  203 + foo->callback([&vals, &val]() { vals.push_back(val); });
  204 + foo->immediate_callback();
  205 + args = {"foo", "--val=45", "foo", "--val=27"};
  206 + run();
  207 + EXPECT_EQ(vals.size(), 2u);
  208 + EXPECT_EQ(vals[0], 45);
  209 + EXPECT_EQ(vals[1], 27);
  210 + vals.clear();
  211 + args = {"foo", "--val=45", "foo", "--val=27", "foo", "--val=36"};
  212 + run();
  213 + EXPECT_EQ(vals.size(), 3u);
  214 + EXPECT_EQ(vals[0], 45);
  215 + EXPECT_EQ(vals[1], 27);
  216 + EXPECT_EQ(vals[2], 36);
179 217 }
180 218  
181 219 TEST_F(TApp, Callbacks) {
... ... @@ -238,6 +276,33 @@ TEST_F(TApp, NoFallThroughPositionals) {
238 276 EXPECT_THROW(run(), CLI::ExtrasError);
239 277 }
240 278  
  279 +TEST_F(TApp, NoFallThroughOptsWithTerminator) {
  280 + int val = 1;
  281 + app.add_option("--val", val);
  282 +
  283 + app.add_subcommand("sub");
  284 +
  285 + args = {"sub", "++", "--val", "2"};
  286 + run();
  287 + EXPECT_EQ(val, 2);
  288 +}
  289 +
  290 +TEST_F(TApp, NoFallThroughPositionalsWithTerminator) {
  291 + int val = 1;
  292 + app.add_option("val", val);
  293 +
  294 + app.add_subcommand("sub");
  295 +
  296 + args = {"sub", "++", "2"};
  297 + run();
  298 + EXPECT_EQ(val, 2);
  299 +
  300 + // try with positional only mark
  301 + args = {"sub", "--", "3"};
  302 + run();
  303 + EXPECT_EQ(val, 3);
  304 +}
  305 +
241 306 TEST_F(TApp, NamelessSubComPositionals) {
242 307  
243 308 auto sub = app.add_subcommand();
... ... @@ -435,6 +500,25 @@ TEST_F(TApp, CallbackOrdering) {
435 500 EXPECT_EQ(2, sub_val);
436 501 }
437 502  
  503 +TEST_F(TApp, CallbackOrderingImmediate) {
  504 + app.fallthrough();
  505 + int val = 1, sub_val = 0;
  506 + app.add_option("--val", val);
  507 +
  508 + auto sub = app.add_subcommand("sub")->immediate_callback();
  509 + sub->callback([&val, &sub_val]() { sub_val = val; });
  510 +
  511 + args = {"sub", "--val=2"};
  512 + run();
  513 + EXPECT_EQ(2, val);
  514 + EXPECT_EQ(1, sub_val);
  515 +
  516 + args = {"--val=2", "sub"};
  517 + run();
  518 + EXPECT_EQ(2, val);
  519 + EXPECT_EQ(2, sub_val);
  520 +}
  521 +
438 522 TEST_F(TApp, RequiredSubCom) {
439 523 app.add_subcommand("sub1");
440 524 app.add_subcommand("sub2");
... ... @@ -780,9 +864,9 @@ TEST_F(SubcommandProgram, OrderedExtras) {
780 864  
781 865 run();
782 866  
783   - EXPECT_EQ(app.remaining(), std::vector<std::string>({"one", "two"}));
784   - EXPECT_EQ(start->remaining(), std::vector<std::string>({"three", "--", "four"}));
785   - EXPECT_EQ(app.remaining(true), std::vector<std::string>({"one", "two", "three", "--", "four"}));
  867 + EXPECT_EQ(app.remaining(), std::vector<std::string>({"one", "two", "four"}));
  868 + EXPECT_EQ(start->remaining(), std::vector<std::string>({"three"}));
  869 + EXPECT_EQ(app.remaining(true), std::vector<std::string>({"one", "two", "four", "three"}));
786 870 }
787 871  
788 872 TEST_F(SubcommandProgram, MixedOrderExtras) {
... ... @@ -1044,6 +1128,43 @@ TEST_F(ManySubcommands, SubcommandDisabled) {
1044 1128 args = {"sub3", "sub4"};
1045 1129 EXPECT_NO_THROW(run());
1046 1130 }
  1131 +
  1132 +TEST_F(ManySubcommands, SubcommandTriggeredOff) {
  1133 +
  1134 + app.allow_extras(false);
  1135 + sub1->allow_extras(false);
  1136 + sub2->allow_extras(false);
  1137 + CLI::TriggerOff(sub1, sub2);
  1138 + args = {"sub1", "sub2"};
  1139 + EXPECT_THROW(run(), CLI::ExtrasError);
  1140 +
  1141 + args = {"sub2", "sub1", "sub3"};
  1142 + EXPECT_NO_THROW(run());
  1143 + CLI::TriggerOff(sub1, {sub3, sub4});
  1144 + EXPECT_THROW(run(), CLI::ExtrasError);
  1145 + args = {"sub1", "sub2", "sub4"};
  1146 + EXPECT_THROW(run(), CLI::ExtrasError);
  1147 +}
  1148 +
  1149 +TEST_F(ManySubcommands, SubcommandTriggeredOn) {
  1150 +
  1151 + app.allow_extras(false);
  1152 + sub1->allow_extras(false);
  1153 + sub2->allow_extras(false);
  1154 + CLI::TriggerOn(sub1, sub2);
  1155 + args = {"sub1", "sub2"};
  1156 + EXPECT_NO_THROW(run());
  1157 +
  1158 + args = {"sub2", "sub1", "sub4"};
  1159 + EXPECT_THROW(run(), CLI::ExtrasError);
  1160 + CLI::TriggerOn(sub1, {sub3, sub4});
  1161 + sub2->disabled_by_default(false);
  1162 + sub2->disabled(false);
  1163 + EXPECT_NO_THROW(run());
  1164 + args = {"sub3", "sub1", "sub2"};
  1165 + EXPECT_THROW(run(), CLI::ExtrasError);
  1166 +}
  1167 +
1047 1168 TEST_F(TApp, UnnamedSub) {
1048 1169 double val;
1049 1170 auto sub = app.add_subcommand("", "empty name");
... ... @@ -1205,3 +1326,40 @@ TEST_F(ManySubcommands, getSubtests) {
1205 1326 CLI::App_p sub3p = app.get_subcommand_ptr(2);
1206 1327 EXPECT_EQ(sub3p.get(), sub3);
1207 1328 }
  1329 +
  1330 +TEST_F(ManySubcommands, defaultDisabledSubcommand) {
  1331 +
  1332 + sub1->fallthrough();
  1333 + sub2->disabled_by_default();
  1334 + run();
  1335 + auto rem = app.remaining();
  1336 + EXPECT_EQ(rem.size(), 1u);
  1337 + EXPECT_EQ(rem[0], "sub2");
  1338 + EXPECT_TRUE(sub2->get_disabled_by_default());
  1339 + sub2->disabled(false);
  1340 + EXPECT_FALSE(sub2->get_disabled());
  1341 + run();
  1342 + // this should disable it again even though it was disabled
  1343 + rem = app.remaining();
  1344 + EXPECT_EQ(rem.size(), 1u);
  1345 + EXPECT_EQ(rem[0], "sub2");
  1346 + EXPECT_TRUE(sub2->get_disabled_by_default());
  1347 + EXPECT_TRUE(sub2->get_disabled());
  1348 +}
  1349 +
  1350 +TEST_F(ManySubcommands, defaultEnabledSubcommand) {
  1351 +
  1352 + sub2->enabled_by_default();
  1353 + run();
  1354 + auto rem = app.remaining();
  1355 + EXPECT_EQ(rem.size(), 0u);
  1356 + EXPECT_TRUE(sub2->get_enabled_by_default());
  1357 + sub2->disabled();
  1358 + EXPECT_TRUE(sub2->get_disabled());
  1359 + run();
  1360 + // this should disable it again even though it was disabled
  1361 + rem = app.remaining();
  1362 + EXPECT_EQ(rem.size(), 0u);
  1363 + EXPECT_TRUE(sub2->get_enabled_by_default());
  1364 + EXPECT_FALSE(sub2->get_disabled());
  1365 +}
... ...