mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-05-02 21:53:51 +00:00
Adding the ability to set custom failure messages
This commit is contained in:
parent
1624b1fd22
commit
02548a64d8
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user