1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 12:13:52 +00:00

CLI::IsMember (#222)

* Adding first draft of Sets

Use IsMember now

Using IsMember as backend for Set

Non-const validator backend

Move set tests to set

Clearer inits

* Drop shortcut

Tighten up classes a bit for MSVC

Check with GCC 4.8 too

* Simpler templates, but more of them

Dropping more type safety for older compilers

Shortcut string set

* Adding shortcut init

Making g++ 4.7 docker image happy

Fix Clang tidy issue with last commit

Adding one more shortcut, adding a couple of tests

* Dropping dual pointer versions of code

* Smarter shortcut syntax

* Adding slighly faster choices

* Cleanup to make InMember simpler

* Drop choices for now, adding some tests

* ValidationError is now always the error from a validator

* Support for other types of initializer lists, including enums

* Factor out type utilities, single version of IsMember code

* Adding a few tests for #224

* Minor cleanup for Validation Error

* Adding tests, moved deprecated tests

* Docs updates
This commit is contained in:
Henry Schreiner 2019-02-19 09:56:02 +01:00 committed by GitHub
parent 3d7de7d25c
commit c9123811d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1152 additions and 725 deletions

View File

@ -24,7 +24,7 @@ matrix:
- .ci/make_and_test.sh 17
# Check style/tidy
- compiler: clang
- compiler: clang
env:
- CHECK_STYLE=yes
script:
@ -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

View File

@ -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

View File

@ -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>
@ -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)
@ -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.
@ -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");`
@ -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.
@ -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)
@ -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)
@ -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.

View File

@ -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)

View File

@ -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);

View File

