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:
parent
1624b1fd22
commit
02548a64d8
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user