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 32 - [Adding options](#adding-options)
33 33 - [Option types](#option-types)
34 34 - [Option options](#option-options)
  35 + - [Getting Results](#getting-results) ๐Ÿšง
35 36 - [Subcommands](#subcommands)
36 37 - [Subcommand options](#subcommand-options)
  38 + - [Option Groups](#option-groups) ๐Ÿšง
37 39 - [Configuration file](#configuration-file)
38 40 - [Inheriting defaults](#inheriting-defaults)
39 41 - [Formatting](#formatting)
... ... @@ -45,6 +47,8 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature
45 47 - [Contribute](#contribute)
46 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 52 ## Background
49 53  
50 54 ### Introduction
... ... @@ -53,7 +57,7 @@ CLI11 provides all the features you expect in a powerful command line parser, wi
53 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 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 62 ### Why write another CLI parser?
59 63  
... ... @@ -61,7 +65,7 @@ An acceptable CLI parser library should be all of the following:
61 65  
62 66 - Easy to include (i.e., header only, one file if possible, **no external requirements**).
63 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 69 - Work on Linux, macOS, and Windows.
66 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 71 - Clear help printing.
... ... @@ -69,7 +73,7 @@ An acceptable CLI parser library should be all of the following:
69 73 - Standard shell idioms supported naturally, like grouping flags, a positional separator, etc.
70 74 - Easy to execute, with help, parse errors, etc. providing correct exit and details.
71 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 77 - Ability to add a configuration file (`ini` format), and produce it as well.
74 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 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 212 // Add subcommands
209 213 App* subcom = app.add_subcommand(name, description);
210 214  
  215 +Option_group *app.add_option_group(name,description); // ๐Ÿšง
  216 +
211 217 // ๐Ÿšง All add_*set* methods deprecated in CLI11 1.8 - use ->transform(CLI::IsMember) instead
212 218 -app.add_set(option_name,
213 219 - variable_to_bind_to, // Same type as stored by set
... ... @@ -223,7 +229,7 @@ App* subcom = app.add_subcommand(name, description);
223 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 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 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 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 340 If `--` is present in the command line that does not end an unlimited option, then
335 341 everything after that is positional only.
336 342  
337   -#### Getting results ๐Ÿšง
  343 +#### Getting results {#getting-results} ๐Ÿšง
  344 +
338 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 351 ### Subcommands
345 352  
... ... @@ -362,19 +369,25 @@ Nameless subcommands function a similarly to groups in the main `App`. If an op
362 369  
363 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 374 - `.ignore_case()`: Ignore the case of this subcommand. Inherited by added subcommands, so is usually used on the main `App`.
368 375 - `.ignore_underscore()`: ๐Ÿ†• Ignore any underscores in the subcommand name. Inherited by added subcommands, so is usually used on the main `App`.
369 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 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 383 - `.require_subcommand()`: Require 1 or more subcommands.
372 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 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 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 391 - `.get_parent()`: Get the parent App or nullptr if called on master App.
379 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 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 397 - `.description(str)`: ๐Ÿ†• Set/change the description.
385 398 - `.get_description()`: Access the description.
386 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 403 - `.name(name)`: Add or change the name.
388 404 - `.callback(void() function)`: Set the callback that runs at the end of parsing. The options have already run at this point.
389 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 414  
399 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 438 ### Configuration file
402 439  
403 440 ```cpp
... ...
examples/CMakeLists.txt
... ... @@ -69,6 +69,7 @@ set_property(TEST subcom_partitioned_none PROPERTY PASS_REGULAR_EXPRESSION
69 69 "This is a timer:"
70 70 "--file is required"
71 71 "Run with --help for more information.")
  72 +
72 73 add_test(NAME subcom_partitioned_all COMMAND subcom_partitioned --file this --count --count -d 1.2)
73 74 set_property(TEST subcom_partitioned_all PROPERTY PASS_REGULAR_EXPRESSION
74 75 "This is a timer:"
... ... @@ -81,6 +82,30 @@ set_property(TEST subcom_partitioned_help PROPERTY PASS_REGULAR_EXPRESSION
81 82 "-f,--file TEXT REQUIRED"
82 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 109 add_cli_exe(validators validators.cpp)
85 110 add_test(NAME validators_help COMMAND validators --help)
86 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 51  
52 52 using App_p = std::shared_ptr<App>;
53 53  
  54 +class Option_group;
54 55 /// Creates a command line program, with very few defaults.
55 56 /** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated
56 57 * add_option methods make it easy to prepare options. Remember to call `.start` before starting your
... ... @@ -80,9 +81,15 @@ class App {
80 81 /// If true, return immediately on an unrecognized option (implies allow_extras) INHERITABLE
81 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 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 93 /// This is a function that runs when complete. Great for subcommands. Can throw.
87 94 std::function<void()> callback_;
88 95  
... ... @@ -132,6 +139,13 @@ class App {
132 139 /// This is a list of the subcommands collected, in order
133 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 150 /// @name Subcommands
137 151 ///@{
... ... @@ -171,6 +185,12 @@ class App {
171 185 /// Max number of subcommands allowed (parsing stops after this number). 0 is unlimited INHERITABLE
172 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 194 /// The group membership INHERITABLE
175 195 std::string group_{"Subcommands"};
176 196  
... ... @@ -261,6 +281,18 @@ class App {
261 281 }
262 282  
263 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 296 /// Will also call App::allow_extras().
265 297 App *allow_config_extras(bool allow = true) {
266 298 allow_extras(allow);
... ... @@ -926,7 +958,7 @@ class App {
926 958 Option *set_config(std::string option_name = "",
927 959 std::string default_filename = "",
928 960 std::string help_message = "Read an ini file",
929   - bool required = false) {
  961 + bool config_required = false) {
930 962  
931 963 // Remove existing config if present
932 964 if(config_ptr_ != nullptr)
... ... @@ -935,7 +967,7 @@ class App {
935 967 // Only add config if option passed
936 968 if(!option_name.empty()) {
937 969 config_name_ = default_filename;
938   - config_required_ = required;
  970 + config_required_ = config_required;
939 971 config_ptr_ = add_option(option_name, config_name_, help_message, !default_filename.empty());
940 972 config_ptr_->configurable(false);
941 973 }
... ... @@ -965,6 +997,17 @@ class App {
965 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 1012 /// @name Subcommmands
970 1013 ///@{
... ... @@ -988,6 +1031,7 @@ class App {
988 1031 subcommands_.push_back(std::move(subcom));
989 1032 return subcommands_.back().get();
990 1033 }
  1034 +
991 1035 /// Check to see if a subcommand is part of this command (doesn't have to be in command line)
992 1036 /// returns the first subcommand if passed a nullptr
993 1037 App *get_subcommand(App *subcom) const {
... ... @@ -1043,6 +1087,22 @@ class App {
1043 1087 /// otherwise modified in a callback
1044 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 1106 /// Changes the group membership
1047 1107 App *group(std::string group_name) {
1048 1108 group_ = group_name;
... ... @@ -1078,6 +1138,35 @@ class App {
1078 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 1170 /// Stop subcommand fallthrough, so that parent commands cannot collect commands after subcommand.
1082 1171 /// Default from parent, usually set on parent.
1083 1172 App *fallthrough(bool value = true) {
... ... @@ -1219,7 +1308,7 @@ class App {
1219 1308 /// Counts the number of times the given option was passed.
1220 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 1312 /// line order; use parsed = false to get the original definition list.)
1224 1313 std::vector<App *> get_subcommands() const { return parsed_subcommands_; }
1225 1314  
... ... @@ -1267,6 +1356,50 @@ class App {
1267 1356 /// Check with name instead of pointer to see if subcommand was selected
1268 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 1404 /// @name Help
1272 1405 ///@{
... ... @@ -1424,12 +1557,24 @@ class App {
1424 1557 /// Get the required max subcommand value
1425 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 1566 /// Get the prefix command status
1428 1567 bool get_prefix_command() const { return prefix_command_; }
1429 1568  
1430 1569 /// Get the status of allow extras
1431 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 1578 /// Get the status of allow extras
1434 1579 bool get_allow_config_extras() const { return allow_config_extras_; }
1435 1580  
... ... @@ -1457,6 +1602,9 @@ class App {
1457 1602 /// Get the name of the current app
1458 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 1608 /// Check the name, case insensitive and underscore insensitive if set
1461 1609 bool check_name(std::string name_to_check) const {
1462 1610 std::string local_name = name_;
... ... @@ -1525,15 +1673,33 @@ class App {
1525 1673 protected:
1526 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 1678 void _validate() const {
1530 1679 auto pcount = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) {
1531 1680 return opt->get_items_expected() < 0 && opt->get_positional();
1532 1681 });
1533 1682 if(pcount > 1)
1534 1683 throw InvalidError(name_);
  1684 +
  1685 + size_t nameless_subs{0};
1535 1686 for(const App_p &app : subcommands_) {
1536 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 1738 }
1573 1739  
1574 1740 for(const App_p &com : subcommands_)
1575   - if(com->check_name(current) && !*com)
  1741 + if(!com->disabled_ && com->check_name(current) && !*com)
1576 1742 return true;
1577 1743  
1578 1744 // Check parent if exists, else return false
... ... @@ -1690,8 +1856,34 @@ class App {
1690 1856  
1691 1857 /// Verify required options and cross requirements. Subcommands too (only if selected).
1692 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 1882 for(const Option_p &opt : options_) {
1694 1883  
  1884 + if(opt->count() != 0) {
  1885 + ++used_options;
  1886 + }
1695 1887 // Required or partially filled
1696 1888 if(opt->get_required() || opt->count() != 0) {
1697 1889 // Make sure enough -N arguments parsed (+N is already handled in parsing function)
... ... @@ -1711,16 +1903,60 @@ class App {
1711 1903 if(opt->count() > 0 && opt_ex->count() != 0)
1712 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 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 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 2095 }
1860 2096  
1861 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 2099 size_t retval = 0;
1864 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 2102 static_cast<int>(opt->count()) < opt->get_items_expected())
1867 2103 retval = static_cast<size_t>(opt->get_items_expected()) - opt->count();
1868 2104  
... ... @@ -1886,7 +2122,7 @@ class App {
1886 2122 }
1887 2123  
1888 2124 for(auto &subc : subcommands_) {
1889   - if(subc->name_.empty()) {
  2125 + if((subc->name_.empty()) && (!subc->disabled_)) {
1890 2126 subc->_parse_positional(args);
1891 2127 if(subc->missing_.empty()) { // check if it was used and is not in the missing category
1892 2128 return;
... ... @@ -1922,6 +2158,8 @@ class App {
1922 2158 if(_count_remaining_positionals(/* required */ true) > 0)
1923 2159 return _parse_positional(args);
1924 2160 for(const App_p &com : subcommands_) {
  2161 + if(com->disabled_)
  2162 + continue;
1925 2163 if(com->check_name(args.back())) {
1926 2164 args.pop_back();
1927 2165 if(std::find(std::begin(parsed_subcommands_), std::end(parsed_subcommands_), com.get()) ==
... ... @@ -1976,11 +2214,12 @@ class App {
1976 2214 // Option not found
1977 2215 if(op_ptr == std::end(options_)) {
1978 2216 for(auto &subc : subcommands_) {
1979   - if(subc->name_.empty()) {
  2217 + if((subc->name_.empty()) && (!(subc->disabled_))) {
1980 2218 subc->_parse_arg(args, current_type);
1981 2219 if(subc->missing_.empty()) { // check if it was used and is not in the missing category
1982 2220 return;
1983 2221 } else {
  2222 + // for unnamed subs they shouldn't trigger a missing argument
1984 2223 args.push_back(std::move(subc->missing_.front().second));
1985 2224 subc->missing_.clear();
1986 2225 }
... ... @@ -2006,8 +2245,8 @@ class App {
2006 2245  
2007 2246 // Make sure we always eat the minimum for unlimited vectors
2008 2247 int collected = 0;
  2248 + int result_count = 0;
2009 2249 // deal with flag like things
2010   - int count = 0;
2011 2250 if(num == 0) {
2012 2251 auto res = op->get_flag_value(arg_name, value);
2013 2252 op->add_result(res);
... ... @@ -2015,22 +2254,22 @@ class App {
2015 2254 }
2016 2255 // --this=value
2017 2256 else if(!value.empty()) {
2018   - op->add_result(value, count);
  2257 + op->add_result(value, result_count);
2019 2258 parse_order_.push_back(op.get());
2020   - collected += count;
  2259 + collected += result_count;
2021 2260 // If exact number expected
2022 2261 if(num > 0)
2023   - num = (num >= count) ? num - count : 0;
  2262 + num = (num >= result_count) ? num - result_count : 0;
2024 2263  
2025 2264 // -Trest
2026 2265 } else if(!rest.empty()) {
2027   - op->add_result(rest, count);
  2266 + op->add_result(rest, result_count);
2028 2267 parse_order_.push_back(op.get());
2029 2268 rest = "";
2030   - collected += count;
  2269 + collected += result_count;
2031 2270 // If exact number expected
2032 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 2275 // Unlimited vector parser
... ... @@ -2043,10 +2282,10 @@ class App {
2043 2282 if(_count_remaining_positionals() > 0)
2044 2283 break;
2045 2284 }
2046   - op->add_result(args.back(), count);
  2285 + op->add_result(args.back(), result_count);
2047 2286 parse_order_.push_back(op.get());
2048 2287 args.pop_back();
2049   - collected += count;
  2288 + collected += result_count;
2050 2289 }
2051 2290  
2052 2291 // Allow -- to end an unlimited list and "eat" it
... ... @@ -2057,9 +2296,9 @@ class App {
2057 2296 while(num > 0 && !args.empty()) {
2058 2297 std::string current_ = args.back();
2059 2298 args.pop_back();
2060   - op->add_result(current_, count);
  2299 + op->add_result(current_, result_count);
2061 2300 parse_order_.push_back(op.get());
2062   - num -= count;
  2301 + num -= result_count;
2063 2302 }
2064 2303  
2065 2304 if(num > 0) {
... ... @@ -2072,6 +2311,72 @@ class App {
2072 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 2382 namespace FailureMessage {
... ...
include/CLI/Error.hpp
... ... @@ -212,6 +212,27 @@ class RequiredError : public ParseError {
212 212 return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands",
213 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 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 58  
59 59 inline std::string Formatter::make_description(const App *app) const {
60 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 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 110 }
91 111  
92 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 116 out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "")
95 117 << get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND"
96 118 : "SUBCOMMANDS")
... ... @@ -118,6 +140,11 @@ inline std::string Formatter::make_help(const App *app, std::string name, AppFor
118 140 return make_expanded(app);
119 141  
120 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 149 out << make_description(app);
123 150 out << make_usage(app, name);
... ... @@ -177,7 +204,7 @@ inline std::string Formatter::make_subcommand(const App *sub) const {
177 204  
178 205 inline std::string Formatter::make_expanded(const App *sub) const {
179 206 std::stringstream out;
180   - out << sub->get_name() << "\n";
  207 + out << sub->get_display_name() << "\n";
181 208  
182 209 out << make_description(sub);
183 210 out << make_positionals(sub);
... ...
include/CLI/Option.hpp
... ... @@ -721,13 +721,16 @@ class Option : public OptionBase&lt;Option&gt; {
721 721 for(const std::string &lname : lnames_)
722 722 if(other.check_lname(lname))
723 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 734 return false;
732 735 }
733 736  
... ... @@ -814,8 +817,8 @@ class Option : public OptionBase&lt;Option&gt; {
814 817 }
815 818  
816 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 822 callback_run_ = false;
820 823 return this;
821 824 }
... ... @@ -940,24 +943,24 @@ class Option : public OptionBase&lt;Option&gt; {
940 943  
941 944 private:
942 945 int _add_result(std::string &&result) {
943   - int count = 0;
  946 + int result_count = 0;
944 947 if(delimiter_ == '\0') {
945 948 results_.push_back(std::move(result));
946   - ++count;
  949 + ++result_count;
947 950 } else {
948 951 if((result.find_first_of(delimiter_) != std::string::npos)) {
949 952 for(const auto &var : CLI::detail::split(result, delimiter_)) {
950 953 if(!var.empty()) {
951 954 results_.push_back(var);
952   - ++count;
  955 + ++result_count;
953 956 }
954 957 }
955 958 } else {
956 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 2 #include <complex>
3 3 #include <cstdlib>
4 4  
  5 +#include "gmock/gmock.h"
  6 +
5 7 TEST_F(TApp, OneFlagShort) {
6 8 app.add_flag("-c,--count");
7 9 args = {"-c"};
... ... @@ -118,6 +120,23 @@ TEST_F(TApp, DashedOptionsSingleString) {
118 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 140 TEST_F(TApp, BoolFlagOverride) {
122 141 bool val;
123 142 auto flg = app.add_flag("--this,--that", val);
... ... @@ -527,6 +546,7 @@ TEST_F(TApp, LotsOfFlags) {
527 546 EXPECT_EQ(2u, app.count("-a"));
528 547 EXPECT_EQ(1u, app.count("-b"));
529 548 EXPECT_EQ(1u, app.count("-A"));
  549 + EXPECT_EQ(app.count_all(), 4u);
530 550 }
531 551  
532 552 TEST_F(TApp, NumberFlags) {
... ... @@ -657,6 +677,7 @@ TEST_F(TApp, ShortOpts) {
657 677 EXPECT_EQ(1u, app.count("-y"));
658 678 EXPECT_EQ((unsigned long long)2, funnyint);
659 679 EXPECT_EQ("zyz", someopt);
  680 + EXPECT_EQ(app.count_all(), 3u);
660 681 }
661 682  
662 683 TEST_F(TApp, DefaultOpts) {
... ...
tests/CMakeLists.txt
... ... @@ -36,6 +36,7 @@ set(CLI11_TESTS
36 36 DeprecatedTest
37 37 StringParseTest
38 38 TrueFalseTest
  39 + OptionGroupTest
39 40 )
40 41  
41 42 if(WIN32)
... ...
tests/FormatterTest.cpp
... ... @@ -154,6 +154,17 @@ TEST(Formatter, AllSub) {
154 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 168 TEST(Formatter, NamelessSub) {
158 169 CLI::App app{"My prog"};
159 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 837 args = {"sub1", "sub2", "sub3"};
838 838 EXPECT_NO_THROW(run());
839 839 EXPECT_EQ(sub2->remaining().size(), 1u);
  840 + EXPECT_EQ(app.count_all(), 2u);
840 841  
841 842 // Currently, setting sub2 to throw causes an extras error
842 843 // In the future, would passing on up to app's extras be better?
... ... @@ -853,6 +854,78 @@ TEST_F(ManySubcommands, MaxCommands) {
853 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 929 TEST_F(TApp, UnnamedSub) {
857 930 double val;
858 931 auto sub = app.add_subcommand("", "empty name");
... ... @@ -886,6 +959,7 @@ TEST_F(TApp, UnnamedSubMix) {
886 959 EXPECT_EQ(val, -3.0);
887 960 EXPECT_EQ(val2, 5.93);
888 961 EXPECT_EQ(val3, 4.56);
  962 + EXPECT_EQ(app.count_all(), 3u);
889 963 }
890 964  
891 965 TEST_F(TApp, UnnamedSubMixExtras) {
... ...
tests/app_helper.hpp
... ... @@ -41,7 +41,7 @@ class TempFile {
41 41 };
42 42  
43 43 inline void put_env(std::string name, std::string value) {
44   -#ifdef _MSC_VER
  44 +#ifdef _WIN32
45 45 _putenv_s(name.c_str(), value.c_str());
46 46 #else
47 47 setenv(name.c_str(), value.c_str(), 1);
... ... @@ -49,7 +49,7 @@ inline void put_env(std::string name, std::string value) {
49 49 }
50 50  
51 51 inline void unset_env(std::string name) {
52   -#ifdef _MSC_VER
  52 +#ifdef _WIN32
53 53 _putenv_s(name.c_str(), "");
54 54 #else
55 55 unsetenv(name.c_str());
... ...