@ -644,51 +644,31 @@ class App {
}
#endif
/// Add set of options (No default, temp reference, such as an inline set)
/// Add set of options (No default, temp reference, such as an inline set) DEPRECATED
template <typename T>
Option *add_set(std::string option_name,
T &member, ///< The selected member of the set
std::set<T> options, ///< The set of possibilities
std::string description = "") {
std::string simple_name = CLI::detail::split(option_name, ',').at(0);
CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) {
bool retval = detail::lexical_cast(res[0], member);
if(!retval)
throw ConversionError(res[0], simple_name);
return std::find(std::begin(options), std::end(options), member) != std::end(options);
};
Option *opt = add_option(option_name, std::move(fun), std::move(description), false);
std::string typeval = detail::type_name<T>();
typeval += " in {" + detail::join(options) + "}";
opt->type_name(typeval);
Option *opt = add_option(option_name, member, std::move(description));
opt->check(IsMember{options});
return opt;
}
/// Add set of options (No default, set can be changed afterwords - do not destroy the set)
/// Add set of options (No default, set can be changed afterwords - do not destroy the set) DEPRECATED
template <typename T>
Option *add_mutable_set(std::string option_name,
T &member, ///< The selected member of the set
const std::set<T> &options, ///< The set of possibilities
std::string description = "") {
std::string simple_name = CLI::detail::split(option_name, ',').at(0);
CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
bool retval = detail::lexical_cast(res[0], member);
if(!retval)
throw ConversionError(res[0], simple_name);
return std::find(std::begin(options), std::end(options), member) != std::end(options);
};
Option *opt = add_option(option_name, std::move(fun), std::move(description), false);
opt->type_name_fn(
[&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; });
Option *opt = add_option(option_name, member, std::move(description));
opt->check(IsMember{&options});
return opt;
}
/// Add set of options (with default, static set, such as an inline set)
/// Add set of options (with default, static set, such as an inline set) DEPRECATED
template <typename T>
Option *add_set(std::string option_name,
T &member, ///< The selected member of the set
@ -696,27 +676,12 @@ class App {
std::string description,
bool defaulted) {
std::string simple_name = CLI::detail::split(option_name, ',').at(0);
CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) {
bool retval = detail::lexical_cast(res[0], member);
if(!retval)
throw ConversionError(res[0], simple_name);
return std::find(std::begin(options), std::end(options), member) != std::end(options);
};
Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
std::string typeval = detail::type_name<T>();
typeval += " in {" + detail::join(options) + "}";
opt->type_name(typeval);
if(defaulted) {
std::stringstream out;
out << member;
opt->default_str(out.str());
}
Option *opt = add_option(option_name, member, std::move(description), defaulted);
opt->check(IsMember{options});
return opt;
}
/// Add set of options (with default, set can be changed afterwards - do not destroy the set)
/// Add set of options (with default, set can be changed afterwards - do not destroy the set) DEPRECATED
template <typename T>
Option *add_mutable_set(std::string option_name,
T &member, ///< The selected member of the set
@ -724,381 +689,166 @@ class App {
std::string description,
bool defaulted) {
std::string simple_name = CLI::detail::split(option_name, ',').at(0);
CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
bool retval = detail::lexical_cast(res[0], member);
if(!retval)
throw ConversionError(res[0], simple_name);
return std::find(std::begin(options), std::end(options), member) != std::end(options);
};
Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
opt->type_name_fn(
[&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; });
if(defaulted) {
std::stringstream out;
out << member;
opt->default_str(out.str());
}
Option *opt = add_option(option_name, member, std::move(description), defaulted);
opt->check(IsMember{&options});
return opt;
}
/// Add set of options, string only, ignore case (no default, static set)
/// Add set of options, string only, ignore case (no default, static set) DEPRECATED
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case)) instead")
Option *add_set_ignore_case(std::string option_name,
std::string &member, ///< The selected member of the set
std::set<std::string> options, ///< The set of possibilities
std::string description = "") {
std::string simple_name = CLI::detail::split(option_name, ',').at(0);
CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) {
member = detail::to_lower(res[0]);
auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
return detail::to_lower(val) == member;
});
if(iter == std::end(options))
throw ConversionError(member, simple_name);
else {
member = *iter;
return true;
}
};
Option *opt = add_option(option_name, std::move(fun), std::move(description), false);
std::string typeval = detail::type_name<std::string>();
typeval += " in {" + detail::join(options) + "}";
opt->type_name(typeval);
Option *opt = add_option(option_name, member, std::move(description));
opt->check(IsMember{options, CLI::ignore_case});
return opt;
}
/// Add set of options, string only, ignore case (no default, set can be changed afterwards - do not destroy the
/// set)
/// set) DEPRECATED
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case)) with a (shared) pointer instead")
Option *add_mutable_set_ignore_case(std::string option_name,
std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of possibilities
std::string description = "") {
std::string simple_name = CLI::detail::split(option_name, ',').at(0);
CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
member = detail::to_lower(res[0]);
auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
return detail::to_lower(val) == member;
});
if(iter == std::end(options))
throw ConversionError(member, simple_name);
else {
member = *iter;
return true;
}
};
Option *opt = add_option(option_name, std::move(fun), std::move(description), false);
opt->type_name_fn([&options]() {
return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
});
Option *opt = add_option(option_name, member, std::move(description));
opt->check(IsMember{&options, CLI::ignore_case});
return opt;
}
/// Add set of options, string only, ignore case (default, static set)
/// Add set of options, string only, ignore case (default, static set) DEPRECATED
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case)) instead")
Option *add_set_ignore_case(std::string option_name,
std::string &member, ///< The selected member of the set
std::set<std::string> options, ///< The set of possibilities
std::string description,
bool defaulted) {
std::string simple_name = CLI::detail::split(option_name, ',').at(0);
CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) {
member = detail::to_lower(res[0]);
auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
return detail::to_lower(val) == member;
});
if(iter == std::end(options))
throw ConversionError(member, simple_name);
else {
member = *iter;
return true;
}
};
Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
std::string typeval = detail::type_name<std::string>();
typeval += " in {" + detail::join(options) + "}";
opt->type_name(typeval);
if(defaulted) {
opt->default_str(member);
}
Option *opt = add_option(option_name, member, std::move(description), defaulted);
opt->check(IsMember{options, CLI::ignore_case});
return opt;
}
/// Add set of options, string only, ignore case (default, set can be changed afterwards - do not destroy the set)
/// DEPRECATED
CLI11_DEPRECATED("Use ->check(CLI::IsMember(...)) with a (shared) pointer instead")
Option *add_mutable_set_ignore_case(std::string option_name,
std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of possibilities
std::string description,
bool defaulted) {
std::string simple_name = CLI::detail::split(option_name, ',').at(0);
CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
member = detail::to_lower(res[0]);
auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
return detail::to_lower(val) == member;
});
if(iter == std::end(options))
throw ConversionError(member, simple_name);
else {
member = *iter;
return true;
}
};
Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
opt->type_name_fn([&options]() {
return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
});
if(defaulted) {
opt->default_str(member);
}
Option *opt = add_option(option_name, member, std::move(description), defaulted);
opt->check(IsMember{&options, CLI::ignore_case});
return opt;
}
/// Add set of options, string only, ignore underscore (no default, static set)
/// Add set of options, string only, ignore underscore (no default, static set) DEPRECATED
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_underscore)) instead")
Option *add_set_ignore_underscore(std::string option_name,
std::string &member, ///< The selected member of the set
std::set<std::string> options, ///< The set of possibilities
std::string description = "") {
std::string simple_name = CLI::detail::split(option_name, ',').at(0);
CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) {
member = detail::remove_underscore(res[0]);
auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
return detail::remove_underscore(val) == member;
});
if(iter == std::end(options))
throw ConversionError(member, simple_name);
else {
member = *iter;
return true;
}
};
Option *opt = add_option(option_name, std::move(fun), std::move(description), false);
std::string typeval = detail::type_name<std::string>();
typeval += " in {" + detail::join(options) + "}";
opt->type_name(typeval);
Option *opt = add_option(option_name, member, std::move(description));
opt->check(IsMember{options, CLI::ignore_underscore});
return opt;
}
/// Add set of options, string only, ignore underscore (no default, set can be changed afterwards - do not destroy
/// the set)
/// the set) DEPRECATED
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead")
Option *add_mutable_set_ignore_underscore(std::string option_name,
std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of possibilities
std::string description = "") {
std::string simple_name = CLI::detail::split(option_name, ',').at(0);
CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
member = detail::remove_underscore(res[0]);
auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
return detail::remove_underscore(val) == member;
});
if(iter == std::end(options))
throw ConversionError(member, simple_name);
else {
member = *iter;
return true;
}
};
Option *opt = add_option(option_name, std::move(fun), std::move(description), false);
opt->type_name_fn([&options]() {
return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
});
Option *opt = add_option(option_name, member, std::move(description));
opt->check(IsMember{options, CLI::ignore_underscore});
return opt;
}
/// Add set of options, string only, ignore underscore (default, static set)
/// Add set of options, string only, ignore underscore (default, static set) DEPRECATED
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_underscore)) instead")
Option *add_set_ignore_underscore(std::string option_name,
std::string &member, ///< The selected member of the set
std::set<std::string> options, ///< The set of possibilities
std::string description,
bool defaulted) {
std::string simple_name = CLI::detail::split(option_name, ',').at(0);
CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) {
member = detail::remove_underscore(res[0]);
auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
return detail::remove_underscore(val) == member;
});
if(iter == std::end(options))
throw ConversionError(member, simple_name);
else {
member = *iter;
return true;
}
};
Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
std::string typeval = detail::type_name<std::string>();
typeval += " in {" + detail::join(options) + "}";
opt->type_name(typeval);
if(defaulted) {
opt->default_str(member);
}
Option *opt = add_option(option_name, member, std::move(description), defaulted);
opt->check(IsMember{options, CLI::ignore_underscore});
return opt;
}
/// Add set of options, string only, ignore underscore (default, set can be changed afterwards - do not destroy the
/// set)
/// set) DEPRECATED
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead")
Option *add_mutable_set_ignore_underscore(std::string option_name,
std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of possibilities
std::string description,
bool defaulted) {
std::string simple_name = CLI::detail::split(option_name, ',').at(0);
CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
member = detail::remove_underscore(res[0]);
auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
return detail::remove_underscore(val) == member;
});
if(iter == std::end(options))
throw ConversionError(member, simple_name);
else {
member = *iter;
return true;
}
};
Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
opt->type_name_fn([&options]() {
return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
});
if(defaulted) {
opt->default_str(member);
}
Option *opt = add_option(option_name, member, std::move(description), defaulted);
opt->check(IsMember{&options, CLI::ignore_underscore});
return opt;
}
/// Add set of options, string only, ignore underscore and case (no default, static set)
/// Add set of options, string only, ignore underscore and case (no default, static set) DEPRECATED
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead")
Option *add_set_ignore_case_underscore(std::string option_name,
std::string &member, ///< The selected member of the set
std::set<std::string> options, ///< The set of possibilities
std::string description = "") {
std::string simple_name = CLI::detail::split(option_name, ',').at(0);
CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) {
member = detail::to_lower(detail::remove_underscore(res[0]));
auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
return detail::to_lower(detail::remove_underscore(val)) == member;
});
if(iter == std::end(options))
throw ConversionError(member, simple_name);
else {
member = *iter;
return true;
}
};
Option *opt = add_option(option_name, std::move(fun), std::move(description), false);
std::string typeval = detail::type_name<std::string>();
typeval += " in {" + detail::join(options) + "}";
opt->type_name(typeval);
Option *opt = add_option(option_name, member, std::move(description));
opt->check(IsMember{options, CLI::ignore_underscore, CLI::ignore_case});
return opt;
}
/// Add set of options, string only, ignore underscore and case (no default, set can be changed afterwards - do not
/// destroy the set)
/// destroy the set) DEPRECATED
CLI11_DEPRECATED(
"Use ->check(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead")
Option *add_mutable_set_ignore_case_underscore(std::string option_name,
std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of possibilities
std::string description = "") {
std::string simple_name = CLI::detail::split(option_name, ',').at(0);
CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
member = detail::to_lower(detail::remove_underscore(res[0]));
auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
return detail::to_lower(detail::remove_underscore(val)) == member;
});
if(iter == std::end(options))
throw ConversionError(member, simple_name);
else {
member = *iter;
return true;
}
};
Option *opt = add_option(option_name, std::move(fun), std::move(description), false);
opt->type_name_fn([&options]() {
return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
});
Option *opt = add_option(option_name, member, std::move(description));
opt->check(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case});
return opt;
}
/// Add set of options, string only, ignore underscore and case (default, static set)
/// Add set of options, string only, ignore underscore and case (default, static set) DEPRECATED
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead")
Option *add_set_ignore_case_underscore(std::string option_name,
std::string &member, ///< The selected member of the set
std::set<std::string> options, ///< The set of possibilities
std::string description,
bool defaulted) {
std::string simple_name = CLI::detail::split(option_name, ',').at(0);
CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) {
member = detail::to_lower(detail::remove_underscore(res[0]));
auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
return detail::to_lower(detail::remove_underscore(val)) == member;
});
if(iter == std::end(options))
throw ConversionError(member, simple_name);
else {
member = *iter;
return true;
}
};
Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
std::string typeval = detail::type_name<std::string>();
typeval += " in {" + detail::join(options) + "}";
opt->type_name(typeval);
if(defaulted) {
opt->default_str(member);
}
Option *opt = add_option(option_name, member, std::move(description), defaulted);
opt->check(IsMember{options, CLI::ignore_underscore, CLI::ignore_case});
return opt;
}
/// Add set of options, string only, ignore underscore and case (default, set can be changed afterwards - do not
/// destroy the set)
/// destroy the set) DEPRECATED
CLI11_DEPRECATED(
"Use ->check(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead")
Option *add_mutable_set_ignore_case_underscore(std::string option_name,
std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of possibilities
std::string description,
bool defaulted) {
std::string simple_name = CLI::detail::split(option_name, ',').at(0);
CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
member = detail::to_lower(detail::remove_underscore(res[0]));
auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
return detail::to_lower(detail::remove_underscore(val)) == member;
});
if(iter == std::end(options))
throw ConversionError(member, simple_name);
else {
member = *iter;
return true;
}
};
Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
opt->type_name_fn([&options]() {
return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
});
if(defaulted) {
opt->default_str(member);
}
Option *opt = add_option(option_name, member, std::move(description), defaulted);
opt->check(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case});
return opt;
}

View File

