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;
} // 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<App>;
@ -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<std::string(const App *, const Error &e)> 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<std::string(const App *, const Error &e)> 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<const CLI::RuntimeError *>(&e) != nullptr)
return e.get_exit_code();
if(e.exit_code != static_cast<int>(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<const CLI::CallForHelp *>(&e) != nullptr) {
std::cout << help();
return e.get_exit_code();
}
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();
}
@ -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<int>(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 {

View File

@ -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<int>(exit_code)), print_help(print_help) {}
Error(std::string parent,
std::string name,
int exit_code = static_cast<int>(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<int>(exit_code)) {}
Error(std::string parent, std::string name, int exit_code = static_cast<int>(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<int>(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<int>(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

View File

@ -359,10 +359,20 @@ class Option : public OptionBase<Option> {
return out;
}
/// The first half of the help print, name plus default, etc
std::string help_name() const {
/// The most discriptive name available
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;
out << get_name(true) << help_aftername();
out << get_name(opt_only) << help_aftername();
return out.str();
}