Skip to content

Commit

Permalink
add some unit tests for the fallthrough_parent command
Browse files Browse the repository at this point in the history
rework return values from _parse_* function to return true if the value was processed false otherwise, this simplified the logic and got rid of the pulling and clearing of the missing fields from option groups.

add TriggerOff and TriggerOn helper functions and some tests for them

add shapes example of multiple callbacks in order.

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

add tests for enabled/disabled by default

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

add enabled and disabled by default functions

add positional_arity example

Add a pre_parse_callback_ for apps.  The Pre parse callback takes an argument for the number of remaining arguments left to process, and will execute prior to parsing for subcommands, and after the first option parse for option_groups.
  • Loading branch information
phlptp committed Mar 5, 2019
1 parent be8a08f commit 2d68c4b
Show file tree
Hide file tree
Showing 9 changed files with 721 additions and 106 deletions.
52 changes: 47 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature
- [Subcommands](#subcommands)
- [Subcommand options](#subcommand-options)
- [Option groups](#option-groups) 🚧
- [Callbacks](#callbacks)
- [Configuration file](#configuration-file)
- [Inheriting defaults](#inheriting-defaults)
- [Formatting](#formatting)
Expand Down Expand Up @@ -235,7 +236,7 @@ Option_group *app.add_option_group(name,description); // 🚧
-app.add_mutable_set_ignore_case_underscore(... // 🆕 String only
```

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`.
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`.

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.

Expand Down Expand Up @@ -275,7 +276,7 @@ The add commands return a pointer to an internally stored `Option`. If you set t
Before parsing, you can set the following options:

- `->required()`: The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works.
- `->expected(N)`: Take `N` values instead of as many as possible, only for vector args. If negative, require at least `-N`; end with `--` or another recognized option.
- `->expected(N)`: Take `N` values instead of as many as possible, only for vector args. If negative, require at least `-N`; end with `--` or another recognized option or subcommand.
- `->type_name(typename)`: Set the name of an Option's type (`type_name_fn` allows a function instead)
- `->type_size(N)`: Set the intrinsic size of an option. The parser will require multiples of this number if negative.
- `->needs(opt)`: This option requires another option to also be present, opt is an `Option` pointer.
Expand Down Expand Up @@ -353,7 +354,7 @@ These Validators can be used by simply passing the name into the `check` or `tra
->check(CLI::Range(0,10));
```

Validators can be merged using `&` and `|` and inverted using `!`
Validators can be merged using `&` and `|` and inverted using `!`🚧
such as
```cpp
->check(CLI::Range(0,10)|CLI::Range(20,30));
Expand Down Expand Up @@ -492,7 +493,9 @@ There are several options that are supported on the main app and subcommands and
- `.count(option_name)`: Returns the number of times a particular option was called
- `.count_all()`: 🚧 Returns the total number of arguments a particular subcommand processed, on the master App it returns the total number of processed commands
- `.name(name)`: Add or change the name.
- `.callback(void() function)`: Set the callback that runs at the end of parsing. The options have already run at this point.
- `.callback(void() function)`: Set the callback that runs at the end of parsing. The options have already run at this point. See [Subcommand callbacks](#callbacks) for some additional details.
- `.immediate_callback()`: 🚧 Specify that the callback for a subcommand should run immediately on completion of a subcommand vs at the completion of all parsing if this option is not used.
- `.pre_parse_callback(void(size_t) function)`: 🚧 Set a callback that executes after the first argument of an application is processed. See [Subcommand callbacks](#callbacks) for some additional details.
- `.allow_extras()`: Do not throw an error if extra arguments are left over.
- `.positionals_at_end()`: 🚧 Specify that positional arguments occur as the last arguments and throw an error if an unexpected positional is encountered.
- `.prefix_command()`: Like `allow_extras`, but stop immediately on the first unrecognized item. It is ideal for allowing your app or subcommand to be a "prefix" to calling another app.
Expand All @@ -505,9 +508,48 @@ There are several options that are supported on the main app and subcommands and
> Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function.
#### Callbacks
A subcommand has two optional callbacks that are executed at different stages of processing. The `preparse_callback` 🚧 is executed once after the first argument of a subcommand or application is processed and gives an argument for the number of remaining arguments to process. For the main app the first argument is considered the program name, for subcommands the first argument is the subcommand name. For Option groups and nameless subcommands the first argument is after the first argument or subcommand is processed from that group.
The second callback is executed after parsing. Depending on the status of the `immediate_callback` flag 🚧. This is either immediately after the parsing of the subcommand. Or if the flag is false, once after parsing of all arguments. If the immediate_callback is set then the callback can be executed multiple times. If the main app or subcommand has a config file, no data from the config file will be reflected in immediate_callback.
For example say an application was set up like
```cpp
app.callback(ac);
sub1=app.add_subcommand("sub1")->callback(c1)->preparse_callback(pc1)->immediate_callback();
sub2=app.add_subcommand("sub2")->callback(c2)->preparse_callback(pc2);
app.preparse_callback( pa1);
... A bunch of other options
```
Then the command line is given as

```
program --opt1 opt1_val sub1 --sub1opt --sub1optb val sub2 --sub2opt sub1 --sub1opt2 sub2 --sub2opt2 val
```

* pa will be called prior to parsing any values with an argument of 14.
* pc1 will be called immediately after processing the sub1 command with a value of 10.
* c1 will be called when the `sub2` command is encountered
* pc2 will be called with value of 6 after the sub2 command is encountered.
* c1 will be called again after the second sub2 command is encountered
* ac will be called after completing the parse
* c2 will be called once after processing all arguments

A subcommand is considered terminated when one of the following conditions are met.
1. There are no more arguments to process
2. Another subcommand is encountered that would not fit in an optional slot of the subcommand
3. The positional_mark(`--`) is encountered and there are no available positional slots in the subcommand.
4. The subcommand_terminator mark(`++`) is encountered

If the `immediate_callback` flag is set then all contained options are processed and the callback is triggered. If a subcommand with an immediate_callback flag is called again, then the contained options are reset, and can be triggered again.



#### Option groups 🚧

The method
The subcommand method
```cpp
.add_option_group(name,description)
```
Expand Down
22 changes: 22 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,28 @@ add_test(NAME option_groups_extra2 COMMAND option_groups --csv --address "192.16
set_property(TEST option_groups_extra2 PROPERTY PASS_REGULAR_EXPRESSION
"at most 1")

add_cli_exe(positional_arity positional_arity.cpp)
add_test(NAME positional_arity1 COMMAND positional_arity one )
set_property(TEST positional_arity1 PROPERTY PASS_REGULAR_EXPRESSION
"File 1 = one")
add_test(NAME positional_arity2 COMMAND positional_arity one two )
set_property(TEST positional_arity2 PROPERTY PASS_REGULAR_EXPRESSION
"File 1 = one"
"File 2 = two")
add_test(NAME positional_arity3 COMMAND positional_arity 1 2 one)
set_property(TEST positional_arity3 PROPERTY PASS_REGULAR_EXPRESSION
"File 1 = one")
add_test(NAME positional_arity_fail COMMAND positional_arity 1 one two)
set_property(TEST positional_arity_fail PROPERTY PASS_REGULAR_EXPRESSION
"Could not convert")

add_cli_exe(shapes shapes.cpp)
add_test(NAME shapes_all COMMAND shapes circle 4.4 circle 10.7 rectangle 4 4 circle 2.3 triangle 4.5 ++ rectangle 2.1 ++ circle 234.675)
set_property(TEST shapes_all PROPERTY PASS_REGULAR_EXPRESSION
"circle2"
"circle4"
"rectangle2 with edges [2.1,2.1]"
"triangel1 with sides [4.5]")

add_cli_exe(ranges ranges.cpp)
add_test(NAME ranges_range COMMAND ranges --range 1 2 3)
Expand Down
38 changes: 38 additions & 0 deletions examples/positional_arity.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "CLI/CLI.hpp"

int main(int argc, char **argv) {

CLI::App app("test for positional arity");

auto numbers = app.add_option_group("numbers", "specify key numbers");
auto files = app.add_option_group("files", "specify files");
int num1 = -1, num2 = -1;
numbers->add_option("num1", num1, "first number");
numbers->add_option("num2", num2, "second number");
std::string file1, file2;
files->add_option("file1", file1, "first file")->required();
files->add_option("file2", file2, "second file");
// set a pre parse callback that turns the numbers group on or off depending on the number of arguments
app.preparse_callback([numbers](size_t arity) {
if(arity <= 2) {
numbers->disabled();
} else {
numbers->disabled(false);
}
});

CLI11_PARSE(app, argc, argv);

if(num1 != -1)
std::cout << "Num1 = " << num1 << '\n';

if(num2 != -1)
std::cout << "Num2 = " << num2 << '\n';

std::cout << "File 1 = " << file1 << '\n';
if(!file2.empty()) {
std::cout << "File 2 = " << file2 << '\n';
}

return 0;
}
48 changes: 48 additions & 0 deletions examples/shapes.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "CLI/CLI.hpp"

int main(int argc, char **argv) {

CLI::App app("load shapes");

app.set_help_all_flag("--help-all");
auto circle = app.add_subcommand("circle", "draw a circle")->immediate_callback();
double radius{0.0};
int circle_counter = 0;
circle->callback([&radius, &circle_counter] {
++circle_counter;
std::cout << "circle" << circle_counter << " with radius " << radius << std::endl;
});

circle->add_option("radius", radius, "the radius of the circle")->required();

auto rect = app.add_subcommand("rectangle", "draw a rectangle")->immediate_callback();
double edge1{0.0};
double edge2{0.0};
int rect_counter = 0;
rect->callback([&edge1, &edge2, &rect_counter] {
++rect_counter;
if(edge2 == 0) {
edge2 = edge1;
}
std::cout << "rectangle" << rect_counter << " with edges [" << edge1 << ',' << edge2 << "]" << std::endl;
edge2 = 0;
});

rect->add_option("edge1", edge1, "the first edge length of the rectangle")->required();
rect->add_option("edge2", edge2, "the second edge length of the rectangle");

auto tri = app.add_subcommand("triangle", "draw a rectangle")->immediate_callback();
std::vector<double> sides;
int tri_counter = 0;
tri->callback([&sides, &tri_counter] {
++tri_counter;

std::cout << "triangle" << tri_counter << " with sides [" << CLI::detail::join(sides) << "]" << std::endl;
});

tri->add_option("sides", sides, "the side lengths of the triangle");

CLI11_PARSE(app, argc, argv);

return 0;
}
Loading

0 comments on commit 2d68c4b

Please sign in to comment.