diff --git a/README.md b/README.md index b7aedfd..4c8b0e7 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,16 @@ though it was given on the command line. Default values are not counted by `Options::count`. +### Implicit values with disabled arguments + +You can specify an option for which an argument cannot be specified using below syntax. + +```cpp +cxxopts::value()->implicit_value("true", cxxopts::ImplicitArgPolicy::Disabled) +``` + +In this case specifying option as `--option=` is disallowed and will throw `specified_disabled_args` exception. Note that `--option value` is not supported for implicit values. It is treated as positional/unmatched. + ## Boolean values Boolean options have a default implicit value of `"true"`, which can be diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp index 7b4e2f1..0bae743 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -364,6 +364,11 @@ CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'"); CXXOPTS_DIAGNOSTIC_PUSH CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") +enum class ImplicitArgPolicy { + Disabled, + Enabled +}; + // some older versions of GCC warn under this warning CXXOPTS_IGNORE_WARNING("-Weffc++") class Value : public std::enable_shared_from_this @@ -394,6 +399,9 @@ class Value : public std::enable_shared_from_this virtual bool has_implicit() const = 0; + virtual bool + has_disabled_args() const = 0; + virtual std::string get_default_value() const = 0; @@ -404,7 +412,7 @@ class Value : public std::enable_shared_from_this default_value(const std::string& value) = 0; virtual std::shared_ptr - implicit_value(const std::string& value) = 0; + implicit_value(const std::string& value, ImplicitArgPolicy arg_policy = ImplicitArgPolicy::Enabled) = 0; virtual std::shared_ptr no_implicit_value() = 0; @@ -482,6 +490,15 @@ class invalid_option_syntax : public parsing { } }; +class specified_disabled_args : public parsing { + public: + explicit specified_disabled_args(const std::string& text) + : parsing("Option " + LQUOTE + text + RQUOTE + + " has disabled_args but argument was specified") + { + } +}; + class no_such_option : public parsing { public: @@ -1271,6 +1288,12 @@ class abstract_value : public Value return m_implicit; } + bool + has_disabled_args() const override + { + return m_implicit && (m_implicit_arg_policy == ImplicitArgPolicy::Disabled); + } + std::shared_ptr default_value(const std::string& value) override { @@ -1280,10 +1303,11 @@ class abstract_value : public Value } std::shared_ptr - implicit_value(const std::string& value) override + implicit_value(const std::string& value, ImplicitArgPolicy arg_policy = ImplicitArgPolicy::Enabled) override { m_implicit = true; m_implicit_value = value; + m_implicit_arg_policy = arg_policy; return shared_from_this(); } @@ -1329,6 +1353,9 @@ class abstract_value : public Value bool m_default = false; bool m_implicit = false; + // NOTE: Only meaningful when m_implicit == true + ImplicitArgPolicy m_implicit_arg_policy = ImplicitArgPolicy::Enabled; + std::string m_default_value{}; std::string m_implicit_value{}; }; @@ -2634,6 +2661,9 @@ OptionParser::parse(int argc, const char* const* argv) { //it must be the last argument if (argu_desc.set_value) { + if(value->value().has_disabled_args()){ + throw_or_mimic(name); + } parse_option(value, name, argu_desc.value); } else{ @@ -2681,6 +2711,9 @@ OptionParser::parse(int argc, const char* const* argv) //equals provided for long option? if (argu_desc.set_value) { + if(opt->value().has_disabled_args()){ + throw_or_mimic(name); + } //parse the option given parse_option(opt, name, argu_desc.value); diff --git a/test/options.cpp b/test/options.cpp index c028ed8..5293291 100644 --- a/test/options.cpp +++ b/test/options.cpp @@ -638,6 +638,69 @@ TEST_CASE("Boolean without implicit value", "[implicit]") } } +TEST_CASE("Implicit value with disabled_args", "[no_value]") +{ + const char prog_name[] = "disabled_args"; + cxxopts::Options options(prog_name, "Implicit value with disabled args"); + options.add_options() + ("b,bool", "bool with disabled_args", cxxopts::value()->implicit_value("true", cxxopts::ImplicitArgPolicy::Disabled)) + ("s,string", "string with disabled_args", cxxopts::value()->implicit_value("value", cxxopts::ImplicitArgPolicy::Disabled)) + ("x,string2", "string with first disabled and then enabled args", + cxxopts::value()->implicit_value("value", cxxopts::ImplicitArgPolicy::Disabled)->no_implicit_value()); + struct testcase{ + std::string name; + Argv argv; + } tests_eq[] = { + { + "exception due to value passed in long arg", + Argv{prog_name, "--bool=true"}, + }, + { + "exception due to value passed in short arg", + Argv{prog_name, "-b=true"}, + }, + { + "exception due to string value", + Argv{prog_name, "-b", "--string=something_else"}, + } + }; + + for(const auto& tc : tests_eq) { + SECTION(tc.name){ + CHECK_THROWS_AS(options.parse(tc.argv.argc(), tc.argv.argv()), + cxxopts::exceptions::specified_disabled_args); + } + } + + SECTION("unmatched string value without ="){ + Argv argv{prog_name, "--string", "new_value"}; + auto result = options.parse(argv.argc(), argv.argv()); + CHECK(result.unmatched().size() == 1); + CHECK(result.unmatched()[0] == "new_value"); + CHECK(result["string"].as() == "value"); + } + SECTION("unmatched short value without ="){ + Argv argv{prog_name, "-s", "new_value"}; + auto result = options.parse(argv.argc(), argv.argv()); + CHECK(result.unmatched().size() == 1); + CHECK(result.unmatched()[0] == "new_value"); + CHECK(result["string"].as() == "value"); + } + SECTION("exception due to short value grouped"){ + Argv argv{prog_name, "-snew_value"}; + // it will be considered as -s -n -e ... because -s has implicit value + CHECK_THROWS_AS(options.parse(argv.argc(), argv.argv()), + cxxopts::exceptions::no_such_option); + } + SECTION("No exception"){ + Argv argv{prog_name, "--string", "-b", "--string2=new_value"}; + auto result = options.parse(argv.argc(), argv.argv()); + CHECK(result["bool"].as() == true); + CHECK(result["string"].as() == "value"); + CHECK(result["string2"].as() == "new_value"); + } +} + TEST_CASE("Default values", "[default]") { cxxopts::Options options("defaults", "has defaults");