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

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

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,10 +674,23 @@ class Option : public OptionBase<Option> {
/// Requires "--" to be removed from string
bool check_lname(std::string name) const {
if(ignore_case_) {
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::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"};