Commit 02548a64d8610ba3dfaf63bfd3dc4f1c68231a1f
Committed by
Henry Schreiner
1 parent
1624b1fd
Adding the ability to set custom failure messages
Showing
3 changed files
with
68 additions
and
41 deletions
include/CLI/App.hpp
| ... | ... | @@ -40,6 +40,11 @@ enum class Classifer { NONE, POSITIONAL_MARK, SHORT, LONG, SUBCOMMAND }; |
| 40 | 40 | struct AppFriend; |
| 41 | 41 | } // namespace detail |
| 42 | 42 | |
| 43 | +namespace FailureMessage { | |
| 44 | +std::string simple(const App *app, const Error &e); | |
| 45 | +std::string help(const App *app, const Error &e); | |
| 46 | +} // namespace FailureMessage | |
| 47 | + | |
| 43 | 48 | class App; |
| 44 | 49 | |
| 45 | 50 | using App_p = std::unique_ptr<App>; |
| ... | ... | @@ -89,6 +94,9 @@ class App { |
| 89 | 94 | /// A pointer to the help flag if there is one INHERITABLE |
| 90 | 95 | Option *help_ptr_{nullptr}; |
| 91 | 96 | |
| 97 | + /// The error message printing function INHERITABLE | |
| 98 | + std::function<std::string(const App *, const Error &e)> failure_message_ = FailureMessage::simple; | |
| 99 | + | |
| 92 | 100 | ///@} |
| 93 | 101 | /// @name Parsing |
| 94 | 102 | ///@{ |
| ... | ... | @@ -157,6 +165,7 @@ class App { |
| 157 | 165 | option_defaults_ = parent_->option_defaults_; |
| 158 | 166 | |
| 159 | 167 | // INHERITABLE |
| 168 | + failure_message_ = parent_->failure_message_; | |
| 160 | 169 | allow_extras_ = parent_->allow_extras_; |
| 161 | 170 | prefix_command_ = parent_->prefix_command_; |
| 162 | 171 | ignore_case_ = parent_->ignore_case_; |
| ... | ... | @@ -712,6 +721,11 @@ class App { |
| 712 | 721 | run_callback(); |
| 713 | 722 | } |
| 714 | 723 | |
| 724 | + /// Provide a function to print a help message. The function gets access to the App pointer and error. | |
| 725 | + void set_failure_message(std::function<std::string(const App *, const Error &e)> function) { | |
| 726 | + failure_message_ = function; | |
| 727 | + } | |
| 728 | + | |
| 715 | 729 | /// Print a nice error message and return the exit code |
| 716 | 730 | int exit(const Error &e) const { |
| 717 | 731 | |
| ... | ... | @@ -719,15 +733,16 @@ class App { |
| 719 | 733 | if(dynamic_cast<const CLI::RuntimeError *>(&e) != nullptr) |
| 720 | 734 | return e.get_exit_code(); |
| 721 | 735 | |
| 736 | + if(dynamic_cast<const CLI::CallForHelp *>(&e) != nullptr) { | |
| 737 | + std::cout << help(); | |
| 738 | + return e.get_exit_code(); | |
| 739 | + } | |
| 740 | + | |
| 722 | 741 | if(e.exit_code != static_cast<int>(ExitCodes::Success)) { |
| 723 | - std::cerr << "ERROR: "; | |
| 724 | - std::cerr << e.what() << std::endl; | |
| 725 | - if(e.print_help) | |
| 726 | - std::cerr << help(); | |
| 727 | - } else { | |
| 728 | - if(e.print_help) | |
| 729 | - std::cout << help(); | |
| 742 | + if(failure_message_) | |
| 743 | + std::cerr << failure_message_(this, e) << std::flush; | |
| 730 | 744 | } |
| 745 | + | |
| 731 | 746 | return e.get_exit_code(); |
| 732 | 747 | } |
| 733 | 748 | |
| ... | ... | @@ -895,7 +910,7 @@ class App { |
| 895 | 910 | out << std::endl << group << ":" << std::endl; |
| 896 | 911 | for(const Option_p &opt : options_) { |
| 897 | 912 | if(opt->nonpositional() && opt->get_group() == group) |
| 898 | - detail::format_help(out, opt->help_name(), opt->get_description(), wid); | |
| 913 | + detail::format_help(out, opt->help_name(true), opt->get_description(), wid); | |
| 899 | 914 | } |
| 900 | 915 | } |
| 901 | 916 | } |
| ... | ... | @@ -1107,23 +1122,20 @@ class App { |
| 1107 | 1122 | // Required |
| 1108 | 1123 | if(opt->get_required()) { |
| 1109 | 1124 | if(opt->count() == 0) { |
| 1110 | - throw RequiredError(opt->get_name() + " is required"); | |
| 1125 | + throw RequiredError(opt->help_name() + " is required"); | |
| 1111 | 1126 | } else if(static_cast<int>(opt->count()) < opt->get_expected()) { |
| 1112 | - if(opt->get_expected() == 1) | |
| 1113 | - throw RequiredError(opt->get_name() + " requires an argument"); | |
| 1114 | - else | |
| 1115 | - throw RequiredError(opt->get_name() + " requires at least " + | |
| 1116 | - std::to_string(opt->get_expected()) + " arguments"); | |
| 1127 | + throw RequiredError(opt->help_name() + " required at least " + std::to_string(opt->get_expected()) + | |
| 1128 | + " arguments"); | |
| 1117 | 1129 | } |
| 1118 | 1130 | } |
| 1119 | 1131 | // Requires |
| 1120 | 1132 | for(const Option *opt_req : opt->requires_) |
| 1121 | 1133 | if(opt->count() > 0 && opt_req->count() == 0) |
| 1122 | - throw RequiresError(opt->get_name(), opt_req->get_name()); | |
| 1134 | + throw RequiresError(opt->single_name(), opt_req->single_name()); | |
| 1123 | 1135 | // Excludes |
| 1124 | 1136 | for(const Option *opt_ex : opt->excludes_) |
| 1125 | 1137 | if(opt->count() > 0 && opt_ex->count() != 0) |
| 1126 | - throw ExcludesError(opt->get_name(), opt_ex->get_name()); | |
| 1138 | + throw ExcludesError(opt->single_name(), opt_ex->single_name()); | |
| 1127 | 1139 | } |
| 1128 | 1140 | |
| 1129 | 1141 | auto selected_subcommands = get_subcommands(); |
| ... | ... | @@ -1415,6 +1427,20 @@ class App { |
| 1415 | 1427 | } |
| 1416 | 1428 | }; |
| 1417 | 1429 | |
| 1430 | +namespace FailureMessage { | |
| 1431 | +inline std::string simple(const App *app, const Error &e) { | |
| 1432 | + std::string header = std::string("ERROR: ") + e.what() + "\n"; | |
| 1433 | + if(app->get_help_ptr() != nullptr) | |
| 1434 | + header += "Run with " + app->get_help_ptr()->single_name() + " for more help\n"; | |
| 1435 | + return header; | |
| 1436 | +}; | |
| 1437 | +inline std::string help(const App *app, const Error &e) { | |
| 1438 | + std::string header = std::string("ERROR: ") + e.what() + "\n"; | |
| 1439 | + header += app->help(); | |
| 1440 | + return header; | |
| 1441 | +}; | |
| 1442 | +} // namespace FailureMessage | |
| 1443 | + | |
| 1418 | 1444 | namespace detail { |
| 1419 | 1445 | /// This class is simply to allow tests access to App's protected functions |
| 1420 | 1446 | struct AppFriend { | ... | ... |
include/CLI/Error.hpp
| ... | ... | @@ -41,25 +41,19 @@ enum class ExitCodes { |
| 41 | 41 | /// All errors derive from this one |
| 42 | 42 | struct Error : public std::runtime_error { |
| 43 | 43 | int exit_code; |
| 44 | - bool print_help; | |
| 45 | 44 | int get_exit_code() const { return exit_code; } |
| 46 | - Error(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass, bool print_help = true) | |
| 47 | - : runtime_error(parent + ": " + name), exit_code(static_cast<int>(exit_code)), print_help(print_help) {} | |
| 48 | - Error(std::string parent, | |
| 49 | - std::string name, | |
| 50 | - int exit_code = static_cast<int>(ExitCodes::BaseClass), | |
| 51 | - bool print_help = true) | |
| 52 | - : runtime_error(parent + ": " + name), exit_code(exit_code), print_help(print_help) {} | |
| 45 | + | |
| 46 | + Error(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass) | |
| 47 | + : runtime_error(parent + ": " + name), exit_code(static_cast<int>(exit_code)) {} | |
| 48 | + Error(std::string parent, std::string name, int exit_code = static_cast<int>(ExitCodes::BaseClass)) | |
| 49 | + : runtime_error(parent + ": " + name), exit_code(exit_code) {} | |
| 53 | 50 | }; |
| 54 | 51 | |
| 55 | 52 | /// Construction errors (not in parsing) |
| 56 | 53 | struct ConstructionError : public Error { |
| 57 | 54 | // Using Error::Error constructors seem to not work on GCC 4.7 |
| 58 | - ConstructionError(std::string parent, | |
| 59 | - std::string name, | |
| 60 | - ExitCodes exit_code = ExitCodes::BaseClass, | |
| 61 | - bool print_help = true) | |
| 62 | - : Error(parent, name, exit_code, print_help) {} | |
| 55 | + ConstructionError(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass) | |
| 56 | + : Error(parent, name, exit_code) {} | |
| 63 | 57 | }; |
| 64 | 58 | |
| 65 | 59 | /// Thrown when an option is set to conflicting values (non-vector and multi args, for example) |
| ... | ... | @@ -83,20 +77,17 @@ struct OptionAlreadyAdded : public ConstructionError { |
| 83 | 77 | |
| 84 | 78 | /// Anything that can error in Parse |
| 85 | 79 | struct ParseError : public Error { |
| 86 | - ParseError(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass, bool print_help = true) | |
| 87 | - : Error(parent, name, exit_code, print_help) {} | |
| 88 | - ParseError(std::string parent, | |
| 89 | - std::string name, | |
| 90 | - int exit_code = static_cast<int>(ExitCodes::BaseClass), | |
| 91 | - bool print_help = true) | |
| 92 | - : Error(parent, name, exit_code, print_help) {} | |
| 80 | + ParseError(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass) | |
| 81 | + : Error(parent, name, exit_code) {} | |
| 82 | + ParseError(std::string parent, std::string name, int exit_code = static_cast<int>(ExitCodes::BaseClass)) | |
| 83 | + : Error(parent, name, exit_code) {} | |
| 93 | 84 | }; |
| 94 | 85 | |
| 95 | 86 | // Not really "errors" |
| 96 | 87 | |
| 97 | 88 | /// This is a successful completion on parsing, supposed to exit |
| 98 | 89 | struct Success : public ParseError { |
| 99 | - Success() : ParseError("Success", "Successfully completed, should be caught and quit", ExitCodes::Success, false) {} | |
| 90 | + Success() : ParseError("Success", "Successfully completed, should be caught and quit", ExitCodes::Success) {} | |
| 100 | 91 | }; |
| 101 | 92 | |
| 102 | 93 | /// -h or --help on command line |
| ... | ... | @@ -107,7 +98,7 @@ struct CallForHelp : public ParseError { |
| 107 | 98 | |
| 108 | 99 | /// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. |
| 109 | 100 | struct RuntimeError : public ParseError { |
| 110 | - RuntimeError(int exit_code = 1) : ParseError("RuntimeError", "runtime error", exit_code, false) {} | |
| 101 | + RuntimeError(int exit_code = 1) : ParseError("RuntimeError", "runtime error", exit_code) {} | |
| 111 | 102 | }; |
| 112 | 103 | |
| 113 | 104 | /// Thrown when parsing an INI file and it is missing | ... | ... |
include/CLI/Option.hpp
| ... | ... | @@ -359,10 +359,20 @@ class Option : public OptionBase<Option> { |
| 359 | 359 | return out; |
| 360 | 360 | } |
| 361 | 361 | |
| 362 | - /// The first half of the help print, name plus default, etc | |
| 363 | - std::string help_name() const { | |
| 362 | + /// The most discriptive name available | |
| 363 | + std::string single_name() const { | |
| 364 | + if(!lnames_.empty()) | |
| 365 | + return lnames_[0]; | |
| 366 | + else if(!snames_.empty()) | |
| 367 | + return snames_[0]; | |
| 368 | + else | |
| 369 | + return pname_; | |
| 370 | + } | |
| 371 | + | |
| 372 | + /// The first half of the help print, name plus default, etc. Setting opt_only to true avoids the positional name. | |
| 373 | + std::string help_name(bool opt_only = false) const { | |
| 364 | 374 | std::stringstream out; |
| 365 | - out << get_name(true) << help_aftername(); | |
| 375 | + out << get_name(opt_only) << help_aftername(); | |
| 366 | 376 | return out.str(); |
| 367 | 377 | } |
| 368 | 378 | ... | ... |