@ -49,7 +49,9 @@ class FormatterBase {
FormatterBase() = default;
FormatterBase(const FormatterBase &) = default;
FormatterBase(FormatterBase &&) = default;
virtual ~FormatterBase() = default;
/// Adding a destructor in this form to work around bug in GCC 4.7
virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default)
/// This is the key method that puts together help
virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0;
@ -93,6 +95,9 @@ class FormatterLambda final : public FormatterBase {
/// Create a FormatterLambda with a lambda function
explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {}
/// Adding a destructor (mostly to make GCC 4.7 happy)
~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default)
/// This will simply call the lambda function
std::string make_help(const App *app, std::string name, AppFormatMode mode) const override {
return lambda_(app, name, mode);

View File

@ -304,7 +304,9 @@ class Option : public OptionBase<Option> {
/// Adds a validator with a built in type name
Option *check(const Validator &validator) {
validators_.emplace_back(validator.func);
if(!validator.tname.empty())
if(validator.tname_function)
type_name_fn(validator.tname_function);
else if(!validator.tname.empty())
type_name(validator.tname);
return this;
}
@ -606,7 +608,14 @@ class Option : public OptionBase<Option> {
if(!validators_.empty()) {
for(std::string &result : results_)
for(const std::function<std::string(std::string &)> &vali : validators_) {
std::string err_msg = vali(result);
std::string err_msg;
try {
err_msg = vali(result);
} catch(const ValidationError &err) {
throw ValidationError(err.what(), get_name());
}
if(!err_msg.empty())
throw ValidationError(get_name(), err_msg);
}

View File

@ -343,4 +343,5 @@ inline std::string &add_quotes_if_needed(std::string &str) {
}
} // namespace detail
} // namespace CLI

View File

@ -119,7 +119,7 @@ class AutoTimer : public Timer {
AutoTimer(std::string title = "Timer", time_print_t time_print = Simple) : Timer(title, time_print) {}
// GCC 4.7 does not support using inheriting constructors.
/// This desctructor prints the string
/// This destructor prints the string
~AutoTimer() { std::cout << to_string() << std::endl; }
};

View File

@ -4,6 +4,7 @@
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
#include <exception>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
@ -12,6 +13,16 @@ namespace CLI {
// Type tools
// Utilities for type enabling
namespace detail {
// Based generally on https://rmf.io/cxx11/almost-static-if
/// Simple empty scoped class
enum class enabler {};
/// An instance to use in EnableIf
constexpr enabler dummy = {};
} // namespace detail
/// A copy of enable_if_t from C++14, compatible with C++11.
///
/// We could check to see if C++14 is being used, but it does not hurt to redefine this
@ -21,24 +32,46 @@ namespace CLI {
template <bool B, class T = void> using enable_if_t = typename std::enable_if<B, T>::type;
/// Check to see if something is a vector (fail check by default)
template <typename T> struct is_vector { static const bool value = false; };
template <typename T> struct is_vector : std::false_type {};
/// Check to see if something is a vector (true if actually a vector)
template <class T, class A> struct is_vector<std::vector<T, A>> { static bool const value = true; };
template <class T, class A> struct is_vector<std::vector<T, A>> : std::true_type {};
/// Check to see if something is bool (fail check by default)
template <typename T> struct is_bool { static const bool value = false; };
template <typename T> struct is_bool : std::false_type {};
/// Check to see if something is bool (true if actually a bool)
template <> struct is_bool<bool> { static bool const value = true; };
template <> struct is_bool<bool> : std::true_type {};
/// Check to see if something is a shared pointer
template <typename T> struct is_shared_ptr : std::false_type {};
/// Check to see if something is a shared pointer (True if really a shared pointer)
template <typename T> struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};
/// Check to see if something is copyable pointer
template <typename T> struct is_copyable_ptr {
static bool const value = is_shared_ptr<T>::value || std::is_pointer<T>::value;
};
/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that
/// pointer_traits<T> be valid.
template <typename T> struct element_type {
using type =
typename std::conditional<is_copyable_ptr<T>::value, typename std::pointer_traits<T>::element_type, T>::type;
};
/// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of
/// the container
template <typename T> struct element_value_type { using type = typename element_type<T>::type::value_type; };
/// This can be specialized to override the type deduction for IsMember.
template <typename T> struct IsMemberType { using type = T; };
/// The main custom type needed here is const char * should be a string.
template <> struct IsMemberType<const char *> { using type = std::string; };
namespace detail {
// Based generally on https://rmf.io/cxx11/almost-static-if
/// Simple empty scoped class
enum class enabler {};
/// An instance to use in EnableIf
constexpr enabler dummy = {};
// Type name print

View File

@ -3,10 +3,12 @@
// Distributed under the 3-Clause BSD License. See accompanying
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
#include "CLI/StringTools.hpp"
#include "CLI/TypeTools.hpp"
#include <functional>
#include <iostream>
#include <memory>
#include <string>
// C standard library
@ -17,6 +19,8 @@
namespace CLI {
class Option;
/// @defgroup validator_group Validators
/// @brief Some validators that are provided
@ -27,17 +31,31 @@ namespace CLI {
/// @{
///
struct Validator {
class Validator {
friend Option;
protected:
/// This is the type name, if empty the type name will not be changed
std::string tname;
/// This is the type function, if empty the tname will be used
std::function<std::string()> tname_function;
/// This it the base function that is to be called.
/// Returns a string error message if validation fails.
std::function<std::string(const std::string &)> func;
std::function<std::string(std::string &)> func;
public:
/// This is the required operator for a validator - provided to help
/// users (CLI11 uses the member `func` directly)
std::string operator()(std::string &str) const { return func(str); };
/// This is the required operator for a validator - provided to help
/// users (CLI11 uses the member `func` directly)
std::string operator()(const std::string &str) const { return func(str); };
std::string operator()(const std::string &str) const {
std::string value = str;
return func(value);
};
/// Combining validators is a new validator
Validator operator&(const Validator &other) const {
@ -45,10 +63,10 @@ struct Validator {
newval.tname = (tname == other.tname ? tname : "");
// Give references (will make a copy in lambda function)
const std::function<std::string(const std::string &filename)> &f1 = func;
const std::function<std::string(const std::string &filename)> &f2 = other.func;
const std::function<std::string(std::string & filename)> &f1 = func;
const std::function<std::string(std::string & filename)> &f2 = other.func;
newval.func = [f1, f2](const std::string &filename) {
newval.func = [f1, f2](std::string &filename) {
std::string s1 = f1(filename);
std::string s2 = f2(filename);
if(!s1.empty() && !s2.empty())
@ -65,10 +83,10 @@ struct Validator {
newval.tname = (tname == other.tname ? tname : "");
// Give references (will make a copy in lambda function)
const std::function<std::string(const std::string &filename)> &f1 = func;
const std::function<std::string(const std::string &filename)> &f2 = other.func;
const std::function<std::string(std::string & filename)> &f1 = func;
const std::function<std::string(std::string & filename)> &f2 = other.func;
newval.func = [f1, f2](const std::string &filename) {
newval.func = [f1, f2](std::string &filename) {
std::string s1 = f1(filename);
std::string s2 = f2(filename);
if(s1.empty() || s2.empty())
@ -86,10 +104,11 @@ struct Validator {
namespace detail {
/// Check for an existing file (returns error message if check fails)
struct ExistingFileValidator : public Validator {
class ExistingFileValidator : public Validator {
public:
ExistingFileValidator() {
tname = "FILE";
func = [](const std::string &filename) {
func = [](std::string &filename) {
struct stat buffer;
bool exist = stat(filename.c_str(), &buffer) == 0;
bool is_dir = (buffer.st_mode & S_IFDIR) != 0;
@ -104,10 +123,11 @@ struct ExistingFileValidator : public Validator {
};
/// Check for an existing directory (returns error message if check fails)
struct ExistingDirectoryValidator : public Validator {
class ExistingDirectoryValidator : public Validator {
public:
ExistingDirectoryValidator() {
tname = "DIR";
func = [](const std::string &filename) {
func = [](std::string &filename) {
struct stat buffer;
bool exist = stat(filename.c_str(), &buffer) == 0;
bool is_dir = (buffer.st_mode & S_IFDIR) != 0;
@ -122,10 +142,11 @@ struct ExistingDirectoryValidator : public Validator {
};
/// Check for an existing path
struct ExistingPathValidator : public Validator {
class ExistingPathValidator : public Validator {
public:
ExistingPathValidator() {
tname = "PATH";
func = [](const std::string &filename) {
func = [](std::string &filename) {
struct stat buffer;
bool const exist = stat(filename.c_str(), &buffer) == 0;
if(!exist) {
@ -137,10 +158,11 @@ struct ExistingPathValidator : public Validator {
};
/// Check for an non-existing path
struct NonexistentPathValidator : public Validator {
class NonexistentPathValidator : public Validator {
public:
NonexistentPathValidator() {
tname = "PATH";
func = [](const std::string &filename) {
func = [](std::string &filename) {
struct stat buffer;
bool exist = stat(filename.c_str(), &buffer) == 0;
if(exist) {
@ -152,10 +174,11 @@ struct NonexistentPathValidator : public Validator {
};
/// Validate the given string is a legal ipv4 address
struct IPV4Validator : public Validator {
class IPV4Validator : public Validator {
public:
IPV4Validator() {
tname = "IPV4";
func = [](const std::string &ip_addr) {
func = [](std::string &ip_addr) {
auto result = CLI::detail::split(ip_addr, '.');
if(result.size() != 4) {
return "Invalid IPV4 address must have four parts " + ip_addr;
@ -177,10 +200,11 @@ struct IPV4Validator : public Validator {
};
/// Validate the argument is a number and greater than or equal to 0
struct PositiveNumber : public Validator {
class PositiveNumber : public Validator {
public:
PositiveNumber() {
tname = "POSITIVE";
func = [](const std::string &number_str) {
func = [](std::string &number_str) {
int number;
if(!detail::lexical_cast(number_str, number)) {
return "Failed parsing number " + number_str;
@ -216,7 +240,8 @@ const detail::IPV4Validator ValidIPV4;
const detail::PositiveNumber PositiveNumber;
/// Produce a range (factory). Min and max are inclusive.
struct Range : public Validator {
class Range : public Validator {
public:
/// This produces a range with min and max inclusive.
///
/// Note that the constructor is templated, but the struct is not, so C++17 is not
@ -226,7 +251,7 @@ struct Range : public Validator {
out << detail::type_name<T>() << " in [" << min << " - " << max << "]";
tname = out.str();
func = [min, max](std::string input) {
func = [min, max](std::string &input) {
T val;
detail::lexical_cast(input, val);
if(val < min || val > max)
@ -240,6 +265,99 @@ struct Range : public Validator {
template <typename T> explicit Range(T max) : Range(static_cast<T>(0), max) {}
};
namespace detail {
template <typename T, enable_if_t<is_copyable_ptr<T>::value, detail::enabler> = detail::dummy>
auto smart_deref(T value) -> decltype(*value) {
return *value;
}
template <typename T, enable_if_t<!is_copyable_ptr<T>::value, detail::enabler> = detail::dummy> T smart_deref(T value) {
return value;
}
} // namespace detail
/// Verify items are in a set
class IsMember : public Validator {
public:
using filter_fn_t = std::function<std::string(std::string)>;
/// This allows in-place construction using an initializer list
template <typename T, typename... Args>
explicit IsMember(std::initializer_list<T> values, Args &&... args)
: IsMember(std::vector<T>(values), std::forward<Args>(args)...) {}
/// This checks to see if an item is in a set (empty function)
template <typename T>
explicit IsMember(T set)
: IsMember(std::move(set),
std::function<typename IsMemberType<typename element_value_type<T>::type>::type(
typename IsMemberType<typename element_value_type<T>::type>::type)>()) {}
/// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
/// both sides of the comparison before computing the comparison.
template <typename T, typename F> explicit IsMember(T set, F filter_function) {
// Get the type of the contained item - requires a container have ::value_type
using item_t = typename element_value_type<T>::type;
using local_item_t = typename IsMemberType<item_t>::type;
// Make a local copy of the filter function, using a std::function if not one already
std::function<local_item_t(local_item_t)> filter_fn = filter_function;
// This is the type name for help, it will take the current version of the set contents
tname_function = [set]() {
std::stringstream out;
out << detail::type_name<item_t>() << " in {" << detail::join(detail::smart_deref(set), ",") << "}";
return out.str();
};
// This is the function that validates
// It stores a copy of the set pointer-like, so shared_ptr will stay alive
func = [set, filter_fn](std::string &input) {
for(const item_t &v : detail::smart_deref(set)) {
local_item_t a = v;
local_item_t b;
if(!detail::lexical_cast(input, b))
throw ValidationError(input); // name is added later
// The filter function might be empty, so don't filter if it is.
if(filter_fn) {
a = filter_fn(a);
b = filter_fn(b);
}
if(a == b) {
// Make sure the version in the input string is identical to the one in the set
// Requires std::stringstream << be supported on T.
if(filter_fn) {
std::stringstream out;
out << v;
input = out.str();
}
// Return empty error string (success)
return std::string();
}
}
// If you reach this point, the result was not found
return input + " not in {" + detail::join(detail::smart_deref(set), ",") + "}";
};
}
/// You can pass in as many filter functions as you like, they nest
template <typename T, typename... Args>
IsMember(T set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other)
: IsMember(std::move(set),
[filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
other...) {}
};
/// Helper function to allow ignore_case to be passed to IsMember
inline std::string ignore_case(std::string item) { return detail::to_lower(item); }
/// Helper function to allow ignore_underscore to be passed to IsMember
inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); }
namespace detail {
/// Split a string into a program name and command line arguments
/// the string is assumed to contain a file name followed by other arguments
@ -266,6 +384,7 @@ inline std::pair<std::string, std::string> split_program_name(std::string comman
ltrim(vals.second);
return vals;
}
} // namespace detail
/// @}

View File

@ -5,8 +5,8 @@ CLANG_FORMAT=saschpe/clang-format:5.0.1
set -evx
docker run -it ${CLANG_FORMAT} --version
docker run -it -v "$(pwd)":/workdir -w /workdir ${CLANG_FORMAT} -style=file -sort-includes -i $(git ls-files -- '*.cpp' '*.hpp')
docker run --rm -it ${CLANG_FORMAT} --version
docker run --rm -it -v "$(pwd)":/workdir -w /workdir ${CLANG_FORMAT} -style=file -sort-includes -i $(git ls-files -- '*.cpp' '*.hpp')
git diff --exit-code --color

View File

@ -1071,7 +1071,7 @@ TEST_F(TApp, CallbackFlagsFalse) {
app.add_flag_function("-v,-f{false},--val,--fval{false}", func);
run();
EXPECT_EQ(value, 0u);
EXPECT_EQ(value, 0);
args = {"-f"};
run();
@ -1100,7 +1100,7 @@ TEST_F(TApp, CallbackFlagsFalseShortcut) {
app.add_flag_function("-v,!-f,--val,!--fval", func);
run();
EXPECT_EQ(value, 0u);
EXPECT_EQ(value, 0);
args = {"-f"};
run();
@ -1317,233 +1317,6 @@ TEST_F(TApp, FileExists) {
EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
}
TEST_F(TApp, InSet) {
std::string choice;
app.add_set("-q,--quick", choice, {"one", "two", "three"});
args = {"--quick", "two"};
run();
EXPECT_EQ("two", choice);
args = {"--quick", "four"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, InSetWithDefault) {
std::string choice = "one";
app.add_set("-q,--quick", choice, {"one", "two", "three"}, "", true);
run();
EXPECT_EQ("one", choice);
args = {"--quick", "two"};
run();
EXPECT_EQ("two", choice);
args = {"--quick", "four"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, InCaselessSetWithDefault) {
std::string choice = "one";
app.add_set_ignore_case("-q,--quick", choice, {"one", "two", "three"}, "", true);
run();
EXPECT_EQ("one", choice);
args = {"--quick", "tWo"};
run();
EXPECT_EQ("two", choice);
args = {"--quick", "four"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, InIntSet) {
int choice;
app.add_set("-q,--quick", choice, {1, 2, 3});
args = {"--quick", "2"};
run();
EXPECT_EQ(2, choice);
args = {"--quick", "4"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, InIntSetWindows) {
int choice;
app.add_set("-q,--quick", choice, {1, 2, 3});
app.allow_windows_style_options();
args = {"/q", "2"};
run();
EXPECT_EQ(2, choice);
args = {"/q4"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(TApp, FailSet) {
int choice;
app.add_set("-q,--quick", choice, {1, 2, 3});
args = {"--quick", "3", "--quick=2"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
args = {"--quick=hello"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, FailMutableSet) {
int choice;
std::set<int> vals{1, 2, 3};
app.add_mutable_set("-q,--quick", choice, vals);
app.add_mutable_set("-s,--slow", choice, vals, "", true);
args = {"--quick=hello"};
EXPECT_THROW(run(), CLI::ConversionError);
args = {"--slow=hello"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, InSetIgnoreCase) {
std::string choice;
app.add_set_ignore_case("-q,--quick", choice, {"one", "Two", "THREE"});
args = {"--quick", "One"};
run();
EXPECT_EQ("one", choice);
args = {"--quick", "two"};
run();
EXPECT_EQ("Two", choice); // Keeps caps from set
args = {"--quick", "ThrEE"};
run();
EXPECT_EQ("THREE", choice); // Keeps caps from set
args = {"--quick", "four"};
EXPECT_THROW(run(), CLI::ConversionError);
args = {"--quick=one", "--quick=two"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, InSetIgnoreCaseMutableValue) {
std::set<std::string> options{"one", "Two", "THREE"};
std::string choice;
app.add_mutable_set_ignore_case("-q,--quick", choice, options);
args = {"--quick", "One"};
run();
EXPECT_EQ("one", choice);
args = {"--quick", "two"};
run();
EXPECT_EQ("Two", choice); // Keeps caps from set
args = {"--quick", "ThrEE"};
run();
EXPECT_EQ("THREE", choice); // Keeps caps from set
options.clear();
args = {"--quick", "ThrEE"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, InSetIgnoreCasePointer) {
std::set<std::string> *options = new std::set<std::string>{"one", "Two", "THREE"};
std::string choice;
app.add_set_ignore_case("-q,--quick", choice, *options);
args = {"--quick", "One"};
run();
EXPECT_EQ("one", choice);
args = {"--quick", "two"};
run();
EXPECT_EQ("Two", choice); // Keeps caps from set
args = {"--quick", "ThrEE"};
run();
EXPECT_EQ("THREE", choice); // Keeps caps from set
delete options;
args = {"--quick", "ThrEE"};
run();
EXPECT_EQ("THREE", choice); // this does not throw a segfault
args = {"--quick", "four"};
EXPECT_THROW(run(), CLI::ConversionError);
args = {"--quick=one", "--quick=two"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, InSetIgnoreUnderscore) {
std::string choice;
app.add_set_ignore_underscore("-q,--quick", choice, {"option_one", "option_two", "optionthree"});
args = {"--quick", "option_one"};
run();
EXPECT_EQ("option_one", choice);
args = {"--quick", "optiontwo"};
run();
EXPECT_EQ("option_two", choice); // Keeps underscore from set
args = {"--quick", "_option_thr_ee"};
run();
EXPECT_EQ("optionthree", choice); // no underscore
args = {"--quick", "Option4"};
EXPECT_THROW(run(), CLI::ConversionError);
args = {"--quick=option_one", "--quick=option_two"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, InSetIgnoreCaseUnderscore) {
std::string choice;
app.add_set_ignore_case_underscore("-q,--quick", choice, {"Option_One", "option_two", "OptionThree"});
args = {"--quick", "option_one"};
run();
EXPECT_EQ("Option_One", choice);
args = {"--quick", "OptionTwo"};
run();
EXPECT_EQ("option_two", choice); // Keeps underscore and case from set
args = {"--quick", "_OPTION_thr_ee"};
run();
EXPECT_EQ("OptionThree", choice); // no underscore
args = {"--quick", "Option4"};
EXPECT_THROW(run(), CLI::ConversionError);
args = {"--quick=option_one", "--quick=option_two"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, VectorFixedString) {
std::vector<std::string> strvec;
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
@ -1895,33 +1668,6 @@ TEST_F(TApp, OptionWithDefaults) {
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, SetWithDefaults) {
int someint = 2;
app.add_set("-a", someint, {1, 2, 3, 4}, "", true);
args = {"-a1", "-a2"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, SetWithDefaultsConversion) {
int someint = 2;
app.add_set("-a", someint, {1, 2, 3, 4}, "", true);
args = {"-a", "hi"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, SetWithDefaultsIC) {
std::string someint = "ho";
app.add_set_ignore_case("-a", someint, {"Hi", "Ho"}, "", true);
args = {"-aHi", "-aHo"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
// Added to test ->transform
TEST_F(TApp, OrderedModifingTransforms) {
std::vector<std::string> val;
@ -1989,69 +1735,6 @@ TEST_F(TApp, CustomDoubleOption) {
EXPECT_DOUBLE_EQ(custom_opt.second, 1.5);
}
// #113
TEST_F(TApp, AddRemoveSetItems) {
std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"};
std::string type1, type2;
app.add_mutable_set("--type1", type1, items);
app.add_mutable_set("--type2", type2, items, "", true);
args = {"--type1", "TYPE1", "--type2", "TYPE2"};
run();
EXPECT_EQ(type1, "TYPE1");
EXPECT_EQ(type2, "TYPE2");
items.insert("TYPE6");
items.insert("TYPE7");
items.erase("TYPE1");
items.erase("TYPE2");
args = {"--type1", "TYPE6", "--type2", "TYPE7"};
run();
EXPECT_EQ(type1, "TYPE6");
EXPECT_EQ(type2, "TYPE7");
args = {"--type1", "TYPE1"};
EXPECT_THROW(run(), CLI::ConversionError);
args = {"--type2", "TYPE2"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, AddRemoveSetItemsNoCase) {
std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"};
std::string type1, type2;
app.add_mutable_set_ignore_case("--type1", type1, items);
app.add_mutable_set_ignore_case("--type2", type2, items, "", true);
args = {"--type1", "TYPe1", "--type2", "TyPE2"};
run();
EXPECT_EQ(type1, "TYPE1");
EXPECT_EQ(type2, "TYPE2");
items.insert("TYPE6");
items.insert("TYPE7");
items.erase("TYPE1");
items.erase("TYPE2");
args = {"--type1", "TyPE6", "--type2", "tYPE7"};
run();
EXPECT_EQ(type1, "TYPE6");
EXPECT_EQ(type2, "TYPE7");
args = {"--type1", "TYPe1"};
EXPECT_THROW(run(), CLI::ConversionError);
args = {"--type2", "TYpE2"};
EXPECT_THROW(run(), CLI::ConversionError);
}
// #128
TEST_F(TApp, RepeatingMultiArgumentOptions) {
std::vector<std::string> entries;

View File

@ -26,6 +26,7 @@ set(CLI11_TESTS
IniTest
SimpleTest
AppTest
SetTest
CreationTest
SubcommandTest
HelpTest
@ -34,6 +35,7 @@ set(CLI11_TESTS
OptionalTest
DeprecatedTest
StringParseTest
TrueFalseTest
)
if(WIN32)

View File

@ -1,12 +1,328 @@
#ifdef CLI11_SINGLE_FILE
#include "CLI11.hpp"
#else
#include "CLI/CLI.hpp"
#endif
#include "gtest/gtest.h"
#include "app_helper.hpp"
TEST(Deprecated, Emtpy) {
// No deprecated features at this time.
EXPECT_TRUE(true);
}
// Classic sets
TEST_F(TApp, SetWithDefaults) {
int someint = 2;
app.add_set("-a", someint, {1, 2, 3, 4}, "", true);
args = {"-a1", "-a2"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, SetWithDefaultsConversion) {
int someint = 2;
app.add_set("-a", someint, {1, 2, 3, 4}, "", true);
args = {"-a", "hi"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, SetWithDefaultsIC) {
std::string someint = "ho";
app.add_set_ignore_case("-a", someint, {"Hi", "Ho"}, "", true);
args = {"-aHi", "-aHo"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, InSet) {
std::string choice;
app.add_set("-q,--quick", choice, {"one", "two", "three"});
args = {"--quick", "two"};
run();
EXPECT_EQ("two", choice);
args = {"--quick", "four"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, InSetWithDefault) {
std::string choice = "one";
app.add_set("-q,--quick", choice, {"one", "two", "three"}, "", true);
run();
EXPECT_EQ("one", choice);
args = {"--quick", "two"};
run();
EXPECT_EQ("two", choice);
args = {"--quick", "four"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, InCaselessSetWithDefault) {
std::string choice = "one";
app.add_set_ignore_case("-q,--quick", choice, {"one", "two", "three"}, "", true);
run();
EXPECT_EQ("one", choice);
args = {"--quick", "tWo"};
run();
EXPECT_EQ("two", choice);
args = {"--quick", "four"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, InIntSet) {
int choice;
app.add_set("-q,--quick", choice, {1, 2, 3});
args = {"--quick", "2"};
run();
EXPECT_EQ(2, choice);
args = {"--quick", "4"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, InIntSetWindows) {
int choice;
app.add_set("-q,--quick", choice, {1, 2, 3});
app.allow_windows_style_options();
args = {"/q", "2"};
run();
EXPECT_EQ(2, choice);
args = {"/q", "4"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"/q4"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(TApp, FailSet) {
int choice;
app.add_set("-q,--quick", choice, {1, 2, 3});
args = {"--quick", "3", "--quick=2"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
args = {"--quick=hello"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, FailMutableSet) {
int choice;
std::set<int> vals{1, 2, 3};
app.add_mutable_set("-q,--quick", choice, vals);
app.add_mutable_set("-s,--slow", choice, vals, "", true);
args = {"--quick=hello"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--slow=hello"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, InSetIgnoreCase) {
std::string choice;
app.add_set_ignore_case("-q,--quick", choice, {"one", "Two", "THREE"});
args = {"--quick", "One"};
run();
EXPECT_EQ("one", choice);
args = {"--quick", "two"};
run();
EXPECT_EQ("Two", choice); // Keeps caps from set
args = {"--quick", "ThrEE"};
run();
EXPECT_EQ("THREE", choice); // Keeps caps from set
args = {"--quick", "four"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--quick=one", "--quick=two"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, InSetIgnoreCaseMutableValue) {
std::set<std::string> options{"one", "Two", "THREE"};
std::string choice;
app.add_mutable_set_ignore_case("-q,--quick", choice, options);
args = {"--quick", "One"};
run();
EXPECT_EQ("one", choice);
args = {"--quick", "two"};
run();
EXPECT_EQ("Two", choice); // Keeps caps from set
args = {"--quick", "ThrEE"};
run();
EXPECT_EQ("THREE", choice); // Keeps caps from set
options.clear();
args = {"--quick", "ThrEE"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, InSetIgnoreCasePointer) {
auto options = std::make_shared<std::set<std::string>>(std::initializer_list<std::string>{"one", "Two", "THREE"});
std::string choice;
app.add_set_ignore_case("-q,--quick", choice, *options);
args = {"--quick", "One"};
run();
EXPECT_EQ("one", choice);
args = {"--quick", "two"};
run();
EXPECT_EQ("Two", choice); // Keeps caps from set
args = {"--quick", "ThrEE"};
run();
EXPECT_EQ("THREE", choice); // Keeps caps from set
options.reset();
args = {"--quick", "ThrEE"};
run();
EXPECT_EQ("THREE", choice); // this does not throw a segfault
args = {"--quick", "four"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--quick=one", "--quick=two"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, InSetIgnoreUnderscore) {
std::string choice;
app.add_set_ignore_underscore("-q,--quick", choice, {"option_one", "option_two", "optionthree"});
args = {"--quick", "option_one"};
run();
EXPECT_EQ("option_one", choice);
args = {"--quick", "optiontwo"};
run();
EXPECT_EQ("option_two", choice); // Keeps underscore from set
args = {"--quick", "_option_thr_ee"};
run();
EXPECT_EQ("optionthree", choice); // no underscore
args = {"--quick", "Option4"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--quick=option_one", "--quick=option_two"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, InSetIgnoreCaseUnderscore) {
std::string choice;
app.add_set_ignore_case_underscore("-q,--quick", choice, {"Option_One", "option_two", "OptionThree"});
args = {"--quick", "option_one"};
run();
EXPECT_EQ("Option_One", choice);
args = {"--quick", "OptionTwo"};
run();
EXPECT_EQ("option_two", choice); // Keeps underscore and case from set
args = {"--quick", "_OPTION_thr_ee"};
run();
EXPECT_EQ("OptionThree", choice); // no underscore
args = {"--quick", "Option4"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--quick=option_one", "--quick=option_two"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
// #113
TEST_F(TApp, AddRemoveSetItems) {
std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"};
std::string type1, type2;
app.add_mutable_set("--type1", type1, items);
app.add_mutable_set("--type2", type2, items, "", true);
args = {"--type1", "TYPE1", "--type2", "TYPE2"};
run();
EXPECT_EQ(type1, "TYPE1");
EXPECT_EQ(type2, "TYPE2");
items.insert("TYPE6");
items.insert("TYPE7");
items.erase("TYPE1");
items.erase("TYPE2");
args = {"--type1", "TYPE6", "--type2", "TYPE7"};
run();
EXPECT_EQ(type1, "TYPE6");
EXPECT_EQ(type2, "TYPE7");
args = {"--type1", "TYPE1"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--type2", "TYPE2"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, AddRemoveSetItemsNoCase) {
std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"};
std::string type1, type2;
app.add_mutable_set_ignore_case("--type1", type1, items);
app.add_mutable_set_ignore_case("--type2", type2, items, "", true);
args = {"--type1", "TYPe1", "--type2", "TyPE2"};
run();
EXPECT_EQ(type1, "TYPE1");
EXPECT_EQ(type2, "TYPE2");
items.insert("TYPE6");
items.insert("TYPE7");
items.erase("TYPE1");
items.erase("TYPE2");
args = {"--type1", "TyPE6", "--type2", "tYPE7"};
run();
EXPECT_EQ(type1, "TYPE6");
EXPECT_EQ(type2, "TYPE7");
args = {"--type1", "TYPe1"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--type2", "TYpE2"};
EXPECT_THROW(run(), CLI::ValidationError);
}

View File

@ -280,7 +280,7 @@ TEST(THelp, IntDefaults) {
int one{1}, two{2};
app.add_option("--one", one, "Help for one", true);
app.add_set("--set", two, {2, 3, 4}, "Help for set", true);
app.add_option("--set", two, "Help for set", true)->check(CLI::IsMember({2, 3, 4}));
std::string help = app.help();
@ -295,7 +295,7 @@ TEST(THelp, SetLower) {
CLI::App app{"My prog"};
std::string def{"One"};
app.add_set_ignore_case("--set", def, {"oNe", "twO", "THREE"}, "Help for set", true);
app.add_option("--set", def, "Help for set", true)->check(CLI::IsMember({"oNe", "twO", "THREE"}));
std::string help = app.help();
@ -795,7 +795,7 @@ TEST(THelp, ChangingSet) {
std::set<int> vals{1, 2, 3};
int val;
app.add_mutable_set("--val", val, vals);
app.add_option("--val", val)->check(CLI::IsMember(&vals));
std::string help = app.help();
@ -816,7 +816,7 @@ TEST(THelp, ChangingSetDefaulted) {
std::set<int> vals{1, 2, 3};
int val = 2;
app.add_mutable_set("--val", val, vals, "", true);
app.add_option("--val", val, "", true)->check(CLI::IsMember(&vals));
std::string help = app.help();
@ -836,7 +836,7 @@ TEST(THelp, ChangingCaselessSet) {
std::set<std::string> vals{"1", "2", "3"};
std::string val;
app.add_mutable_set_ignore_case("--val", val, vals);
app.add_option("--val", val)->check(CLI::IsMember(&vals, CLI::ignore_case));
std::string help = app.help();
@ -857,7 +857,7 @@ TEST(THelp, ChangingCaselessSetDefaulted) {
std::set<std::string> vals{"1", "2", "3"};
std::string val = "2";
app.add_mutable_set_ignore_case("--val", val, vals, "", true);
app.add_option("--val", val, "", true)->check(CLI::IsMember(&vals, CLI::ignore_case));
std::string help = app.help();

View File

@ -603,9 +603,9 @@ TEST_F(TApp, IniFlags) {
run();
EXPECT_EQ(2, two);
EXPECT_EQ(true, three);
EXPECT_EQ(true, four);
EXPECT_EQ(true, five);
EXPECT_TRUE(three);
EXPECT_TRUE(four);
EXPECT_TRUE(five);
}
TEST_F(TApp, IniFalseFlags) {
@ -631,9 +631,9 @@ TEST_F(TApp, IniFalseFlags) {
run();
EXPECT_EQ(-2, two);
EXPECT_EQ(false, three);
EXPECT_EQ(true, four);
EXPECT_EQ(true, five);
EXPECT_FALSE(three);
EXPECT_TRUE(four);
EXPECT_TRUE(five);
}
TEST_F(TApp, IniFalseFlagsDef) {
@ -659,9 +659,9 @@ TEST_F(TApp, IniFalseFlagsDef) {
run();
EXPECT_EQ(-2, two);
EXPECT_EQ(true, three);
EXPECT_EQ(false, four);
EXPECT_EQ(true, five);
EXPECT_TRUE(three);
EXPECT_FALSE(four);
EXPECT_TRUE(five);
}
TEST_F(TApp, IniOutputSimple) {
@ -767,7 +767,7 @@ TEST_F(TApp, IniOutputFlag) {
TEST_F(TApp, IniOutputSet) {
int v;
app.add_set("--simple", v, {1, 2, 3});
app.add_option("--simple", v)->check(CLI::IsMember({1, 2, 3}));
args = {"--simple=2"};

451
tests/SetTest.cpp Normal file
View File

@ -0,0 +1,451 @@
#include "app_helper.hpp"
static_assert(CLI::is_shared_ptr<std::shared_ptr<int>>::value == true, "is_shared_ptr should work on shared pointers");
static_assert(CLI::is_shared_ptr<int *>::value == false, "is_shared_ptr should work on pointers");
static_assert(CLI::is_shared_ptr<int>::value == false, "is_shared_ptr should work on non-pointers");
static_assert(CLI::is_copyable_ptr<std::shared_ptr<int>>::value == true,
"is_copyable_ptr should work on shared pointers");
static_assert(CLI::is_copyable_ptr<int *>::value == true, "is_copyable_ptr should work on pointers");
static_assert(CLI::is_copyable_ptr<int>::value == false, "is_copyable_ptr should work on non-pointers");
TEST_F(TApp, SimpleSets) {
std::string value;
auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set<std::string>({"one", "two", "three"})});
args = {"-s", "one"};
run();
EXPECT_EQ(1u, app.count("-s"));
EXPECT_EQ(1u, app.count("--set"));
EXPECT_EQ(1u, opt->count());
EXPECT_EQ(value, "one");
}
TEST_F(TApp, SimpleSetsPtrs) {
auto set = std::shared_ptr<std::set<std::string>>(new std::set<std::string>{"one", "two", "three"});
std::string value;
auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{set});
args = {"-s", "one"};
run();
EXPECT_EQ(1u, app.count("-s"));
EXPECT_EQ(1u, app.count("--set"));
EXPECT_EQ(1u, opt->count());
EXPECT_EQ(value, "one");
set->insert("four");
args = {"-s", "four"};
run();
EXPECT_EQ(1u, app.count("-s"));
EXPECT_EQ(1u, app.count("--set"));
EXPECT_EQ(1u, opt->count());
EXPECT_EQ(value, "four");
}
TEST_F(TApp, SimiShortcutSets) {
std::string value;
auto opt = app.add_option("--set", value)->check(CLI::IsMember({"one", "two", "three"}));
args = {"--set", "one"};
run();
EXPECT_EQ(1u, app.count("--set"));
EXPECT_EQ(1u, opt->count());
EXPECT_EQ(value, "one");
std::string value2;
auto opt2 = app.add_option("--set2", value2)->check(CLI::IsMember({"One", "two", "three"}, CLI::ignore_case));
args = {"--set2", "onE"};
run();
EXPECT_EQ(1u, app.count("--set2"));
EXPECT_EQ(1u, opt2->count());
EXPECT_EQ(value2, "One");
std::string value3;
auto opt3 = app.add_option("--set3", value3)
->check(CLI::IsMember({"O_ne", "two", "three"}, CLI::ignore_case, CLI::ignore_underscore));
args = {"--set3", "onE"};
run();
EXPECT_EQ(1u, app.count("--set3"));
EXPECT_EQ(1u, opt3->count());
EXPECT_EQ(value3, "O_ne");
}
TEST_F(TApp, SetFromCharStarArrayVector) {
constexpr const char *names[] = {"one", "two", "three"};
std::string value;
auto opt = app.add_option("-s,--set", value)
->check(CLI::IsMember{std::vector<std::string>(std::begin(names), std::end(names))});
args = {"-s", "one"};
run();
EXPECT_EQ(1u, app.count("-s"));
EXPECT_EQ(1u, app.count("--set"));
EXPECT_EQ(1u, opt->count());
EXPECT_EQ(value, "one");
}
TEST_F(TApp, OtherTypeSets) {
int value;
std::vector<int> set = {2, 3, 4};
auto opt = app.add_option("--set", value)->check(CLI::IsMember(set));
args = {"--set", "3"};
run();
EXPECT_EQ(1u, app.count("--set"));
EXPECT_EQ(1u, opt->count());
EXPECT_EQ(value, 3);
args = {"--set", "5"};
EXPECT_THROW(run(), CLI::ValidationError);
std::vector<int> set2 = {-2, 3, 4};
auto opt2 = app.add_option("--set2", value)->check(CLI::IsMember(set2, [](int x) { return std::abs(x); }));
args = {"--set2", "-3"};
run();
EXPECT_EQ(1u, app.count("--set2"));
EXPECT_EQ(1u, opt2->count());
EXPECT_EQ(value, 3);
args = {"--set2", "-3"};
run();
EXPECT_EQ(1u, app.count("--set2"));
EXPECT_EQ(1u, opt2->count());
EXPECT_EQ(value, 3);
args = {"--set2", "2"};
run();
EXPECT_EQ(1u, app.count("--set2"));
EXPECT_EQ(1u, opt2->count());
EXPECT_EQ(value, -2);
}
TEST_F(TApp, NumericalSets) {
int value;
auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set<int>({1, 2, 3})});
args = {"-s", "1"};
run();
EXPECT_EQ(1u, app.count("-s"));
EXPECT_EQ(1u, app.count("--set"));
EXPECT_EQ(1u, opt->count());
EXPECT_EQ(value, 1);
}
// Converted original set tests
TEST_F(TApp, SetWithDefaults) {
int someint = 2;
app.add_option("-a", someint, "", true)->check(CLI::IsMember({1, 2, 3, 4}));
args = {"-a1", "-a2"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, SetWithDefaultsConversion) {
int someint = 2;
app.add_option("-a", someint, "", true)->check(CLI::IsMember({1, 2, 3, 4}));
args = {"-a", "hi"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, SetWithDefaultsIC) {
std::string someint = "ho";
app.add_option("-a", someint, "", true)->check(CLI::IsMember({"Hi", "Ho"}));
args = {"-aHi", "-aHo"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, InSet) {
std::string choice;
app.add_option("-q,--quick", choice)->check(CLI::IsMember({"one", "two", "three"}));
args = {"--quick", "two"};
run();
EXPECT_EQ("two", choice);
args = {"--quick", "four"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, InSetWithDefault) {
std::string choice = "one";
app.add_option("-q,--quick", choice, "", true)->check(CLI::IsMember({"one", "two", "three"}));
run();
EXPECT_EQ("one", choice);
args = {"--quick", "two"};
run();
EXPECT_EQ("two", choice);
args = {"--quick", "four"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, InCaselessSetWithDefault) {
std::string choice = "one";
app.add_option("-q,--quick", choice, "", true)->check(CLI::IsMember({"one", "two", "three"}, CLI::ignore_case));
run();
EXPECT_EQ("one", choice);
args = {"--quick", "tWo"};
run();
EXPECT_EQ("two", choice);
args = {"--quick", "four"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, InIntSet) {
int choice;
app.add_option("-q,--quick", choice)->check(CLI::IsMember({1, 2, 3}));
args = {"--quick", "2"};
run();
EXPECT_EQ(2, choice);
args = {"--quick", "4"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, InIntSetWindows) {
int choice;
app.add_option("-q,--quick", choice)->check(CLI::IsMember({1, 2, 3}));
app.allow_windows_style_options();
args = {"/q", "2"};
run();
EXPECT_EQ(2, choice);
args = {"/q", "4"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"/q4"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(TApp, FailSet) {
int choice;
app.add_option("-q,--quick", choice)->check(CLI::IsMember({1, 2, 3}));
args = {"--quick", "3", "--quick=2"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
args = {"--quick=hello"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, FailMutableSet) {
int choice;
auto vals = std::shared_ptr<std::set<int>>(new std::set<int>({1, 2, 3}));
app.add_option("-q,--quick", choice)->check(CLI::IsMember(vals));
app.add_option("-s,--slow", choice, "", true)->check(CLI::IsMember(vals));
args = {"--quick=hello"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--slow=hello"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, InSetIgnoreCase) {
std::string choice;
app.add_option("-q,--quick", choice)->check(CLI::IsMember({"one", "Two", "THREE"}, CLI::ignore_case));
args = {"--quick", "One"};
run();
EXPECT_EQ("one", choice);
args = {"--quick", "two"};
run();
EXPECT_EQ("Two", choice); // Keeps caps from set
args = {"--quick", "ThrEE"};
run();
EXPECT_EQ("THREE", choice); // Keeps caps from set
args = {"--quick", "four"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--quick=one", "--quick=two"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, InSetIgnoreCaseMutableValue) {
std::set<std::string> options{"one", "Two", "THREE"};
std::string choice;
app.add_option("-q,--quick", choice)->check(CLI::IsMember(&options, CLI::ignore_case));
args = {"--quick", "One"};
run();
EXPECT_EQ("one", choice);
args = {"--quick", "two"};
run();
EXPECT_EQ("Two", choice); // Keeps caps from set
args = {"--quick", "ThrEE"};
run();
EXPECT_EQ("THREE", choice); // Keeps caps from set
options.clear();
args = {"--quick", "ThrEE"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, InSetIgnoreCasePointer) {
std::set<std::string> *options = new std::set<std::string>{"one", "Two", "THREE"};
std::string choice;
app.add_option("-q,--quick", choice)->check(CLI::IsMember(*options, CLI::ignore_case));
args = {"--quick", "One"};
run();
EXPECT_EQ("one", choice);
args = {"--quick", "two"};
run();
EXPECT_EQ("Two", choice); // Keeps caps from set
args = {"--quick", "ThrEE"};
run();
EXPECT_EQ("THREE", choice); // Keeps caps from set
delete options;
args = {"--quick", "ThrEE"};
run();
EXPECT_EQ("THREE", choice); // this does not throw a segfault
args = {"--quick", "four"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--quick=one", "--quick=two"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, InSetIgnoreUnderscore) {
std::string choice;
app.add_option("-q,--quick", choice)
->check(CLI::IsMember({"option_one", "option_two", "optionthree"}, CLI::ignore_underscore));
args = {"--quick", "option_one"};
run();
EXPECT_EQ("option_one", choice);
args = {"--quick", "optiontwo"};
run();
EXPECT_EQ("option_two", choice); // Keeps underscore from set
args = {"--quick", "_option_thr_ee"};
run();
EXPECT_EQ("optionthree", choice); // no underscore
args = {"--quick", "Option4"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--quick=option_one", "--quick=option_two"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, InSetIgnoreCaseUnderscore) {
std::string choice;
app.add_option("-q,--quick", choice)
->check(CLI::IsMember({"Option_One", "option_two", "OptionThree"}, CLI::ignore_case, CLI::ignore_underscore));
args = {"--quick", "option_one"};
run();
EXPECT_EQ("Option_One", choice);
args = {"--quick", "OptionTwo"};
run();
EXPECT_EQ("option_two", choice); // Keeps underscore and case from set
args = {"--quick", "_OPTION_thr_ee"};
run();
EXPECT_EQ("OptionThree", choice); // no underscore
args = {"--quick", "Option4"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--quick=option_one", "--quick=option_two"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
// #113
TEST_F(TApp, AddRemoveSetItems) {
std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"};
std::string type1, type2;
app.add_option("--type1", type1)->check(CLI::IsMember(&items));
app.add_option("--type2", type2, "", true)->check(CLI::IsMember(&items));
args = {"--type1", "TYPE1", "--type2", "TYPE2"};
run();
EXPECT_EQ(type1, "TYPE1");
EXPECT_EQ(type2, "TYPE2");
items.insert("TYPE6");
items.insert("TYPE7");
items.erase("TYPE1");
items.erase("TYPE2");
args = {"--type1", "TYPE6", "--type2", "TYPE7"};
run();
EXPECT_EQ(type1, "TYPE6");
EXPECT_EQ(type2, "TYPE7");
args = {"--type1", "TYPE1"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--type2", "TYPE2"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, AddRemoveSetItemsNoCase) {
std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"};
std::string type1, type2;
app.add_option("--type1", type1)->check(CLI::IsMember(&items, CLI::ignore_case));
app.add_option("--type2", type2, "", true)->check(CLI::IsMember(&items, CLI::ignore_case));
args = {"--type1", "TYPe1", "--type2", "TyPE2"};
run();
EXPECT_EQ(type1, "TYPE1");
EXPECT_EQ(type2, "TYPE2");
items.insert("TYPE6");
items.insert("TYPE7");
items.erase("TYPE1");
items.erase("TYPE2");
args = {"--type1", "TyPE6", "--type2", "tYPE7"};
run();
EXPECT_EQ(type1, "TYPE6");
EXPECT_EQ(type2, "TYPE7");
args = {"--type1", "TYPe1"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--type2", "TYpE2"};
EXPECT_THROW(run(), CLI::ValidationError);
}

View File

@ -888,8 +888,8 @@ TEST_F(TApp, UnnamedSubMixExtras) {
run();
EXPECT_EQ(val, -3.0);
EXPECT_EQ(val2, 5.93);
EXPECT_EQ(app.remaining_size(), 2);
EXPECT_EQ(sub->remaining_size(), 0);
EXPECT_EQ(app.remaining_size(), 2u);
EXPECT_EQ(sub->remaining_size(), 0u);
}
TEST_F(TApp, UnnamedSubNoExtras) {
@ -901,8 +901,8 @@ TEST_F(TApp, UnnamedSubNoExtras) {
run();
EXPECT_EQ(val, -3.0);
EXPECT_EQ(val2, 5.93);
EXPECT_EQ(app.remaining_size(), 0);
EXPECT_EQ(sub->remaining_size(), 0);
EXPECT_EQ(app.remaining_size(), 0u);
EXPECT_EQ(sub->remaining_size(), 0u);
}
TEST(SharedSubTests, SharedSubcommand) {

30
tests/TrueFalseTest.cpp Normal file
View File

@ -0,0 +1,30 @@
#include "app_helper.hpp"
/// This allows a set of strings to be run over by a test
struct TApp_TBO : public TApp, public ::testing::WithParamInterface<const char *> {};
TEST_P(TApp_TBO, TrueBoolOption) {
bool value = false; // Not used, but set just in case
app.add_option("-b,--bool", value);
args = {"--bool", GetParam()};
run();
EXPECT_EQ(1u, app.count("--bool"));
EXPECT_TRUE(value);
}
// Change to INSTANTIATE_TEST_SUITE_P in GTest master
INSTANTIATE_TEST_CASE_P(TrueBoolOptions, TApp_TBO, ::testing::Values("true", "on", "True", "ON"), );
/// This allows a set of strings to be run over by a test
struct TApp_FBO : public TApp, public ::testing::WithParamInterface<const char *> {};
TEST_P(TApp_FBO, FalseBoolOptions) {
bool value = true; // Not used, but set just in case
app.add_option("-b,--bool", value);
args = {"--bool", GetParam()};
run();
EXPECT_EQ(1u, app.count("--bool"));
EXPECT_FALSE(value);
}
INSTANTIATE_TEST_CASE_P(FalseBoolOptions, TApp_FBO, ::testing::Values("false", "off", "False", "OFF"), );