Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI::IsMember #222

Merged
merged 16 commits into from
Feb 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ matrix:
- .ci/make_and_test.sh 17

# Check style/tidy
- compiler: clang
- compiler: clang
env:
- CHECK_STYLE=yes
script:
Expand Down Expand Up @@ -104,6 +104,20 @@ matrix:
conan upload "*" -c -r origin --all
fi

# GCC 4.8
- compiler: gcc
env:
- GCC_VER=4.8
addons:
apt:
packages:
- g++-4.8
install:
- export CC=gcc-4.8
- export CXX=g++-4.8
script:
- .ci/make_and_test.sh 11

# macOS and clang
- os: osx
compiler: clang
Expand Down
16 changes: 14 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
## Version 1.8: Flags and Sets
## Version 1.8: Sets and Flags

This version adds inverted flags, which can cancel or reduce the count of flags, and can also support basic number assignment. A new `add_option_fn` lets you more easily program CLI11 options with the types you choose. Vector options now support a custom separator. Apps can now be composed with unnamed subcommand support.
Set handling has been completely replaced by a new backend that works as a Validator. This provides a single interface instead of the 16 different functions in App. It also allows ordered collections to be used, custom functions for filtering, and better help and error messages. Also new are inverted flags, which can cancel or reduce the count of flags, and can also support basic number assignment. A new `add_option_fn` lets you more easily program CLI11 options with the types you choose. Vector options now support a custom separator. Apps can now be composed with unnamed subcommand support.

