diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 46fb6504..8bc787c9 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -31,6 +31,8 @@ namespace CLI { (app).parse((argc), (argv)); \ } catch(const CLI::ParseError &e) { \ return (app).exit(e); \ + } catch(const CLI::RuntimeError &e) { \ + return e.get_exit_code(); \ } #endif diff --git a/include/CLI/Error.hpp b/include/CLI/Error.hpp index e820f265..30887fb1 100644 --- a/include/CLI/Error.hpp +++ b/include/CLI/Error.hpp @@ -79,6 +79,14 @@ struct OptionAlreadyAdded : public ConstructionError { : ConstructionError("OptionAlreadyAdded", name, ExitCodes::OptionAlreadyAdded) {} }; +// Runtime Errors + +/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. +struct RuntimeError : public Error { + RuntimeError(int exit_code = 1) + : Error("RuntimeError", "runtime error", exit_code, false) {} +}; + // Parsing errors /// Anything that can error in Parse diff --git a/tests/SubcommandTest.cpp b/tests/SubcommandTest.cpp index 1fb7f924..52f7acff 100644 --- a/tests/SubcommandTest.cpp +++ b/tests/SubcommandTest.cpp @@ -168,6 +168,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);