Commit 0631189b4d5ebf71a270832aa8b5e4701a31e449

Authored by Philip Top
Committed by Henry Schreiner
1 parent 4a460810

Option groups (#227)

* change the move function to _move_option and add an additional test

add a validation check on min options to make sure it is even possible to succeed.

add some additional tests to cover code paths and potential errors.

add a number of additional tests and checks and fix some issues with the add function in option_groups

clean up example and help formatting

add option_groups example to play with

move create_option_group to a member function using a dummy template

add some optionGroup tests

add min and max options calls and an associated Error call

* add ranges example,  add excludes to app for options and subcommands.

* add some tests on ranges, and some subcommand tests with exclusion

* add tests in optionGroups for some invalid inputs

* add required option to subcommands and option_groups

* add disabled flag

* add disable option to subcommands and some more tests

* start work on ReadMe modifications

* update the readme with descriptions of function and methods added for option_groups

* clear up gcc 4.7 warnings

* some update to the Readme and a few more warnings fixed

* Minor readme touchup
README.md
@@ -32,8 +32,10 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature @@ -32,8 +32,10 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature
32 - [Adding options](#adding-options) 32 - [Adding options](#adding-options)
33 - [Option types](#option-types) 33 - [Option types](#option-types)
34 - [Option options](#option-options) 34 - [Option options](#option-options)
  35 + - [Getting Results](#getting-results) ๐Ÿšง
35 - [Subcommands](#subcommands) 36 - [Subcommands](#subcommands)
36 - [Subcommand options](#subcommand-options) 37 - [Subcommand options](#subcommand-options)
  38 + - [Option Groups](#option-groups) ๐Ÿšง
37 - [Configuration file](#configuration-file) 39 - [Configuration file](#configuration-file)
38 - [Inheriting defaults](#inheriting-defaults) 40 - [Inheriting defaults](#inheriting-defaults)
39 - [Formatting](#formatting) 41 - [Formatting](#formatting)
@@ -45,6 +47,8 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature @@ -45,6 +47,8 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature
45 - [Contribute](#contribute) 47 - [Contribute](#contribute)
46 - [License](#license) 48 - [License](#license)
47 49
  50 +Features that were added in the last released major version are marked with "๐Ÿ†•". Features only available in master are marked with "๐Ÿšง".
  51 +
48 ## Background 52 ## Background
49 53
50 ### Introduction 54 ### Introduction
@@ -53,7 +57,7 @@ CLI11 provides all the features you expect in a powerful command line parser, wi @@ -53,7 +57,7 @@ CLI11 provides all the features you expect in a powerful command line parser, wi
53 It is tested on [Travis][], [AppVeyor][], and [Azure][], and is being included in the [GooFit GPU fitting framework][goofit]. It was inspired by [`plumbum.cli`][plumbum] for Python. CLI11 has a user friendly introduction in this README, a more in-depth tutorial [GitBook][], as well as [API documentation][api-docs] generated by Travis. 57 It is tested on [Travis][], [AppVeyor][], and [Azure][], and is being included in the [GooFit GPU fitting framework][goofit]. It was inspired by [`plumbum.cli`][plumbum] for Python. CLI11 has a user friendly introduction in this README, a more in-depth tutorial [GitBook][], as well as [API documentation][api-docs] generated by Travis.
54 See the [changelog](./CHANGELOG.md) or [GitHub Releases][] for details for current and past releases. Also see the [Version 1.0 post][], [Version 1.3 post][], or [Version 1.6 post][] for more information. 58 See the [changelog](./CHANGELOG.md) or [GitHub Releases][] for details for current and past releases. Also see the [Version 1.0 post][], [Version 1.3 post][], or [Version 1.6 post][] for more information.
55 59
56 -You can be notified when new releases are made by subscribing to <https://github.com/CLIUtils/CLI11/releases.atom> on an RSS reader, like Feedly. 60 +You can be notified when new releases are made by subscribing to <https://github.com/CLIUtils/CLI11/releases.atom> on an RSS reader, like Feedly, or use the releases mode of the github watching tool.
57 61
58 ### Why write another CLI parser? 62 ### Why write another CLI parser?
59 63
@@ -61,7 +65,7 @@ An acceptable CLI parser library should be all of the following: @@ -61,7 +65,7 @@ An acceptable CLI parser library should be all of the following:
61 65
62 - Easy to include (i.e., header only, one file if possible, **no external requirements**). 66 - Easy to include (i.e., header only, one file if possible, **no external requirements**).
63 - Short, simple syntax: This is one of the main reasons to use a CLI parser, it should make variables from the command line nearly as easy to define as any other variables. If most of your program is hidden in CLI parsing, this is a problem for readability. 67 - Short, simple syntax: This is one of the main reasons to use a CLI parser, it should make variables from the command line nearly as easy to define as any other variables. If most of your program is hidden in CLI parsing, this is a problem for readability.
64 -- C++11 or better: Should work with GCC 4.7+ (such as GCC 4.8 on CentOS 7), Clang 3.5+, AppleClang 7+, NVCC 7.0+, or MSVC 2015+. 68 +- C++11 or better: Should work with GCC 4.8+ (default on CentOS/RHEL 7), Clang 3.5+, AppleClang 7+, NVCC 7.0+, or MSVC 2015+.
65 - Work on Linux, macOS, and Windows. 69 - Work on Linux, macOS, and Windows.
66 - Well tested using [Travis][] (Linux and macOS) and [AppVeyor][] (Windows) or [Azure][] (all three). "Well" is defined as having good coverage measured by [CodeCov][]. 70 - Well tested using [Travis][] (Linux and macOS) and [AppVeyor][] (Windows) or [Azure][] (all three). "Well" is defined as having good coverage measured by [CodeCov][].
67 - Clear help printing. 71 - Clear help printing.
@@ -69,7 +73,7 @@ An acceptable CLI parser library should be all of the following: @@ -69,7 +73,7 @@ An acceptable CLI parser library should be all of the following:
69 - Standard shell idioms supported naturally, like grouping flags, a positional separator, etc. 73 - Standard shell idioms supported naturally, like grouping flags, a positional separator, etc.
70 - Easy to execute, with help, parse errors, etc. providing correct exit and details. 74 - Easy to execute, with help, parse errors, etc. providing correct exit and details.
71 - Easy to extend as part of a framework that provides "applications" to users. 75 - Easy to extend as part of a framework that provides "applications" to users.
72 -- Usable subcommand syntax, with support for multiple subcommands, nested subcommands, and optional fallthrough (explained later). 76 +- Usable subcommand syntax, with support for multiple subcommands, nested subcommands, option groups, and optional fallthrough (explained later).
73 - Ability to add a configuration file (`ini` format), and produce it as well. 77 - Ability to add a configuration file (`ini` format), and produce it as well.
74 - Produce real values that can be used directly in code, not something you have pay compute time to look up, for HPC applications. 78 - Produce real values that can be used directly in code, not something you have pay compute time to look up, for HPC applications.
75 - Work with standard types, simple custom types, and extensible to exotic types. 79 - Work with standard types, simple custom types, and extensible to exotic types.
@@ -208,6 +212,8 @@ app.add_flag_callback(option_name,function&lt;void(void)&gt;,help_string=&quot;&quot;) // ๐Ÿšง @@ -208,6 +212,8 @@ app.add_flag_callback(option_name,function&lt;void(void)&gt;,help_string=&quot;&quot;) // ๐Ÿšง
208 // Add subcommands 212 // Add subcommands
209 App* subcom = app.add_subcommand(name, description); 213 App* subcom = app.add_subcommand(name, description);
210 214
  215 +Option_group *app.add_option_group(name,description); // ๐Ÿšง
  216 +
211 // ๐Ÿšง All add_*set* methods deprecated in CLI11 1.8 - use ->transform(CLI::IsMember) instead 217 // ๐Ÿšง All add_*set* methods deprecated in CLI11 1.8 - use ->transform(CLI::IsMember) instead
212 -app.add_set(option_name, 218 -app.add_set(option_name,
213 - variable_to_bind_to, // Same type as stored by set 219 - variable_to_bind_to, // Same type as stored by set
@@ -223,7 +229,7 @@ App* subcom = app.add_subcommand(name, description); @@ -223,7 +229,7 @@ App* subcom = app.add_subcommand(name, description);
223 -app.add_mutable_set_ignore_case_underscore(... // ๐Ÿ†• String only 229 -app.add_mutable_set_ignore_case_underscore(... // ๐Ÿ†• String only
224 ``` 230 ```
225 231
226 -An option name must start with a alphabetic character, underscore, or a number. For long options, anything but an equals sign or a comma is valid after that, though 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`. 232 +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`.
227 233
228 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. 234 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.
229 235
@@ -246,7 +252,7 @@ app.add_flag(&quot;-1{1},-2{2},-3{3}&quot;,result,&quot;numerical flag&quot;) // ๐Ÿšง @@ -246,7 +252,7 @@ app.add_flag(&quot;-1{1},-2{2},-3{3}&quot;,result,&quot;numerical flag&quot;) // ๐Ÿšง
246 using any of those flags on the command line will result in the specified number in the output. Similar things can be done for string values, and enumerations, as long as the default value can be converted to the given type. 252 using any of those flags on the command line will result in the specified number in the output. Similar things can be done for string values, and enumerations, as long as the default value can be converted to the given type.
247 253
248 254
249 -On a C++14 compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure. 255 +On a `C++14` compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure.
250 256
251 On a compiler that supports C++17's `__has_include`, you can also use `std::optional`, `std::experimental::optional`, and `boost::optional` directly in an `add_option` call. If you don't have `__has_include`, you can define `CLI11_BOOST_OPTIONAL 1` before including CLI11 to manually add support (or 0 to remove) for `boost::optional`. See [CLI11 Internals][] for information on how this was done and how you can add your own converters. 257 On a compiler that supports C++17's `__has_include`, you can also use `std::optional`, `std::experimental::optional`, and `boost::optional` directly in an `add_option` call. If you don't have `__has_include`, you can define `CLI11_BOOST_OPTIONAL 1` before including CLI11 to manually add support (or 0 to remove) for `boost::optional`. See [CLI11 Internals][] for information on how this was done and how you can add your own converters.
252 258
@@ -334,12 +340,13 @@ You can access a vector of pointers to the parsed options in the original order @@ -334,12 +340,13 @@ You can access a vector of pointers to the parsed options in the original order
334 If `--` is present in the command line that does not end an unlimited option, then 340 If `--` is present in the command line that does not end an unlimited option, then
335 everything after that is positional only. 341 everything after that is positional only.
336 342
337 -#### Getting results ๐Ÿšง 343 +#### Getting results {#getting-results} ๐Ÿšง
  344 +
338 In most cases the fastest and easiest way is to return the results through a callback or variable specified in one of the `add_*` functions. But there are situations where this is not possible or desired. For these cases the results may be obtained through one of the following functions. Please note that these functions will do any type conversions and processing during the call so should not used in performance critical code: 345 In most cases the fastest and easiest way is to return the results through a callback or variable specified in one of the `add_*` functions. But there are situations where this is not possible or desired. For these cases the results may be obtained through one of the following functions. Please note that these functions will do any type conversions and processing during the call so should not used in performance critical code:
339 346
340 -- `results()`: retrieves a vector of strings with all the results in the order they were given.  
341 -- `results(variable_to_bind_to)`: ๐Ÿšง gets the results according to the MultiOptionPolicy and converts them just like the `add_option_function` with a variable.  
342 -- `Value=as<type>()`: ๐Ÿšง returns the result or default value directly as the specified type if possible, can be vector to return all results, and a non-vector to get the result according to the MultiOptionPolicy in place. 347 +- `results()`: Retrieves a vector of strings with all the results in the order they were given.
  348 +- `results(variable_to_bind_to)`: ๐Ÿšง Gets the results according to the MultiOptionPolicy and converts them just like the `add_option_function` with a variable.
  349 +- `Value=as<type>()`: ๐Ÿšง Returns the result or default value directly as the specified type if possible, can be vector to return all results, and a non-vector to get the result according to the MultiOptionPolicy in place.
343 350
344 ### Subcommands 351 ### Subcommands
345 352
@@ -362,19 +369,25 @@ Nameless subcommands function a similarly to groups in the main `App`. If an op @@ -362,19 +369,25 @@ Nameless subcommands function a similarly to groups in the main `App`. If an op
362 369
363 #### Subcommand options 370 #### Subcommand options
364 371
365 -There are several options that are supported on the main app and subcommands. These are: 372 +There are several options that are supported on the main app and subcommands and option_groups. These are:
366 373
367 - `.ignore_case()`: Ignore the case of this subcommand. Inherited by added subcommands, so is usually used on the main `App`. 374 - `.ignore_case()`: Ignore the case of this subcommand. Inherited by added subcommands, so is usually used on the main `App`.
368 - `.ignore_underscore()`: ๐Ÿ†• Ignore any underscores in the subcommand name. Inherited by added subcommands, so is usually used on the main `App`. 375 - `.ignore_underscore()`: ๐Ÿ†• Ignore any underscores in the subcommand name. Inherited by added subcommands, so is usually used on the main `App`.
369 - `.allow_windows_style_options()`: ๐Ÿ†• Allow command line options to be parsed in the form of `/s /long /file:file_name.ext` This option does not change how options are specified in the `add_option` calls or the ability to process options in the form of `-s --long --file=file_name.ext` 376 - `.allow_windows_style_options()`: ๐Ÿ†• Allow command line options to be parsed in the form of `/s /long /file:file_name.ext` This option does not change how options are specified in the `add_option` calls or the ability to process options in the form of `-s --long --file=file_name.ext`
370 - `.fallthrough()`: Allow extra unmatched options and positionals to "fall through" and be matched on a parent command. Subcommands always are allowed to fall through. 377 - `.fallthrough()`: Allow extra unmatched options and positionals to "fall through" and be matched on a parent command. Subcommands always are allowed to fall through.
  378 +- `.disable()`: ๐Ÿšง Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group.
  379 +- `.exludes(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.
  380 +- `.require_option()`: ๐Ÿšง Require 1 or more options or option groups be used.
  381 +- `.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 0 or more.
  382 +- `.require_option(min, max)`: ๐Ÿšง Explicitly set min and max allowed options or option groups. Setting `max` to 0 is unlimited.
371 - `.require_subcommand()`: Require 1 or more subcommands. 383 - `.require_subcommand()`: Require 1 or more subcommands.
372 - `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default 0 or more. 384 - `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default 0 or more.
373 - `.require_subcommand(min, max)`: Explicitly set min and max allowed subcommands. Setting `max` to 0 is unlimited. 385 - `.require_subcommand(min, max)`: Explicitly set min and max allowed subcommands. Setting `max` to 0 is unlimited.
374 -- `.add_subcommand(name="", description="")` Add a subcommand, returns a pointer to the internally stored subcommand.  
375 -- `.add_subcommand(shared_ptr<App>)` ๐Ÿšง Add a subcommand by shared_ptr, returns a pointer to the internally stored subcommand. 386 +- `.add_subcommand(name="", description="")`: Add a subcommand, returns a pointer to the internally stored subcommand.
  387 +- `.add_subcommand(shared_ptr<App>)`: ๐Ÿšง Add a subcommand by shared_ptr, returns a pointer to the internally stored subcommand.
376 - `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line. 388 - `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line.
377 -- `.get_subcommands(filter)`: The list of subcommands given on the command line. 389 +- `.get_subcommands(filter)`: The list of subcommands that match a particular filter function.
  390 +- `.add_option_group(name="", description="")`: ๐Ÿšง Add an option group to an App, an option group is specialized subcommand intended for containing groups of options or other groups for controlling how options interact.
378 - `.get_parent()`: Get the parent App or nullptr if called on master App. 391 - `.get_parent()`: Get the parent App or nullptr if called on master App.
379 - `.get_option(name)`: Get an option pointer by option name will throw if the specified option is not available, nameless subcommands are also searched 392 - `.get_option(name)`: Get an option pointer by option name will throw if the specified option is not available, nameless subcommands are also searched
380 - `.get_option_no_throw(name)`: ๐Ÿšง Get an option pointer by option name. This function will return a `nullptr` instead of throwing if the option is not available. 393 - `.get_option_no_throw(name)`: ๐Ÿšง Get an option pointer by option name. This function will return a `nullptr` instead of throwing if the option is not available.
@@ -384,6 +397,9 @@ There are several options that are supported on the main app and subcommands. Th @@ -384,6 +397,9 @@ There are several options that are supported on the main app and subcommands. Th
384 - `.description(str)`: ๐Ÿ†• Set/change the description. 397 - `.description(str)`: ๐Ÿ†• Set/change the description.
385 - `.get_description()`: Access the description. 398 - `.get_description()`: Access the description.
386 - `.parsed()`: True if this subcommand was given on the command line. 399 - `.parsed()`: True if this subcommand was given on the command line.
  400 +- `.count()`: Returns the number of times the subcommand was called
  401 +- `.count(option_name)`: Returns the number of times a particular option was called
  402 +- `.count_all()`: ๐Ÿšง Returns the total number of arguments a particular subcommand had, on the master App it returns the total number of processed commands
387 - `.name(name)`: Add or change the name. 403 - `.name(name)`: Add or change the name.
388 - `.callback(void() function)`: Set the callback that runs at the end of parsing. The options have already run at this point. 404 - `.callback(void() function)`: Set the callback that runs at the end of parsing. The options have already run at this point.
389 - `.allow_extras()`: Do not throw an error if extra arguments are left over. 405 - `.allow_extras()`: Do not throw an error if extra arguments are left over.
@@ -398,6 +414,27 @@ There are several options that are supported on the main app and subcommands. Th @@ -398,6 +414,27 @@ There are several options that are supported on the main app and subcommands. Th
398 414
399 > Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function. 415 > Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function.
400 416
  417 +#### Option Groups ๐Ÿšง {#option-groups}
  418 +
  419 +The method
  420 +```cpp
  421 +.add_option_group(name,description)
  422 +```
  423 +Will create an option Group, and return a pointer to it. An option group allows creation of a collection of options, similar to the groups function on options, but with additional controls and requirements. They allow specific sets of options to be composed and controlled as a collective. For an example see [range test](./tests/ranges.cpp). Option groups are a specialization of an App so all [functions](#subcommand-options) that work with an App also work on option groups. Options can be created as part of an option group using the add functions just like a subcommand, or previously created options can be added through
  424 +```cpp
  425 +ogroup->add_option(option_pointer)
  426 +```
  427 +```cpp
  428 +ogroup->add_options(option_pointer)
  429 +```
  430 +```cpp
  431 +ogroup->add_options(option1,option2,option3,...)
  432 +```
  433 +The option pointers used in this function must be options defined in the parent application of the option group otherwise an error will be generated.
  434 +Options in an option group are searched for a command line match after any options in the main app, so any positionals in the main app would be matched first. So care must be taken to make sure of the order when using positional arguments and option groups.
  435 +Option groups work well with `excludes` and `require_options` methods, as an Application will treat an option group as a single option for the purpose of counting and requirements. Option groups allow specifying requirements such as requiring 1 of 3 options in one group and 1 of 3 options in a different group. Option groups can contain other groups as well. Disabling an option group will turn off all options within the group.
  436 +
  437 +
401 ### Configuration file 438 ### Configuration file
402 439
403 ```cpp 440 ```cpp
examples/CMakeLists.txt
@@ -69,6 +69,7 @@ set_property(TEST subcom_partitioned_none PROPERTY PASS_REGULAR_EXPRESSION @@ -69,6 +69,7 @@ set_property(TEST subcom_partitioned_none PROPERTY PASS_REGULAR_EXPRESSION
69 "This is a timer:" 69 "This is a timer:"
70 "--file is required" 70 "--file is required"
71 "Run with --help for more information.") 71 "Run with --help for more information.")
  72 +
72 add_test(NAME subcom_partitioned_all COMMAND subcom_partitioned --file this --count --count -d 1.2) 73 add_test(NAME subcom_partitioned_all COMMAND subcom_partitioned --file this --count --count -d 1.2)
73 set_property(TEST subcom_partitioned_all PROPERTY PASS_REGULAR_EXPRESSION 74 set_property(TEST subcom_partitioned_all PROPERTY PASS_REGULAR_EXPRESSION
74 "This is a timer:" 75 "This is a timer:"
@@ -81,6 +82,30 @@ set_property(TEST subcom_partitioned_help PROPERTY PASS_REGULAR_EXPRESSION @@ -81,6 +82,30 @@ set_property(TEST subcom_partitioned_help PROPERTY PASS_REGULAR_EXPRESSION
81 "-f,--file TEXT REQUIRED" 82 "-f,--file TEXT REQUIRED"
82 "-d,--double FLOAT") 83 "-d,--double FLOAT")
83 84
  85 +add_cli_exe(option_groups option_groups.cpp)
  86 +add_test(NAME option_groups_missing COMMAND option_groups )
  87 +set_property(TEST option_groups_missing PROPERTY PASS_REGULAR_EXPRESSION
  88 + "Exactly 1 option from"
  89 + "is required")
  90 +add_test(NAME option_groups_extra COMMAND option_groups --csv --binary)
  91 +set_property(TEST option_groups_extra PROPERTY PASS_REGULAR_EXPRESSION
  92 + "and 2 were given")
  93 +add_test(NAME option_groups_extra2 COMMAND option_groups --csv --address "192.168.1.1" -o "test.out")
  94 +set_property(TEST option_groups_extra2 PROPERTY PASS_REGULAR_EXPRESSION
  95 + "at most 1")
  96 +
  97 +
  98 +add_cli_exe(ranges ranges.cpp)
  99 +add_test(NAME ranges_range COMMAND ranges --range 1 2 3)
  100 +set_property(TEST ranges_range PROPERTY PASS_REGULAR_EXPRESSION
  101 + "[2:1:3]")
  102 +add_test(NAME ranges_minmax COMMAND ranges --min 2 --max 3)
  103 +set_property(TEST ranges_minmax PROPERTY PASS_REGULAR_EXPRESSION
  104 + "[2:1:3]")
  105 +add_test(NAME ranges_error COMMAND ranges --min 2 --max 3 --step 1 --range 1 2 3)
  106 +set_property(TEST ranges_error PROPERTY PASS_REGULAR_EXPRESSION
  107 + "Exactly 1 option from")
  108 +
84 add_cli_exe(validators validators.cpp) 109 add_cli_exe(validators validators.cpp)
85 add_test(NAME validators_help COMMAND validators --help) 110 add_test(NAME validators_help COMMAND validators --help)
86 set_property(TEST validators_help PROPERTY PASS_REGULAR_EXPRESSION 111 set_property(TEST validators_help PROPERTY PASS_REGULAR_EXPRESSION
examples/option_groups.cpp 0 โ†’ 100644
  1 +#include "CLI/CLI.hpp"
  2 +
  3 +int main(int argc, char **argv) {
  4 +
  5 + CLI::App app("data output specification");
  6 + app.set_help_all_flag("--help-all", "Expand all help");
  7 +
  8 + auto format = app.add_option_group("output_format", "formatting type for output");
  9 + auto target = app.add_option_group("output target", "target location for the output");
  10 + bool csv = false;
  11 + bool human = false;
  12 + bool binary = false;
  13 + format->add_flag("--csv", csv, "specify the output in csv format");
  14 + format->add_flag("--human", human, "specify the output in human readable text format");
  15 + format->add_flag("--binary", binary, "specify the output in binary format");
  16 + // require one of the options to be selected
  17 + format->require_option(1);
  18 + std::string fileLoc;
  19 + std::string networkAddress;
  20 + target->add_option("-o,--file", fileLoc, "specify the file location of the output");
  21 + target->add_option("--address", networkAddress, "specify a network address to send the file");
  22 +
  23 + // require at most one of the target options
  24 + target->require_option(0, 1);
  25 + CLI11_PARSE(app, argc, argv);
  26 +
  27 + std::string format_type = (csv) ? std::string("CSV") : ((human) ? "human readable" : "binary");
  28 + std::cout << "Selected " << format_type << "format" << std::endl;
  29 + if(fileLoc.empty()) {
  30 + std::cout << " sent to file " << fileLoc << std::endl;
  31 + } else if(networkAddress.empty()) {
  32 + std::cout << " sent over network to " << networkAddress << std::endl;
  33 + } else {
  34 + std::cout << " sent to std::cout" << std::endl;
  35 + }
  36 +
  37 + return 0;
  38 +}
examples/ranges.cpp 0 โ†’ 100644
  1 +#include "CLI/CLI.hpp"
  2 +
  3 +int main(int argc, char **argv) {
  4 +
  5 + CLI::App app{"App to demonstrate exclusionary option groups."};
  6 +
  7 + std::vector<int> range;
  8 + app.add_option("--range,-R", range, "A range")->expected(-2);
  9 +
  10 + auto ogroup = app.add_option_group("min_max_step", "set the min max and step");
  11 + int min, max, step = 1;
  12 + ogroup->add_option("--min,-m", min, "The minimum")->required();
  13 + ogroup->add_option("--max,-M", max, "The maximum")->required();
  14 + ogroup->add_option("--step,-s", step, "The step", true);
  15 +
  16 + app.require_option(1);
  17 +
  18 + CLI11_PARSE(app, argc, argv);
  19 +
  20 + if(!range.empty()) {
  21 + if(range.size() == 2) {
  22 + min = range[0];
  23 + max = range[1];
  24 + }
  25 + if(range.size() >= 3) {
  26 + step = range[0];
  27 + min = range[1];
  28 + max = range[2];
  29 + }
  30 + }
  31 + std::cout << "range is [" << min << ':' << step << ':' << max << "]\n";
  32 + return 0;
  33 +}
include/CLI/App.hpp
@@ -51,6 +51,7 @@ class App; @@ -51,6 +51,7 @@ class App;
51 51
52 using App_p = std::shared_ptr<App>; 52 using App_p = std::shared_ptr<App>;
53 53
  54 +class Option_group;
54 /// Creates a command line program, with very few defaults. 55 /// Creates a command line program, with very few defaults.
55 /** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated 56 /** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated
56 * add_option methods make it easy to prepare options. Remember to call `.start` before starting your 57 * add_option methods make it easy to prepare options. Remember to call `.start` before starting your
@@ -80,9 +81,15 @@ class App { @@ -80,9 +81,15 @@ class App {
80 /// If true, return immediately on an unrecognized option (implies allow_extras) INHERITABLE 81 /// If true, return immediately on an unrecognized option (implies allow_extras) INHERITABLE
81 bool prefix_command_{false}; 82 bool prefix_command_{false};
82 83
83 - /// if set to true the name was automatically generated from the command line vs a user set name 84 + /// If set to true the name was automatically generated from the command line vs a user set name
84 bool has_automatic_name_{false}; 85 bool has_automatic_name_{false};
85 86
  87 + /// If set to true the subcommand is required to be processed and used, ignored for main app
  88 + bool required_{false};
  89 +
  90 + /// If set to true the subcommand is disabled and cannot be used, ignored for main app
  91 + bool disabled_{false};
  92 +
86 /// This is a function that runs when complete. Great for subcommands. Can throw. 93 /// This is a function that runs when complete. Great for subcommands. Can throw.
87 std::function<void()> callback_; 94 std::function<void()> callback_;
88 95
@@ -132,6 +139,13 @@ class App { @@ -132,6 +139,13 @@ class App {
132 /// This is a list of the subcommands collected, in order 139 /// This is a list of the subcommands collected, in order
133 std::vector<App *> parsed_subcommands_; 140 std::vector<App *> parsed_subcommands_;
134 141
  142 + /// this is a list of subcommands that are exclusionary to this one
  143 + std::set<App *> exclude_subcommands_;
  144 +
  145 + /// This is a list of options which are exclusionary to this App, if the options were used this subcommand should
  146 + /// not be
  147 + std::set<Option *> exclude_options_;
  148 +
135 ///@} 149 ///@}
136 /// @name Subcommands 150 /// @name Subcommands
137 ///@{ 151 ///@{
@@ -171,6 +185,12 @@ class App { @@ -171,6 +185,12 @@ class App {
171 /// Max number of subcommands allowed (parsing stops after this number). 0 is unlimited INHERITABLE 185 /// Max number of subcommands allowed (parsing stops after this number). 0 is unlimited INHERITABLE
172 size_t require_subcommand_max_ = 0; 186 size_t require_subcommand_max_ = 0;
173 187
  188 + /// Minimum required options (not inheritable!)
  189 + size_t require_option_min_ = 0;
  190 +
  191 + /// Max number of options allowed. 0 is unlimited (not inheritable)
  192 + size_t require_option_max_ = 0;
  193 +
174 /// The group membership INHERITABLE 194 /// The group membership INHERITABLE
175 std::string group_{"Subcommands"}; 195 std::string group_{"Subcommands"};
176 196
@@ -261,6 +281,18 @@ class App { @@ -261,6 +281,18 @@ class App {
261 } 281 }
262 282
263 /// Remove the error when extras are left over on the command line. 283 /// Remove the error when extras are left over on the command line.
  284 + App *required(bool require = true) {
  285 + required_ = require;
  286 + return this;
  287 + }
  288 +
  289 + /// Disable the subcommand or option group
  290 + App *disabled(bool disable = true) {
  291 + disabled_ = disable;
  292 + return this;
  293 + }
  294 +
  295 + /// Remove the error when extras are left over on the command line.
264 /// Will also call App::allow_extras(). 296 /// Will also call App::allow_extras().
265 App *allow_config_extras(bool allow = true) { 297 App *allow_config_extras(bool allow = true) {
266 allow_extras(allow); 298 allow_extras(allow);
@@ -926,7 +958,7 @@ class App { @@ -926,7 +958,7 @@ class App {
926 Option *set_config(std::string option_name = "", 958 Option *set_config(std::string option_name = "",
927 std::string default_filename = "", 959 std::string default_filename = "",
928 std::string help_message = "Read an ini file", 960 std::string help_message = "Read an ini file",
929 - bool required = false) { 961 + bool config_required = false) {
930 962
931 // Remove existing config if present 963 // Remove existing config if present
932 if(config_ptr_ != nullptr) 964 if(config_ptr_ != nullptr)
@@ -935,7 +967,7 @@ class App { @@ -935,7 +967,7 @@ class App {
935 // Only add config if option passed 967 // Only add config if option passed
936 if(!option_name.empty()) { 968 if(!option_name.empty()) {
937 config_name_ = default_filename; 969 config_name_ = default_filename;
938 - config_required_ = required; 970 + config_required_ = config_required;
939 config_ptr_ = add_option(option_name, config_name_, help_message, !default_filename.empty()); 971 config_ptr_ = add_option(option_name, config_name_, help_message, !default_filename.empty());
940 config_ptr_->configurable(false); 972 config_ptr_->configurable(false);
941 } 973 }
@@ -965,6 +997,17 @@ class App { @@ -965,6 +997,17 @@ class App {
965 return false; 997 return false;
966 } 998 }
967 999
  1000 + /// creates an option group as part of the given app
  1001 + template <typename T = Option_group>
  1002 + T *add_option_group(std::string group_name, std::string group_description = "") {
  1003 + auto option_group = std::make_shared<T>(std::move(group_description), group_name, nullptr);
  1004 + auto ptr = option_group.get();
  1005 + // move to App_p for overload resolution on older gcc versions
  1006 + App_p app_ptr = std::dynamic_pointer_cast<App>(option_group);
  1007 + add_subcommand(std::move(app_ptr));
  1008 + return ptr;
  1009 + }
  1010 +
968 ///@} 1011 ///@}
969 /// @name Subcommmands 1012 /// @name Subcommmands
970 ///@{ 1013 ///@{
@@ -988,6 +1031,7 @@ class App { @@ -988,6 +1031,7 @@ class App {
988 subcommands_.push_back(std::move(subcom)); 1031 subcommands_.push_back(std::move(subcom));
989 return subcommands_.back().get(); 1032 return subcommands_.back().get();
990 } 1033 }
  1034 +
991 /// Check to see if a subcommand is part of this command (doesn't have to be in command line) 1035 /// Check to see if a subcommand is part of this command (doesn't have to be in command line)
992 /// returns the first subcommand if passed a nullptr 1036 /// returns the first subcommand if passed a nullptr
993 App *get_subcommand(App *subcom) const { 1037 App *get_subcommand(App *subcom) const {
@@ -1043,6 +1087,22 @@ class App { @@ -1043,6 +1087,22 @@ class App {
1043 /// otherwise modified in a callback 1087 /// otherwise modified in a callback
1044 size_t count() const { return parsed_; } 1088 size_t count() const { return parsed_; }
1045 1089
  1090 + /// Get a count of all the arguments processed in options and subcommands, this excludes arguments which were
  1091 + /// treated as extras.
  1092 + size_t count_all() const {
  1093 + size_t cnt{0};
  1094 + for(auto &opt : options_) {
  1095 + cnt += opt->count();
  1096 + }
  1097 + for(auto &sub : subcommands_) {
  1098 + cnt += sub->count_all();
  1099 + }
  1100 + if(!get_name().empty()) { // for named subcommands add the number of times the subcommand was called
  1101 + cnt += parsed_;
  1102 + }
  1103 + return cnt;
  1104 + }
  1105 +
1046 /// Changes the group membership 1106 /// Changes the group membership
1047 App *group(std::string group_name) { 1107 App *group(std::string group_name) {
1048 group_ = group_name; 1108 group_ = group_name;
@@ -1078,6 +1138,35 @@ class App { @@ -1078,6 +1138,35 @@ class App {
1078 return this; 1138 return this;
1079 } 1139 }
1080 1140
  1141 + /// The argumentless form of require option requires 1 or more options be used
  1142 + App *require_option() {
  1143 + require_option_min_ = 1;
  1144 + require_option_max_ = 0;
  1145 + return this;
  1146 + }
  1147 +
  1148 + /// Require an option to be given (does not affect help call)
  1149 + /// The number required can be given. Negative values indicate maximum
  1150 + /// number allowed (0 for any number).
  1151 + App *require_option(int value) {
  1152 + if(value < 0) {
  1153 + require_option_min_ = 0;
  1154 + require_option_max_ = static_cast<size_t>(-value);
  1155 + } else {
  1156 + require_option_min_ = static_cast<size_t>(value);
  1157 + require_option_max_ = static_cast<size_t>(value);
  1158 + }
  1159 + return this;
  1160 + }
  1161 +
  1162 + /// Explicitly control the number of options required. Setting 0
  1163 + /// for the max means unlimited number allowed. Max number inheritable.
  1164 + App *require_option(size_t min, size_t max) {
  1165 + require_option_min_ = min;
  1166 + require_option_max_ = max;
  1167 + return this;
  1168 + }
  1169 +
1081 /// Stop subcommand fallthrough, so that parent commands cannot collect commands after subcommand. 1170 /// Stop subcommand fallthrough, so that parent commands cannot collect commands after subcommand.
1082 /// Default from parent, usually set on parent. 1171 /// Default from parent, usually set on parent.
1083 App *fallthrough(bool value = true) { 1172 App *fallthrough(bool value = true) {
@@ -1219,7 +1308,7 @@ class App { @@ -1219,7 +1308,7 @@ class App {
1219 /// Counts the number of times the given option was passed. 1308 /// Counts the number of times the given option was passed.
1220 size_t count(std::string option_name) const { return get_option(option_name)->count(); } 1309 size_t count(std::string option_name) const { return get_option(option_name)->count(); }
1221 1310
1222 - /// Get a subcommand pointer list to the currently selected subcommands (after parsing by by default, in command 1311 + /// Get a subcommand pointer list to the currently selected subcommands (after parsing by default, in command
1223 /// line order; use parsed = false to get the original definition list.) 1312 /// line order; use parsed = false to get the original definition list.)
1224 std::vector<App *> get_subcommands() const { return parsed_subcommands_; } 1313 std::vector<App *> get_subcommands() const { return parsed_subcommands_; }
1225 1314
@@ -1267,6 +1356,50 @@ class App { @@ -1267,6 +1356,50 @@ class App {
1267 /// Check with name instead of pointer to see if subcommand was selected 1356 /// Check with name instead of pointer to see if subcommand was selected
1268 bool got_subcommand(std::string subcommand_name) const { return get_subcommand(subcommand_name)->parsed_ > 0; } 1357 bool got_subcommand(std::string subcommand_name) const { return get_subcommand(subcommand_name)->parsed_ > 0; }
1269 1358
  1359 + /// Sets excluded options for the subcommand
  1360 + App *excludes(Option *opt) {
  1361 + if(opt == nullptr) {
  1362 + throw OptionNotFound("nullptr passed");
  1363 + }
  1364 + exclude_options_.insert(opt);
  1365 + return this;
  1366 + }
  1367 +
  1368 + /// Sets excluded subcommands for the subcommand
  1369 + App *excludes(App *app) {
  1370 + if((app == this) || (app == nullptr)) {
  1371 + throw OptionNotFound("nullptr passed");
  1372 + }
  1373 + auto res = exclude_subcommands_.insert(app);
  1374 + // subcommand exclusion should be symmetric
  1375 + if(res.second) {
  1376 + app->exclude_subcommands_.insert(this);
  1377 + }
  1378 + return this;
  1379 + }
  1380 +
  1381 + /// Removes an option from the excludes list of this subcommand
  1382 + bool remove_excludes(Option *opt) {
  1383 + auto iterator = std::find(std::begin(exclude_options_), std::end(exclude_options_), opt);
  1384 + if(iterator != std::end(exclude_options_)) {
  1385 + exclude_options_.erase(iterator);
  1386 + return true;
  1387 + } else {
  1388 + return false;
  1389 + }
  1390 + }
  1391 +
  1392 + /// Removes a subcommand from this excludes list of this subcommand
  1393 + bool remove_excludes(App *app) {
  1394 + auto iterator = std::find(std::begin(exclude_subcommands_), std::end(exclude_subcommands_), app);
  1395 + if(iterator != std::end(exclude_subcommands_)) {
  1396 + exclude_subcommands_.erase(iterator);
  1397 + return true;
  1398 + } else {
  1399 + return false;
  1400 + }
  1401 + }
  1402 +
1270 ///@} 1403 ///@}
1271 /// @name Help 1404 /// @name Help
1272 ///@{ 1405 ///@{
@@ -1424,12 +1557,24 @@ class App { @@ -1424,12 +1557,24 @@ class App {
1424 /// Get the required max subcommand value 1557 /// Get the required max subcommand value
1425 size_t get_require_subcommand_max() const { return require_subcommand_max_; } 1558 size_t get_require_subcommand_max() const { return require_subcommand_max_; }
1426 1559
  1560 + /// Get the required min option value
  1561 + size_t get_require_option_min() const { return require_option_min_; }
  1562 +
  1563 + /// Get the required max option value
  1564 + size_t get_require_option_max() const { return require_option_max_; }
  1565 +
1427 /// Get the prefix command status 1566 /// Get the prefix command status
1428 bool get_prefix_command() const { return prefix_command_; } 1567 bool get_prefix_command() const { return prefix_command_; }
1429 1568
1430 /// Get the status of allow extras 1569 /// Get the status of allow extras
1431 bool get_allow_extras() const { return allow_extras_; } 1570 bool get_allow_extras() const { return allow_extras_; }
1432 1571
  1572 + /// Get the status of required
  1573 + bool get_required() const { return required_; }
  1574 +
  1575 + /// Get the status of required
  1576 + bool get_disabled() const { return disabled_; }
  1577 +
1433 /// Get the status of allow extras 1578 /// Get the status of allow extras
1434 bool get_allow_config_extras() const { return allow_config_extras_; } 1579 bool get_allow_config_extras() const { return allow_config_extras_; }
1435 1580
@@ -1457,6 +1602,9 @@ class App { @@ -1457,6 +1602,9 @@ class App {
1457 /// Get the name of the current app 1602 /// Get the name of the current app
1458 std::string get_name() const { return name_; } 1603 std::string get_name() const { return name_; }
1459 1604
  1605 + /// Get a display name for an app
  1606 + std::string get_display_name() const { return (!name_.empty()) ? name_ : "[Option Group: " + get_group() + "]"; }
  1607 +
1460 /// Check the name, case insensitive and underscore insensitive if set 1608 /// Check the name, case insensitive and underscore insensitive if set
1461 bool check_name(std::string name_to_check) const { 1609 bool check_name(std::string name_to_check) const {
1462 std::string local_name = name_; 1610 std::string local_name = name_;
@@ -1525,15 +1673,33 @@ class App { @@ -1525,15 +1673,33 @@ class App {
1525 protected: 1673 protected:
1526 /// Check the options to make sure there are no conflicts. 1674 /// Check the options to make sure there are no conflicts.
1527 /// 1675 ///
1528 - /// Currently checks to see if multiple positionals exist with -1 args 1676 + /// Currently checks to see if multiple positionals exist with -1 args and checks if the min and max options are
  1677 + /// feasible
1529 void _validate() const { 1678 void _validate() const {
1530 auto pcount = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) { 1679 auto pcount = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) {
1531 return opt->get_items_expected() < 0 && opt->get_positional(); 1680 return opt->get_items_expected() < 0 && opt->get_positional();
1532 }); 1681 });
1533 if(pcount > 1) 1682 if(pcount > 1)
1534 throw InvalidError(name_); 1683 throw InvalidError(name_);
  1684 +
  1685 + size_t nameless_subs{0};
1535 for(const App_p &app : subcommands_) { 1686 for(const App_p &app : subcommands_) {
1536 app->_validate(); 1687 app->_validate();
  1688 + if(app->get_name().empty())
  1689 + ++nameless_subs;
  1690 + }
  1691 +
  1692 + if(require_option_min_ > 0) {
  1693 + if(require_option_max_ > 0) {
  1694 + if(require_option_max_ < require_option_min_) {
  1695 + throw(InvalidError("Required min options greater than required max options",
  1696 + ExitCodes::InvalidError));
  1697 + }
  1698 + }
  1699 + if(require_option_min_ > (options_.size() + nameless_subs)) {
  1700 + throw(InvalidError("Required min options greater than number of available options",
  1701 + ExitCodes::InvalidError));
  1702 + }
1537 } 1703 }
1538 } 1704 }
1539 1705
@@ -1572,7 +1738,7 @@ class App { @@ -1572,7 +1738,7 @@ class App {
1572 } 1738 }
1573 1739
1574 for(const App_p &com : subcommands_) 1740 for(const App_p &com : subcommands_)
1575 - if(com->check_name(current) && !*com) 1741 + if(!com->disabled_ && com->check_name(current) && !*com)
1576 return true; 1742 return true;
1577 1743
1578 // Check parent if exists, else return false 1744 // Check parent if exists, else return false
@@ -1690,8 +1856,34 @@ class App { @@ -1690,8 +1856,34 @@ class App {
1690 1856
1691 /// Verify required options and cross requirements. Subcommands too (only if selected). 1857 /// Verify required options and cross requirements. Subcommands too (only if selected).
1692 void _process_requirements() { 1858 void _process_requirements() {
  1859 + // check excludes
  1860 + bool excluded{false};
  1861 + std::string excluder;
  1862 + for(auto &opt : exclude_options_) {
  1863 + if(opt->count() > 0) {
  1864 + excluded = true;
  1865 + excluder = opt->get_name();
  1866 + }
  1867 + }
  1868 + for(auto &subc : exclude_subcommands_) {
  1869 + if(subc->count_all() > 0) {
  1870 + excluded = true;
  1871 + excluder = subc->get_display_name();
  1872 + }
  1873 + }
  1874 + if(excluded) {
  1875 + if(count_all() > 0) {
  1876 + throw ExcludesError(get_display_name(), excluder);
  1877 + }
  1878 + // if we are excluded but didn't receive anything, just return
  1879 + return;
  1880 + }
  1881 + size_t used_options = 0;
1693 for(const Option_p &opt : options_) { 1882 for(const Option_p &opt : options_) {
1694 1883
  1884 + if(opt->count() != 0) {
  1885 + ++used_options;
  1886 + }
1695 // Required or partially filled 1887 // Required or partially filled
1696 if(opt->get_required() || opt->count() != 0) { 1888 if(opt->get_required() || opt->count() != 0) {
1697 // Make sure enough -N arguments parsed (+N is already handled in parsing function) 1889 // Make sure enough -N arguments parsed (+N is already handled in parsing function)
@@ -1711,16 +1903,60 @@ class App { @@ -1711,16 +1903,60 @@ class App {
1711 if(opt->count() > 0 && opt_ex->count() != 0) 1903 if(opt->count() > 0 && opt_ex->count() != 0)
1712 throw ExcludesError(opt->get_name(), opt_ex->get_name()); 1904 throw ExcludesError(opt->get_name(), opt_ex->get_name());
1713 } 1905 }
1714 -  
1715 - auto selected_subcommands = get_subcommands();  
1716 - if(require_subcommand_min_ > selected_subcommands.size())  
1717 - throw RequiredError::Subcommand(require_subcommand_min_); 1906 + // check for the required number of subcommands
  1907 + if(require_subcommand_min_ > 0) {
  1908 + auto selected_subcommands = get_subcommands();
  1909 + if(require_subcommand_min_ > selected_subcommands.size())
  1910 + throw RequiredError::Subcommand(require_subcommand_min_);
  1911 + }
1718 1912
1719 // Max error cannot occur, the extra subcommand will parse as an ExtrasError or a remaining item. 1913 // Max error cannot occur, the extra subcommand will parse as an ExtrasError or a remaining item.
1720 1914
  1915 + // run this loop to check how many unnamed subcommands were actually used since they are considered options from
  1916 + // the perspective of an App
1721 for(App_p &sub : subcommands_) { 1917 for(App_p &sub : subcommands_) {
1722 - if((sub->count() > 0) || (sub->name_.empty()))  
1723 - sub->_process_requirements(); 1918 + if(sub->disabled_)
  1919 + continue;
  1920 + if((sub->name_.empty()) && (sub->count_all() > 0)) {
  1921 + ++used_options;
  1922 + }
  1923 + }
  1924 +
  1925 + if((require_option_min_ > used_options) ||
  1926 + ((require_option_max_ > 0) && (require_option_max_ < used_options))) {
  1927 + auto option_list = detail::join(options_, [](const Option_p &ptr) { return ptr->get_name(false, true); });
  1928 + if(option_list.compare(0, 10, "-h,--help,") == 0) {
  1929 + option_list.erase(0, 10);
  1930 + }
  1931 + auto subc_list = get_subcommands([](App *app) { return ((app->get_name().empty()) && (!app->disabled_)); });
  1932 + if(!subc_list.empty()) {
  1933 + option_list += "," + detail::join(subc_list, [](const App *app) { return app->get_display_name(); });
  1934 + }
  1935 + throw RequiredError::Option(require_option_min_, require_option_max_, used_options, option_list);
  1936 + }
  1937 +
  1938 + // now process the requirements for subcommands if needed
  1939 + for(App_p &sub : subcommands_) {
  1940 + if(sub->disabled_)
  1941 + continue;
  1942 + if((sub->name_.empty()) && (sub->required_ == false)) {
  1943 + if(sub->count_all() == 0) {
  1944 + if((require_option_min_ > 0) && (require_option_min_ <= used_options)) {
  1945 + continue;
  1946 + // if we have met the requirement and there is nothing in this option group skip checking
  1947 + // requirements
  1948 + }
  1949 + if((require_option_max_ > 0) && (used_options >= require_option_min_)) {
  1950 + continue;
  1951 + // if we have met the requirement and there is nothing in this option group skip checking
  1952 + // requirements
  1953 + }
  1954 + }
  1955 + }
  1956 + sub->_process_requirements();
  1957 + if((sub->required_) && (sub->count_all() == 0)) {
  1958 + throw(CLI::RequiredError(sub->get_display_name()));
  1959 + }
1724 } 1960 }
1725 } 1961 }
1726 1962
@@ -1859,10 +2095,10 @@ class App { @@ -1859,10 +2095,10 @@ class App {
1859 } 2095 }
1860 2096
1861 /// Count the required remaining positional arguments 2097 /// Count the required remaining positional arguments
1862 - size_t _count_remaining_positionals(bool required = false) const { 2098 + size_t _count_remaining_positionals(bool required_only = false) const {
1863 size_t retval = 0; 2099 size_t retval = 0;
1864 for(const Option_p &opt : options_) 2100 for(const Option_p &opt : options_)
1865 - if(opt->get_positional() && (!required || opt->get_required()) && opt->get_items_expected() > 0 && 2101 + if(opt->get_positional() && (!required_only || opt->get_required()) && opt->get_items_expected() > 0 &&
1866 static_cast<int>(opt->count()) < opt->get_items_expected()) 2102 static_cast<int>(opt->count()) < opt->get_items_expected())
1867 retval = static_cast<size_t>(opt->get_items_expected()) - opt->count(); 2103 retval = static_cast<size_t>(opt->get_items_expected()) - opt->count();
1868 2104
@@ -1886,7 +2122,7 @@ class App { @@ -1886,7 +2122,7 @@ class App {
1886 } 2122 }
1887 2123
1888 for(auto &subc : subcommands_) { 2124 for(auto &subc : subcommands_) {
1889 - if(subc->name_.empty()) { 2125 + if((subc->name_.empty()) && (!subc->disabled_)) {
1890 subc->_parse_positional(args); 2126 subc->_parse_positional(args);
1891 if(subc->missing_.empty()) { // check if it was used and is not in the missing category 2127 if(subc->missing_.empty()) { // check if it was used and is not in the missing category
1892 return; 2128 return;
@@ -1922,6 +2158,8 @@ class App { @@ -1922,6 +2158,8 @@ class App {
1922 if(_count_remaining_positionals(/* required */ true) > 0) 2158 if(_count_remaining_positionals(/* required */ true) > 0)
1923 return _parse_positional(args); 2159 return _parse_positional(args);
1924 for(const App_p &com : subcommands_) { 2160 for(const App_p &com : subcommands_) {
  2161 + if(com->disabled_)
  2162 + continue;
1925 if(com->check_name(args.back())) { 2163 if(com->check_name(args.back())) {
1926 args.pop_back(); 2164 args.pop_back();
1927 if(std::find(std::begin(parsed_subcommands_), std::end(parsed_subcommands_), com.get()) == 2165 if(std::find(std::begin(parsed_subcommands_), std::end(parsed_subcommands_), com.get()) ==
@@ -1976,11 +2214,12 @@ class App { @@ -1976,11 +2214,12 @@ class App {
1976 // Option not found 2214 // Option not found
1977 if(op_ptr == std::end(options_)) { 2215 if(op_ptr == std::end(options_)) {
1978 for(auto &subc : subcommands_) { 2216 for(auto &subc : subcommands_) {
1979 - if(subc->name_.empty()) { 2217 + if((subc->name_.empty()) && (!(subc->disabled_))) {
1980 subc->_parse_arg(args, current_type); 2218 subc->_parse_arg(args, current_type);
1981 if(subc->missing_.empty()) { // check if it was used and is not in the missing category 2219 if(subc->missing_.empty()) { // check if it was used and is not in the missing category
1982 return; 2220 return;
1983 } else { 2221 } else {
  2222 + // for unnamed subs they shouldn't trigger a missing argument
1984 args.push_back(std::move(subc->missing_.front().second)); 2223 args.push_back(std::move(subc->missing_.front().second));
1985 subc->missing_.clear(); 2224 subc->missing_.clear();
1986 } 2225 }
@@ -2006,8 +2245,8 @@ class App { @@ -2006,8 +2245,8 @@ class App {
2006 2245
2007 // Make sure we always eat the minimum for unlimited vectors 2246 // Make sure we always eat the minimum for unlimited vectors
2008 int collected = 0; 2247 int collected = 0;
  2248 + int result_count = 0;
2009 // deal with flag like things 2249 // deal with flag like things
2010 - int count = 0;  
2011 if(num == 0) { 2250 if(num == 0) {
2012 auto res = op->get_flag_value(arg_name, value); 2251 auto res = op->get_flag_value(arg_name, value);
2013 op->add_result(res); 2252 op->add_result(res);
@@ -2015,22 +2254,22 @@ class App { @@ -2015,22 +2254,22 @@ class App {
2015 } 2254 }
2016 // --this=value 2255 // --this=value
2017 else if(!value.empty()) { 2256 else if(!value.empty()) {
2018 - op->add_result(value, count); 2257 + op->add_result(value, result_count);
2019 parse_order_.push_back(op.get()); 2258 parse_order_.push_back(op.get());
2020 - collected += count; 2259 + collected += result_count;
2021 // If exact number expected 2260 // If exact number expected
2022 if(num > 0) 2261 if(num > 0)
2023 - num = (num >= count) ? num - count : 0; 2262 + num = (num >= result_count) ? num - result_count : 0;
2024 2263
2025 // -Trest 2264 // -Trest
2026 } else if(!rest.empty()) { 2265 } else if(!rest.empty()) {
2027 - op->add_result(rest, count); 2266 + op->add_result(rest, result_count);
2028 parse_order_.push_back(op.get()); 2267 parse_order_.push_back(op.get());
2029 rest = ""; 2268 rest = "";
2030 - collected += count; 2269 + collected += result_count;
2031 // If exact number expected 2270 // If exact number expected
2032 if(num > 0) 2271 if(num > 0)
2033 - num = (num >= count) ? num - count : 0; 2272 + num = (num >= result_count) ? num - result_count : 0;
2034 } 2273 }
2035 2274
2036 // Unlimited vector parser 2275 // Unlimited vector parser
@@ -2043,10 +2282,10 @@ class App { @@ -2043,10 +2282,10 @@ class App {
2043 if(_count_remaining_positionals() > 0) 2282 if(_count_remaining_positionals() > 0)
2044 break; 2283 break;
2045 } 2284 }
2046 - op->add_result(args.back(), count); 2285 + op->add_result(args.back(), result_count);
2047 parse_order_.push_back(op.get()); 2286 parse_order_.push_back(op.get());
2048 args.pop_back(); 2287 args.pop_back();
2049 - collected += count; 2288 + collected += result_count;
2050 } 2289 }
2051 2290
2052 // Allow -- to end an unlimited list and "eat" it 2291 // Allow -- to end an unlimited list and "eat" it
@@ -2057,9 +2296,9 @@ class App { @@ -2057,9 +2296,9 @@ class App {
2057 while(num > 0 && !args.empty()) { 2296 while(num > 0 && !args.empty()) {
2058 std::string current_ = args.back(); 2297 std::string current_ = args.back();
2059 args.pop_back(); 2298 args.pop_back();
2060 - op->add_result(current_, count); 2299 + op->add_result(current_, result_count);
2061 parse_order_.push_back(op.get()); 2300 parse_order_.push_back(op.get());
2062 - num -= count; 2301 + num -= result_count;
2063 } 2302 }
2064 2303
2065 if(num > 0) { 2304 if(num > 0) {
@@ -2072,6 +2311,72 @@ class App { @@ -2072,6 +2311,72 @@ class App {
2072 args.push_back(rest); 2311 args.push_back(rest);
2073 } 2312 }
2074 } 2313 }
  2314 +
  2315 + public:
  2316 + /// function that could be used by subclasses of App to shift options around into subcommands
  2317 + void _move_option(Option *opt, App *app) {
  2318 + if(opt == nullptr) {
  2319 + throw OptionNotFound("the option is NULL");
  2320 + }
  2321 + // verify that the give app is actually a subcommand
  2322 + bool found = false;
  2323 + for(auto &subc : subcommands_) {
  2324 + if(app == subc.get()) {
  2325 + found = true;
  2326 + }
  2327 + }
  2328 + if(!found) {
  2329 + throw OptionNotFound("The Given app is not a subcommand");
  2330 + }
  2331 +
  2332 + if((help_ptr_ == opt) || (help_all_ptr_ == opt))
  2333 + throw OptionAlreadyAdded("cannot move help options");
  2334 +
  2335 + if((config_ptr_ == opt))
  2336 + throw OptionAlreadyAdded("cannot move config file options");
  2337 +
  2338 + auto iterator =
  2339 + std::find_if(std::begin(options_), std::end(options_), [opt](const Option_p &v) { return v.get() == opt; });
  2340 + if(iterator != std::end(options_)) {
  2341 + const auto &opt_p = *iterator;
  2342 + if(std::find_if(std::begin(app->options_), std::end(app->options_), [&opt_p](const Option_p &v) {
  2343 + return (*v == *opt_p);
  2344 + }) == std::end(app->options_)) {
  2345 + // only erase after the insertion was successful
  2346 + app->options_.push_back(std::move(*iterator));
  2347 + options_.erase(iterator);
  2348 + } else {
  2349 + throw OptionAlreadyAdded(opt->get_name());
  2350 + }
  2351 + } else {
  2352 + throw OptionNotFound("could not locate the given App");
  2353 + }
  2354 + }
  2355 +};
  2356 +
  2357 +/// Extension of App to better manage groups of options
  2358 +class Option_group : public App {
  2359 + public:
  2360 + Option_group(std::string group_description, std::string group_name, App *parent)
  2361 + : App(std::move(group_description), "", parent) {
  2362 + group(group_name);
  2363 + }
  2364 + using App::add_option;
  2365 + /// add an existing option to the Option_group
  2366 + Option *add_option(Option *opt) {
  2367 + if(get_parent() == nullptr) {
  2368 + throw OptionNotFound("Unable to locate the specified option");
  2369 + }
  2370 + get_parent()->_move_option(opt, this);
  2371 + return opt;
  2372 + }
  2373 + /// add an existing option to the Option_group
  2374 + void add_options(Option *opt) { add_option(opt); }
  2375 + /// add a bunch of options to the group
  2376 + template <typename... Args> void add_options(Option *opt, Args... args) {
  2377 + add_option(opt);
  2378 + add_options(args...);
  2379 + }
2075 }; 2380 };
2076 2381
2077 namespace FailureMessage { 2382 namespace FailureMessage {
include/CLI/Error.hpp
@@ -212,6 +212,27 @@ class RequiredError : public ParseError { @@ -212,6 +212,27 @@ class RequiredError : public ParseError {
212 return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", 212 return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands",
213 ExitCodes::RequiredError); 213 ExitCodes::RequiredError);
214 } 214 }
  215 + static RequiredError Option(size_t min_option, size_t max_option, size_t used, const std::string &option_list) {
  216 + if((min_option == 1) && (max_option == 1) && (used == 0))
  217 + return RequiredError("Exactly 1 option from [" + option_list + "]");
  218 + else if((min_option == 1) && (max_option == 1) && (used > 1))
  219 + return RequiredError("Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) +
  220 + " were given",
  221 + ExitCodes::RequiredError);
  222 + else if((min_option == 1) && (used == 0))
  223 + return RequiredError("At least 1 option from [" + option_list + "]");
  224 + else if(used < min_option)
  225 + return RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " +
  226 + std::to_string(used) + "were given from [" + option_list + "]",
  227 + ExitCodes::RequiredError);
  228 + else if(max_option == 1)
  229 + return RequiredError("Requires at most 1 options be given from [" + option_list + "]",
  230 + ExitCodes::RequiredError);
  231 + else
  232 + return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " +
  233 + std::to_string(used) + "were given from [" + option_list + "]",
  234 + ExitCodes::RequiredError);
  235 + }
215 }; 236 };
216 237
217 /// Thrown when the wrong number of arguments has been received 238 /// Thrown when the wrong number of arguments has been received
include/CLI/Formatter.hpp
@@ -58,7 +58,27 @@ inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) co @@ -58,7 +58,27 @@ inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) co
58 58
59 inline std::string Formatter::make_description(const App *app) const { 59 inline std::string Formatter::make_description(const App *app) const {
60 std::string desc = app->get_description(); 60 std::string desc = app->get_description();
61 - 61 + auto min_options = app->get_require_option_min();
  62 + auto max_options = app->get_require_option_max();
  63 + if(app->get_required()) {
  64 + desc += " REQUIRED ";
  65 + }
  66 + if((max_options == min_options) && (min_options > 0)) {
  67 + if(min_options == 1) {
  68 + desc += " \n[Exactly 1 of the following options is required]";
  69 + } else {
  70 + desc += " \n[Exactly " + std::to_string(min_options) + "options from the following list are required]";
  71 + }
  72 + } else if(max_options > 0) {
  73 + if(min_options > 0) {
  74 + desc += " \n[Between " + std::to_string(min_options) + " and " + std::to_string(max_options) +
  75 + " of the follow options are required]";
  76 + } else {
  77 + desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]";
  78 + }
  79 + } else if(min_options > 0) {
  80 + desc += " \n[At least " + std::to_string(min_options) + " of the following options are required]";
  81 + }
62 return (!desc.empty()) ? desc + "\n" : std::string{}; 82 return (!desc.empty()) ? desc + "\n" : std::string{};
63 } 83 }
64 84
@@ -90,7 +110,9 @@ inline std::string Formatter::make_usage(const App *app, std::string name) const @@ -90,7 +110,9 @@ inline std::string Formatter::make_usage(const App *app, std::string name) const
90 } 110 }
91 111
92 // Add a marker if subcommands are expected or optional 112 // Add a marker if subcommands are expected or optional
93 - if(!app->get_subcommands({}).empty()) { 113 + if(!app->get_subcommands(
  114 + [](const CLI::App *subc) { return ((!subc->get_disabled()) && (!subc->get_name().empty())); })
  115 + .empty()) {
94 out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "") 116 out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "")
95 << get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND" 117 << get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND"
96 : "SUBCOMMANDS") 118 : "SUBCOMMANDS")
@@ -118,6 +140,11 @@ inline std::string Formatter::make_help(const App *app, std::string name, AppFor @@ -118,6 +140,11 @@ inline std::string Formatter::make_help(const App *app, std::string name, AppFor
118 return make_expanded(app); 140 return make_expanded(app);
119 141
120 std::stringstream out; 142 std::stringstream out;
  143 + if((app->get_name().empty()) && (app->get_parent() != nullptr)) {
  144 + if(app->get_group() != "Subcommands") {
  145 + out << app->get_group() << ':';
  146 + }
  147 + }
121 148
122 out << make_description(app); 149 out << make_description(app);
123 out << make_usage(app, name); 150 out << make_usage(app, name);
@@ -177,7 +204,7 @@ inline std::string Formatter::make_subcommand(const App *sub) const { @@ -177,7 +204,7 @@ inline std::string Formatter::make_subcommand(const App *sub) const {
177 204
178 inline std::string Formatter::make_expanded(const App *sub) const { 205 inline std::string Formatter::make_expanded(const App *sub) const {
179 std::stringstream out; 206 std::stringstream out;
180 - out << sub->get_name() << "\n"; 207 + out << sub->get_display_name() << "\n";
181 208
182 out << make_description(sub); 209 out << make_description(sub);
183 out << make_positionals(sub); 210 out << make_positionals(sub);
include/CLI/Option.hpp
@@ -721,13 +721,16 @@ class Option : public OptionBase&lt;Option&gt; { @@ -721,13 +721,16 @@ class Option : public OptionBase&lt;Option&gt; {
721 for(const std::string &lname : lnames_) 721 for(const std::string &lname : lnames_)
722 if(other.check_lname(lname)) 722 if(other.check_lname(lname))
723 return true; 723 return true;
724 - // We need to do the inverse, just in case we are ignore_case or ignore underscore  
725 - for(const std::string &sname : other.snames_)  
726 - if(check_sname(sname))  
727 - return true;  
728 - for(const std::string &lname : other.lnames_)  
729 - if(check_lname(lname))  
730 - return true; 724 +
  725 + if(ignore_case_ ||
  726 + ignore_underscore_) { // We need to do the inverse, in case we are ignore_case or ignore underscore
  727 + for(const std::string &sname : other.snames_)
  728 + if(check_sname(sname))
  729 + return true;
  730 + for(const std::string &lname : other.lnames_)
  731 + if(check_lname(lname))
  732 + return true;
  733 + }
731 return false; 734 return false;
732 } 735 }
733 736
@@ -814,8 +817,8 @@ class Option : public OptionBase&lt;Option&gt; { @@ -814,8 +817,8 @@ class Option : public OptionBase&lt;Option&gt; {
814 } 817 }
815 818
816 /// Puts a result at the end and get a count of the number of arguments actually added 819 /// Puts a result at the end and get a count of the number of arguments actually added
817 - Option *add_result(std::string s, int &count) {  
818 - count = _add_result(std::move(s)); 820 + Option *add_result(std::string s, int &results_added) {
  821 + results_added = _add_result(std::move(s));
819 callback_run_ = false; 822 callback_run_ = false;
820 return this; 823 return this;
821 } 824 }
@@ -940,24 +943,24 @@ class Option : public OptionBase&lt;Option&gt; { @@ -940,24 +943,24 @@ class Option : public OptionBase&lt;Option&gt; {
940 943
941 private: 944 private:
942 int _add_result(std::string &&result) { 945 int _add_result(std::string &&result) {
943 - int count = 0; 946 + int result_count = 0;
944 if(delimiter_ == '\0') { 947 if(delimiter_ == '\0') {
945 results_.push_back(std::move(result)); 948 results_.push_back(std::move(result));
946 - ++count; 949 + ++result_count;
947 } else { 950 } else {
948 if((result.find_first_of(delimiter_) != std::string::npos)) { 951 if((result.find_first_of(delimiter_) != std::string::npos)) {
949 for(const auto &var : CLI::detail::split(result, delimiter_)) { 952 for(const auto &var : CLI::detail::split(result, delimiter_)) {
950 if(!var.empty()) { 953 if(!var.empty()) {
951 results_.push_back(var); 954 results_.push_back(var);
952 - ++count; 955 + ++result_count;
953 } 956 }
954 } 957 }
955 } else { 958 } else {
956 results_.push_back(std::move(result)); 959 results_.push_back(std::move(result));
957 - ++count; 960 + ++result_count;
958 } 961 }
959 } 962 }
960 - return count; 963 + return result_count;
961 } 964 }
962 }; 965 };
963 966
tests/AppTest.cpp
@@ -2,6 +2,8 @@ @@ -2,6 +2,8 @@
2 #include <complex> 2 #include <complex>
3 #include <cstdlib> 3 #include <cstdlib>
4 4
  5 +#include "gmock/gmock.h"
  6 +
5 TEST_F(TApp, OneFlagShort) { 7 TEST_F(TApp, OneFlagShort) {
6 app.add_flag("-c,--count"); 8 app.add_flag("-c,--count");
7 args = {"-c"}; 9 args = {"-c"};
@@ -118,6 +120,23 @@ TEST_F(TApp, DashedOptionsSingleString) { @@ -118,6 +120,23 @@ TEST_F(TApp, DashedOptionsSingleString) {
118 EXPECT_EQ(2u, app.count("--that")); 120 EXPECT_EQ(2u, app.count("--that"));
119 } 121 }
120 122
  123 +TEST_F(TApp, RequireOptionsError) {
  124 + using ::testing::HasSubstr;
  125 + using ::testing::Not;
  126 + app.add_flag("-c");
  127 + app.add_flag("--q");
  128 + app.add_flag("--this,--that");
  129 + app.require_option(1, 2);
  130 + try {
  131 + app.parse("-c --q --this --that");
  132 + } catch(const CLI::RequiredError &re) {
  133 + EXPECT_THAT(re.what(), Not(HasSubstr("-h,--help")));
  134 + }
  135 +
  136 + EXPECT_NO_THROW(app.parse("-c --q"));
  137 + EXPECT_NO_THROW(app.parse("-c --this --that"));
  138 +}
  139 +
121 TEST_F(TApp, BoolFlagOverride) { 140 TEST_F(TApp, BoolFlagOverride) {
122 bool val; 141 bool val;
123 auto flg = app.add_flag("--this,--that", val); 142 auto flg = app.add_flag("--this,--that", val);
@@ -527,6 +546,7 @@ TEST_F(TApp, LotsOfFlags) { @@ -527,6 +546,7 @@ TEST_F(TApp, LotsOfFlags) {
527 EXPECT_EQ(2u, app.count("-a")); 546 EXPECT_EQ(2u, app.count("-a"));
528 EXPECT_EQ(1u, app.count("-b")); 547 EXPECT_EQ(1u, app.count("-b"));
529 EXPECT_EQ(1u, app.count("-A")); 548 EXPECT_EQ(1u, app.count("-A"));
  549 + EXPECT_EQ(app.count_all(), 4u);
530 } 550 }
531 551
532 TEST_F(TApp, NumberFlags) { 552 TEST_F(TApp, NumberFlags) {
@@ -657,6 +677,7 @@ TEST_F(TApp, ShortOpts) { @@ -657,6 +677,7 @@ TEST_F(TApp, ShortOpts) {
657 EXPECT_EQ(1u, app.count("-y")); 677 EXPECT_EQ(1u, app.count("-y"));
658 EXPECT_EQ((unsigned long long)2, funnyint); 678 EXPECT_EQ((unsigned long long)2, funnyint);
659 EXPECT_EQ("zyz", someopt); 679 EXPECT_EQ("zyz", someopt);
  680 + EXPECT_EQ(app.count_all(), 3u);
660 } 681 }
661 682
662 TEST_F(TApp, DefaultOpts) { 683 TEST_F(TApp, DefaultOpts) {
tests/CMakeLists.txt
@@ -36,6 +36,7 @@ set(CLI11_TESTS @@ -36,6 +36,7 @@ set(CLI11_TESTS
36 DeprecatedTest 36 DeprecatedTest
37 StringParseTest 37 StringParseTest
38 TrueFalseTest 38 TrueFalseTest
  39 + OptionGroupTest
39 ) 40 )
40 41
41 if(WIN32) 42 if(WIN32)
tests/FormatterTest.cpp
@@ -154,6 +154,17 @@ TEST(Formatter, AllSub) { @@ -154,6 +154,17 @@ TEST(Formatter, AllSub) {
154 EXPECT_THAT(help, HasSubstr("subcom")); 154 EXPECT_THAT(help, HasSubstr("subcom"));
155 } 155 }
156 156
  157 +TEST(Formatter, AllSubRequired) {
  158 + CLI::App app{"My prog"};
  159 + CLI::App *sub = app.add_subcommand("subcom", "This");
  160 + sub->add_flag("--insub", "MyFlag");
  161 + sub->required();
  162 + std::string help = app.help("", CLI::AppFormatMode::All);
  163 + EXPECT_THAT(help, HasSubstr("--insub"));
  164 + EXPECT_THAT(help, HasSubstr("subcom"));
  165 + EXPECT_THAT(help, HasSubstr("REQUIRED"));
  166 +}
  167 +
157 TEST(Formatter, NamelessSub) { 168 TEST(Formatter, NamelessSub) {
158 CLI::App app{"My prog"}; 169 CLI::App app{"My prog"};
159 CLI::App *sub = app.add_subcommand("", "This subcommand"); 170 CLI::App *sub = app.add_subcommand("", "This subcommand");
tests/OptionGroupTest.cpp 0 โ†’ 100644
  1 +#include "app_helper.hpp"
  2 +
  3 +#include "gmock/gmock.h"
  4 +#include "gtest/gtest.h"
  5 +
  6 +using ::testing::HasSubstr;
  7 +using ::testing::Not;
  8 +
  9 +using vs_t = std::vector<std::string>;
  10 +
  11 +TEST_F(TApp, BasicOptionGroup) {
  12 + auto ogroup = app.add_option_group("clusters");
  13 + int res;
  14 + ogroup->add_option("--test1", res);
  15 + ogroup->add_option("--test2", res);
  16 + ogroup->add_option("--test3", res);
  17 +
  18 + args = {"--test1", "5"};
  19 + run();
  20 + EXPECT_EQ(res, 5);
  21 + EXPECT_EQ(app.count_all(), 1u);
  22 +}
  23 +
  24 +TEST_F(TApp, BasicOptionGroupExact) {
  25 + auto ogroup = app.add_option_group("clusters");
  26 + int res;
  27 + ogroup->add_option("--test1", res);
  28 + ogroup->add_option("--test2", res);
  29 + ogroup->add_option("--test3", res);
  30 + int val2;
  31 + app.add_option("--option", val2);
  32 + ogroup->require_option(1);
  33 + args = {"--test1", "5"};
  34 + run();
  35 + EXPECT_EQ(res, 5);
  36 +
  37 + args = {"--test1", "5", "--test2", "4"};
  38 + EXPECT_THROW(run(), CLI::RequiredError);
  39 +
  40 + args = {"--option", "9"};
  41 + EXPECT_THROW(run(), CLI::RequiredError);
  42 +
  43 + std::string help = ogroup->help();
  44 + auto exactloc = help.find("[Exactly 1");
  45 + EXPECT_NE(exactloc, std::string::npos);
  46 +}
  47 +
  48 +TEST_F(TApp, BasicOptionGroupExactTooMany) {
  49 + auto ogroup = app.add_option_group("clusters");
  50 + int res;
  51 + ogroup->add_option("--test1", res);
  52 + ogroup->add_option("--test2", res);
  53 + ogroup->add_option("--test3", res);
  54 + int val2;
  55 + app.add_option("--option", val2);
  56 + ogroup->require_option(10);
  57 + args = {"--test1", "5"};
  58 + EXPECT_THROW(run(), CLI::InvalidError);
  59 +}
  60 +
  61 +TEST_F(TApp, BasicOptionGroupMinMax) {
  62 + auto ogroup = app.add_option_group("clusters");
  63 + int res;
  64 + ogroup->add_option("--test1", res);
  65 + ogroup->add_option("--test2", res);
  66 + ogroup->add_option("--test3", res);
  67 + int val2;
  68 + app.add_option("--option", val2);
  69 + ogroup->require_option(1, 1);
  70 + args = {"--test1", "5"};
  71 + run();
  72 + EXPECT_EQ(res, 5);
  73 +
  74 + args = {"--test1", "5", "--test2", "4"};
  75 + EXPECT_THROW(run(), CLI::RequiredError);
  76 +
  77 + args = {"--option", "9"};
  78 + EXPECT_THROW(run(), CLI::RequiredError);
  79 +
  80 + std::string help = ogroup->help();
  81 + auto exactloc = help.find("[Exactly 1");
  82 + EXPECT_NE(exactloc, std::string::npos);
  83 +}
  84 +
  85 +TEST_F(TApp, BasicOptionGroupMinMaxDifferent) {
  86 + auto ogroup = app.add_option_group("clusters");
  87 + int res;
  88 + ogroup->add_option("--test1", res);
  89 + ogroup->add_option("--test2", res);
  90 + ogroup->add_option("--test3", res);
  91 + int val2;
  92 + app.add_option("--option", val2);
  93 + ogroup->require_option(1, 2);
  94 + args = {"--test1", "5"};
  95 + run();
  96 + EXPECT_EQ(res, 5);
  97 +
  98 + args = {"--test1", "5", "--test2", "4"};
  99 + EXPECT_NO_THROW(run());
  100 + EXPECT_EQ(app.count_all(), 2);
  101 +
  102 + args = {"--option", "9"};
  103 + EXPECT_THROW(run(), CLI::RequiredError);
  104 +
  105 + args = {"--test1", "5", "--test2", "4", "--test3=5"};
  106 + EXPECT_THROW(run(), CLI::RequiredError);
  107 +
  108 + std::string help = ogroup->help();
  109 + auto exactloc = help.find("[Between 1 and 2");
  110 + EXPECT_NE(exactloc, std::string::npos);
  111 +}
  112 +
  113 +TEST_F(TApp, BasicOptionGroupMinMaxDifferentReversed) {
  114 + auto ogroup = app.add_option_group("clusters");
  115 + int res;
  116 + ogroup->add_option("--test1", res);
  117 + ogroup->add_option("--test2", res);
  118 + ogroup->add_option("--test3", res);
  119 + int val2;
  120 + app.add_option("--option", val2);
  121 + ogroup->require_option(2, 1);
  122 + EXPECT_EQ(ogroup->get_require_option_min(), 2);
  123 + EXPECT_EQ(ogroup->get_require_option_max(), 1);
  124 + args = {"--test1", "5"};
  125 + EXPECT_THROW(run(), CLI::InvalidError);
  126 + ogroup->require_option(1, 2);
  127 + EXPECT_NO_THROW(run());
  128 + EXPECT_EQ(res, 5);
  129 + EXPECT_EQ(ogroup->get_require_option_min(), 1);
  130 + EXPECT_EQ(ogroup->get_require_option_max(), 2);
  131 + args = {"--test1", "5", "--test2", "4"};
  132 + EXPECT_NO_THROW(run());
  133 +
  134 + args = {"--option", "9"};
  135 + EXPECT_THROW(run(), CLI::RequiredError);
  136 +
  137 + args = {"--test1", "5", "--test2", "4", "--test3=5"};
  138 + EXPECT_THROW(run(), CLI::RequiredError);
  139 +
  140 + std::string help = ogroup->help();
  141 + auto exactloc = help.find("[Between 1 and 2");
  142 + EXPECT_NE(exactloc, std::string::npos);
  143 +}
  144 +
  145 +TEST_F(TApp, BasicOptionGroupMax) {
  146 + auto ogroup = app.add_option_group("clusters");
  147 + int res;
  148 + ogroup->add_option("--test1", res);
  149 + ogroup->add_option("--test2", res);
  150 + ogroup->add_option("--test3", res);
  151 + int val2;
  152 + app.add_option("--option", val2);
  153 + ogroup->require_option(-2);
  154 + args = {"--test1", "5"};
  155 + run();
  156 + EXPECT_EQ(res, 5);
  157 +
  158 + args = {"--option", "9"};
  159 + EXPECT_NO_THROW(run());
  160 +
  161 + args = {"--test1", "5", "--test2", "4", "--test3=5"};
  162 + EXPECT_THROW(run(), CLI::RequiredError);
  163 +
  164 + std::string help = ogroup->help();
  165 + auto exactloc = help.find("[At most 2");
  166 + EXPECT_NE(exactloc, std::string::npos);
  167 +}
  168 +
  169 +TEST_F(TApp, BasicOptionGroupMax1) {
  170 + auto ogroup = app.add_option_group("clusters");
  171 + int res;
  172 + ogroup->add_option("--test1", res);
  173 + ogroup->add_option("--test2", res);
  174 + ogroup->add_option("--test3", res);
  175 + int val2;
  176 + app.add_option("--option", val2);
  177 + ogroup->require_option(-1);
  178 + args = {"--test1", "5"};
  179 + run();
  180 + EXPECT_EQ(res, 5);
  181 +
  182 + args = {"--option", "9"};
  183 + EXPECT_NO_THROW(run());
  184 +
  185 + args = {"--test1", "5", "--test2", "4"};
  186 + EXPECT_THROW(run(), CLI::RequiredError);
  187 +
  188 + std::string help = ogroup->help();
  189 + auto exactloc = help.find("[At most 1");
  190 + EXPECT_NE(exactloc, std::string::npos);
  191 +}
  192 +
  193 +TEST_F(TApp, BasicOptionGroupMin) {
  194 + auto ogroup = app.add_option_group("clusters");
  195 + int res;
  196 + ogroup->add_option("--test1", res);
  197 + ogroup->add_option("--test2", res);
  198 + ogroup->add_option("--test3", res);
  199 + int val2;
  200 + app.add_option("--option", val2);
  201 + ogroup->require_option();
  202 +
  203 + args = {"--option", "9"};
  204 + EXPECT_THROW(run(), CLI::RequiredError);
  205 +
  206 + args = {"--test1", "5", "--test2", "4", "--test3=5"};
  207 + EXPECT_NO_THROW(run());
  208 +
  209 + std::string help = ogroup->help();
  210 + auto exactloc = help.find("[At least 1");
  211 + EXPECT_NE(exactloc, std::string::npos);
  212 +}
  213 +
  214 +TEST_F(TApp, BasicOptionGroupExact2) {
  215 + auto ogroup = app.add_option_group("clusters");
  216 + int res;
  217 + ogroup->add_option("--test1", res);
  218 + ogroup->add_option("--test2", res);
  219 + ogroup->add_option("--test3", res);
  220 + int val2;
  221 + app.add_option("--option", val2);
  222 + ogroup->require_option(2);
  223 +
  224 + args = {"--option", "9"};
  225 + EXPECT_THROW(run(), CLI::RequiredError);
  226 +
  227 + args = {"--test1", "5", "--test2", "4", "--test3=5"};
  228 + EXPECT_THROW(run(), CLI::RequiredError);
  229 +
  230 + args = {"--test1", "5", "--test3=5"};
  231 + EXPECT_NO_THROW(run());
  232 +
  233 + std::string help = ogroup->help();
  234 + auto exactloc = help.find("[Exactly 2");
  235 + EXPECT_NE(exactloc, std::string::npos);
  236 +}
  237 +
  238 +TEST_F(TApp, BasicOptionGroupMin2) {
  239 + auto ogroup = app.add_option_group("clusters");
  240 + int res;
  241 + ogroup->add_option("--test1", res);
  242 + ogroup->add_option("--test2", res);
  243 + ogroup->add_option("--test3", res);
  244 + int val2;
  245 + app.add_option("--option", val2);
  246 + ogroup->require_option(2, 0);
  247 +
  248 + args = {"--option", "9"};
  249 + EXPECT_THROW(run(), CLI::RequiredError);
  250 +
  251 + args = {"--test1", "5", "--test2", "4", "--test3=5"};
  252 + EXPECT_NO_THROW(run());
  253 +
  254 + std::string help = ogroup->help();
  255 + auto exactloc = help.find("[At least 2");
  256 + EXPECT_NE(exactloc, std::string::npos);
  257 +}
  258 +
  259 +TEST_F(TApp, BasicOptionGroupMinMoved) {
  260 +
  261 + int res;
  262 + auto opt1 = app.add_option("--test1", res);
  263 + auto opt2 = app.add_option("--test2", res);
  264 + auto opt3 = app.add_option("--test3", res);
  265 + int val2;
  266 + app.add_option("--option", val2);
  267 +
  268 + auto ogroup = app.add_option_group("clusters");
  269 + ogroup->require_option();
  270 + ogroup->add_option(opt1);
  271 + ogroup->add_option(opt2);
  272 + ogroup->add_option(opt3);
  273 +
  274 + args = {"--option", "9"};
  275 + EXPECT_THROW(run(), CLI::RequiredError);
  276 +
  277 + args = {"--test1", "5", "--test2", "4", "--test3=5"};
  278 + EXPECT_NO_THROW(run());
  279 +
  280 + std::string help = app.help();
  281 + auto exactloc = help.find("[At least 1");
  282 + auto oloc = help.find("--test1");
  283 + EXPECT_NE(exactloc, std::string::npos);
  284 + EXPECT_NE(oloc, std::string::npos);
  285 + EXPECT_LT(exactloc, oloc);
  286 +}
  287 +
  288 +TEST_F(TApp, BasicOptionGroupMinMovedAsGroup) {
  289 +
  290 + int res;
  291 + auto opt1 = app.add_option("--test1", res);
  292 + auto opt2 = app.add_option("--test2", res);
  293 + auto opt3 = app.add_option("--test3", res);
  294 + int val2;
  295 + app.add_option("--option", val2);
  296 +
  297 + auto ogroup = app.add_option_group("clusters");
  298 + ogroup->require_option();
  299 + ogroup->add_options(opt1, opt2, opt3);
  300 +
  301 + EXPECT_THROW(ogroup->add_options(opt1), CLI::OptionNotFound);
  302 + args = {"--option", "9"};
  303 + EXPECT_THROW(run(), CLI::RequiredError);
  304 +
  305 + args = {"--test1", "5", "--test2", "4", "--test3=5"};
  306 + EXPECT_NO_THROW(run());
  307 +
  308 + std::string help = app.help();
  309 + auto exactloc = help.find("[At least 1");
  310 + auto oloc = help.find("--test1");
  311 + EXPECT_NE(exactloc, std::string::npos);
  312 + EXPECT_NE(oloc, std::string::npos);
  313 + EXPECT_LT(exactloc, oloc);
  314 +}
  315 +
  316 +TEST_F(TApp, BasicOptionGroupAddFailures) {
  317 +
  318 + int res;
  319 + auto opt1 = app.add_option("--test1", res);
  320 + app.set_config("--config");
  321 + int val2;
  322 + app.add_option("--option", val2);
  323 +
  324 + auto ogroup = app.add_option_group("clusters");
  325 + EXPECT_THROW(ogroup->add_options(app.get_config_ptr()), CLI::OptionAlreadyAdded);
  326 + EXPECT_THROW(ogroup->add_options(app.get_help_ptr()), CLI::OptionAlreadyAdded);
  327 +
  328 + auto sub = app.add_subcommand("sub", "subcommand");
  329 + auto opt2 = sub->add_option("--option2", val2);
  330 +
  331 + EXPECT_THROW(ogroup->add_option(opt2), CLI::OptionNotFound);
  332 +
  333 + EXPECT_THROW(ogroup->add_options(nullptr), CLI::OptionNotFound);
  334 +
  335 + ogroup->add_option(opt1);
  336 +
  337 + auto opt3 = app.add_option("--test1", res);
  338 +
  339 + EXPECT_THROW(ogroup->add_option(opt3), CLI::OptionAlreadyAdded);
  340 +}
  341 +
  342 +TEST_F(TApp, BasicOptionGroupScrewedUpMove) {
  343 +
  344 + int res;
  345 + auto opt1 = app.add_option("--test1", res);
  346 + auto opt2 = app.add_option("--test2", res);
  347 + int val2;
  348 + app.add_option("--option", val2);
  349 +
  350 + auto ogroup = app.add_option_group("clusters");
  351 + ogroup->require_option();
  352 + auto ogroup2 = ogroup->add_option_group("clusters2");
  353 + EXPECT_THROW(ogroup2->add_options(opt1, opt2), CLI::OptionNotFound);
  354 +
  355 + CLI::Option_group EmptyGroup("description", "new group", nullptr);
  356 +
  357 + EXPECT_THROW(EmptyGroup.add_option(opt2), CLI::OptionNotFound);
  358 + EXPECT_THROW(app._move_option(opt2, ogroup2), CLI::OptionNotFound);
  359 +}
  360 +
  361 +TEST_F(TApp, InvalidOptions) {
  362 + auto ogroup = app.add_option_group("clusters");
  363 + CLI::Option *opt = nullptr;
  364 + EXPECT_THROW(ogroup->excludes(opt), CLI::OptionNotFound);
  365 + CLI::App *app_p = nullptr;
  366 + EXPECT_THROW(ogroup->excludes(app_p), CLI::OptionNotFound);
  367 + EXPECT_THROW(ogroup->excludes(ogroup), CLI::OptionNotFound);
  368 + EXPECT_THROW(ogroup->add_option(opt), CLI::OptionNotFound);
  369 +}
  370 +
  371 +struct ManyGroups : public TApp {
  372 +
  373 + CLI::Option_group *main;
  374 + CLI::Option_group *g1;
  375 + CLI::Option_group *g2;
  376 + CLI::Option_group *g3;
  377 + std::string name1;
  378 + std::string name2;
  379 + std::string name3;
  380 + std::string val1;
  381 + std::string val2;
  382 + std::string val3;
  383 + ManyGroups() {
  384 + main = app.add_option_group("main", "the main outer group");
  385 + g1 = main->add_option_group("g1", "group1 description");
  386 + g2 = main->add_option_group("g2", "group2 description");
  387 + g3 = main->add_option_group("g3", "group3 description");
  388 + g1->add_option("--name1", name1)->required();
  389 + g1->add_option("--val1", val1);
  390 + g2->add_option("--name2", name2)->required();
  391 + g2->add_option("--val2", val2);
  392 + g3->add_option("--name3", name3)->required();
  393 + g3->add_option("--val3", val3);
  394 + }
  395 +
  396 + void remove_required() {
  397 + g1->get_option("--name1")->required(false);
  398 + g2->get_option("--name2")->required(false);
  399 + g3->get_option("--name3")->required(false);
  400 + g1->required(false);
  401 + g2->required(false);
  402 + g3->required(false);
  403 + }
  404 +};
  405 +
  406 +TEST_F(ManyGroups, SingleGroup) {
  407 + // only 1 group can be used
  408 + main->require_option(1);
  409 + args = {"--name1", "test"};
  410 + run();
  411 + EXPECT_EQ(name1, "test");
  412 +
  413 + args = {"--name2", "test", "--val2", "tval"};
  414 +
  415 + run();
  416 + EXPECT_EQ(val2, "tval");
  417 +
  418 + args = {"--name1", "test", "--val2", "tval"};
  419 +
  420 + EXPECT_THROW(run(), CLI::RequiredError);
  421 +}
  422 +
  423 +TEST_F(ManyGroups, SingleGroupError) {
  424 + // only 1 group can be used
  425 + main->require_option(1);
  426 + args = {"--name1", "test", "--name2", "test3"};
  427 + EXPECT_THROW(run(), CLI::RequiredError);
  428 +}
  429 +
  430 +TEST_F(ManyGroups, AtMostOneGroup) {
  431 + // only 1 group can be used
  432 + main->require_option(0, 1);
  433 + args = {"--name1", "test", "--name2", "test3"};
  434 + EXPECT_THROW(run(), CLI::RequiredError);
  435 +
  436 + args = {};
  437 + EXPECT_NO_THROW(run());
  438 +}
  439 +
  440 +TEST_F(ManyGroups, AtLeastTwoGroups) {
  441 + // only 1 group can be used
  442 + main->require_option(2, 0);
  443 + args = {"--name1", "test", "--name2", "test3"};
  444 + run();
  445 +
  446 + args = {"--name1", "test"};
  447 + EXPECT_THROW(run(), CLI::RequiredError);
  448 +}
  449 +
  450 +TEST_F(ManyGroups, BetweenOneAndTwoGroups) {
  451 + // only 1 group can be used
  452 + main->require_option(1, 2);
  453 + args = {"--name1", "test", "--name2", "test3"};
  454 + run();
  455 +
  456 + args = {"--name1", "test"};
  457 + run();
  458 +
  459 + args = {};
  460 + EXPECT_THROW(run(), CLI::RequiredError);
  461 +
  462 + args = {"--name1", "test", "--name2", "test3", "--name3=test3"};
  463 + EXPECT_THROW(run(), CLI::RequiredError);
  464 +}
  465 +
  466 +TEST_F(ManyGroups, RequiredFirst) {
  467 + // only 1 group can be used
  468 + remove_required();
  469 + g1->required();
  470 +
  471 + EXPECT_TRUE(g1->get_required());
  472 + EXPECT_FALSE(g2->get_required());
  473 + args = {"--name1", "test", "--name2", "test3"};
  474 + run();
  475 +
  476 + args = {"--name2", "test"};
  477 + try {
  478 + run();
  479 + } catch(const CLI::RequiredError &re) {
  480 + EXPECT_THAT(re.what(), HasSubstr("g1"));
  481 + }
  482 +
  483 + args = {"--name1", "test", "--name2", "test3", "--name3=test3"};
  484 + EXPECT_NO_THROW(run());
  485 +}
  486 +
  487 +TEST_F(ManyGroups, DisableFirst) {
  488 + // only 1 group can be used
  489 + remove_required();
  490 + g1->disabled();
  491 +
  492 + EXPECT_TRUE(g1->get_disabled());
  493 + EXPECT_FALSE(g2->get_disabled());
  494 + args = {"--name2", "test"};
  495 +
  496 + run();
  497 +
  498 + args = {"--name1", "test", "--name2", "test3"};
  499 + EXPECT_THROW(run(), CLI::ExtrasError);
  500 + g1->disabled(false);
  501 + args = {"--name1", "test", "--name2", "test3", "--name3=test3"};
  502 + EXPECT_NO_THROW(run());
  503 +}
tests/SubcommandTest.cpp
@@ -837,6 +837,7 @@ TEST_F(ManySubcommands, MaxCommands) { @@ -837,6 +837,7 @@ TEST_F(ManySubcommands, MaxCommands) {
837 args = {"sub1", "sub2", "sub3"}; 837 args = {"sub1", "sub2", "sub3"};
838 EXPECT_NO_THROW(run()); 838 EXPECT_NO_THROW(run());
839 EXPECT_EQ(sub2->remaining().size(), 1u); 839 EXPECT_EQ(sub2->remaining().size(), 1u);
  840 + EXPECT_EQ(app.count_all(), 2u);
840 841
841 // Currently, setting sub2 to throw causes an extras error 842 // Currently, setting sub2 to throw causes an extras error
842 // In the future, would passing on up to app's extras be better? 843 // In the future, would passing on up to app's extras be better?
@@ -853,6 +854,78 @@ TEST_F(ManySubcommands, MaxCommands) { @@ -853,6 +854,78 @@ TEST_F(ManySubcommands, MaxCommands) {
853 EXPECT_THROW(run(), CLI::ExtrasError); 854 EXPECT_THROW(run(), CLI::ExtrasError);
854 } 855 }
855 856
  857 +TEST_F(ManySubcommands, SubcommandExclusion) {
  858 +
  859 + sub1->excludes(sub3);
  860 + sub2->excludes(sub3);
  861 + args = {"sub1", "sub2"};
  862 + EXPECT_NO_THROW(run());
  863 +
  864 + args = {"sub1", "sub2", "sub3"};
  865 + EXPECT_THROW(run(), CLI::ExcludesError);
  866 +
  867 + args = {"sub1", "sub2", "sub4"};
  868 + EXPECT_NO_THROW(run());
  869 + EXPECT_EQ(app.count_all(), 3u);
  870 +
  871 + args = {"sub3", "sub4"};
  872 + EXPECT_NO_THROW(run());
  873 +}
  874 +
  875 +TEST_F(ManySubcommands, SubcommandOptionExclusion) {
  876 +
  877 + auto excluder_flag = app.add_flag("--exclude");
  878 + sub1->excludes(excluder_flag)->fallthrough();
  879 + sub2->excludes(excluder_flag)->fallthrough();
  880 + sub3->fallthrough();
  881 + sub4->fallthrough();
  882 + args = {"sub3", "sub4", "--exclude"};
  883 + EXPECT_NO_THROW(run());
  884 +
  885 + // the option comes later so doesn't exclude
  886 + args = {"sub1", "sub3", "--exclude"};
  887 + EXPECT_THROW(run(), CLI::ExcludesError);
  888 +
  889 + args = {"--exclude", "sub2", "sub4"};
  890 + EXPECT_THROW(run(), CLI::ExcludesError);
  891 +
  892 + args = {"sub1", "--exclude", "sub2", "sub4"};
  893 + try {
  894 + run();
  895 + } catch(const CLI::ExcludesError &ee) {
  896 + EXPECT_NE(std::string(ee.what()).find("sub1"), std::string::npos);
  897 + }
  898 +}
  899 +
  900 +TEST_F(ManySubcommands, SubcommandRequired) {
  901 +
  902 + sub1->required();
  903 + args = {"sub1", "sub2"};
  904 + EXPECT_NO_THROW(run());
  905 +
  906 + args = {"sub1", "sub2", "sub3"};
  907 + EXPECT_NO_THROW(run());
  908 +
  909 + args = {"sub3", "sub4"};
  910 + EXPECT_THROW(run(), CLI::RequiredError);
  911 +}
  912 +
  913 +TEST_F(ManySubcommands, SubcommandDisabled) {
  914 +
  915 + sub3->disabled();
  916 + args = {"sub1", "sub2"};
  917 + EXPECT_NO_THROW(run());
  918 +
  919 + args = {"sub1", "sub2", "sub3"};
  920 + app.allow_extras(false);
  921 + sub2->allow_extras(false);
  922 + EXPECT_THROW(run(), CLI::ExtrasError);
  923 + args = {"sub3", "sub4"};
  924 + EXPECT_THROW(run(), CLI::ExtrasError);
  925 + sub3->disabled(false);
  926 + args = {"sub3", "sub4"};
  927 + EXPECT_NO_THROW(run());
  928 +}
856 TEST_F(TApp, UnnamedSub) { 929 TEST_F(TApp, UnnamedSub) {
857 double val; 930 double val;
858 auto sub = app.add_subcommand("", "empty name"); 931 auto sub = app.add_subcommand("", "empty name");
@@ -886,6 +959,7 @@ TEST_F(TApp, UnnamedSubMix) { @@ -886,6 +959,7 @@ TEST_F(TApp, UnnamedSubMix) {
886 EXPECT_EQ(val, -3.0); 959 EXPECT_EQ(val, -3.0);
887 EXPECT_EQ(val2, 5.93); 960 EXPECT_EQ(val2, 5.93);
888 EXPECT_EQ(val3, 4.56); 961 EXPECT_EQ(val3, 4.56);
  962 + EXPECT_EQ(app.count_all(), 3u);
889 } 963 }
890 964
891 TEST_F(TApp, UnnamedSubMixExtras) { 965 TEST_F(TApp, UnnamedSubMixExtras) {
tests/app_helper.hpp
@@ -41,7 +41,7 @@ class TempFile { @@ -41,7 +41,7 @@ class TempFile {
41 }; 41 };
42 42
43 inline void put_env(std::string name, std::string value) { 43 inline void put_env(std::string name, std::string value) {
44 -#ifdef _MSC_VER 44 +#ifdef _WIN32
45 _putenv_s(name.c_str(), value.c_str()); 45 _putenv_s(name.c_str(), value.c_str());
46 #else 46 #else
47 setenv(name.c_str(), value.c_str(), 1); 47 setenv(name.c_str(), value.c_str(), 1);
@@ -49,7 +49,7 @@ inline void put_env(std::string name, std::string value) { @@ -49,7 +49,7 @@ inline void put_env(std::string name, std::string value) {
49 } 49 }
50 50
51 inline void unset_env(std::string name) { 51 inline void unset_env(std::string name) {
52 -#ifdef _MSC_VER 52 +#ifdef _WIN32
53 _putenv_s(name.c_str(), ""); 53 _putenv_s(name.c_str(), "");
54 #else 54 #else
55 unsetenv(name.c_str()); 55 unsetenv(name.c_str());