* New `CL::IsMember` validator replaces set validation [#222]
* Much more powerful flags with different values [#211]
* `add_option` now supports bool due to unified bool handling [#211]
* Support for composable unnamed subcommands [#216]
* Custom vector separator [#209], [#221]
* Validators added for IP4 addresses and positive numbers [#210]

> ### Converting from CLI11 1.7:
>
> * `app.add_set("--name", value, {"choice1", "choice2"})` should become `app.add_option("--name", value)->check(CLI::IsMember({"choice1", "choice2"}))`
> * The `_mutable` versions of this can be replaced by passing a pointer or shared pointer into `IsMember`
> * The `_ignore_case` version of this can be replaced by adding `CLI::ignore_case` to the argument list in `IsMember`
> * The `_ignore_underscore` version of this can be replaced by adding `CLI::ignore_underscore` to the argument list in `IsMember`
> * The `_ignore_case_underscore` version of this can be replaced by adding both functions listed above to the argument list in `IsMember`
> * An error with sets now produces a `ValidationError` instead of a `ConversionError`

[#209]: https://github.com/CLIUtils/CLI11/pull/209
[#210]: https://github.com/CLIUtils/CLI11/pull/210
[#211]: https://github.com/CLIUtils/CLI11/pull/211
[#216]: https://github.com/CLIUtils/CLI11/pull/216
[#221]: https://github.com/CLIUtils/CLI11/pull/221
[#222]: https://github.com/CLIUtils/CLI11/pull/222


## Version 1.7.1: Quick patch
Expand Down
47 changes: 24 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,15 @@ GTEST_COLOR=1 CTEST_OUTPUT_ON_FAILURE=1 make test
To set up, add options, and run, your main function will look something like this:

```cpp
CLI::App app{"App description"};
int main(int charc, char** argv) {
CLI::App app{"App description"};

std::string filename = "default";
app.add_option("-f,--file", filename, "A help string");
std::string filename = "default";
app.add_option("-f,--file", filename, "A help string");

CLI11_PARSE(app, argc, argv);
CLI11_PARSE(app, argc, argv);
return 0;
}
```

<details><summary>Note: If you don't like macros, this is what that macro expands to: (click to expand)</summary><p>
Expand All @@ -175,7 +178,7 @@ While all options internally are the same type, there are several ways to add an

```cpp
app.add_option(option_name,
variable_to_bind_to, // int, float, vector, or string-like
variable_to_bind_to, // bool, int, float, vector, or string-like
help_string="",
default=false)

Expand All @@ -196,24 +199,10 @@ app.add_flag_function(option_name,
function <void(int count)>,
help_string="")

app.add_set(option_name,
variable_to_bind_to, // Same type as stored by set
set_of_possible_options, // Set will be copied, ignores changes
help_string="",
default=false)
app.add_mutable_set(... // Set can change later, keeps reference

app.add_set_ignore_case(... // String only
app.add_mutable_set_ignore_case(... // String only
app.add_set_ignore_underscore(... // String only
app.add_mutable_set_ignore_underscore(... // String only
app.add_set_ignore_case_underscore(... // String only
app.add_mutable_set_ignore_case_underscore(... // String only

App* subcom = app.add_subcommand(name, description);
```

An option name must start with a alphabetic character or underscore. For long options, anything but an equals sign or a comma is valid after that. 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` or `add_set`. The set options allow your users to pick from a set of predefined options, and you can use an initializer list directly if you like. If you need to modify the set later, use the `mutable` forms.
An option name must start with a alphabetic character or underscore. For long options, anything but an equals sign or a comma is valid after that. 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 All @@ -229,7 +218,7 @@ app.add_flag_function(option_name,
help_string="")
```

allow a syntax for the option names to default particular options to a false value if some flags are passed. For example:
which allow a syntax for the option names to default particular options to a false value if some flags are passed. For example:

```cpp
app.add_flag("--flag,!--no-flag,result,"help for flag");`
Expand Down Expand Up @@ -271,6 +260,7 @@ Before parsing, you can set the following options:
- `->ignore_underscore()`: Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with "optionone". This does not apply to short form options since they only have one character
- `->description(str)`: Set/change the description.
- `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy).
- `->check(CLI::IsMember(...))`: Require an option be a member of a given set. See below for options.
- `->check(CLI::ExistingFile)`: Requires that the file exists if given.
- `->check(CLI::ExistingDirectory)`: Requires that the directory exists.
- `->check(CLI::ExistingPath)`: Requires that the path (file or directory) exists.
Expand All @@ -282,8 +272,19 @@ Before parsing, you can set the following options:
- `->each(void(std::string)>`: Run this function on each value received, as it is received.
- `->configurable(false)`: Disable this option from being in a configuration file.


These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. Check takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results. Validate can also be a subclass of `CLI::Validator`, in which case it can also set the type name and can be combined with `&` and `|` (all built-in validators are this sort).

The `IsMember` validator lets you specify a set of predefined options. You can pass any container or copyable pointer (including `std::shared_ptr`) to a container to this validator; the container just needs to be iterable and have a `::value_type`. The type should be convertible from a string. You can use an initializer list directly if you like. If you need to modify the set later, the pointer form lets you do that; the type message and check will correctly refer to the current version of the set.
After specifying a set of options, you can also specify "filter" functions of the form `T(T)`, where `T` is the type of the values. The most common choices probably will be `CLI::ignore_case` an `CLI::ignore_underscore`.
Here are some examples
of `IsMember`:

- `CLI::IsMember({"choice1", "choice2"})`: Select from exact match to choices.
- `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`: Match things like `Choice_1`, too.
- `CLI::IsMember(std::set<int>({2,3,4}))`: Most containers and types work; you just need `std::begin`, `std::end`, and `::value_type`.
- `auto p = std::make_shared<std::vector<std::string>>(std::initializer_list<std::string>("one", "two")); CLI::IsMember(p)`: You can modify `p` later.

On the command line, options can be given as:

- `-a` (flag)
Expand All @@ -296,7 +297,7 @@ On the command line, options can be given as:
- `--file filename` (space)
- `--file=filename` (equals)

If allow_windows_style_options() is specified in the application or subcommand options can also be given as:
If `allow_windows_style_options()` is specified in the application or subcommand options can also be given as:
- `/a` (flag)
- `/f filename` (option)
- `/long` (long flag)
Expand Down Expand Up @@ -359,7 +360,7 @@ There are several options that are supported on the main app and subcommands. Th
- `.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.
- `.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.
- `.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.
- `.footer(message)`: Set text to appear at the bottom of the help string.
- `.set_help_flag(name, message)`: Set the help flag name and message, returns a pointer to the created option.
Expand Down
2 changes: 1 addition & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ add_cli_exe(enum enum.cpp)
add_test(NAME enum_pass COMMAND enum -l 1)
add_test(NAME enum_fail COMMAND enum -l 4)
set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION
"Could not convert: --level = 4")
"--level: 4 not in {0,1,2}")

add_cli_exe(modhelp modhelp.cpp)
add_test(NAME modhelp COMMAND modhelp -a test -h)
Expand Down
3 changes: 2 additions & 1 deletion examples/enum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ int main(int argc, char **argv) {
CLI::App app;

Level level;
app.add_set("-l,--level", level, {Level::High, Level::Medium, Level::Low}, "Level settings")
app.add_option("-l,--level", level, "Level settings")
->check(CLI::IsMember({Level::High, Level::Medium, Level::Low}))
->type_name("enum/Level in {High=0, Medium=1, Low=2}");

CLI11_PARSE(app, argc, argv);
Expand Down
Loading