From 700db24204f2b64b758cf165947ff51c3da4f249 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Wed, 20 Feb 2019 07:52:45 -0800 Subject: [PATCH] remove template for operator[] and adjust some tests add some comments in readME about performance move operator[] to return const Option * Apply suggestions from code review Co-Authored-By: phlptp update readme and add some IniTests and fix a bug from the tests add_flag_callback add a few tests to capture the different paths fix incorrectly updated CMAKE file, and add some subcommand test for option finding add disable_flag_override and work out some kinks in the find option functions add some more tests and fix a few bugs in as<> function for options Allow general flag types and default values, add shortcut notation for retrieving values --- README.md | 66 ++++++--- examples/CMakeLists.txt | 5 + examples/digit_args.cpp | 15 ++ include/CLI/App.hpp | 278 +++++++++++++++++++++++------------- include/CLI/ConfigFwd.hpp | 10 +- include/CLI/Error.hpp | 3 + include/CLI/Option.hpp | 150 +++++++++++++++++-- include/CLI/Split.hpp | 37 +++-- include/CLI/StringTools.hpp | 92 ++++-------- include/CLI/TypeTools.hpp | 76 +++++++--- tests/AppTest.cpp | 171 ++++++++++++++++++++-- tests/CreationTest.cpp | 3 + tests/HelpersTest.cpp | 18 +-- tests/IniTest.cpp | 62 +++++++- tests/SubcommandTest.cpp | 12 +- 15 files changed, 739 insertions(+), 259 deletions(-) create mode 100644 examples/digit_args.cpp diff --git a/README.md b/README.md index fc3aa5511..6fa7d5e6a 100644 --- a/README.md +++ b/README.md @@ -177,13 +177,15 @@ The initialization is just one line, adding options is just two each. The parse While all options internally are the same type, there are several ways to add an option depending on what you need. The supported values are: ```cpp +app.add_option(option_name, help_str="") + app.add_option(option_name, - variable_to_bind_to, // bool, int, float, vector, enum, or string-like + 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) app.add_option_function(option_name, - function , // int, float, enum, vector, or string-like + function , // int, bool, float, enum, vector, or string-like, or anything with a defined conversion from a string help_string="") app.add_complex(... // Special case: support for complex numbers @@ -192,17 +194,19 @@ app.add_flag(option_name, help_string="") app.add_flag(option_name, - int_or_bool, + variable_to_bind_to, // bool, int, float, vector, enum, or string-like, or anything with a defined conversion from a string help_string="") app.add_flag_function(option_name, - function , + function , help_string="") +app.add_flag_callback(option_name,function,help_string="") + App* subcom = app.add_subcommand(name, description); ``` -An option name must start with a alphabetic character or underscore. For long options, anything but an equals sign or a comma is valid after that. 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`. +An option name must start with a alphabetic character, underscore, or a number. For long options, anything but an equals sign or a comma is valid after that, though 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(...` 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. @@ -210,27 +214,37 @@ Flag options specified through the functions ```cpp app.add_flag(option_name, - int_or_bool, + help_string="") + +app.add_flag(option_name, + variable_to_bind_to, help_string="") app.add_flag_function(option_name, - function , + function , help_string="") + +app.add_flag_callback(option_name,function,help_string="") ``` -which allow a syntax for the option names to default particular options to a false value if some flags are passed. For example: +which 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: ```cpp app.add_flag("--flag,!--no-flag,result,"help for flag");` `````` specifies that if `--flag` is passed on the command line result will be true or contain a value of 1. If `--no-flag` is -passed result will contain false or -1 if result is a signed integer type, or 0 if it is an unsigned type. An +passed `result` will contain false or -1 if `result` is a signed integer type, or 0 if it is an unsigned type. An alternative form of the syntax is more explicit: `"--flag,--no-flag{false}"`; this is equivalent to the previous -example. This also works for short form options `"-f,!-n"` or `"-f,-n{false}"` If `int_or_bool` is a boolean value the -default behavior is to take the last value given, while if `int_or_bool` is an integer type the behavior will be to sum -all the given arguments and return the result. This can be modified if needed by changing the `multi_option_policy` on -each flag (this is not inherited). +example. This also works for short form options `"-f,!-n"` or `"-f,-n{false}"` If `variable_to_bind_to` is anything but an integer value the +default behavior is to take the last value given, while if `variable_to_bind_to` is an integer type the behavior will be to sum +all the given arguments and return the result. This can be modified if needed by changing the `multi_option_policy` on each flag (this is not inherited). +The default value can be any value For example if you wished to define a numerical flag +```cpp +app.add_flag("-1{1},-2{2},-3{3}",result,"numerical flag") +``` +using any of those flags on the command line will result in the specified number in the output. Similar things can be done for string values, and enumerations, as long as the default value can be converted to the given type. + On a C++14 compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure. @@ -258,6 +272,7 @@ 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 option 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. - `->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(CLI::IsMember(...))`: Require an option be a member of a given set. See below for options. @@ -296,7 +311,7 @@ On the command line, options can be given as: - `-ffilename` (no space required) - `-abcf filename` (flags and option can be combined) - `--long` (long flag) -- `--long_flag=true` (long flag with equals) +- `--long_flag=true` (long flag with equals to override default value) - `--file filename` (space) - `--file=filename` (equals) @@ -306,9 +321,10 @@ If `allow_windows_style_options()` is specified in the application or subcommand - `/long` (long flag) - `/file filename` (space) - `/file:filename` (colon) +- `/long_flag:false (long flag with : to override the default value) = Windows style options do not allow combining short options or values not separated from the short option like with `-` options -Long flag options may be given with and `=` to allow specifying a false value See [config files](#configuration-file) for details on the values supported. NOTE: only the `=` or `:` for windows-style options may be used for this, using a space will result in the argument being interpreted as a positional argument. This syntax can override the default (true or false) values. +Long flag options may be given with an `=` to allow specifying a false value, or some other value to the flag. See [config files](#configuration-file) for details on the values supported. NOTE: only the `=` or `:` for windows-style options may be used for this, using a space will result in the argument being interpreted as a positional argument. This syntax can override the default values, and can be disabled by using `disable_flag_override()`. Extra positional arguments will cause the program to exit, so at least one positional option with a vector is recommended if you want to allow extraneous arguments. If you set `.allow_extras()` on the main `App`, you will not get an error. You can access the missing options using `remaining` (if you have subcommands, `app.remaining(true)` will get all remaining options, subcommands included). @@ -317,10 +333,18 @@ You can access a vector of pointers to the parsed options in the original order If `--` is present in the command line that does not end an unlimited option, then everything after that is positional only. +#### 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: + +- `results()`: retrieves a vector of strings with all the results in the order they were given. +- `results(variable_to_bind_to)`: gets the results according to the MultiOptionPolicy and converts them just like the `add_option_function` with a variable. +- `results(vector_type_variable,delimiter)`: gets the results to a vector type and uses a delimiter to further split the values +- `Value=as()`: returns the result or default value directly as the specified type if possible. +- `Vector_value=as(delimiter): same the results function with the delimiter but returns the value directly. + ### Subcommands -Subcommands are supported, and can be nested infinitely. 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. `->ignore_underscore()` is similar, but for underscores. Children inherit the current setting from the parent. You cannot add multiple matching subcommand names at the same level (including ignore -case). +Subcommands are supported, and can be nested infinitely. 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. `->ignore_underscore()` is similar, but for underscores. Children inherit the current setting from the parent. You cannot add multiple matching subcommand names at the same level (including `ignore_case` and `ignore_underscore`). If you want to require that at least one subcommand is given, use `.require_subcommand()` on the parent app. You can optionally give an exact number of subcommands to require, as well. If you give two arguments, that sets the min and max number allowed. 0 for the max number allowed will allow an unlimited number of subcommands. As a handy shortcut, a single negative value N will set "up to N" values. Limiting the maximum number allows you to keep arguments that match a previous @@ -335,7 +359,7 @@ You are allowed to throw `CLI::Success` in the callbacks. Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved). Subcommands may also have an empty name either by calling `add_subcommand` with an empty string for the name or with no arguments. -Nameless subcommands function a little like groups in the main `App`. If an option is not defined in the main App, all nameless subcommands are checked as well. This allows for the options to be defined in a composable group. The `add_subcommand` function has an overload for adding a `shared_ptr` so the subcommand(s) could be defined in different components and merged into a main `App`, or possibly multiple `Apps`. Multiple nameless subcommands are allowed. +Nameless subcommands function a similarly to groups in the main `App`. If an option is not defined in the main App, all nameless subcommands are checked as well. This allows for the options to be defined in a composable group. The `add_subcommand` function has an overload for adding a `shared_ptr` so the subcommand(s) could be defined in different components and merged into a main `App`, or possibly multiple `Apps`. Multiple nameless subcommands are allowed. #### Subcommand options @@ -353,7 +377,8 @@ There are several options that are supported on the main app and subcommands. Th - `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line. - `.get_subcommands(filter)`: The list of subcommands given on the command line. - `.get_parent()`: Get the parent App or nullptr if called on master App. -- `.get_option(name)`: Get an option pointer by option name +- `.get_option(name)`: Get an option pointer by option name will throw if the specified option is not available, nameless subcommands are also searched +- `.get_option_no_throw(name)`: Get an option pointer by option name. This function will return a `nullptr` instead of throwing if the option is not available. - `.get_options(filter)`: Get the list of all defined option pointers (useful for processing the app for custom output formats). - `.parse_order()`: Get the list of option pointers in the order they were parsed (including duplicates). - `.formatter(fmt)`: Set a formatter, with signature `std::string(const App*, std::string, AppFormatMode)`. See Formatting for more details. @@ -370,6 +395,7 @@ There are several options that are supported on the main app and subcommands. Th - `.set_help_all_flag(name, message)`: Set the help all flag name and message, returns a pointer to the created option. Expands subcommands. - `.failure_message(func)`: Set the failure message function. Two provided: `CLI::FailureMessage::help` and `CLI::FailureMessage::simple` (the default). - `.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() 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. @@ -408,7 +434,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`, `ignore_underscore`, `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`, `ignore_underscore`, and `ignore_case`. To set these defaults, you should set the `option_defaults()` object, for example: +Options have defaults for `group`, `required`, `disable_flag_override`,`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(); diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 01590c319..ba80b69d0 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -132,6 +132,11 @@ add_test(NAME enum_fail COMMAND enum -l 4) set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION "--level: 4 not in {High,Medium,Low} | 4 not in {0,1,2}") +add_cli_exe(digit_args digit_args.cpp) +add_test(NAME digit_args COMMAND digit_args -h) +set_property(TEST digit_args PROPERTY PASS_REGULAR_EXPRESSION + "-3{3}") + add_cli_exe(modhelp modhelp.cpp) add_test(NAME modhelp COMMAND modhelp -a test -h) set_property(TEST modhelp PROPERTY PASS_REGULAR_EXPRESSION diff --git a/examples/digit_args.cpp b/examples/digit_args.cpp new file mode 100644 index 000000000..c2193c875 --- /dev/null +++ b/examples/digit_args.cpp @@ -0,0 +1,15 @@ +#include +#include + +int main(int argc, char **argv) { + CLI::App app; + + int val; + // add a set of flags with default values associate with them + app.add_flag("-1{1},-2{2},-3{3},-4{4},-5{5},-6{6}, -7{7}, -8{8}, -9{9}", val, "compression level"); + + CLI11_PARSE(app, argc, argv); + + std::cout << "value = " << val << std::endl; + return 0; +} diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index b30022f85..8295b03ff 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -402,9 +402,22 @@ class App { opt->type_name(detail::type_name()); 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); + } + + /// Add option with description but with no variable assignment or callback + template ::value && std::is_constructible::value, detail::enabler> = + detail::dummy> + Option *add_option(std::string option_name, T &option_description) { + return add_option(option_name, CLI::callback_t(), option_description, false); + } /// Add option for non-vectors with a default print - template ::value, detail::enabler> = detail::dummy> + template ::value && !std::is_const::value, detail::enabler> = detail::dummy> Option *add_option(std::string option_name, T &variable, ///< The variable to set std::string option_description, @@ -523,7 +536,8 @@ class App { } /// Set a help flag, replace the existing one if present - Option *set_help_flag(std::string flag_name = "", std::string help_description = "") { + Option *set_help_flag(std::string flag_name = "", const std::string &help_description = "") { + // take flag_description by const reference otherwise add_flag tries to assign to help_description if(help_ptr_ != nullptr) { remove_option(help_ptr_); help_ptr_ = nullptr; @@ -531,7 +545,7 @@ class App { // Empty name will simply remove the help flag if(!flag_name.empty()) { - help_ptr_ = add_flag(flag_name, std::move(help_description)); + help_ptr_ = add_flag(flag_name, help_description); help_ptr_->configurable(false); } @@ -539,7 +553,8 @@ class App { } /// Set a help all flag, replaced the existing one if present - Option *set_help_all_flag(std::string help_name = "", std::string help_description = "") { + Option *set_help_all_flag(std::string help_name = "", const std::string &help_description = "") { + // take flag_description by const reference otherwise add_flag tries to assign to flag_description if(help_all_ptr_ != nullptr) { remove_option(help_all_ptr_); help_all_ptr_ = nullptr; @@ -547,109 +562,149 @@ class App { // Empty name will simply remove the help all flag if(!help_name.empty()) { - help_all_ptr_ = add_flag(help_name, std::move(help_description)); + help_all_ptr_ = add_flag(help_name, help_description); help_all_ptr_->configurable(false); } return help_all_ptr_; } - /// Add option for flag - Option *add_flag(std::string flag_name, std::string flag_description = "") { - CLI::callback_t fun = [](CLI::results_t) { return true; }; - Option *opt = add_option(flag_name, fun, flag_description, false); - if(opt->get_positional()) - throw IncorrectConstruction::PositionalFlag(flag_name); + private: + /// Internal function for adding a flag + Option *_add_flag_internal(std::string flag_name, CLI::callback_t fun, std::string flag_description) { + Option *opt; + if(detail::has_default_flag_values(flag_name)) { + // check for default values and if it has them + auto flag_defaults = detail::get_default_flag_values(flag_name); + detail::remove_default_flag_values(flag_name); + opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false); + for(const auto &fname : flag_defaults) + opt->fnames_.push_back(fname.first); + opt->default_flag_values_ = std::move(flag_defaults); + } else { + opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false); + } + // flags cannot have positional values + if(opt->get_positional()) { + auto pos_name = opt->get_name(true); + remove_option(opt); + throw IncorrectConstruction::PositionalFlag(pos_name); + } + opt->type_size(0); return opt; } - /// Add option for flag integer + public: + /// Add a flag with no description or variable assignment + Option *add_flag(std::string flag_name) { return _add_flag_internal(flag_name, CLI::callback_t(), std::string{}); } + + /// Add flag with description but with no variable assignment or callback + /// takes a constant string, if a variable string is passed that variable will be assigned the results from the + /// flag + template ::value && std::is_constructible::value, detail::enabler> = + detail::dummy> + Option *add_flag(std::string flag_name, T &flag_description) { + return _add_flag_internal(flag_name, CLI::callback_t(), flag_description); + } + + /// Add option for flag with integer result - defaults to allowing multiple passings, but can be forced to one if + /// `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used. template ::value && !is_bool::value, detail::enabler> = detail::dummy> Option *add_flag(std::string flag_name, T &flag_count, ///< A variable holding the count std::string flag_description = "") { flag_count = 0; - Option *opt; CLI::callback_t fun = [&flag_count](CLI::results_t res) { - detail::sum_flag_vector(res, flag_count); + try { + detail::sum_flag_vector(res, flag_count); + } catch(const std::invalid_argument &) { + return false; + } return true; }; - if(detail::has_false_flags(flag_name)) { - std::vector neg = detail::get_false_flags(flag_name); - detail::remove_false_flag_notation(flag_name); - opt = add_option(flag_name, fun, flag_description, false); - opt->fnames_ = std::move(neg); - } else { - opt = add_option(flag_name, fun, flag_description, false); - } - - if(opt->get_positional()) - throw IncorrectConstruction::PositionalFlag(flag_name); - opt->type_size(0); - return opt; + return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); } - /// Bool version - defaults to allowing multiple passings, but can be forced to one if - /// `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used. - template ::value, detail::enabler> = detail::dummy> + /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes that + /// can be converted from a string + template ::value && !std::is_const::value && + (!std::is_integral::value || is_bool::value) && + !std::is_constructible, T>::value, + detail::enabler> = detail::dummy> Option *add_flag(std::string flag_name, T &flag_result, ///< A variable holding true if passed std::string flag_description = "") { - flag_result = false; - Option *opt; + CLI::callback_t fun = [&flag_result](CLI::results_t res) { - flag_result = (res[0][0] != '-'); - return res.size() == 1; + if(res.size() != 1) { + return false; + } + return CLI::detail::lexical_cast(res[0], flag_result); }; - if(detail::has_false_flags(flag_name)) { - std::vector neg = detail::get_false_flags(flag_name); - detail::remove_false_flag_notation(flag_name); - opt = add_option(flag_name, fun, std::move(flag_description), false); - opt->fnames_ = std::move(neg); - } else { - opt = add_option(flag_name, fun, std::move(flag_description), false); - } + Option *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); + opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast); + return opt; + } - if(opt->get_positional()) - throw IncorrectConstruction::PositionalFlag(flag_name); - opt->type_size(0); + /// Vector version to capture multiple flags. + template , T>::value, detail::enabler> = detail::dummy> + Option *add_flag(std::string flag_name, + std::vector &flag_results, ///< A vector of values with the flag results + std::string flag_description = "") { + CLI::callback_t fun = [&flag_results](CLI::results_t res) { + bool retval = true; + for(const auto &elem : res) { + flag_results.emplace_back(); + retval &= detail::lexical_cast(elem, flag_results.back()); + } + return retval; + }; + return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); + } + + /// Add option for callback that is triggered with a true flag and takes no arguments + Option *add_flag_callback(std::string flag_name, + std::function function, ///< A function to call, void(void) + std::string flag_description = "") { + + CLI::callback_t fun = [function](CLI::results_t res) { + if(res.size() != 1) { + return false; + } + bool trigger; + auto result = CLI::detail::lexical_cast(res[0], trigger); + if(trigger) + function(); + return result; + }; + Option *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast); return opt; } - /// Add option for callback + /// Add option for callback with an integer value Option *add_flag_function(std::string flag_name, - std::function function, ///< A function to call, void(size_t) + std::function function, ///< A function to call, void(int) std::string flag_description = "") { CLI::callback_t fun = [function](CLI::results_t res) { - int flag_count = 0; + int64_t flag_count = 0; detail::sum_flag_vector(res, flag_count); function(flag_count); return true; }; - Option *opt; - if(detail::has_false_flags(flag_name)) { - std::vector neg = detail::get_false_flags(flag_name); - detail::remove_false_flag_notation(flag_name); - opt = add_option(flag_name, fun, std::move(flag_description), false); - opt->fnames_ = std::move(neg); - } else { - opt = add_option(flag_name, fun, std::move(flag_description), false); - } - - if(opt->get_positional()) - throw IncorrectConstruction::PositionalFlag(flag_name); - opt->type_size(0); - return opt; + return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); } #ifdef CLI11_CPP14 /// Add option for callback (C++14 or better only) Option *add_flag(std::string flag_name, - std::function function, ///< A function to call, void(int) + std::function function, ///< A function to call, void(int) std::string flag_description = "") { return add_flag_function(std::move(flag_name), std::move(function), std::move(flag_description)); } @@ -1187,14 +1242,7 @@ class App { ///@{ /// Counts the number of times the given option was passed. - size_t count(std::string option_name) const { - for(const Option_p &opt : options_) { - if(opt->check_name(option_name)) { - return opt->count(); - } - } - throw OptionNotFound(option_name); - } + size_t count(std::string option_name) const { return get_option(option_name)->count(); } /// Get a subcommand pointer list to the currently selected subcommands (after parsing by by default, in command /// line order; use parsed = false to get the original definition list.) @@ -1312,26 +1360,68 @@ class App { return options; } - /// Get an option by name - const Option *get_option(std::string option_name) const { - for(const Option_p &opt : options_) { + /// Get an option by name (noexcept non-const version) + Option *get_option_no_throw(std::string option_name) noexcept { + for(Option_p &opt : options_) { if(opt->check_name(option_name)) { return opt.get(); } } - throw OptionNotFound(option_name); + for(auto &subc : subcommands_) { + // also check down into nameless subcommands + if(subc->get_name().empty()) { + auto opt = subc->get_option_no_throw(option_name); + if(opt != nullptr) { + return opt; + } + } + } + return nullptr; } - /// Get an option by name (non-const version) - Option *get_option(std::string option_name) { - for(Option_p &opt : options_) { + /// Get an option by name (noexcept const version) + const Option *get_option_no_throw(std::string option_name) const noexcept { + for(const Option_p &opt : options_) { if(opt->check_name(option_name)) { return opt.get(); } } - throw OptionNotFound(option_name); + for(const auto &subc : subcommands_) { + // also check down into nameless subcommands + if(subc->get_name().empty()) { + auto opt = subc->get_option_no_throw(option_name); + if(opt != nullptr) { + return opt; + } + } + } + return nullptr; + } + + /// Get an option by name + const Option *get_option(std::string option_name) const { + auto opt = get_option_no_throw(option_name); + if(opt == nullptr) { + throw OptionNotFound(option_name); + } + return opt; + } + + /// Get an option by name (non-const version) + Option *get_option(std::string option_name) { + auto opt = get_option_no_throw(option_name); + if(opt == nullptr) { + throw OptionNotFound(option_name); + } + return opt; } + /// Shortcut bracket operator for getting a pointer to an option + const Option *operator[](const std::string &option_name) const { return get_option(option_name); } + + /// Shortcut bracket operator for getting a pointer to an option + const Option *operator[](const char *option_name) const { return get_option(option_name); } + /// Check the status of ignore_case bool get_ignore_case() const { return ignore_case_; } @@ -1351,7 +1441,7 @@ class App { const std::string &get_group() const { return group_; } /// Get footer. - std::string get_footer() const { return footer_; } + const std::string &get_footer() const { return footer_; } /// Get the required min subcommand value size_t get_require_subcommand_min() const { return require_subcommand_min_; } @@ -1526,7 +1616,7 @@ class App { return detail::Classifier::LONG; if(detail::split_short(current, dummy1, dummy2)) return detail::Classifier::SHORT; - if((allow_windows_style_options_) && (detail::split_windows(current, dummy1, dummy2))) + if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2))) return detail::Classifier::WINDOWS; return detail::Classifier::NONE; } @@ -1735,10 +1825,8 @@ class App { } } - Option *op; - try { - op = get_option("--" + item.name); - } catch(const OptionNotFound &) { + Option *op = get_option_no_throw("--" + item.name); + if(op == nullptr) { // If the option was not present if(get_allow_config_extras()) // Should we worry about classifying the extras properly? @@ -1753,9 +1841,8 @@ class App { // Flag parsing if(op->get_type_size() == 0) { auto res = config_formatter_->to_flag(item); - if(op->check_fname(item.name)) { - res = (res == "1") ? "-1" : ((res[0] == '-') ? res.substr(1) : std::string("-" + res)); - } + res = op->get_flag_value(item.name, res); + op->add_result(res); } else { @@ -1894,7 +1981,7 @@ class App { throw HorribleError("Short parsed but missing! You should not see this"); break; case detail::Classifier::WINDOWS: - if(!detail::split_windows(current, arg_name, value)) + if(!detail::split_windows_style(current, arg_name, value)) throw HorribleError("windows option parsed but missing! You should not see this"); break; default: @@ -1946,16 +2033,9 @@ class App { int collected = 0; // deal with flag like things if(num == 0) { - try { - auto res = (value.empty()) ? std ::string("1") : detail::to_flag_value(value); - if(op->check_fname(arg_name)) { - res = (res == "1") ? "-1" : ((res[0] == '-') ? res.substr(1) : std::string("-" + res)); - } - op->add_result(res); - parse_order_.push_back(op.get()); - } catch(const std::invalid_argument &) { - throw ConversionError::TrueFalse(arg_name); - } + auto res = op->get_flag_value(arg_name, value); + op->add_result(res); + parse_order_.push_back(op.get()); } // --this=value else if(!value.empty()) { diff --git a/include/CLI/ConfigFwd.hpp b/include/CLI/ConfigFwd.hpp index 4044e36c7..9e9de6986 100644 --- a/include/CLI/ConfigFwd.hpp +++ b/include/CLI/ConfigFwd.hpp @@ -69,14 +69,10 @@ class Config { /// Convert a configuration into an app virtual std::vector from_config(std::istream &) const = 0; - /// Convert a flag to a bool representation + /// Get a flag value virtual std::string to_flag(const ConfigItem &item) const { if(item.inputs.size() == 1) { - try { - return detail::to_flag_value(item.inputs.at(0)); - } catch(const std::invalid_argument &) { - throw ConversionError::TrueFalse(item.fullname()); - } + return item.inputs.at(0); } throw ConversionError::TooManyInputsFlag(item.fullname()); } @@ -90,7 +86,7 @@ class Config { return from_config(input); } - /// virtual destructor + /// Virtual destructor virtual ~Config() = default; }; diff --git a/include/CLI/Error.hpp b/include/CLI/Error.hpp index 254d5b67b..7e518d867 100644 --- a/include/CLI/Error.hpp +++ b/include/CLI/Error.hpp @@ -231,6 +231,9 @@ class ArgumentMismatch : public ParseError { static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); } + static ArgumentMismatch FlagOverride(std::string name) { + return ArgumentMismatch(name + " was given a disallowed flag override"); + } }; /// Thrown when a requires option is missing diff --git a/include/CLI/Option.hpp b/include/CLI/Option.hpp index 1c386b7d2..ab481c105 100644 --- a/include/CLI/Option.hpp +++ b/include/CLI/Option.hpp @@ -28,7 +28,7 @@ class App; using Option_p = std::unique_ptr