1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-05-01 05:03:52 +00:00

Adding (untested) ignore case abilities

This commit is contained in:
Henry Fredrick Schreiner 2017-02-20 13:22:42 -05:00
parent 64e39c2515
commit 196012a9f7
5 changed files with 85 additions and 19 deletions

View File

@ -1,7 +1,8 @@
## Version 0.5 (in progress)
* Subcommands now can be "chained", that is, left over arguments can now include subcommands that then get parsed. Subcommands are now a list (`get_subcommands`). Added `got_subcommand(App_or_name)` to check for subcommands.
* Added `.allow_extras()` to disable error on failure. Parse returns a vector of leftover options. Renamed error to `ExtrasError`, and now triggers on extra options too.
* `->ignore_case()` added to subcommands, options, and `add_set_ignore_case` (untested). Subcommand inherit setting from parent App on creation.
* Subcommands now can be "chained", that is, left over arguments can now include subcommands that then get parsed. Subcommands are now a list (`get_subcommands`). Added `got_subcommand(App_or_name)` to check for subcommands. (untested)
* Added `.allow_extras()` to disable error on failure. Parse returns a vector of leftover options. Renamed error to `ExtrasError`, and now triggers on extra options too. (untested)
* Added `require_subcommand` to `App`, to simplify forcing subcommands. Do not "chain" with `add_subcommand`, since that is the subcommand, not the master `App`.
* Added printout of ini file text given parsed options, skips flags.
* Support for quotes and spaces in ini files

View File

