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

change add_option_function to take a std::function with void return type and update some corresponding test cases and documentation

Allow option groups to use ignore_case ignore_underscore for inheritance

Allow option groups to specify allow_extras even if the parent app doesn't in which case extra options flow down into the option_group.
This commit is contained in:
Philip Top 2019-03-07 07:32:44 -08:00 committed by Henry Schreiner
parent 3f9fafd916
commit 059f6ef254
4 changed files with 89 additions and 35 deletions

View File

@ -238,7 +238,7 @@ Option_group *app.add_option_group(name,description); // 🚧
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 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.
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.
🚧 Flag options specified through the `add_flag*` functions allow a syntax for the option names to default particular options to a false value or any other value if some flags are passed. For example:
@ -431,7 +431,7 @@ Validators have a few functions to query the current values
* `get_description()`:🚧 Will return a description string
* `get_name()`:🚧 Will return the Validator name
* `get_active()`:🚧 Will return the current active state, true if the Validator is active.
* `get_modifying()` 🚧 Will return true if the Validator is allowed to modify the input, this can be controlled via `non_modifying()`🚧 method, though it is recommended to let check and transform function manipulate it if needed.
* `get_modifying()`: 🚧 Will return true if the Validator is allowed to modify the input, this can be controlled via the `non_modifying()`🚧 method, though it is recommended to let check and transform function manipulate it if needed.
#### Getting results
In most cases the fastest and easiest way is to return the results through a callback or variable specified in one of the `add_*` functions. But there are situations where this is not possible or desired. For these cases the results may be obtained through one of the following functions. Please note that these functions will do any type conversions and processing during the call so should not used in performance critical code:
@ -468,6 +468,8 @@ There are several options that are supported on the main app and subcommands and
- `.allow_windows_style_options()`: 🆕 Allow command line options to be parsed in the form of `/s /long /file:file_name.ext` This option does not change how options are specified in the `add_option` calls or the ability to process options in the form of `-s --long --file=file_name.ext`
- `.fallthrough()`: Allow extra unmatched options and positionals to "fall through" and be matched on a parent command. Subcommands always are allowed to fall through.
- `.disable()`: 🚧 Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group.
- `.disabled_by_default()`:🚧 Specify that at the start of parsing the subcommand should be disabled. This is useful for allowing some Subcommands to trigger others.
- `.enabled_by_default()`: 🚧 Specify that at the start of each parse the subcommand/option_group should be enabled. This is useful for allowing some Subcommands to disable others.
- `.exludes(option_or_subcommand)`: 🚧 If given an option pointer or pointer to another subcommand, these subcommands cannot be given together. In the case of options, if the option is passed the subcommand cannot be used and will generate an error.
- `.require_option()`: 🚧 Require 1 or more options or option groups be used.
- `.require_option(N)`: 🚧 Require `N` options or option groups if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more.
@ -506,7 +508,7 @@ There are several options that are supported on the main app and subcommands and
- `.group(name)`: Set a group name, defaults to `"Subcommands"`. Setting `""` will be hide the subcommand.
- `[option_name]`: 🚧 retrieve a const pointer to an option given by `option_name` for Example `app["--flag1"]` will get a pointer to the option for the "--flag1" value, `app["--flag1"]->as<bool>() will get the results of the command line for a flag
> Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function.
> Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function, and any positional argument will match before repeated subcommand names.
#### Callbacks
@ -573,7 +575,16 @@ ogroup->add_options(option1,option2,option3,...)
The option pointers used in this function must be options defined in the parent application of the option group otherwise an error will be generated.
Options in an option group are searched for a command line match after any options in the main app, so any positionals in the main app would be matched first. So care must be taken to make sure of the order when using positional arguments and option groups.
Option groups work well with `excludes` and `require_options` methods, as an Application will treat an option group as a single option for the purpose of counting and requirements. Option groups allow specifying requirements such as requiring 1 of 3 options in one group and 1 of 3 options in a different group. Option groups can contain other groups as well. Disabling an option group will turn off all options within the group.
Option groups work well with `excludes` and `require_options` methods, as an Application will treat an option group as a single option for the purpose of counting and requirements. Option groups allow specifying requirements such as requiring 1 of 3 options in one group and 1 of 3 options in a different group. Option groups can contain other groups as well. Disabling an option group will turn off all options within the group.
The `CLI::TriggerOn`🚧 and `CLI::TriggerOff`🚧 methods are helper methods to allow the use of options/subcommands from one group to trigger another group on or off.
```cpp
CLI::TriggerOn(group1_pointer, triggered_group);
CLI::TriggerOff(group2_pointer, disabled_group);
```
These functions make use of the `preparse_callback`, `enabled_by_default()` and `disabled_by_default`. The triggered group may be a vector of group pointers. These methods should only used once per group and will override any previous use of the underlying functions. More complex arrangements can be accomplished using similar methodology with a different preparse_callback function that does more.
### Configuration file

View File

@ -351,7 +351,7 @@ class App {
/// Ignore case. Subcommands inherit value.
App *ignore_case(bool value = true) {
ignore_case_ = value;
if(parent_ != nullptr) {
if(parent_ != nullptr && !name_.empty()) {
for(const auto &subc : parent_->subcommands_) {
if(subc.get() != this && (this->check_name(subc->name_) || subc->check_name(this->name_)))
throw OptionAlreadyAdded(subc->name_);
@ -375,7 +375,7 @@ class App {
/// Ignore underscore. Subcommands inherit value.
App *ignore_underscore(bool value = true) {
ignore_underscore_ = value;
if(parent_ != nullptr) {
if(parent_ != nullptr && !name_.empty()) {
for(const auto &subc : parent_->subcommands_) {
if(subc.get() != this && (this->check_name(subc->name_) || subc->check_name(this->name_)))
throw OptionAlreadyAdded(subc->name_);
@ -460,14 +460,14 @@ class App {
/// Add option for a callback of a specific type
template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy>
Option *add_option_function(std::string option_name,
const std::function<bool(const T &)> &func, ///< the callback to execute
const std::function<void(const T &)> &func, ///< the callback to execute
std::string option_description = "") {
CLI::callback_t fun = [func](CLI::results_t res) {
T variable;
bool result = detail::lexical_cast(res[0], variable);
if(result) {
return func(variable);
func(variable);
}
return result;
};
@ -562,7 +562,7 @@ class App {
/// Add option for a vector callback of a specific type
template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>
Option *add_option_function(std::string option_name,
const std::function<bool(const T &)> &func, ///< the callback to execute
const std::function<void(const T &)> &func, ///< the callback to execute
std::string option_description = "") {
CLI::callback_t fun = [func](CLI::results_t res) {
@ -574,7 +574,7 @@ class App {
retval &= detail::lexical_cast(elem, values.back());
}
if(retval) {
return func(values);
func(values);
}
return retval;
};
@ -1696,9 +1696,19 @@ class App {
for(const std::pair<detail::Classifier, std::string> &miss : missing_) {
miss_list.push_back(std::get<1>(miss));
}
// Recurse into subcommands
// Get from a subcommand that may allow extras
if(recurse) {
if(!allow_extras_) {
for(const auto &sub : subcommands_) {
if(sub->name_.empty() && !sub->missing_.empty()) {
for(const std::pair<detail::Classifier, std::string> &miss : sub->missing_) {
miss_list.push_back(std::get<1>(miss));
}
}
}
}
// Recurse into subcommands
for(const App *sub : parsed_subcommands_) {
std::vector<std::string> output = sub->remaining(recurse);
std::copy(std::begin(output), std::end(output), std::back_inserter(miss_list));
@ -1713,6 +1723,7 @@ class App {
std::begin(missing_), std::end(missing_), [](const std::pair<detail::Classifier, std::string> &val) {
return val.first != detail::Classifier::POSITIONAL_MARK;
}));
if(recurse) {
for(const App_p &sub : subcommands_) {
remaining_options += sub->remaining_size(recurse);
@ -2174,7 +2185,7 @@ class App {
if((!_has_remaining_positionals()) && (parent_ != nullptr)) {
retval = false;
} else {
missing_.emplace_back(classifier, "--");
_move_to_missing(classifier, "--");
}
break;
case detail::Classifier::SUBCOMMAND_TERMINATOR:
@ -2276,11 +2287,11 @@ class App {
return false;
}
/// We are out of other options this goes to missing
missing_.emplace_back(detail::Classifier::NONE, positional);
_move_to_missing(detail::Classifier::NONE, positional);
args.pop_back();
if(prefix_command_) {
while(!args.empty()) {
missing_.emplace_back(detail::Classifier::NONE, args.back());
_move_to_missing(detail::Classifier::NONE, args.back());
args.pop_back();
}
}
@ -2393,7 +2404,7 @@ class App {
}
// Otherwise, add to missing
args.pop_back();
missing_.emplace_back(current_type, current);
_move_to_missing(current_type, current);
return true;
}
@ -2505,6 +2516,23 @@ class App {
return fallthrough_parent;
}
/// Helper function to place extra values in the most appropriate position
void _move_to_missing(detail::Classifier val_type, const std::string &val) {
if(allow_extras_ || subcommands_.empty()) {
missing_.emplace_back(val_type, val);
return;
}
// allow extra arguments to be places in an option group if it is allowed there
for(auto &subc : subcommands_) {
if(subc->name_.empty() && subc->allow_extras_) {
subc->missing_.emplace_back(val_type, val);
return;
}
}
// if we haven't found any place to put them yet put them in missing
missing_.emplace_back(val_type, val);
}
public:
/// function that could be used by subclasses of App to shift options around into subcommands
void _move_option(Option *opt, App *app) {

View File

@ -437,10 +437,7 @@ TEST_F(TApp, OneStringAgain) {
TEST_F(TApp, OneStringFunction) {
std::string str;
app.add_option_function<std::string>("-s,--string", [&str](const std::string &val) {
str = val;
return true;
});
app.add_option_function<std::string>("-s,--string", [&str](const std::string &val) { str = val; });
args = {"--string", "mystring"};
run();
EXPECT_EQ(1u, app.count("-s"));
@ -450,10 +447,7 @@ TEST_F(TApp, OneStringFunction) {
TEST_F(TApp, doubleFunction) {
double res;
app.add_option_function<double>("--val", [&res](double val) {
res = std::abs(val + 54);
return true;
});
app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
args = {"--val", "-354.356"};
run();
EXPECT_EQ(res, 300.356);
@ -463,10 +457,7 @@ TEST_F(TApp, doubleFunction) {
TEST_F(TApp, doubleFunctionFail) {
double res;
app.add_option_function<double>("--val", [&res](double val) {
res = std::abs(val + 54);
return true;
});
app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
args = {"--val", "not_double"};
EXPECT_THROW(run(), CLI::ConversionError);
}
@ -476,7 +467,6 @@ TEST_F(TApp, doubleVectorFunction) {
app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
res = val;
std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
return true;
});
args = {"--val", "5", "--val", "6", "--val", "7"};
run();
@ -491,7 +481,6 @@ TEST_F(TApp, doubleVectorFunctionFail) {
app.add_option_function<std::vector<double>>(vstring, [&res](const std::vector<double> &val) {
res = val;
std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
return true;
});
args = {"--val", "five", "--val", "nine", "--val", "7"};
EXPECT_THROW(run(), CLI::ConversionError);
@ -2063,11 +2052,7 @@ TEST_F(TApp, CustomUserSepParseFunction) {
std::vector<int> vals = {1, 2, 3};
args = {"--idx", "1,2,3"};
app.add_option_function<std::vector<int>>("--idx",
[&vals](std::vector<int> v) {
vals = std::move(v);
return true;
})
app.add_option_function<std::vector<int>>("--idx", [&vals](std::vector<int> v) { vals = std::move(v); })
->delimiter(',');
run();
EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));

View File

@ -575,6 +575,36 @@ TEST_F(ManyGroups, CallbackOrder) {
EXPECT_EQ(callback_order, std::vector<int>({1, 2, 3}));
}
// Test the fallthrough for extra arguments
TEST_F(ManyGroups, ExtrasFallDown) {
remove_required();
args = {"--test1", "--flag", "extra"};
EXPECT_THROW(run(), CLI::ExtrasError);
main->allow_extras();
EXPECT_NO_THROW(run());
EXPECT_EQ(app.remaining_size(true), 3u);
EXPECT_EQ(main->remaining_size(), 3u);
std::vector<std::string> extras{"--test1", "--flag", "extra"};
EXPECT_EQ(app.remaining(true), extras);
EXPECT_EQ(main->remaining(), extras);
}
// Test the option Inheritance
TEST_F(ManyGroups, Inheritance) {
remove_required();
g1->ignore_case();
g1->ignore_underscore();
auto t2 = g1->add_subcommand("t2");
args = {"T2", "t_2"};
EXPECT_TRUE(t2->get_ignore_underscore());
EXPECT_TRUE(t2->get_ignore_case());
run();
EXPECT_EQ(t2->count(), 2u);
}
struct ManyGroupsPreTrigger : public ManyGroups {
size_t triggerMain, trigger1{87u}, trigger2{34u}, trigger3{27u};
ManyGroupsPreTrigger() {