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

Adding the ability to set custom failure messages

This commit is contained in:
Henry Fredrick Schreiner 2017-11-22 12:16:02 -05:00 committed by Henry Schreiner
parent 1624b1fd22
commit 02548a64d8
3 changed files with 69 additions and 42 deletions

View File

@ -40,6 +40,11 @@ enum class Classifer { NONE, POSITIONAL_MARK, SHORT, LONG, SUBCOMMAND };
struct AppFriend; struct AppFriend;
} // namespace detail } // 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; class App;
using App_p = std::unique_ptr<App>; using App_p = std::unique_ptr<App>;
@ -89,6 +94,9 @@ class App {
/// A pointer to the help flag if there is one INHERITABLE /// A pointer to the help flag if there is one INHERITABLE
Option *help_ptr_{nullptr}; Option *help_ptr_{nullptr};
/// The error message printing function INHERITABLE
std::function<std::string(const App *, const Error &e)> failure_message_ = FailureMessage::simple;
///@} ///@}
/// @name Parsing /// @name Parsing
///@{ ///@{
@ -157,6 +165,7 @@ class App {
option_defaults_ = parent_->option_defaults_; option_defaults_ = parent_->option_defaults_;
// INHERITABLE // INHERITABLE
failure_message_ = parent_->failure_message_;
allow_extras_ = parent_->allow_extras_; allow_extras_ = parent_->allow_extras_;
prefix_command_ = parent_->prefix_command_; prefix_command_ = parent_->prefix_command_;
ignore_case_ = parent_->ignore_case_; ignore_case_ = parent_->ignore_case_;
@ -712,6 +721,11 @@ class App {
run_callback(); 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<std::string(const App *, const Error &e)> function) {
failure_message_ = function;
}
/// Print a nice error message and return the exit code /// Print a nice error message and return the exit code
int exit(const Error &e) const { int exit(const Error &e) const {
@ -719,15 +733,16 @@ class App {
if(dynamic_cast<const CLI::RuntimeError *>(&e) != nullptr) if(dynamic_cast<const CLI::RuntimeError *>(&e) != nullptr)
return e.get_exit_code(); return e.get_exit_code();
if(e.exit_code != static_cast<int>(ExitCodes::Success)) { if(dynamic_cast<const CLI::CallForHelp *>(&e) != nullptr) {
std::cerr << "ERROR: "; std::cout << help();
std::cerr << e.what() << std::endl; return e.get_exit_code();
if(e.print_help)
std::cerr << help();
} else {
if(e.print_help)
std::cout << help();
} }
if(e.exit_code != static_cast<int>(ExitCodes::Success)) {
if(failure_message_)
std::cerr << failure_message_(this, e) << std::flush;
}
return e.get_exit_code(); return e.get_exit_code();
} }
@ -895,7 +910,7 @@ class App {
out << std::endl << group << ":" << std::endl; out << std::endl << group << ":" << std::endl;
for(const Option_p &opt : options_) { for(const Option_p &opt : options_) {
if(opt->nonpositional() && opt->get_group() == group) 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 // Required
if(opt->get_required()) { if(opt->get_required()) {
if(opt->count() == 0) { if(opt->count() == 0) {
throw RequiredError(opt->get_name() + " is required"); throw RequiredError(opt->help_name() + " is required");
} else if(static_cast<int>(opt->count()) < opt->get_expected()) { } else if(static_cast<int>(opt->count()) < opt->get_expected()) {
if(opt->get_expected() == 1) throw RequiredError(opt->help_name() + " required at least " + std::to_string(opt->get_expected()) +
throw RequiredError(opt->get_name() + " requires an argument"); " arguments");
else
throw RequiredError(opt->get_name() + " requires at least " +
std::to_string(opt->get_expected()) + " arguments");
} }
} }
// Requires // Requires
for(const Option *opt_req : opt->requires_) for(const Option *opt_req : opt->requires_)
if(opt->count() > 0 && opt_req->count() == 0) 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 // Excludes
for(const Option *opt_ex : opt->excludes_) for(const Option *opt_ex : opt->excludes_)
if(opt->count() > 0 && opt_ex->count() != 0) 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(); 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 { namespace detail {
/// This class is simply to allow tests access to App's protected functions /// This class is simply to allow tests access to App's protected functions
struct AppFriend { struct AppFriend {

View File

@ -41,25 +41,19 @@ enum class ExitCodes {
/// All errors derive from this one /// All errors derive from this one
struct Error : public std::runtime_error { struct Error : public std::runtime_error {
int exit_code; int exit_code;
bool print_help;
int get_exit_code() const { return exit_code; } 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<int>(exit_code)), print_help(print_help) {} Error(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass)
Error(std::string parent, : runtime_error(parent + ": " + name), exit_code(static_cast<int>(exit_code)) {}
std::string name, Error(std::string parent, std::string name, int exit_code = static_cast<int>(ExitCodes::BaseClass))
int exit_code = static_cast<int>(ExitCodes::BaseClass), : runtime_error(parent + ": " + name), exit_code(exit_code) {}
bool print_help = true)
: runtime_error(parent + ": " + name), exit_code(exit_code), print_help(print_help) {}
}; };
/// Construction errors (not in parsing) /// Construction errors (not in parsing)
struct ConstructionError : public Error { struct ConstructionError : public Error {
// Using Error::Error constructors seem to not work on GCC 4.7 // Using Error::Error constructors seem to not work on GCC 4.7
ConstructionError(std::string parent, ConstructionError(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass)
std::string name, : Error(parent, name, exit_code) {}
ExitCodes exit_code = ExitCodes::BaseClass,
bool print_help = true)
: Error(parent, name, exit_code, print_help) {}
}; };
/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) /// 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 /// Anything that can error in Parse
struct ParseError : public Error { struct ParseError : public Error {
ParseError(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass, bool print_help = true) ParseError(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass)
: Error(parent, name, exit_code, print_help) {} : Error(parent, name, exit_code) {}
ParseError(std::string parent, ParseError(std::string parent, std::string name, int exit_code = static_cast<int>(ExitCodes::BaseClass))
std::string name, : Error(parent, name, exit_code) {}
int exit_code = static_cast<int>(ExitCodes::BaseClass),
bool print_help = true)
: Error(parent, name, exit_code, print_help) {}
}; };
// Not really "errors" // Not really "errors"
/// This is a successful completion on parsing, supposed to exit /// This is a successful completion on parsing, supposed to exit
struct Success : public ParseError { 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 /// -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. /// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code.
struct RuntimeError : public ParseError { 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 /// Thrown when parsing an INI file and it is missing

View File

@ -359,10 +359,20 @@ class Option : public OptionBase<Option> {
return out; return out;
} }
/// The first half of the help print, name plus default, etc /// The most discriptive name available
std::string help_name() const { std::string single_name() const {
if(!lnames_.empty())
return lnames_[0];
else if(!snames_.empty())
return snames_[0];
else
return pname_;
}
/// The first half of the help print, name plus default, etc. Setting opt_only to true avoids the positional name.
std::string help_name(bool opt_only = false) const {
std::stringstream out; std::stringstream out;
out << get_name(true) << help_aftername(); out << get_name(opt_only) << help_aftername();
return out.str(); return out.str();
} }