1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-30 04:33:53 +00:00

To string and default option revamp (#242)

* First streaming version

* Using to_string instead

* Switching to new backend

* Moving to capture function for defaults

* Rename capture_default + _str

* defaultval -> default_str, added always_capture_default

* Fix style

* Adding tests and docs to readme

* Dropping macOS on Travis (supported through Azure)
This commit is contained in:
Henry Schreiner 2019-04-28 22:44:30 +02:00 committed by GitHub
parent ca4bc6a6fc
commit d81843002a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 527 additions and 146 deletions

View File

@ -118,16 +118,6 @@ matrix:
script:
- .ci/make_and_test.sh 11
# macOS and clang
- os: osx
compiler: clang
install:
- brew update
- echo 'brew "python"' > Brewfile
- echo 'brew "ccache"' >> Brewfile
- brew bundle
- python -m ensurepip --user
install: skip
script:

View File

@ -1,22 +1,30 @@
## Version 1.8: Sets and Flags (IN PROGRESS)
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. You can also use a collection of pairs (like `std::map`) to transform the match into an output. Also new are inverted flags, which can cancel or reduce the count of flags, and can also support general flag types. 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. You can also use a collection of pairs (like `std::map`) to transform the match into an output. Also new are inverted flags, which can cancel or reduce the count of flags, and can also support general flag types. 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. The final bool "defaults" flag when creating options has been replaced by `->capture_default_str()` (ending an old limitation in construction made this possible); the old method is still available but may be removed in future versions.
* Replaced default help capture: `.add_option("name", value, "", True)` becomes `.add_option("name", value)->capture_default_str()` [#242]
* Added `.always_capture_default()` [#242]
* New `CLI::IsMember` validator replaces set validation [#222]
* IsMember also supports container of pairs, transform allows modification of result [#228]
* Much more powerful flags with different values [#211], general types [#235]
* `add_option` now supports bool due to unified bool handling [#211]
* Support for composable unnamed subcommands [#216]
* Reparsing is better supported with `.remaining_for_passthrough()` [#265]
* Custom vector separator using `->delimiter(char)` [#209], [#221], [#240]
* Validators added for IP4 addresses and positive numbers [#210]
* Validators added for IP4 addresses and positive numbers [#210] and numbers [#262]
* Minimum required Boost for optional Optionals has been corrected to 1.61 [#226]
* Positionals can stop options from being parsed with `app.positionals_at_end()` [#223]
* Added `validate_positionals` [#262]
* Positional parsing is much more powerful [#251], duplicates supported []#247]
* Validators can be negated with `!` [#230], and now handle tname functions [#228]
* Better enum support and streaming helper [#233] and [#228]
* Cleanup for shadow warnings [#232]
* Better alignment on multiline descriptions [#269]
* Better support for aarch64 [#266]
> ### Converting from CLI11 1.7:
>
> * `.add_option(..., true)` should be replaced by `.add_option(...)->capture_default_str()` or `app.option_defaults()->always_capture_default()` can be used
> * `app.add_set("--name", value, {"choice1", "choice2"})` should become `app.add_option("--name", value)->check(CLI::IsMember({"choice1", "choice2"}))`
> * 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`
@ -39,6 +47,13 @@ Set handling has been completely replaced by a new backend that works as a Valid
[#233]: https://github.com/CLIUtils/CLI11/pull/233
[#235]: https://github.com/CLIUtils/CLI11/pull/235
[#240]: https://github.com/CLIUtils/CLI11/pull/240
[#242]: https://github.com/CLIUtils/CLI11/pull/242
[#247]: https://github.com/CLIUtils/CLI11/pull/247
[#251]: https://github.com/CLIUtils/CLI11/pull/251
[#262]: https://github.com/CLIUtils/CLI11/pull/262
[#265]: https://github.com/CLIUtils/CLI11/pull/265
[#266]: https://github.com/CLIUtils/CLI11/pull/266
[#269]: https://github.com/CLIUtils/CLI11/pull/269
## Version 1.7.1: Quick patch

View File

@ -74,7 +74,7 @@ An acceptable CLI parser library should be all of the following:
- Short, simple syntax: This is one of the main reasons to use a CLI parser, it should make variables from the command line nearly as easy to define as any other variables. If most of your program is hidden in CLI parsing, this is a problem for readability.
- C++11 or better: Should work with GCC 4.8+ (default on CentOS/RHEL 7), Clang 3.5+, AppleClang 7+, NVCC 7.0+, or MSVC 2015+.
- Work on Linux, macOS, and Windows.
- Well tested using [Travis][] (Linux and macOS) and [AppVeyor][] (Windows) or [Azure][] (all three). "Well" is defined as having good coverage measured by [CodeCov][].
- Well tested using [Travis][] (Linux) and [AppVeyor][] (Windows) or [Azure][] (all three). "Well" is defined as having good coverage measured by [CodeCov][].
- Clear help printing.
- Nice error messages.
- Standard shell idioms supported naturally, like grouping flags, a positional separator, etc.
@ -193,8 +193,7 @@ app.add_option(option_name, help_str="") // 🚧
app.add_option(option_name,
variable_to_bind_to, // bool, int, float, vector, 🚧 enum, or string-like, or anything with a defined conversion from a string
help_string="",
default=false)
help_string="")
app.add_option_function<type>(option_name,
function <void(const type &value)>, // 🚧 int, bool, float, enum, vector, or string-like, or anything with a defined conversion from a string
@ -225,8 +224,7 @@ Option_group *app.add_option_group(name,description); // 🚧
-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)
- help_string="")
-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
@ -236,7 +234,7 @@ Option_group *app.add_option_group(name,description); // 🚧
-app.add_mutable_set_ignore_case_underscore(... // 🆕 String only
```
An option name must start with a alphabetic character, underscore, a number 🚧, '?'🚧, or '@'🚧. For long options, after the first character '.', and '-' are also valid characters. For the `add_flag*` functions '{' has special meaning. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on the help line for its positional form. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option`.
An option name must start with a alphabetic character, underscore, a number 🚧, '?' 🚧, or '@' 🚧. For long options, after the first character '.', and '-' are also valid characters. For the `add_flag*` functions '{' has special meaning. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on the help line for its positional form.
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. The function should throw an error (`CLI::ConversionError` or `CLI::ValidationError` possibly) if the value is not valid.
@ -285,16 +283,19 @@ Before parsing, you can set the following options:
- `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden).
- `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
- `->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
- `->disable_flag_override()`: 🚧 from the command line long form flag options can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line. The `=` does not work with short form flag options.
- `->disable_flag_override()`: 🚧 From the command line long form flag options can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line. The `=` does not work with short form flag options.
- `->delimiter(char)`: 🚧 allows specification of a custom delimiter for separating single arguments into vector arguments, for example specifying `->delimiter(',')` on an option would result in `--opt=1,2,3` producing 3 elements of a vector and the equivalent of --opt 1 2 3 assuming opt is a vector value.
- `->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(std::string(const std::string &), validator_name="",validator_description="")`: 🚧 define a check function. The function should return a non empty string with the error message if the check fails
- `->check(std::string(const std::string &), validator_name="",validator_description="")`: 🚧 Define a check function. The function should return a non empty string with the error message if the check fails
- `->check(Validator)`:🚧 Use a Validator object to do the check see [Validators](#validators) for a description of available Validators and how to create new ones.
- `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options.
- `->transform(Validator)`: uses a Validator object to do the transformation see [Validators](#validators) for a description of available Validators and how to create new ones.
- `->each(void(const std::string &)>`: Run this function on each value received, as it is received. It should throw a `ValidationError` if an error is encountered.
- `->configurable(false)`: Disable this option from being in a configuration file.
`->capture_default_str()`: 🚧 Store the current value attached and display it in the help string.
`->default_function(std::string())`: 🚧 Advanced: Change the function that `capture_default_str()` uses.
`->always_capture_default()`: 🚧 Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`.
These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. The `each` function 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 `each`, `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. Operations added through `transform` are executed first in reverse order of addition, and `check` and `each` are run following the transform functions in order of addition. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results.
@ -349,20 +350,25 @@ CLI11 has several Validators built-in that perform some common checks
- `CLI::ValidIPV4`: 🚧 Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'`.
These Validators can be used by simply passing the name into the `check` or `transform` methods on an option
```cpp
->check(CLI::ExistingFile);
->check(CLI::Range(0,10));
```
Validators can be merged using `&` and `|` and inverted using `!`🚧. For example
Validators can be merged using `&` and `|` and inverted using `!` 🚧. For example:
```cpp
->check(CLI::Range(0,10)|CLI::Range(20,30));
```
will produce a check to ensure a value is between 0 and 10 or 20 and 30.
```cpp
->check(!CLI::PositiveNumber);
```
will produce a check for a number less than 0;
will produce a check for a number less than 0.
##### Transforming Validators
There are a few built in Validators that let you transform values if used with the `transform` function. If they also do some checks then they can be used `check` but some may do nothing in that case.

View File

@ -28,8 +28,8 @@ class ConfigJSON : public CLI::Config {
j[name] = opt->results();
// If the option has a default and is requested by optional argument
else if(default_also && !opt->get_defaultval().empty())
j[name] = opt->get_defaultval();
else if(default_also && !opt->get_default_str().empty())
j[name] = opt->get_default_str();
// Flag, one passed
} else if(opt->count() == 1) {

View File

@ -437,17 +437,33 @@ class App {
Option *add_option(std::string option_name,
callback_t option_callback,
std::string option_description = "",
bool defaulted = false) {
Option myopt{option_name, option_description, option_callback, defaulted, this};
bool defaulted = false,
std::function<std::string()> func = {}) {
Option myopt{option_name, option_description, option_callback, this};
if(std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) {
return *v == myopt;
}) == std::end(options_)) {
options_.emplace_back();
Option_p &option = options_.back();
option.reset(new Option(option_name, option_description, option_callback, defaulted, this));
option.reset(new Option(option_name, option_description, option_callback, this));
// Set the default string capture function
option->default_function(func);
// For compatibility with CLI11 1.7 and before, capture the default string here
if(defaulted)
option->capture_default_str();
// Transfer defaults to the new option
option_defaults_.copy_to(option.get());
// Don't bother to capture if we already did
if(!defaulted && option->get_always_capture_default())
option->capture_default_str();
return option.get();
} else
throw OptionAlreadyAdded(myopt.get_name());
}
@ -456,12 +472,16 @@ class App {
template <typename T, enable_if_t<!is_vector<T>::value & !std::is_const<T>::value, detail::enabler> = detail::dummy>
Option *add_option(std::string option_name,
T &variable, ///< The variable to set
std::string option_description = "") {
std::string option_description = "",
bool defaulted = false) {
CLI::callback_t fun = [&variable](CLI::results_t res) { return detail::lexical_cast(res[0], variable); };
auto fun = [&variable](CLI::results_t res) { return detail::lexical_cast(res[0], variable); };
Option *opt = add_option(option_name, fun, option_description, false);
Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() {
return std::string(CLI::detail::to_string(variable));
});
opt->type_name(detail::type_name<T>());
return opt;
}
@ -471,7 +491,7 @@ class App {
const std::function<void(const T &)> &func, ///< the callback to execute
std::string option_description = "") {
CLI::callback_t fun = [func](CLI::results_t res) {
auto fun = [func](CLI::results_t res) {
T variable;
bool result = detail::lexical_cast(res[0], variable);
if(result) {
@ -484,6 +504,7 @@ class App {
opt->type_name(detail::type_name<T>());
return opt;
}
/// Add option with no description or variable assignment
Option *add_option(std::string option_name) {
return add_option(option_name, CLI::callback_t(), std::string{}, false);
@ -497,34 +518,14 @@ class App {
return add_option(option_name, CLI::callback_t(), option_description, false);
}
/// Add option for non-vectors with a default print, allow template to specify conversion type
template <typename T,
typename XC = T,
enable_if_t<!is_vector<XC>::value && !std::is_const<XC>::value, detail::enabler> = detail::dummy>
Option *add_option(std::string option_name,
T &variable, ///< The variable to set
std::string option_description,
bool defaulted) {
static_assert(std::is_constructible<T, XC>::value, "assign type must be assignable from conversion type");
CLI::callback_t fun = [&variable](CLI::results_t res) { return detail::lexical_cast<XC>(res[0], variable); };
Option *opt = add_option(option_name, fun, option_description, defaulted);
opt->type_name(detail::type_name<XC>());
if(defaulted) {
std::stringstream out;
out << variable;
opt->default_str(out.str());
}
return opt;
}
/// Add option for vectors
template <typename T>
Option *add_option(std::string option_name,
std::vector<T> &variable, ///< The variable vector to set
std::string option_description = "") {
std::string option_description = "",
bool defaulted = false) {
CLI::callback_t fun = [&variable](CLI::results_t res) {
auto fun = [&variable](CLI::results_t res) {
bool retval = true;
variable.clear();
variable.reserve(res.size());
@ -536,35 +537,18 @@ class App {
return (!variable.empty()) && retval;
};
Option *opt = add_option(option_name, fun, option_description, false);
opt->type_name(detail::type_name<T>())->type_size(-1);
return opt;
}
/// Add option for vectors with defaulted argument
template <typename T>
Option *add_option(std::string option_name,
std::vector<T> &variable, ///< The variable vector to set
std::string option_description,
bool defaulted) {
CLI::callback_t fun = [&variable](CLI::results_t res) {
bool retval = true;
variable.clear();
variable.reserve(res.size());
for(const auto &elem : res) {
variable.emplace_back();
retval &= detail::lexical_cast(elem, variable.back());
}
return (!variable.empty()) && retval;
auto default_function = [&variable]() {
std::vector<std::string> defaults;
defaults.resize(variable.size());
std::transform(variable.begin(), variable.end(), defaults.begin(), [](T &val) {
return std::string(CLI::detail::to_string(val));
});
return std::string("[" + detail::join(defaults) + "]");
};
Option *opt = add_option(option_name, fun, option_description, defaulted);
Option *opt = add_option(option_name, fun, option_description, defaulted, default_function);
opt->type_name(detail::type_name<T>())->type_size(-1);
if(defaulted)
opt->default_str("[" + detail::join(variable) + "]");
return opt;
}
@ -995,13 +979,16 @@ class App {
return worked;
};
CLI::Option *opt = add_option(option_name, std::move(fun), std::move(option_description), defaulted);
opt->type_name(label)->type_size(2);
if(defaulted) {
auto default_function = [&variable]() {
std::stringstream out;
out << variable;
opt->default_str(out.str());
}
return out.str();
};
CLI::Option *opt =
add_option(option_name, std::move(fun), std::move(option_description), defaulted, default_function);
opt->type_name(label)->type_size(2);
return opt;
}

View File

@ -32,8 +32,8 @@ ConfigINI::to_config(const App *app, bool default_also, bool write_description,
value = detail::ini_join(opt->results());
// If the option has a default and is requested by optional argument
else if(default_also && !opt->get_defaultval().empty())
value = opt->get_defaultval();
else if(default_also && !opt->get_default_str().empty())
value = opt->get_default_str();
// Flag, one passed
} else if(opt->count() == 1) {
value = "true";

View File

@ -232,8 +232,8 @@ inline std::string Formatter::make_option_opts(const Option *opt) const {
if(opt->get_type_size() != 0) {
if(!opt->get_type_name().empty())
out << " " << get_label(opt->get_type_name());
if(!opt->get_defaultval().empty())
out << "=" << opt->get_defaultval();
if(!opt->get_default_str().empty())
out << "=" << opt->get_default_str();
if(opt->get_expected() > 1)
out << " x " << opt->get_expected();
if(opt->get_expected() == -1)

View File

@ -50,10 +50,16 @@ template <typename CRTP> class OptionBase {
/// Allow this option to be given in a configuration file
bool configurable_{true};
/// Disable overriding flag values with '=value'
bool disable_flag_override_{false};
/// Specify a delimiter character for vector arguments
char delimiter_{'\0'};
/// Automatically capture default value
bool always_capture_default_{false};
/// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too)
MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
@ -66,6 +72,7 @@ template <typename CRTP> class OptionBase {
other->configurable(configurable_);
other->disable_flag_override(disable_flag_override_);
other->delimiter(delimiter_);
other->always_capture_default(always_capture_default_);
other->multi_option_policy(multi_option_policy_);
}
@ -87,6 +94,11 @@ template <typename CRTP> class OptionBase {
/// Support Plumbum term
CRTP *mandatory(bool value = true) { return required(value); }
CRTP *always_capture_default(bool value = true) {
always_capture_default_ = value;
return static_cast<CRTP *>(this);
}
// Getters
/// Get the group of this option
@ -107,7 +119,12 @@ template <typename CRTP> class OptionBase {
/// The status of configurable
bool get_disable_flag_override() const { return disable_flag_override_; }
/// Get the current delimeter char
char get_delimiter() const { return delimiter_; }
/// Return true if this will automatically capture the default value for help printing
bool get_always_capture_default() const { return always_capture_default_; }
/// The status of the multi option policy
MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; }
@ -219,16 +236,16 @@ class Option : public OptionBase<Option> {
/// The description for help strings
std::string description_;
/// A human readable default value, usually only set if default is true in creation
std::string defaultval_;
/// A human readable default value, either manually set, captured, or captured by default
std::string default_str_;
/// A human readable type value, set when App creates this
///
/// This is a lambda function so "types" can be dynamic, such as when a set prints its contents.
std::function<std::string()> type_name_{[]() { return std::string(); }};
/// True if this option has a default
bool default_{false};
/// Run this function to capture a default (ignore if empty)
std::function<std::string()> default_function_;
///@}
/// @name Configuration
@ -277,10 +294,8 @@ class Option : public OptionBase<Option> {
Option(std::string option_name,
std::string option_description,
std::function<bool(results_t)> callback,
bool defaulted,
App *parent)
: description_(std::move(option_description)), default_(defaulted), parent_(parent),
callback_(std::move(callback)) {
: description_(std::move(option_description)), parent_(parent), callback_(std::move(callback)) {
std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name));
}
@ -306,6 +321,7 @@ class Option : public OptionBase<Option> {
/// Set the number of expected arguments (Flags don't use this)
Option *expected(int value) {
// Break if this is a flag
if(type_size_ == 0)
throw IncorrectConstruction::SetFlag(get_name(true, true));
@ -532,8 +548,12 @@ class Option : public OptionBase<Option> {
/// The set of options excluded
std::set<Option *> get_excludes() const { return excludes_; }
/// The default value (for help printing) DEPRECATED Use get_default_str() instead
CLI11_DEPRECATED("Use get_default_str() instead")
std::string get_defaultval() const { return default_str_; }
/// The default value (for help printing)
std::string get_defaultval() const { return defaultval_; }
std::string get_default_str() const { return default_str_; }
/// Get the callback function
callback_t get_callback() const { return callback_; }
@ -571,9 +591,6 @@ class Option : public OptionBase<Option> {
((multi_option_policy_ != MultiOptionPolicy::Throw || (expected_ < 0 && type_size_ < 0) ? -1 : 1));
}
/// True if this has a default value
int get_default() const { return default_; }
/// True if the argument can be given directly
bool get_positional() const { return pname_.length() > 0; }
@ -843,7 +860,7 @@ class Option : public OptionBase<Option> {
void results(T &output) const {
bool retval;
if(results_.empty()) {
retval = detail::lexical_cast(defaultval_, output);
retval = detail::lexical_cast(default_str_, output);
} else if(results_.size() == 1) {
retval = detail::lexical_cast(results_[0], output);
} else {
@ -917,13 +934,27 @@ class Option : public OptionBase<Option> {
return this;
}
/// Set the default value string representation
Option *default_str(std::string val) {
defaultval_ = val;
/// Set a capture function for the default. Mostly used by App.
Option *default_function(const std::function<std::string()> &func) {
default_function_ = func;
return this;
}
/// Set the default value string representation and evaluate
/// Capture the default value from the original value (if it can be captured)
Option *capture_default_str() {
if(default_function_) {
default_str_ = default_function_();
}
return this;
}
/// Set the default value string representation (does not change the contained value)
Option *default_str(std::string val) {
default_str_ = val;
return this;
}
/// Set the default value string representation and evaluate into the bound value
Option *default_val(std::string val) {
default_str(val);
auto old_results = results_;

View File

@ -46,6 +46,7 @@
#endif
#if CLI11_BOOST_OPTIONAL
#include <boost/optional.hpp>
#include <boost/optional/optional_io.hpp>
#endif
// [CLI11:verbatim]

View File

@ -124,6 +124,43 @@ struct pair_adaptor<
}
};
// Check for streamability
// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream
template <typename S, typename T> class is_streamable {
template <typename SS, typename TT>
static auto test(int) -> decltype(std::declval<SS &>() << std::declval<TT>(), std::true_type());
template <typename, typename> static auto test(...) -> std::false_type;
public:
static const bool value = decltype(test<S, T>(0))::value;
};
/// Convert an object to a string (directly forward if this can become a string)
template <typename T, enable_if_t<std::is_constructible<std::string, T>::value, detail::enabler> = detail::dummy>
auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
return std::forward<T>(value);
}
/// Convert an object to a string (streaming must be supported for that type)
template <typename T,
enable_if_t<!std::is_constructible<std::string, T>::value && is_streamable<std::stringstream, T>::value,
detail::enabler> = detail::dummy>
std::string to_string(T &&value) {
std::stringstream stream;
stream << value;
return stream.str();
}
/// If conversion is not supported, return an empty string (streaming is not supported for that type)
template <typename T,
enable_if_t<!std::is_constructible<std::string, T>::value && !is_streamable<std::stringstream, T>::value,
detail::enabler> = detail::dummy>
std::string to_string(T &&) {
return std::string{};
}
// Type name print
/// Was going to be based on

View File

@ -674,8 +674,8 @@ TEST_F(TApp, DefaultOpts) {
int i = 3;
std::string s = "HI";
app.add_option("-i,i", i, "", false);
app.add_option("-s,s", s, "", true);
app.add_option("-i,i", i);
app.add_option("-s,s", s)->capture_default_str(); // Used to be different
args = {"-i2", "9"};
@ -1511,7 +1511,7 @@ TEST_F(TApp, VectorDefaultedFixedString) {
std::vector<std::string> strvec{"one"};
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
CLI::Option *opt = app.add_option("-s,--string", strvec, "", true)->expected(3);
CLI::Option *opt = app.add_option("-s,--string", strvec, "")->expected(3)->capture_default_str();
EXPECT_EQ(3, opt->get_expected());
args = {"--string", "mystring", "mystring2", "mystring3"};
@ -1523,7 +1523,7 @@ TEST_F(TApp, VectorDefaultedFixedString) {
TEST_F(TApp, DefaultedResult) {
std::string sval = "NA";
int ival;
auto opts = app.add_option("--string", sval, "", true);
auto opts = app.add_option("--string", sval)->capture_default_str();
auto optv = app.add_option("--val", ival);
args = {};
run();
@ -1947,7 +1947,7 @@ TEST_F(TApp, FallthroughParents) {
TEST_F(TApp, OptionWithDefaults) {
int someint = 2;
app.add_option("-a", someint, "", true);
app.add_option("-a", someint)->capture_default_str();
args = {"-a1", "-a2"};
@ -2091,7 +2091,7 @@ TEST_F(TApp, CustomUserSepParse) {
app.remove_option(opt);
app.add_option("--idx", vals, "", true)->delimiter(',');
app.add_option("--idx", vals)->delimiter(',')->capture_default_str();
run();
EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
}
@ -2131,7 +2131,7 @@ TEST_F(TApp, CustomUserSepParse2) {
app.remove_option(opt);
app.add_option("--idx", vals, "", true)->delimiter(',');
app.add_option("--idx", vals, "")->delimiter(',')->capture_default_str();
run();
EXPECT_EQ(vals, std::vector<int>({1, 2}));
}
@ -2185,13 +2185,13 @@ TEST_F(TApp, CustomUserSepParse4) {
std::vector<int> vals;
args = {"--idx", "1, 2"};
auto opt = app.add_option("--idx", vals)->delimiter(',');
auto opt = app.add_option("--idx", vals)->delimiter(',')->capture_default_str();
run();
EXPECT_EQ(vals, std::vector<int>({1, 2}));
app.remove_option(opt);
app.add_option("--idx", vals, "", true)->delimiter(',');
app.add_option("--idx", vals)->delimiter(',');
run();
EXPECT_EQ(vals, std::vector<int>({1, 2}));
}
@ -2207,7 +2207,7 @@ TEST_F(TApp, CustomUserSepParse5) {
app.remove_option(opt);
args = {"this", "is", "a", "test"};
app.add_option("bar", bar, "bar", true);
app.add_option("bar", bar, "bar")->capture_default_str();
run();
EXPECT_EQ(bar, std::vector<std::string>({"this", "is", "a", "test"}));
}

View File

@ -728,4 +728,36 @@ TEST(ValidatorTests, ValidatorDefaults) {
EXPECT_EQ(V2.get_description(), "check");
EXPECT_TRUE(V2.get_active());
EXPECT_TRUE(V2.get_modifying());
// This class only support streaming in, not out
}
class Unstreamable {
private:
int x_ = -1;
public:
Unstreamable() {}
int get_x() const { return x_; }
void set_x(int x) { x_ = x; }
};
std::istream &operator>>(std::istream &in, Unstreamable &value) {
int x;
in >> x;
value.set_x(x);
return in;
}
TEST_F(TApp, MakeUnstreamableOptiions) {
Unstreamable value;
app.add_option("--value", value);
// This used to fail to build, since it tries to stream from Unstreamable
app.add_option("--value2", value, "", false);
std::vector<Unstreamable> values;
app.add_option("--values", values);
// This used to fail to build, since it tries to stream from Unstreamable
app.add_option("--values2", values, "", false);
}

View File

@ -1,5 +1,10 @@
#include "app_helper.hpp"
#include "gmock/gmock.h"
using ::testing::HasSubstr;
using ::testing::Not;
TEST(Deprecated, Emtpy) {
// No deprecated features at this time.
EXPECT_TRUE(true);
@ -326,3 +331,212 @@ TEST_F(TApp, AddRemoveSetItemsNoCase) {
args = {"--type2", "TYpE2"};
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST(THelp, Defaults) {
CLI::App app{"My prog"};
int one{1}, two{2};
app.add_option("--one", one, "Help for one", true);
app.add_option("--set", two, "Help for set", true)->check(CLI::IsMember({2, 3, 4}));
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("--one"));
EXPECT_THAT(help, HasSubstr("--set"));
EXPECT_THAT(help, HasSubstr("1"));
EXPECT_THAT(help, HasSubstr("=2"));
EXPECT_THAT(help, HasSubstr("2,3,4"));
}
TEST(THelp, VectorOpts) {
CLI::App app{"My prog"};
std::vector<int> x = {1, 2};
app.add_option("-q,--quick", x, "", true);
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("INT=[1,2] ..."));
}
TEST(THelp, SetLower) {
CLI::App app{"My prog"};
std::string def{"One"};
app.add_option("--set", def, "Help for set", true)->check(CLI::IsMember({"oNe", "twO", "THREE"}));
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("--set"));
EXPECT_THAT(help, HasSubstr("=One"));
EXPECT_THAT(help, HasSubstr("oNe"));
EXPECT_THAT(help, HasSubstr("twO"));
EXPECT_THAT(help, HasSubstr("THREE"));
}
TEST(THelp, ChangingSetDefaulted) {
CLI::App app;
std::set<int> vals{1, 2, 3};
int val = 2;
app.add_option("--val", val, "", true)->check(CLI::IsMember(&vals));
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("1"));
EXPECT_THAT(help, Not(HasSubstr("4")));
vals.insert(4);
vals.erase(1);
help = app.help();
EXPECT_THAT(help, Not(HasSubstr("1")));
EXPECT_THAT(help, HasSubstr("4"));
}
TEST(THelp, ChangingCaselessSetDefaulted) {
CLI::App app;
std::set<std::string> vals{"1", "2", "3"};
std::string val = "2";
app.add_option("--val", val, "", true)->check(CLI::IsMember(&vals, CLI::ignore_case));
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("1"));
EXPECT_THAT(help, Not(HasSubstr("4")));
vals.insert("4");
vals.erase("1");
help = app.help();
EXPECT_THAT(help, Not(HasSubstr("1")));
EXPECT_THAT(help, HasSubstr("4"));
}
TEST_F(TApp, DefaultOpts) {
int i = 3;
std::string s = "HI";
app.add_option("-i,i", i, "", false);
app.add_option("-s,s", s, "", true);
args = {"-i2", "9"};
run();
EXPECT_EQ(1u, app.count("i"));
EXPECT_EQ(1u, app.count("-s"));
EXPECT_EQ(2, i);
EXPECT_EQ("9", s);
}
TEST_F(TApp, VectorDefaultedFixedString) {
std::vector<std::string> strvec{"one"};
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
CLI::Option *opt = app.add_option("-s,--string", strvec, "", true)->expected(3);
EXPECT_EQ(3, opt->get_expected());
args = {"--string", "mystring", "mystring2", "mystring3"};
run();
EXPECT_EQ(3u, app.count("--string"));
EXPECT_EQ(answer, strvec);
}
TEST_F(TApp, DefaultedResult) {
std::string sval = "NA";
int ival;
auto opts = app.add_option("--string", sval, "", true);
auto optv = app.add_option("--val", ival);
args = {};
run();
EXPECT_EQ(sval, "NA");
std::string nString;
opts->results(nString);
EXPECT_EQ(nString, "NA");
int newIval;
EXPECT_THROW(optv->results(newIval), CLI::ConversionError);
optv->default_str("442");
optv->results(newIval);
EXPECT_EQ(newIval, 442);
}
TEST_F(TApp, OptionWithDefaults) {
int someint = 2;
app.add_option("-a", someint, "", true);
args = {"-a1", "-a2"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
// #209
TEST_F(TApp, CustomUserSepParse) {
std::vector<int> vals = {1, 2, 3};
args = {"--idx", "1,2,3"};
auto opt = app.add_option("--idx", vals)->delimiter(',');
run();
EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
std::vector<int> vals2;
// check that the results vector gets the results in the same way
opt->results(vals2);
EXPECT_EQ(vals2, vals);
app.remove_option(opt);
app.add_option("--idx", vals, "", true)->delimiter(',');
run();
EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
}
// #209
TEST_F(TApp, CustomUserSepParse2) {
std::vector<int> vals = {1, 2, 3};
args = {"--idx", "1,2,"};
auto opt = app.add_option("--idx", vals)->delimiter(',');
run();
EXPECT_EQ(vals, std::vector<int>({1, 2}));
app.remove_option(opt);
app.add_option("--idx", vals, "", true)->delimiter(',');
run();
EXPECT_EQ(vals, std::vector<int>({1, 2}));
}
//
// #209
TEST_F(TApp, CustomUserSepParse4) {
std::vector<int> vals;
args = {"--idx", "1, 2"};
auto opt = app.add_option("--idx", vals, "", true)->delimiter(',');
run();
EXPECT_EQ(vals, std::vector<int>({1, 2}));
app.remove_option(opt);
app.add_option("--idx", vals)->delimiter(',');
run();
EXPECT_EQ(vals, std::vector<int>({1, 2}));
}
// #218
TEST_F(TApp, CustomUserSepParse5) {
std::vector<std::string> bar;
args = {"this", "is", "a", "test"};
auto opt = app.add_option("bar", bar, "bar");
run();
EXPECT_EQ(bar, std::vector<std::string>({"this", "is", "a", "test"}));
app.remove_option(opt);
args = {"this", "is", "a", "test"};
app.add_option("bar", bar, "bar", true);
run();
EXPECT_EQ(bar, std::vector<std::string>({"this", "is", "a", "test"}));
}

View File

@ -119,7 +119,7 @@ TEST(THelp, MultiOpts) {
TEST(THelp, VectorOpts) {
CLI::App app{"My prog"};
std::vector<int> x = {1, 2};
app.add_option("-q,--quick", x, "", true);
app.add_option("-q,--quick", x)->capture_default_str();
std::string help = app.help();
@ -299,8 +299,8 @@ TEST(THelp, IntDefaults) {
CLI::App app{"My prog"};
int one{1}, two{2};
app.add_option("--one", one, "Help for one", true);
app.add_option("--set", two, "Help for set", true)->check(CLI::IsMember({2, 3, 4}));
app.add_option("--one", one, "Help for one")->capture_default_str();
app.add_option("--set", two, "Help for set")->capture_default_str()->check(CLI::IsMember({2, 3, 4}));
std::string help = app.help();
@ -313,9 +313,10 @@ TEST(THelp, IntDefaults) {
TEST(THelp, SetLower) {
CLI::App app{"My prog"};
app.option_defaults()->always_capture_default();
std::string def{"One"};
app.add_option("--set", def, "Help for set", true)->check(CLI::IsMember({"oNe", "twO", "THREE"}));
app.add_option("--set", def, "Help for set")->check(CLI::IsMember({"oNe", "twO", "THREE"}));
std::string help = app.help();
@ -866,7 +867,7 @@ TEST(THelp, ChangingSetDefaulted) {
std::set<int> vals{1, 2, 3};
int val = 2;
app.add_option("--val", val, "", true)->check(CLI::IsMember(&vals));
app.add_option("--val", val, "")->check(CLI::IsMember(&vals))->capture_default_str();
std::string help = app.help();
@ -881,6 +882,7 @@ TEST(THelp, ChangingSetDefaulted) {
EXPECT_THAT(help, Not(HasSubstr("1")));
EXPECT_THAT(help, HasSubstr("4"));
}
TEST(THelp, ChangingCaselessSet) {
CLI::App app;
@ -904,10 +906,11 @@ TEST(THelp, ChangingCaselessSet) {
TEST(THelp, ChangingCaselessSetDefaulted) {
CLI::App app;
app.option_defaults()->always_capture_default();
std::set<std::string> vals{"1", "2", "3"};
std::string val = "2";
app.add_option("--val", val, "", true)->check(CLI::IsMember(&vals, CLI::ignore_case));
app.add_option("--val", val)->check(CLI::IsMember(&vals, CLI::ignore_case));
std::string help = app.help();
@ -922,3 +925,50 @@ TEST(THelp, ChangingCaselessSetDefaulted) {
EXPECT_THAT(help, Not(HasSubstr("1")));
EXPECT_THAT(help, HasSubstr("4"));
}
// New defaults tests (1.8)
TEST(THelp, ChangingDefaults) {
CLI::App app;
std::vector<int> x = {1, 2};
CLI::Option *opt = app.add_option("-q,--quick", x);
x = {3, 4};
opt->capture_default_str();
x = {5, 6};
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("INT=[3,4] ..."));
}
TEST(THelp, ChangingDefaultsWithAutoCapture) {
CLI::App app;
app.option_defaults()->always_capture_default();
std::vector<int> x = {1, 2};
app.add_option("-q,--quick", x);
x = {3, 4};
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("INT=[1,2] ..."));
}
TEST(THelp, FunctionDefaultString) {
CLI::App app;
std::vector<int> x = {1, 2};
CLI::Option *opt = app.add_option("-q,--quick", x);
opt->default_function([]() { return std::string("Powerful"); });
opt->capture_default_str();
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("INT=Powerful"));
}

View File

@ -7,6 +7,24 @@
#include <fstream>
#include <string>
class NotStreamable {};
class Streamable {};
std::ostream &operator<<(std::ostream &out, const Streamable &) { return out << "Streamable"; }
TEST(TypeTools, Streaming) {
EXPECT_EQ(CLI::detail::to_string(NotStreamable{}), "");
EXPECT_EQ(CLI::detail::to_string(Streamable{}), "Streamable");
EXPECT_EQ(CLI::detail::to_string(5), "5");
EXPECT_EQ(CLI::detail::to_string("string"), std::string("string"));
EXPECT_EQ(CLI::detail::to_string(std::string("string")), std::string("string"));
}
TEST(Split, SimpleByToken) {
auto out = CLI::detail::split("one.two.three", '.');
ASSERT_EQ(3u, out.size());