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

Add ignore underscore (#185)

* add ignore_underscore test cases and options to app

* add ignore_underscore for add_sets and some more tests for the sets and subcommands

* add some documentation lines and some failing tests

* update readme with ignore_underscore option

* remove failing tests from known issue

* remove empty line for code coverage
This commit is contained in:
Philip Top 2019-01-03 00:22:09 -08:00 committed by Henry Schreiner
parent c3d8d4a2d0
commit a83109002c
7 changed files with 586 additions and 17 deletions

View File

@ -162,7 +162,7 @@ try {
}
```
The try/catch block ensures that `-h,--help` or a parse error will exit with the correct return code (selected from `CLI::ExitCodes`). (The return here should be inside `main`). You should not assume that the option values have been set inside the catch block; for example, help flags intentionally short-circuit all other processing for speed and to ensure required options and the like do not interfere.
The try/catch block ensures that `-h,--help` or a parse error will exit with the correct return code (selected from `CLI::ExitCodes`). (The return here should be inside `main`). You should not assume that the option values have been set inside the catch block; for example, help flags intentionally short-circuit all other processing for speed and to ensure required options and the like do not interfere.
</p></details>
</br>
@ -197,6 +197,10 @@ app.add_set(option_name,
app.add_set_ignore_case(... // String only
app.add_set_ignore_underscore(... // String only
app.add_set_ignore_case_underscore(... // String only
App* subcom = app.add_subcommand(name, description);
```
@ -212,7 +216,7 @@ On a compiler that supports C++17's `__has_include`, you can also use `std::opti
- `"this"` Can only be passed positionally
- `"-a,-b,-c"` No limit to the number of non-positional option names
The add commands return a pointer to an internally stored `Option`. If you set the final argument to true, the default value is captured and printed on the command line with the help flag. This option can be used directly to check for the count (`->count()`) after parsing to avoid a string based lookup.
The add commands return a pointer to an internally stored `Option`. If you set the final argument to true, the default value is captured and printed on the command line with the help flag. This option can be used directly to check for the count (`->count()`) after parsing to avoid a string based lookup.
#### Option options
@ -227,6 +231,7 @@ Before parsing, you can set the following options:
- `->envname(name)`: Gets the value from the environment if present and not passed on the command line.
- `->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
- `->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 always default to take last).
- `->check(CLI::ExistingFile)`: Requires that the file exists if given.
- `->check(CLI::ExistingDirectory)`: Requires that the directory exists.
@ -279,6 +284,8 @@ Multiple subcommands are allowed, to allow [`Click`][click] like series of comma
There are several options that are supported on the main app and subcommands. These are:
- `.ignore_case()`: Ignore the case of this subcommand. Inherited by added subcommands, so is usually used on the main `App`.
- `.ignore_underscore()`: Ignore any underscores in the subcommand name. Inherited by added subcommands, so is usually used on the main `App`.
- `.fallthrough()`: Allow extra unmatched options and positionals to "fall through" and be matched on a parent command. Subcommands always are allowed to fall through.
- `.require_subcommand()`: Require 1 or more subcommands.
- `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default 0 or more.
@ -340,7 +347,7 @@ arguments, use `.config_to_str(default_also=false, prefix="", write_description=
Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are `allow_extras`, `prefix_command`, `ignore_case`, `fallthrough`, `group`, `footer`, and maximum number of required subcommands. The help flag existence, name, and description are inherited, as well.
Options have defaults for `group`, `required`, `multi_option_policy`, and `ignore_case`. To set these defaults, you should set the `option_defaults()` object, for example:
Options have defaults for `group`, `required`, `multi_option_policy`, `ignore_underscore`, and `ignore_case`. To set these defaults, you should set the `option_defaults()` object, for example:
```cpp
app.option_defaults()->required();
@ -351,7 +358,7 @@ The default settings for options are inherited to subcommands, as well.
### Formatting
The job of formatting help printouts is delegated to a formatter callable object on Apps and Options. You are free to replace either formatter by calling `formatter(fmt)` on an `App`, where fmt is any copyable callable with the correct signature.
The job of formatting help printouts is delegated to a formatter callable object on Apps and Options. You are free to replace either formatter by calling `formatter(fmt)` on an `App`, where fmt is any copyable callable with the correct signature.
CLI11 comes with a default App formatter functional, `Formatter`. It is customizable; you can set `label(key, value)` to replace the default labels like `REQUIRED`, and `column_width(n)` to set the width of the columns before you add the functional to the app or option. You can also override almost any stage of the formatting process in a subclass of either formatter. If you want to make a new formatter from scratch, you can do
that too; you just need to implement the correct signature. The first argument is a const pointer to the in question. The formatter will get a `std::string` usage name as the second option, and a `AppFormatMode` mode for the final option. It should return a `std::string`.
@ -387,7 +394,7 @@ app.add_option("--fancy-count", [](std::vector<std::string> val){
### Utilities
There are a few other utilities that are often useful in CLI programming. These are in separate headers, and do not appear in `CLI11.hpp`, but are completely independent and can be used as needed. The `Timer`/`AutoTimer` class allows you to easily time a block of code, with custom print output.
There are a few other utilities that are often useful in CLI programming. These are in separate headers, and do not appear in `CLI11.hpp`, but are completely independent and can be used as needed. The `Timer`/`AutoTimer` class allows you to easily time a block of code, with custom print output.
```cpp
{

View File

@ -139,6 +139,9 @@ class App {
/// If true, the program name is not case sensitive INHERITABLE
bool ignore_case_{false};
/// If true, the program should ignore underscores INHERITABLE
bool ignore_underscore_{false};
/// Allow subcommand fallthrough, so that parent commands can collect commands after subcommand. INHERITABLE
bool fallthrough_{false};
@ -195,6 +198,7 @@ class App {
allow_config_extras_ = parent_->allow_config_extras_;
prefix_command_ = parent_->prefix_command_;
ignore_case_ = parent_->ignore_case_;
ignore_underscore_ = parent_->ignore_underscore_;
fallthrough_ = parent_->fallthrough_;
group_ = parent_->group_;
footer_ = parent_->footer_;
@ -265,6 +269,18 @@ class App {
return this;
}
/// Ignore underscore. Subcommand inherit value.
App *ignore_underscore(bool value = true) {
ignore_underscore_ = value;
if(parent_ != nullptr) {
for(const auto &subc : parent_->subcommands_) {
if(subc.get() != this && (this->check_name(subc->name_) || subc->check_name(this->name_)))
throw OptionAlreadyAdded(subc->name_);
}
}
return this;
}
/// Set the help formatter
App *formatter(std::shared_ptr<FormatterBase> fmt) {
formatter_ = fmt;
@ -510,7 +526,7 @@ class App {
}
#endif
/// Add set of options (No default, temp refernce, such as an inline set)
/// Add set of options (No default, temp reference, such as an inline set)
template <typename T>
Option *add_set(std::string name,
T &member, ///< The selected member of the set
@ -532,7 +548,7 @@ class App {
return opt;
}
/// Add set of options (No default, non-temp refernce, such as an existing set)
/// Add set of options (No default, non-temp reference, such as an existing set)
template <typename T>
Option *add_set(std::string name,
T &member, ///< The selected member of the set
@ -558,7 +574,7 @@ class App {
template <typename T>
Option *add_set(std::string name,
T &member, ///< The selected member of the set
const std::set<T> &&options, ///< The set of posibilities
const std::set<T> &&options, ///< The set of possibilities
std::string description,
bool defaulted) {
@ -582,11 +598,11 @@ class App {
return opt;
}
/// Add set of options (with default, L value refernce, such as an existing set)
/// Add set of options (with default, L value reference, such as an existing set)
template <typename T>
Option *add_set(std::string name,
T &member, ///< The selected member of the set
const std::set<T> &options, ///< The set of posibilities
const std::set<T> &options, ///< The set of possibilities
std::string description,
bool defaulted) {
@ -668,7 +684,7 @@ class App {
/// Add set of options, string only, ignore case (default, R value)
Option *add_set_ignore_case(std::string name,
std::string &member, ///< The selected member of the set
const std::set<std::string> &&options, ///< The set of posibilities
const std::set<std::string> &&options, ///< The set of possibilities
std::string description,
bool defaulted) {
@ -699,7 +715,7 @@ class App {
/// Add set of options, string only, ignore case (default, L value)
Option *add_set_ignore_case(std::string name,
std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of posibilities
const std::set<std::string> &options, ///< The set of possibilities
std::string description,
bool defaulted) {
@ -727,6 +743,242 @@ class App {
return opt;
}
/// Add set of options, string only, ignore underscore (no default, R value)
Option *add_set_ignore_underscore(std::string 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(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(name, fun, description, false);
std::string typeval = detail::type_name<std::string>();
typeval += " in {" + detail::join(options) + "}";
opt->type_name(typeval);
return opt;
}
/// Add set of options, string only, ignore underscore (no default, L value)
Option *add_set_ignore_underscore(std::string 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(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(name, fun, description, false);
opt->type_name_fn([&options]() {
return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
});
return opt;
}
/// Add set of options, string only, ignore underscore (default, R value)
Option *add_set_ignore_underscore(std::string 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(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(name, fun, 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);
}
return opt;
}
/// Add set of options, string only, ignore underscore (default, L value)
Option *add_set_ignore_underscore(std::string 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(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(name, fun, 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);
}
return opt;
}
/// Add set of options, string only, ignore underscore and case(no default, R value)
Option *add_set_ignore_case_underscore(std::string 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(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(name, fun, description, false);
std::string typeval = detail::type_name<std::string>();
typeval += " in {" + detail::join(options) + "}";
opt->type_name(typeval);
return opt;
}
/// Add set of options, string only, ignore underscore and case(no default, L value)
Option *add_set_ignore_case_underscore(std::string 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(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(name, fun, description, false);
opt->type_name_fn([&options]() {
return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
});
return opt;
}
/// Add set of options, string only, ignore underscore and case (default, R value)
Option *add_set_ignore_case_underscore(std::string 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(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(name, fun, 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);
}
return opt;
}
/// Add set of options, string only, ignore underscore and case (default, L value)
Option *add_set_ignore_case_underscore(std::string 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(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(name, fun, 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);
}
return opt;
}
/// Add a complex number
template <typename T>
Option *add_complex(std::string name,
@ -1122,6 +1374,9 @@ class App {
/// Check the status of ignore_case
bool get_ignore_case() const { return ignore_case_; }
/// Check the status of ignore_underscore
bool get_ignore_underscore() const { return ignore_underscore_; }
/// Check the status of fallthrough
bool get_fallthrough() const { return fallthrough_; }
@ -1170,9 +1425,13 @@ class App {
/// Get the name of the current app
std::string get_name() const { return name_; }
/// Check the name, case insensitive if set
/// Check the name, case insensitive and underscore insensitive if set
bool check_name(std::string name_to_check) const {
std::string local_name = name_;
if(ignore_underscore_) {
local_name = detail::remove_underscore(name_);
name_to_check = detail::remove_underscore(name_to_check);
}
if(ignore_case_) {
local_name = detail::to_lower(name_);
name_to_check = detail::to_lower(name_to_check);

View File

@ -45,6 +45,9 @@ template <typename CRTP> class OptionBase {
/// Ignore the case when matching (option, not value)
bool ignore_case_{false};
/// Ignore underscores when matching (option, not value)
bool ignore_underscore_{false};
/// Allow this option to be given in a configuration file
bool configurable_{true};
@ -56,6 +59,7 @@ template <typename CRTP> class OptionBase {
other->group(group_);
other->required(required_);
other->ignore_case(ignore_case_);
other->ignore_underscore(ignore_underscore_);
other->configurable(configurable_);
other->multi_option_policy(multi_option_policy_);
}
@ -90,6 +94,9 @@ template <typename CRTP> class OptionBase {
/// The status of ignore case
bool get_ignore_case() const { return ignore_case_; }
/// The status of ignore_underscore
bool get_ignore_underscore() const { return ignore_underscore_; }
/// The status of configurable
bool get_configurable() const { return configurable_; }
@ -145,6 +152,12 @@ class OptionDefaults : public OptionBase<OptionDefaults> {
ignore_case_ = value;
return this;
}
/// Ignore underscores in the option name
OptionDefaults *ignore_underscore(bool value = true) {
ignore_underscore_ = value;
return this;
}
};
class Option : public OptionBase<Option> {
@ -411,6 +424,20 @@ class Option : public OptionBase<Option> {
return this;
}
/// Ignore underscores in the option names
///
/// The template hides the fact that we don't have the definition of App yet.
/// You are never expected to add an argument to the template here.
template <typename T = App> Option *ignore_underscore(bool value = true) {
ignore_underscore_ = value;
auto *parent = dynamic_cast<T *>(parent_);
for(const Option_p &opt : parent->options_)
if(opt.get() != this && *opt == *this)
throw OptionAlreadyAdded(opt->get_name(true, true));
return this;
}
/// Take the last argument if given multiple times (or another policy)
Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
@ -602,7 +629,7 @@ class Option : public OptionBase<Option> {
for(const std::string &lname : lnames_)
if(other.check_lname(lname))
return true;
// We need to do the inverse, just in case we are ignore_case
// We need to do the inverse, just in case we are ignore_case or ignore underscore
for(const std::string &sname : other.snames_)
if(check_sname(sname))
return true;
@ -625,13 +652,17 @@ class Option : public OptionBase<Option> {
local_pname = detail::to_lower(local_pname);
name = detail::to_lower(name);
}
if(ignore_underscore_) {
local_pname = detail::remove_underscore(local_pname);
name = detail::remove_underscore(name);
}
return name == local_pname;
}
}
/// Requires "-" to be removed from string
bool check_sname(std::string name) const {
if(ignore_case_) {
if(ignore_case_) { // there can be no extra underscores in check_sname
name = detail::to_lower(name);
return std::find_if(std::begin(snames_), std::end(snames_), [&name](std::string local_sname) {
return detail::to_lower(local_sname) == name;
@ -643,9 +674,22 @@ class Option : public OptionBase<Option> {
/// Requires "--" to be removed from string
bool check_lname(std::string name) const {
if(ignore_case_) {
name = detail::to_lower(name);
if(ignore_underscore_) {
name = detail::to_lower(detail::remove_underscore(name));
return std::find_if(std::begin(lnames_), std::end(lnames_), [&name](std::string local_sname) {
return detail::to_lower(detail::remove_underscore(local_sname)) == name;
}) != std::end(lnames_);
} else {
name = detail::to_lower(name);
return std::find_if(std::begin(lnames_), std::end(lnames_), [&name](std::string local_sname) {
return detail::to_lower(local_sname) == name;
}) != std::end(lnames_);
}
} else if(ignore_underscore_) {
name = detail::remove_underscore(name);
return std::find_if(std::begin(lnames_), std::end(lnames_), [&name](std::string local_sname) {
return detail::to_lower(local_sname) == name;
return detail::remove_underscore(local_sname) == name;
}) != std::end(lnames_);
} else
return std::find(std::begin(lnames_), std::end(lnames_), name) != std::end(lnames_);

View File

@ -142,6 +142,12 @@ inline std::string to_lower(std::string str) {
return str;
}
/// remove underscores from a string
inline std::string remove_underscore(std::string str) {
str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str));
return str;
}
/// Split a string '"one two" "three"' into 'one two', 'three'
inline std::vector<std::string> split_up(std::string str) {

View File

@ -994,6 +994,115 @@ TEST_F(TApp, InSetIgnoreCase) {
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
/*
TEST_F(TApp, InSetIgnoreCaseLValue) {
std::set<std::string> options{"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.clear();
args = {"--quick", "ThrEE"};
run();
EXPECT_EQ("THREE", choice); // this will now fail since options was cleared
args = {"--quick", "four"};
EXPECT_THROW(run(), CLI::ConversionError);
args = {"--quick=one", "--quick=two"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
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 could cause a seg fault
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"};

View File

@ -92,6 +92,17 @@ TEST_F(TApp, MultipleSubcomMatchingWithCaseFirst) {
EXPECT_THROW(app.add_subcommand("fIrst"), CLI::OptionAlreadyAdded);
}
TEST_F(TApp, MultipleSubcomMatchingWithUnderscore) {
app.add_subcommand("first_option")->ignore_underscore();
EXPECT_THROW(app.add_subcommand("firstoption"), CLI::OptionAlreadyAdded);
}
TEST_F(TApp, MultipleSubcomMatchingWithUnderscoreFirst) {
app.ignore_underscore();
app.add_subcommand("first_option");
EXPECT_THROW(app.add_subcommand("firstoption"), CLI::OptionAlreadyAdded);
}
TEST_F(TApp, MultipleSubcomMatchingWithCaseInplace) {
app.add_subcommand("first");
auto first = app.add_subcommand("fIrst");
@ -106,6 +117,20 @@ TEST_F(TApp, MultipleSubcomMatchingWithCaseInplace2) {
EXPECT_THROW(first->ignore_case(), CLI::OptionAlreadyAdded);
}
TEST_F(TApp, MultipleSubcomMatchingWithUnderscoreInplace) {
app.add_subcommand("first_option");
auto first = app.add_subcommand("firstoption");
EXPECT_THROW(first->ignore_underscore(), CLI::OptionAlreadyAdded);
}
TEST_F(TApp, MultipleSubcomMatchingWithUnderscoreInplace2) {
auto first = app.add_subcommand("firstoption");
app.add_subcommand("first_option");
EXPECT_THROW(first->ignore_underscore(), CLI::OptionAlreadyAdded);
}
TEST_F(TApp, MultipleSubcomNoMatchingInplace2) {
auto first = app.add_subcommand("first");
auto second = app.add_subcommand("second");
@ -114,6 +139,14 @@ TEST_F(TApp, MultipleSubcomNoMatchingInplace2) {
EXPECT_NO_THROW(second->ignore_case());
}
TEST_F(TApp, MultipleSubcomNoMatchingInplaceUnderscore2) {
auto first = app.add_subcommand("first_option");
auto second = app.add_subcommand("second_option");
EXPECT_NO_THROW(first->ignore_underscore());
EXPECT_NO_THROW(second->ignore_underscore());
}
TEST_F(TApp, IncorrectConstructionFlagPositional1) { EXPECT_THROW(app.add_flag("cat"), CLI::IncorrectConstruction); }
TEST_F(TApp, IncorrectConstructionFlagPositional2) {
@ -262,6 +295,68 @@ TEST_F(TApp, CheckNameNoCase) {
EXPECT_TRUE(pos2->check_name("pos2"));
}
TEST_F(TApp, CheckNameNoUnderscore) {
auto long1 = app.add_flag("--longoption1")->ignore_underscore();
auto long2 = app.add_flag("--long_option2")->ignore_underscore();
int x, y;
auto pos1 = app.add_option("pos_option_1", x)->ignore_underscore();
auto pos2 = app.add_option("posoption2", y)->ignore_underscore();
EXPECT_TRUE(long1->check_name("--long_option1"));
EXPECT_TRUE(long1->check_name("--longoption_1"));
EXPECT_TRUE(long1->check_name("--longoption1"));
EXPECT_TRUE(long1->check_name("--long__opt_ion__1"));
EXPECT_TRUE(long1->check_name("--__l_o_n_g_o_p_t_i_o_n_1"));
EXPECT_TRUE(long2->check_name("--long_option2"));
EXPECT_TRUE(long2->check_name("--longoption2"));
EXPECT_TRUE(long2->check_name("--longoption_2"));
EXPECT_TRUE(long2->check_name("--long__opt_ion__2"));
EXPECT_TRUE(long2->check_name("--__l_o_n_go_p_t_i_o_n_2__"));
EXPECT_TRUE(pos1->check_name("pos_option1"));
EXPECT_TRUE(pos1->check_name("pos_option_1"));
EXPECT_TRUE(pos1->check_name("pos_o_p_t_i_on_1"));
EXPECT_TRUE(pos1->check_name("posoption1"));
EXPECT_TRUE(pos2->check_name("pos_option2"));
EXPECT_TRUE(pos2->check_name("pos_option_2"));
EXPECT_TRUE(pos2->check_name("pos_o_p_t_i_on_2"));
EXPECT_TRUE(pos2->check_name("posoption2"));
}
TEST_F(TApp, CheckNameNoCaseNoUnderscore) {
auto long1 = app.add_flag("--LongoptioN1")->ignore_underscore()->ignore_case();
auto long2 = app.add_flag("--long_Option2")->ignore_case()->ignore_underscore();
int x, y;
auto pos1 = app.add_option("pos_Option_1", x)->ignore_underscore()->ignore_case();
auto pos2 = app.add_option("posOption2", y)->ignore_case()->ignore_underscore();
EXPECT_TRUE(long1->check_name("--Long_Option1"));
EXPECT_TRUE(long1->check_name("--lONgoption_1"));
EXPECT_TRUE(long1->check_name("--LongOption1"));
EXPECT_TRUE(long1->check_name("--long__Opt_ion__1"));
EXPECT_TRUE(long1->check_name("--__l_o_N_g_o_P_t_i_O_n_1"));
EXPECT_TRUE(long2->check_name("--long_Option2"));
EXPECT_TRUE(long2->check_name("--LongOption2"));
EXPECT_TRUE(long2->check_name("--longOPTION_2"));
EXPECT_TRUE(long2->check_name("--long__OPT_ion__2"));
EXPECT_TRUE(long2->check_name("--__l_o_n_GO_p_t_i_o_n_2__"));
EXPECT_TRUE(pos1->check_name("POS_Option1"));
EXPECT_TRUE(pos1->check_name("pos_option_1"));
EXPECT_TRUE(pos1->check_name("pos_o_p_t_i_on_1"));
EXPECT_TRUE(pos1->check_name("posoption1"));
EXPECT_TRUE(pos2->check_name("pos_option2"));
EXPECT_TRUE(pos2->check_name("pos_OPTION_2"));
EXPECT_TRUE(pos2->check_name("poS_o_p_T_I_on_2"));
EXPECT_TRUE(pos2->check_name("PosOption2"));
}
TEST_F(TApp, PreSpaces) {
int x;
auto myapp = app.add_option(" -a, --long, other", x);
@ -301,6 +396,12 @@ TEST_F(TApp, OptionFromDefaults) {
auto opt3 = app.add_option("--simple3", x);
EXPECT_TRUE(opt3->get_required());
EXPECT_TRUE(opt3->get_ignore_case());
app.option_defaults()->required()->ignore_underscore();
auto opt4 = app.add_option("--simple4", x);
EXPECT_TRUE(opt4->get_required());
EXPECT_TRUE(opt4->get_ignore_underscore());
}
TEST_F(TApp, OptionFromDefaultsSubcommands) {
@ -308,6 +409,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) {
EXPECT_FALSE(app.option_defaults()->get_required());
EXPECT_EQ(app.option_defaults()->get_multi_option_policy(), CLI::MultiOptionPolicy::Throw);
EXPECT_FALSE(app.option_defaults()->get_ignore_case());
EXPECT_FALSE(app.option_defaults()->get_ignore_underscore());
EXPECT_TRUE(app.option_defaults()->get_configurable());
EXPECT_EQ(app.option_defaults()->get_group(), "Options");
@ -315,6 +417,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) {
->required()
->multi_option_policy(CLI::MultiOptionPolicy::TakeLast)
->ignore_case()
->ignore_underscore()
->configurable(false)
->group("Something");
@ -323,6 +426,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) {
EXPECT_TRUE(app2->option_defaults()->get_required());
EXPECT_EQ(app2->option_defaults()->get_multi_option_policy(), CLI::MultiOptionPolicy::TakeLast);
EXPECT_TRUE(app2->option_defaults()->get_ignore_case());
EXPECT_TRUE(app2->option_defaults()->get_ignore_underscore());
EXPECT_FALSE(app2->option_defaults()->get_configurable());
EXPECT_EQ(app2->option_defaults()->get_group(), "Something");
}
@ -352,6 +456,7 @@ TEST_F(TApp, SubcommandDefaults) {
EXPECT_FALSE(app.get_allow_extras());
EXPECT_FALSE(app.get_prefix_command());
EXPECT_FALSE(app.get_ignore_case());
EXPECT_FALSE(app.get_ignore_underscore());
EXPECT_FALSE(app.get_fallthrough());
EXPECT_EQ(app.get_footer(), "");
EXPECT_EQ(app.get_group(), "Subcommands");
@ -361,6 +466,7 @@ TEST_F(TApp, SubcommandDefaults) {
app.allow_extras();
app.prefix_command();
app.ignore_case();
app.ignore_underscore();
app.fallthrough();
app.footer("footy");
app.group("Stuff");
@ -372,6 +478,7 @@ TEST_F(TApp, SubcommandDefaults) {
EXPECT_TRUE(app2->get_allow_extras());
EXPECT_TRUE(app2->get_prefix_command());
EXPECT_TRUE(app2->get_ignore_case());
EXPECT_TRUE(app2->get_ignore_underscore());
EXPECT_TRUE(app2->get_fallthrough());
EXPECT_EQ(app2->get_footer(), "footy");
EXPECT_EQ(app2->get_group(), "Stuff");

View File

@ -530,6 +530,43 @@ TEST_F(TApp, SubcomInheritCaseCheck) {
EXPECT_EQ(sub2, app.get_subcommands().at(0));
}
TEST_F(SubcommandProgram, UnderscoreCheck) {
args = {"start_"};
EXPECT_THROW(run(), CLI::ExtrasError);
args = {"start"};
run();
start->ignore_underscore();
run();
args = {"_start_"};
run();
}
TEST_F(TApp, SubcomInheritUnderscoreCheck) {
app.ignore_underscore();
auto sub1 = app.add_subcommand("sub_option1");
auto sub2 = app.add_subcommand("sub_option2");
run();
EXPECT_EQ((size_t)0, app.get_subcommands().size());
EXPECT_EQ((size_t)2, app.get_subcommands({}).size());
EXPECT_EQ((size_t)1, app.get_subcommands([](const CLI::App *s) { return s->get_name() == "sub_option1"; }).size());
args = {"suboption1"};
run();
EXPECT_EQ(sub1, app.get_subcommands().at(0));
EXPECT_EQ((size_t)1, app.get_subcommands().size());
app.clear();
EXPECT_EQ((size_t)0, app.get_subcommands().size());
args = {"_suboption2"};
run();
EXPECT_EQ(sub2, app.get_subcommands().at(0));
}
TEST_F(SubcommandProgram, HelpOrder) {
args = {"-h"};