Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support runtime errors. #45

Merged
merged 4 commits into from
Nov 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ App* subcom = app.add_subcommand(name, discription);

An option name must start with a alphabetic character or underscore. For long options, anything but an equals sign or a comma is valid after that. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on help line for its positional form. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option` or `add_set`. The set options allow your users to pick from a set of predefined options.

On a C++14 compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a `CLI::ParseError` to signal a failure.
On a C++14 compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure.

### Example

Expand Down Expand Up @@ -193,7 +193,7 @@ If you want to require at least one subcommand is given, use `.require_subcomman
If an `App` (main or subcommand) has been parsed on the command line, `->parsed` will be true (or convert directly to bool).
All `App`s have a `get_subcommands()` method, which returns a list of pointers to the subcommands passed on the command line. A `got_subcommand(App_or_name)` method is also provided that will check to see if an `App` pointer or a string name was collected on the command line.

For many cases, however, using an app's callback may be easier. Every app executes a callback function after it parses; just use a lambda function (with capture to get parsed values) to `.set_callback`. If you throw `CLI::Success`, you can
For many cases, however, using an app's callback may be easier. Every app executes a callback function after it parses; just use a lambda function (with capture to get parsed values) to `.set_callback`. If you throw `CLI::Success` or `CLI::RuntimeError(return_value)`, you can
even exit the program through the callback. The main `App` has a callback slot, as well, but it is generally not as useful.
If you want only one, use `app.require_subcommand(1)`. You are allowed to throw `CLI::Success` in the callbacks.
Multiple subcommands are allowed, to allow [`Click`][Click] like series of commands (order is preserved).
Expand Down
7 changes: 2 additions & 5 deletions examples/enum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ int main(int argc, char **argv) {
app.add_set("-l,--level", level, {High, Medium, Low}, "Level settings")
->set_type_name("enum/Level in {High=0, Medium=1, Low=2}");

try {
app.parse(argc, argv);
} catch(CLI::Error const &e) {
app.exit(e);
}
CLI11_PARSE(app, argc, argv);

return 0;
}
2 changes: 1 addition & 1 deletion examples/groups.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ int main(int argc, char **argv) {

try {
app.parse(argc, argv);
} catch(const CLI::Error &e) {
} catch(const CLI::ParseError &e) {
return app.exit(e);
}

Expand Down
2 changes: 1 addition & 1 deletion examples/inter_argument_order.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ int main(int argc, char **argv) {

app.add_flag("--z,--x"); // Random other flags

// Standard parsing lines (copy and paste in)
// Standard parsing lines (copy and paste in, or use CLI11_PARSE)
try {
app.parse(argc, argv);
} catch(const CLI::ParseError &e) {
Expand Down
6 changes: 1 addition & 5 deletions examples/prefix_command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ int main(int argc, char **argv) {
std::vector<int> vals;
app.add_option("--vals,-v", vals)->expected(1);

try {
app.parse(argc, argv);
} catch(const CLI::ParseError &e) {
return app.exit(e);
}
CLI11_PARSE(app, argc, argv);

std::vector<std::string> more_comms = app.remaining();

Expand Down
6 changes: 1 addition & 5 deletions examples/subcommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@ int main(int argc, char **argv) {

CLI::Option *s = stop->add_flag("-c,--count", "Counter");

try {
app.parse(argc, argv);
} catch(const CLI::Error &e) {
return app.exit(e);
}
CLI11_PARSE(app, argc, argv);

std::cout << "Working on file: " << file << ", direct count: " << start->count("--file") << std::endl;
std::cout << "Working on count: " << s->count() << ", direct count: " << stop->count("--count") << std::endl;
Expand Down
5 changes: 5 additions & 0 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,11 @@ class App {

/// Print a nice error message and return the exit code
int exit(const Error &e) const {

/// Avoid printing anything if this is a CLI::RuntimeError
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;
Expand Down
10 changes: 10 additions & 0 deletions include/CLI/Error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ struct OptionAlreadyAdded : public ConstructionError {
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) {}
};

// Not really "errors"
Expand All @@ -100,6 +105,11 @@ struct CallForHelp : public ParseError {
: ParseError("CallForHelp", "This should be caught in your main function, see examples", ExitCodes::Success) {}
};

/// 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) {}
};

/// Thrown when parsing an INI file and it is missing
struct FileError : public ParseError {
FileError(std::string name) : ParseError("FileError", name, ExitCodes::File) {}
Expand Down
30 changes: 30 additions & 0 deletions tests/SubcommandTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,36 @@ TEST_F(TApp, Callbacks) {
EXPECT_TRUE(val);
}

TEST_F(TApp, RuntimeErrorInCallback) {
auto sub1 = app.add_subcommand("sub1");
sub1->set_callback([]() { throw CLI::RuntimeError(); });
auto sub2 = app.add_subcommand("sub2");
sub2->set_callback([]() { throw CLI::RuntimeError(2); });

args = {"sub1"};
EXPECT_THROW(run(), CLI::RuntimeError);

app.reset();
args = {"sub1"};
try {
run();
} catch(const CLI::RuntimeError &e) {
EXPECT_EQ(1, e.get_exit_code());
}

app.reset();
args = {"sub2"};
EXPECT_THROW(run(), CLI::RuntimeError);

app.reset();
args = {"sub2"};
try {
run();
} catch(const CLI::RuntimeError &e) {
EXPECT_EQ(2, e.get_exit_code());
}
}

TEST_F(TApp, NoFallThroughOpts) {
int val = 1;
app.add_option("--val", val);
Expand Down