From f507f3e1c8ac613f0932cff430c2a84d55ecc2f5 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Wed, 31 Jul 2019 08:01:27 -0700 Subject: [PATCH] fix clear of config_ptr fix incorrect parenthesis update some clang-tidy fixes mainly else after return but a few conversions from into to bool add extra newline before footer add an extra field to the extra Error add a footer callback for help operations --- README.md | 1 + include/CLI/App.hpp | 49 ++++++++++++---------- include/CLI/ConfigFwd.hpp | 3 +- include/CLI/Error.hpp | 30 ++++++++------ include/CLI/Formatter.hpp | 18 ++++---- include/CLI/FormatterFwd.hpp | 2 +- include/CLI/Option.hpp | 79 +++++++++++++++++------------------- include/CLI/Split.hpp | 17 ++++---- include/CLI/Validators.hpp | 14 ++++--- tests/FormatterTest.cpp | 8 ++-- tests/HelpTest.cpp | 29 ++++++++++++- tests/IniTest.cpp | 3 +- 12 files changed, 146 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index ceae57561..f3ece0dc7 100644 --- a/README.md +++ b/README.md @@ -554,6 +554,7 @@ There are several options that are supported on the main app and subcommands and - `.positionals_at_end()`: 🆕 Specify that positional arguments occur as the last arguments and throw an error if an unexpected positional is encountered. - `.prefix_command()`: Like `allow_extras`, but stop immediately on the first unrecognized item. It is ideal for allowing your app or subcommand to be a "prefix" to calling another app. - `.footer(message)`: Set text to appear at the bottom of the help string. +- `.footer(std::string())`: 🚧 Set a callback to generate a string that will appear at the end of the help string. - `.set_help_flag(name, message)`: Set the help flag name and message, returns a pointer to the created option. - `.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). diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 7974e061f..43ceb6538 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -118,6 +118,9 @@ class App { /// Footer to put after all options in the help output INHERITABLE std::string footer_; + /// This is a function that generates a footer to put after all other options in help output + std::function footer_callback_; + /// A pointer to the help flag if there is one INHERITABLE Option *help_ptr_{nullptr}; @@ -461,9 +464,8 @@ class App { option->capture_default_str(); return option.get(); - - } else - throw OptionAlreadyAdded(myopt.get_name()); + } + throw OptionAlreadyAdded(myopt.get_name()); } /// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`) @@ -950,6 +952,7 @@ class App { remove_option(config_ptr_); config_name_ = ""; config_required_ = false; // Not really needed, but complete + config_ptr_ = nullptr; // need to remove the config_ptr completely } // Only add config if option passed @@ -1423,25 +1426,23 @@ class App { /// Removes an option from the excludes list of this subcommand bool remove_excludes(Option *opt) { auto iterator = std::find(std::begin(exclude_options_), std::end(exclude_options_), opt); - if(iterator != std::end(exclude_options_)) { - exclude_options_.erase(iterator); - return true; - } else { + if(iterator == std::end(exclude_options_)) { return false; } + exclude_options_.erase(iterator); + return true; } /// Removes a subcommand from this excludes list of this subcommand bool remove_excludes(App *app) { auto iterator = std::find(std::begin(exclude_subcommands_), std::end(exclude_subcommands_), app); - if(iterator != std::end(exclude_subcommands_)) { - auto other_app = *iterator; - exclude_subcommands_.erase(iterator); - other_app->remove_excludes(this); - return true; - } else { + if(iterator == std::end(exclude_subcommands_)) { return false; } + auto other_app = *iterator; + exclude_subcommands_.erase(iterator); + other_app->remove_excludes(this); + return true; } ///@} @@ -1453,7 +1454,11 @@ class App { footer_ = std::move(footer_string); return this; } - + /// Set footer. + App *footer(std::function footer_function) { + footer_callback_ = std::move(footer_function); + return this; + } /// Produce a string that could be read in as a config of the current values of the App. Set default_also to /// include default arguments. Prefix will add a string to the beginning of each option. std::string config_to_str(bool default_also = false, bool write_description = false) const { @@ -1470,10 +1475,10 @@ class App { // Delegate to subcommand if needed auto selected_subcommands = get_subcommands(); - if(!selected_subcommands.empty()) + if(!selected_subcommands.empty()) { return selected_subcommands.at(0)->help(prev, mode); - else - return formatter_->make_help(this, prev, mode); + } + return formatter_->make_help(this, prev, mode); } ///@} @@ -1592,8 +1597,8 @@ class App { /// Get the group of this subcommand const std::string &get_group() const { return group_; } - /// Get footer. - const std::string &get_footer() const { return footer_; } + /// Generate and return the footer. + std::string get_footer() const { return (footer_callback_) ? footer_callback_() + '\n' + footer_ : footer_; } /// Get the required min subcommand value size_t get_require_subcommand_min() const { return require_subcommand_min_; } @@ -2090,7 +2095,7 @@ class App { if(!(allow_extras_ || prefix_command_)) { size_t num_left_over = remaining_size(); if(num_left_over > 0) { - throw ExtrasError(remaining(false)); + throw ExtrasError(name_, remaining(false)); } } @@ -2107,7 +2112,7 @@ class App { size_t num_left_over = remaining_size(); if(num_left_over > 0) { args = remaining(false); - throw ExtrasError(args); + throw ExtrasError(name_, args); } } @@ -2372,7 +2377,7 @@ class App { } if(positionals_at_end_) { - throw CLI::ExtrasError(args); + throw CLI::ExtrasError(name_, args); } /// If this is an option group don't deal with it if(parent_ != nullptr && name_.empty()) { diff --git a/include/CLI/ConfigFwd.hpp b/include/CLI/ConfigFwd.hpp index 9e9de6986..c5eda8076 100644 --- a/include/CLI/ConfigFwd.hpp +++ b/include/CLI/ConfigFwd.hpp @@ -93,7 +93,8 @@ class Config { /// This converter works with INI files class ConfigINI : public Config { public: - std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override; + std::string + to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override; std::vector from_config(std::istream &input) const override { std::string line; diff --git a/include/CLI/Error.hpp b/include/CLI/Error.hpp index 59687f5c8..115bd66d4 100644 --- a/include/CLI/Error.hpp +++ b/include/CLI/Error.hpp @@ -206,32 +206,32 @@ class RequiredError : public ParseError { CLI11_ERROR_DEF(ParseError, RequiredError) explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} static RequiredError Subcommand(size_t min_subcom) { - if(min_subcom == 1) + if(min_subcom == 1) { return RequiredError("A subcommand"); - else - return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", - ExitCodes::RequiredError); + } + return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", + ExitCodes::RequiredError); } static RequiredError Option(size_t min_option, size_t max_option, size_t used, const std::string &option_list) { if((min_option == 1) && (max_option == 1) && (used == 0)) return RequiredError("Exactly 1 option from [" + option_list + "]"); - else if((min_option == 1) && (max_option == 1) && (used > 1)) + if((min_option == 1) && (max_option == 1) && (used > 1)) return RequiredError("Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) + " were given", ExitCodes::RequiredError); - else if((min_option == 1) && (used == 0)) + if((min_option == 1) && (used == 0)) return RequiredError("At least 1 option from [" + option_list + "]"); - else if(used < min_option) + if(used < min_option) return RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " + std::to_string(used) + "were given from [" + option_list + "]", ExitCodes::RequiredError); - else if(max_option == 1) + if(max_option == 1) return RequiredError("Requires at most 1 options be given from [" + option_list + "]", ExitCodes::RequiredError); - else - return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " + - std::to_string(used) + "were given from [" + option_list + "]", - ExitCodes::RequiredError); + + return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " + + std::to_string(used) + "were given from [" + option_list + "]", + ExitCodes::RequiredError); } }; @@ -279,6 +279,12 @@ class ExtrasError : public ParseError { : "The following argument was not expected: ") + detail::rjoin(args, " "), ExitCodes::ExtrasError) {} + ExtrasError(const std::string &name, std::vector args) + : ExtrasError(name, + (args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} }; /// Thrown when extra values are found in an INI file diff --git a/include/CLI/Formatter.hpp b/include/CLI/Formatter.hpp index 9f99515e6..a5bdd709f 100644 --- a/include/CLI/Formatter.hpp +++ b/include/CLI/Formatter.hpp @@ -28,8 +28,8 @@ inline std::string Formatter::make_positionals(const App *app) const { if(opts.empty()) return std::string(); - else - return make_group(get_label("Positionals"), true, opts); + + return make_group(get_label("Positionals"), true, opts); } inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) const { @@ -126,10 +126,10 @@ inline std::string Formatter::make_usage(const App *app, std::string name) const inline std::string Formatter::make_footer(const App *app) const { std::string footer = app->get_footer(); - if(!footer.empty()) - return footer + "\n"; - else - return ""; + if(footer.empty()) { + return std::string{}; + } + return footer + "\n"; } inline std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const { @@ -151,7 +151,7 @@ inline std::string Formatter::make_help(const App *app, std::string name, AppFor out << make_positionals(app); out << make_groups(app, mode); out << make_subcommands(app, mode); - out << make_footer(app); + out << '\n' << make_footer(app); return out.str(); } @@ -222,8 +222,8 @@ inline std::string Formatter::make_expanded(const App *sub) const { inline std::string Formatter::make_option_name(const Option *opt, bool is_positional) const { if(is_positional) return opt->get_name(true, false); - else - return opt->get_name(false, true); + + return opt->get_name(false, true); } inline std::string Formatter::make_option_opts(const Option *opt) const { diff --git a/include/CLI/FormatterFwd.hpp b/include/CLI/FormatterFwd.hpp index ebaace645..4a398245a 100644 --- a/include/CLI/FormatterFwd.hpp +++ b/include/CLI/FormatterFwd.hpp @@ -144,7 +144,7 @@ class Formatter : public FormatterBase { virtual std::string make_usage(const App *app, std::string name) const; /// This puts everything together - std::string make_help(const App *, std::string, AppFormatMode) const override; + std::string make_help(const App * /*app*/, std::string, AppFormatMode) const override; ///@} /// @name Options diff --git a/include/CLI/Option.hpp b/include/CLI/Option.hpp index 024e2fb7d..5c1a2a258 100644 --- a/include/CLI/Option.hpp +++ b/include/CLI/Option.hpp @@ -307,7 +307,7 @@ class Option : public OptionBase