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,6 +40,11 @@ enum class Classifer { NONE, POSITIONAL_MARK, SHORT, LONG, SUBCOMMAND };
40 struct AppFriend; 40 struct AppFriend;
41 } // namespace detail 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 class App; 48 class App;
44 49
45 using App_p = std::unique_ptr<App>; 50 using App_p = std::unique_ptr<App>;
@@ -89,6 +94,9 @@ class App { @@ -89,6 +94,9 @@ class App {
89 /// A pointer to the help flag if there is one INHERITABLE 94 /// A pointer to the help flag if there is one INHERITABLE
90 Option *help_ptr_{nullptr}; 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 /// @name Parsing 101 /// @name Parsing
94 ///@{ 102 ///@{
@@ -157,6 +165,7 @@ class App { @@ -157,6 +165,7 @@ class App {
157 option_defaults_ = parent_->option_defaults_; 165 option_defaults_ = parent_->option_defaults_;
158 166
159 // INHERITABLE 167 // INHERITABLE
  168 + failure_message_ = parent_->failure_message_;
160 allow_extras_ = parent_->allow_extras_; 169 allow_extras_ = parent_->allow_extras_;
161 prefix_command_ = parent_->prefix_command_; 170 prefix_command_ = parent_->prefix_command_;
162 ignore_case_ = parent_->ignore_case_; 171 ignore_case_ = parent_->ignore_case_;
@@ -712,6 +721,11 @@ class App { @@ -712,6 +721,11 @@ class App {
712 run_callback(); 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 /// Print a nice error message and return the exit code 729 /// Print a nice error message and return the exit code
716 int exit(const Error &e) const { 730 int exit(const Error &e) const {
717 731
@@ -719,15 +733,16 @@ class App { @@ -719,15 +733,16 @@ class App {
719 if(dynamic_cast<const CLI::RuntimeError *>(&e) != nullptr) 733 if(dynamic_cast<const CLI::RuntimeError *>(&e) != nullptr)
720 return e.get_exit_code(); 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 if(e.exit_code != static_cast<int>(ExitCodes::Success)) { 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 return e.get_exit_code(); 746 return e.get_exit_code();
732 } 747 }
733 748
@@ -895,7 +910,7 @@ class App { @@ -895,7 +910,7 @@ class App {
895 out << std::endl << group << ":" << std::endl; 910 out << std::endl << group << ":" << std::endl;
896 for(const Option_p &opt : options_) { 911 for(const Option_p &opt : options_) {
897 if(opt->nonpositional() && opt->get_group() == group) 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,23 +1122,20 @@ class App {
1107 // Required 1122 // Required
1108 if(opt->get_required()) { 1123 if(opt->get_required()) {
1109 if(opt->count() == 0) { 1124 if(opt->count() == 0) {
1110 - throw RequiredError(opt->get_name() + " is required"); 1125 + throw RequiredError(opt->help_name() + " is required");
1111 } else if(static_cast<int>(opt->count()) < opt->get_expected()) { 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 // Requires 1131 // Requires
1120 for(const Option *opt_req : opt->requires_) 1132 for(const Option *opt_req : opt->requires_)
1121 if(opt->count() > 0 && opt_req->count() == 0) 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 // Excludes 1135 // Excludes
1124 for(const Option *opt_ex : opt->excludes_) 1136 for(const Option *opt_ex : opt->excludes_)
1125 if(opt->count() > 0 && opt_ex->count() != 0) 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 auto selected_subcommands = get_subcommands(); 1141 auto selected_subcommands = get_subcommands();
@@ -1415,6 +1427,20 @@ class App { @@ -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 namespace detail { 1444 namespace detail {
1419 /// This class is simply to allow tests access to App's protected functions 1445 /// This class is simply to allow tests access to App's protected functions
1420 struct AppFriend { 1446 struct AppFriend {
include/CLI/Error.hpp
@@ -41,25 +41,19 @@ enum class ExitCodes { @@ -41,25 +41,19 @@ enum class ExitCodes {
41 /// All errors derive from this one 41 /// All errors derive from this one
42 struct Error : public std::runtime_error { 42 struct Error : public std::runtime_error {
43 int exit_code; 43 int exit_code;
44 - bool print_help;  
45 int get_exit_code() const { return exit_code; } 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 /// Construction errors (not in parsing) 52 /// Construction errors (not in parsing)
56 struct ConstructionError : public Error { 53 struct ConstructionError : public Error {
57 // Using Error::Error constructors seem to not work on GCC 4.7 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 /// Thrown when an option is set to conflicting values (non-vector and multi args, for example) 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,20 +77,17 @@ struct OptionAlreadyAdded : public ConstructionError {
83 77
84 /// Anything that can error in Parse 78 /// Anything that can error in Parse
85 struct ParseError : public Error { 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 // Not really "errors" 86 // Not really "errors"
96 87
97 /// This is a successful completion on parsing, supposed to exit 88 /// This is a successful completion on parsing, supposed to exit
98 struct Success : public ParseError { 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 /// -h or --help on command line 93 /// -h or --help on command line
@@ -107,7 +98,7 @@ struct CallForHelp : public ParseError { @@ -107,7 +98,7 @@ struct CallForHelp : public ParseError {
107 98
108 /// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. 99 /// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code.
109 struct RuntimeError : public ParseError { 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 /// Thrown when parsing an INI file and it is missing 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,10 +359,20 @@ class Option : public OptionBase&lt;Option&gt; {
359 return out; 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 std::stringstream out; 374 std::stringstream out;
365 - out << get_name(true) << help_aftername(); 375 + out << get_name(opt_only) << help_aftername();
366 return out.str(); 376 return out.str();
367 } 377 }
368 378