@ -102,6 +102,8 @@ app.add_set(option_name,
help_string="",
default=false)
app.add_set_ignore_case(... // String only
app.add_config(option_name,
default_file_name="",
help_string="Read an ini file",
@ -125,14 +127,15 @@ Adding a configuration option is special. If it is present, it will be read alon
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. Before parsing, you can set the following options:
* `->required()`: The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works.
* `->expected(N)`: Take `N` values instead of as many as possible, only for vector args
* `->requires(opt)`: This option requires another option to also be present, opt is an `Option` pointer
* `->excludes(opt)`: This option cannot be given with `opt` present, opt is an `Option` pointer
* `->envname(name)`: Gets the value from the environment if present and not passed on the command line
* `->expected(N)`: Take `N` values instead of as many as possible, only for vector args.
* `->requires(opt)`: This option requires another option to also be present, opt is an `Option` pointer.
* `->excludes(opt)`: This option cannot be given with `opt` present, opt is an `Option` pointer.
* `->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"`.
* `->check(CLI::ExistingFile)`: Requires that the file exists if given
* `->check(CLI::ExistingDirectory)`: Requires that the directory exists
* `->check(CLI::NonexistentPath)`: Requires that the path does not exist
* `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
* `->check(CLI::ExistingFile)`: Requires that the file exists if given.
* `->check(CLI::ExistingDirectory)`: Requires that the directory exists.
* `->check(CLI::NonexistentPath)`: Requires that the path does not exist.
* `->check(CLI::Range(min,max))`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0.
These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. Check takes any function that has the signature `bool(std::string)`. If you want to change the default help option, it is available through `get_help_ptr`.
@ -157,7 +160,7 @@ everything after that is positional only.
## Subcommands
Subcommands are naturally supported, with an infinite depth. To add a subcommand, call the `add_subcommand` method with a name and an optional description. This gives a pointer to an `App` that behaves just like the main app, and can take options or further subcommands.
Subcommands are naturally supported, with an infinite depth. To add a subcommand, call the `add_subcommand` method with a name and an optional description. This gives a pointer to an `App` that behaves just like the main app, and can take options or further subcommands. Add `->ignore_case()` to a subcommand to allow any variation of caps to also be accepted. Children inherit the current setting from the parent.
All `App`s have a `get_subcommands()` method, which returns a list of pointers to the subcommand passed on the command line. A simple compare of these pointers to each subcommand allows choosing based on subcommand, facilitated by a `got_subcommand(App_or_name) method that will check the list for you. For many cases, however, using an app's callback may be easier. Every app executes a callback function after it parses; just use a lambda function (with capture to get parsed values) to `.add_callback`. If you throw `CLI::Success`, you can
even exit the program through the callback. The main `App` has a callback slot, as well, but it is generally not as useful.

View File

@ -123,6 +123,7 @@ public:
subcommands.emplace_back(new App(description, help));
subcommands.back()->name = name_;
subcommands.back()->allow_extras();
subcommands.back()->case_insensitive = case_insensitive;
return subcommands.back().get();
}
@ -312,6 +313,42 @@ public:
return retval;
}
/// Add set of options, string only, ignore case
Option* add_set_ignore_case(
std::string name_,
std::string &member, ///< The selected member of the set
std::set<std::string> _options, ///< The set of posibilities
std::string description="",
bool defaulted=false
) {
CLI::callback_t fun = [&member, _options](CLI::results_t res){
if(res.size()!=1) {
return false;
}
if(res[0].size()!=1) {
return false;
}
member = detail::to_lower(res.at(0).at(0));
auto iter = std::find_if(std::begin(_options), std::end(_options),
[&member](std::string val){return detail::to_lower(val) == member;});
if(iter == std::end(_options))
return false;
else {
member = *iter;
return true;
}
};
Option* retval = add_option(name_, fun, description, defaulted);
retval->typeval = detail::type_name<std::string>();
retval->typeval += " in {" + detail::join(_options) + "}";
if(defaulted) {
retval->defaultval = detail::to_lower(member);
}
return retval;
}
/// Add a configuration ini file option
Option* add_config(std::string name_="--config",
@ -504,8 +541,8 @@ public:
return local_name == name_to_check;
}
/// Accept any case
App* anycase(bool value = true) {
/// Ignore case
App* ignore_case(bool value = true) {
case_insensitive = value;
return this;
}

View File

@ -41,6 +41,7 @@ protected:
bool _required {false};
int _expected {1};
bool allow_vector {false};
bool case_insensitive {false};
std::vector<std::function<bool(std::string)>> _validators;
std::set<Option*> _requires;
@ -232,6 +233,12 @@ public:
return detail::join(name_list);
}
/// Ignore case
Option* ignore_case(bool value = true) {
case_insensitive = value;
return this;
}
/// Check a name. Requires "-" or "--" for short / long, supports positional name
bool check_name(std::string name) const {
@ -239,18 +246,36 @@ public:
return check_lname(name.substr(2));
else if (name.length()>1 && name.substr(0,1) == "-")
return check_sname(name.substr(1));
else
return name == pname;
else {
std::string local_pname = pname;
if(case_insensitive) {
local_pname = detail::to_lower(local_pname);
name = detail::to_lower(name);
}
return name == local_pname;
}
}
/// Requires "-" to be removed from string
bool check_sname(const std::string& name) const {
return std::find(std::begin(snames), std::end(snames), name) != std::end(snames);
bool check_sname(std::string name) const {
if(case_insensitive) {
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;})
!= std::end(snames);
} else
return std::find(std::begin(snames), std::end(snames), name) != std::end(snames);
}
/// Requires "--" to be removed from string
bool check_lname(const std::string& name) const {
return std::find(std::begin(lnames), std::end(lnames), name) != std::end(lnames);
bool check_lname(std::string name) const {
if(case_insensitive) {
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
return std::find(std::begin(lnames), std::end(lnames), name) != std::end(lnames);
}

View File

@ -116,7 +116,7 @@ inline bool valid_name_string(const std::string &str) {
/// Return a lower case version of a string
std::string inline to_lower(std::string str) {
std::transform(std::begin(str), std::end(str), std::begin(str),
[](const std::string::value_type x){return std::tolower(x,std::locale());});
[](const std::string::value_type &x){return std::tolower(x,std::locale());});
return str;
}