diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index b9b3c14a..3d5bf96e 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -40,6 +40,11 @@ enum class Classifer { NONE, POSITIONAL_MARK, SHORT, LONG, SUBCOMMAND }; struct AppFriend; } // namespace detail +namespace FailureMessage { +std::string simple(const App *app, const Error &e); +std::string help(const App *app, const Error &e); +} // namespace FailureMessage + class App; using App_p = std::unique_ptr; @@ -89,6 +94,9 @@ class App { /// A pointer to the help flag if there is one INHERITABLE Option *help_ptr_{nullptr}; + /// The error message printing function INHERITABLE + std::function failure_message_ = FailureMessage::simple; + ///@} /// @name Parsing ///@{ @@ -157,6 +165,7 @@ class App { option_defaults_ = parent_->option_defaults_; // INHERITABLE + failure_message_ = parent_->failure_message_; allow_extras_ = parent_->allow_extras_; prefix_command_ = parent_->prefix_command_; ignore_case_ = parent_->ignore_case_; @@ -712,6 +721,11 @@ class App { run_callback(); } + /// Provide a function to print a help message. The function gets access to the App pointer and error. + void set_failure_message(std::function function) { + failure_message_ = function; + } + /// Print a nice error message and return the exit code int exit(const Error &e) const { @@ -719,15 +733,16 @@ class App { if(dynamic_cast(&e) != nullptr) return e.get_exit_code(); - if(e.exit_code != static_cast(ExitCodes::Success)) { - std::cerr << "ERROR: "; - std::cerr << e.what() << std::endl; - if(e.print_help) - std::cerr << help(); - } else { - if(e.print_help) - std::cout << help(); + if(dynamic_cast(&e) != nullptr) { + std::cout << help(); + return e.get_exit_code(); } + + if(e.exit_code != static_cast(ExitCodes::Success)) { + if(failure_message_) + std::cerr << failure_message_(this, e) << std::flush; + } + return e.get_exit_code(); } @@ -895,7 +910,7 @@ class App { out << std::endl << group << ":" << std::endl; for(const Option_p &opt : options_) { if(opt->nonpositional() && opt->get_group() == group) - detail::format_help(out, opt->help_name(), opt->get_description(), wid); + detail::format_help(out, opt->help_name(true), opt->get_description(), wid); } } } @@ -1107,23 +1122,20 @@ class App { // Required if(opt->get_required()) { if(opt->count() == 0) { - throw RequiredError(opt->get_name() + " is required"); + throw RequiredError(opt->help_name() + " is required"); } else if(static_cast(opt->count()) < opt->get_expected()) { - if(opt->get_expected() == 1) - throw RequiredError(opt->get_name() + " requires an argument"); - else - throw RequiredError(opt->get_name() + " requires at least " + - std::to_string(opt->get_expected()) + " arguments"); + throw RequiredError(opt->help_name() + " required at least " + std::to_string(opt->get_expected()) + + " arguments"); } } // Requires for(const Option *opt_req : opt->requires_) if(opt->count() > 0 && opt_req->count() == 0) - throw RequiresError(opt->get_name(), opt_req->get_name()); + throw RequiresError(opt->single_name(), opt_req->single_name()); // Excludes for(const Option *opt_ex : opt->excludes_) if(opt->count() > 0 && opt_ex->count() != 0) - throw ExcludesError(opt->get_name(), opt_ex->get_name()); + throw ExcludesError(opt->single_name(), opt_ex->single_name()); } auto selected_subcommands = get_subcommands(); @@ -1415,6 +1427,20 @@ class App { } }; +namespace FailureMessage { +inline std::string simple(const App *app, const Error &e) { + std::string header = std::string("ERROR: ") + e.what() + "\n"; + if(app->get_help_ptr() != nullptr) + header += "Run with " + app->get_help_ptr()->single_name() + " for more help\n"; + return header; +}; +inline std::string help(const App *app, const Error &e) { + std::string header = std::string("ERROR: ") + e.what() + "\n"; + header += app->help(); + return header; +}; +} // namespace FailureMessage + namespace detail { /// This class is simply to allow tests access to App's protected functions struct AppFriend { diff --git a/include/CLI/Error.hpp b/include/CLI/Error.hpp index 4631b714..e5b549a5 100644 --- a/include/CLI/Error.hpp +++ b/include/CLI/Error.hpp @@ -41,25 +41,19 @@ enum class ExitCodes { /// All errors derive from this one struct Error : public std::runtime_error { int exit_code; - bool print_help; int get_exit_code() const { return exit_code; } - Error(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass, bool print_help = true) - : runtime_error(parent + ": " + name), exit_code(static_cast(exit_code)), print_help(print_help) {} - Error(std::string parent, - std::string name, - int exit_code = static_cast(ExitCodes::BaseClass), - bool print_help = true) - : runtime_error(parent + ": " + name), exit_code(exit_code), print_help(print_help) {} + + Error(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass) + : runtime_error(parent + ": " + name), exit_code(static_cast(exit_code)) {} + Error(std::string parent, std::string name, int exit_code = static_cast(ExitCodes::BaseClass)) + : runtime_error(parent + ": " + name), exit_code(exit_code) {} }; /// Construction errors (not in parsing) struct ConstructionError : public Error { // Using Error::Error constructors seem to not work on GCC 4.7 - ConstructionError(std::string parent, - std::string name, - ExitCodes exit_code = ExitCodes::BaseClass, - bool print_help = true) - : Error(parent, name, exit_code, print_help) {} + ConstructionError(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass) + : Error(parent, name, exit_code) {} }; /// Thrown when an option is set to conflicting values (non-vector and multi args, for example) @@ -83,20 +77,17 @@ struct OptionAlreadyAdded : public ConstructionError { /// Anything that can error in Parse struct ParseError : public Error { - ParseError(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass, bool print_help = true) - : Error(parent, name, exit_code, print_help) {} - ParseError(std::string parent, - std::string name, - int exit_code = static_cast(ExitCodes::BaseClass), - bool print_help = true) - : Error(parent, name, exit_code, print_help) {} + ParseError(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass) + : Error(parent, name, exit_code) {} + ParseError(std::string parent, std::string name, int exit_code = static_cast(ExitCodes::BaseClass)) + : Error(parent, name, exit_code) {} }; // Not really "errors" /// This is a successful completion on parsing, supposed to exit struct Success : public ParseError { - Success() : ParseError("Success", "Successfully completed, should be caught and quit", ExitCodes::Success, false) {} + Success() : ParseError("Success", "Successfully completed, should be caught and quit", ExitCodes::Success) {} }; /// -h or --help on command line @@ -107,7 +98,7 @@ struct CallForHelp : public ParseError { /// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. struct RuntimeError : public ParseError { - RuntimeError(int exit_code = 1) : ParseError("RuntimeError", "runtime error", exit_code, false) {} + RuntimeError(int exit_code = 1) : ParseError("RuntimeError", "runtime error", exit_code) {} }; /// Thrown when parsing an INI file and it is missing diff --git a/include/CLI/Option.hpp b/include/CLI/Option.hpp index bf0161d9..344f511c 100644 --- a/include/CLI/Option.hpp +++ b/include/CLI/Option.hpp @@ -359,10 +359,20 @@ class Option : public OptionBase