Commit 02548a64d8610ba3dfaf63bfd3dc4f1c68231a1f

Authored by Henry Fredrick Schreiner
Committed by Henry Schreiner
1 parent 1624b1fd

Adding the ability to set custom failure messages

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&lt;Option&gt; {
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  
... ...