diff --git a/README.md b/README.md index 252bc3f..5ef040e 100644 --- a/README.md +++ b/README.md @@ -186,11 +186,14 @@ app.add_option_function(option_name, app.add_complex(... // Special case: support for complex numbers app.add_flag(option_name, - int_or_bool = nothing, + help_string="") + +app.add_flag(option_name, + int_or_bool, help_string="") app.add_flag_function(option_name, - function , + function , help_string="") app.add_set(option_name, @@ -212,7 +215,33 @@ 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` or `add_set`. The set options allow your users to pick from a set of predefined options, and you can use an initializer list directly if you like. If you need to modify the set later, use the `mutable` forms. -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 +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. + +Flag options specified through the functions + +```cpp +app.add_flag(option_name, + int_or_bool, + help_string="") + +app.add_flag_function(option_name, + function , + help_string="") +``` + +allow a syntax for the option names to default particular options to a false 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 +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 modifed if needed by changing the `multi_option_policy` on +each flag (this is not inherited). 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. @@ -241,7 +270,7 @@ Before parsing, you can set the following options: - `->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 - `->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 always default to take last). +- `->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::ExistingFile)`: Requires that the file exists if given. - `->check(CLI::ExistingDirectory)`: Requires that the directory exists. - `->check(CLI::ExistingPath)`: Requires that the path (file or directory) exists. @@ -261,6 +290,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) - `--file filename` (space) - `--file=filename` (equals) @@ -272,6 +302,8 @@ If allow_windows_style_options() is specified in the application or subcommand o - `/file:filename` (colon) = 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. + 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). @@ -360,7 +392,7 @@ in_subcommand = Wow sub.subcommand = true ``` -Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`; or `false`, `off`, `0`, `no` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not mean that subcommand was passed, it just sets the "defaults". You cannot set positional-only arguments or force subcommands to be present in the command line. +Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`,`enable`; or `false`, `off`, `0`, `no`,`disable` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not mean that subcommand was passed, it just sets the "defaults". You cannot set positional-only arguments or force subcommands to be present in the command line. To print a configuration file from the passed arguments, use `.config_to_str(default_also=false, prefix="", write_description=false)`, where `default_also` will also show any defaulted arguments, `prefix` will add a prefix, and `write_description` will include option descriptions. diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 6ec49c9..ec5e08d 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -538,7 +538,6 @@ class App { /// Add option for flag Option *add_flag(std::string flag_name, std::string description = "") { CLI::callback_t fun = [](CLI::results_t) { return true; }; - Option *opt = add_option(flag_name, fun, description, false); if(opt->get_positional()) throw IncorrectConstruction::PositionalFlag(flag_name); @@ -552,14 +551,21 @@ class App { Option *add_flag(std::string flag_name, T &flag_count, ///< A variable holding the count std::string description = "") { - flag_count = 0; + Option *opt; CLI::callback_t fun = [&flag_count](CLI::results_t res) { - flag_count = static_cast(res.size()); + detail::sum_flag_vector(res, flag_count); 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, description, false); + opt->fnames_ = std::move(neg); + } else { + opt = add_option(flag_name, fun, description, false); + } - Option *opt = add_option(flag_name, fun, description, false); if(opt->get_positional()) throw IncorrectConstruction::PositionalFlag(flag_name); opt->type_size(0); @@ -570,16 +576,23 @@ class App { /// `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used. template ::value, detail::enabler> = detail::dummy> Option *add_flag(std::string flag_name, - T &flag_count, ///< A variable holding true if passed + T &flag_result, ///< A variable holding true if passed std::string description = "") { - - flag_count = false; - CLI::callback_t fun = [&flag_count](CLI::results_t res) { - flag_count = true; + 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(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(description), false); + opt->fnames_ = std::move(neg); + } else { + opt = add_option(flag_name, fun, std::move(description), false); + } - Option *opt = add_option(flag_name, fun, description, false); if(opt->get_positional()) throw IncorrectConstruction::PositionalFlag(flag_name); opt->type_size(0); @@ -589,15 +602,25 @@ class App { /// Add option for callback 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(size_t) std::string description = "") { CLI::callback_t fun = [function](CLI::results_t res) { - function(res.size()); + int 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(description), false); + opt->fnames_ = std::move(neg); + } else { + opt = add_option(flag_name, fun, std::move(description), false); + } - Option *opt = add_option(flag_name, fun, description, false); if(opt->get_positional()) throw IncorrectConstruction::PositionalFlag(flag_name); opt->type_size(0); @@ -607,9 +630,9 @@ class App { #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(size_t) + std::function function, ///< A function to call, void(int) std::string description = "") { - return add_flag_function(flag_name, std::move(function), description); + return add_flag_function(std::move(flag_name), std::move(function), std::move(description)); } #endif @@ -628,7 +651,7 @@ class App { return std::find(std::begin(options), std::end(options), member) != std::end(options); }; - Option *opt = add_option(option_name, std::move(fun), description, false); + Option *opt = add_option(option_name, std::move(fun), std::move(description), false); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; opt->type_name(typeval); @@ -650,7 +673,7 @@ class App { return std::find(std::begin(options), std::end(options), member) != std::end(options); }; - Option *opt = add_option(option_name, std::move(fun), description, false); + Option *opt = add_option(option_name, std::move(fun), std::move(description), false); opt->type_name_fn( [&options]() { return std::string(detail::type_name()) + " in {" + detail::join(options) + "}"; }); @@ -673,7 +696,7 @@ class App { return std::find(std::begin(options), std::end(options), member) != std::end(options); }; - Option *opt = add_option(option_name, std::move(fun), description, defaulted); + Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; opt->type_name(typeval); @@ -701,7 +724,7 @@ class App { return std::find(std::begin(options), std::end(options), member) != std::end(options); }; - Option *opt = add_option(option_name, std::move(fun), description, defaulted); + Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted); opt->type_name_fn( [&options]() { return std::string(detail::type_name()) + " in {" + detail::join(options) + "}"; }); if(defaulted) { @@ -732,7 +755,7 @@ class App { } }; - Option *opt = add_option(option_name, std::move(fun), description, false); + Option *opt = add_option(option_name, std::move(fun), std::move(description), false); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; opt->type_name(typeval); @@ -761,7 +784,7 @@ class App { } }; - Option *opt = add_option(option_name, std::move(fun), description, false); + Option *opt = add_option(option_name, std::move(fun), std::move(description), false); opt->type_name_fn([&options]() { return std::string(detail::type_name()) + " in {" + detail::join(options) + "}"; }); @@ -790,7 +813,7 @@ class App { } }; - Option *opt = add_option(option_name, std::move(fun), description, defaulted); + Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; opt->type_name(typeval); @@ -821,7 +844,7 @@ class App { } }; - Option *opt = add_option(option_name, std::move(fun), description, defaulted); + Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted); opt->type_name_fn([&options]() { return std::string(detail::type_name()) + " in {" + detail::join(options) + "}"; }); @@ -851,7 +874,7 @@ class App { } }; - Option *opt = add_option(option_name, std::move(fun), description, false); + Option *opt = add_option(option_name, std::move(fun), std::move(description), false); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; opt->type_name(typeval); @@ -880,7 +903,7 @@ class App { } }; - Option *opt = add_option(option_name, std::move(fun), description, false); + Option *opt = add_option(option_name, std::move(fun), std::move(description), false); opt->type_name_fn([&options]() { return std::string(detail::type_name()) + " in {" + detail::join(options) + "}"; }); @@ -909,7 +932,7 @@ class App { } }; - Option *opt = add_option(option_name, std::move(fun), description, defaulted); + Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; opt->type_name(typeval); @@ -941,7 +964,7 @@ class App { } }; - Option *opt = add_option(option_name, std::move(fun), description, defaulted); + Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted); opt->type_name_fn([&options]() { return std::string(detail::type_name()) + " in {" + detail::join(options) + "}"; }); @@ -971,7 +994,7 @@ class App { } }; - Option *opt = add_option(option_name, std::move(fun), description, false); + Option *opt = add_option(option_name, std::move(fun), std::move(description), false); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; opt->type_name(typeval); @@ -1000,7 +1023,7 @@ class App { } }; - Option *opt = add_option(option_name, std::move(fun), description, false); + Option *opt = add_option(option_name, std::move(fun), std::move(description), false); opt->type_name_fn([&options]() { return std::string(detail::type_name()) + " in {" + detail::join(options) + "}"; }); @@ -1029,7 +1052,7 @@ class App { } }; - Option *opt = add_option(option_name, std::move(fun), description, defaulted); + Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; opt->type_name(typeval); @@ -1061,7 +1084,7 @@ class App { } }; - Option *opt = add_option(option_name, std::move(fun), description, defaulted); + Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted); opt->type_name_fn([&options]() { return std::string(detail::type_name()) + " in {" + detail::join(options) + "}"; }); @@ -1090,7 +1113,7 @@ class App { return worked; }; - CLI::Option *opt = add_option(option_name, std::move(fun), description, defaulted); + CLI::Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted); opt->type_name(label)->type_size(2); if(defaulted) { std::stringstream out; @@ -1149,7 +1172,7 @@ class App { /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag App *add_subcommand(std::string subcommand_name = "", std::string description = "") { - CLI::App_p subcom = std::shared_ptr(new App(description, subcommand_name, this)); + CLI::App_p subcom = std::shared_ptr(new App(std::move(description), subcommand_name, this)); return add_subcommand(std::move(subcom)); } @@ -1186,7 +1209,7 @@ class App { } /// Get a pointer to subcommand by index App *get_subcommand(int index = 0) const { - if((index >= 0) && (index < subcommands_.size())) + if((index >= 0) && (index < static_cast(subcommands_.size()))) return subcommands_[index].get(); throw OptionNotFound(std::to_string(index)); } @@ -1211,7 +1234,7 @@ class App { /// Get an owning pointer to subcommand by index CLI::App_p get_subcommand_ptr(int index = 0) const { - if((index >= 0) && (index < subcommands_.size())) + if((index >= 0) && (index < static_cast(subcommands_.size()))) return subcommands_[index]; throw OptionNotFound(std::to_string(index)); } @@ -1957,7 +1980,12 @@ class App { if(op->empty()) { // Flag parsing if(op->get_type_size() == 0) { - op->set_results(config_formatter_->to_flag(item)); + 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)); + } + op->add_result(res); + } else { op->set_results(item.inputs); op->run_callback(); @@ -2138,18 +2166,27 @@ class App { // Make sure we always eat the minimum for unlimited vectors 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); + } + } // --this=value - if(!value.empty()) { + else if(!value.empty()) { // If exact number expected if(num > 0) num--; op->add_result(value); parse_order_.push_back(op.get()); collected += 1; - } else if(num == 0) { - op->add_result(""); - parse_order_.push_back(op.get()); // -Trest } else if(!rest.empty()) { if(num > 0) diff --git a/include/CLI/ConfigFwd.hpp b/include/CLI/ConfigFwd.hpp index 53be440..31068ae 100644 --- a/include/CLI/ConfigFwd.hpp +++ b/include/CLI/ConfigFwd.hpp @@ -69,27 +69,16 @@ class Config { /// Convert a configuration into an app virtual std::vector from_config(std::istream &) const = 0; - /// Convert a flag to a bool - virtual std::vector to_flag(const ConfigItem &item) const { + /// Convert a flag to a bool representation + virtual std::string to_flag(const ConfigItem &item) const { if(item.inputs.size() == 1) { - std::string val = item.inputs.at(0); - val = detail::to_lower(val); - - if(val == "true" || val == "on" || val == "yes") { - return std::vector(1); - } else if(val == "false" || val == "off" || val == "no") { - return std::vector(); - } else { - try { - size_t ui = std::stoul(val); - return std::vector(ui); - } catch(const std::invalid_argument &) { - throw ConversionError::TrueFalse(item.fullname()); - } + try { + return detail::to_flag_value(item.inputs.at(0)); + } catch(const std::invalid_argument &) { + throw ConversionError::TrueFalse(item.fullname()); } - } else { - throw ConversionError::TooManyInputsFlag(item.fullname()); } + throw ConversionError::TooManyInputsFlag(item.fullname()); } /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure diff --git a/include/CLI/Formatter.hpp b/include/CLI/Formatter.hpp index 1b177bc..ae0efaa 100644 --- a/include/CLI/Formatter.hpp +++ b/include/CLI/Formatter.hpp @@ -59,10 +59,7 @@ inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) co inline std::string Formatter::make_description(const App *app) const { std::string desc = app->get_description(); - if(!desc.empty()) - return desc + "\n"; - else - return ""; + return (!desc.empty()) ? desc + "\n" : std::string{}; } inline std::string Formatter::make_usage(const App *app, std::string name) const { @@ -243,7 +240,6 @@ inline std::string Formatter::make_option_usage(const Option *opt) const { out << "(" << std::to_string(opt->get_expected()) << "x)"; else if(opt->get_expected() < 0) out << "..."; - return opt->get_required() ? out.str() : "[" + out.str() + "]"; } diff --git a/include/CLI/Option.hpp b/include/CLI/Option.hpp index 7f2a121..28bd260 100644 --- a/include/CLI/Option.hpp +++ b/include/CLI/Option.hpp @@ -173,6 +173,10 @@ class Option : public OptionBase