feat: Allow implicit values with disabled arguments (#492)

* feat: Allow implicit values with disabled arguments
This commit is contained in:
Nitin Kumar 2026-03-03 12:51:57 +05:30 committed by GitHub
parent 370de72bfe
commit 25f319275e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 108 additions and 2 deletions

View File

@ -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<bool>()->implicit_value("true", cxxopts::ImplicitArgPolicy::Disabled)
```
In this case specifying option as `--option=<value>` 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

View File

@ -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<Value>
@ -394,6 +399,9 @@ class Value : public std::enable_shared_from_this<Value>
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<Value>
default_value(const std::string& value) = 0;
virtual std::shared_ptr<Value>
implicit_value(const std::string& value) = 0;
implicit_value(const std::string& value, ImplicitArgPolicy arg_policy = ImplicitArgPolicy::Enabled) = 0;
virtual std::shared_ptr<Value>
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<Value>
default_value(const std::string& value) override
{
@ -1280,10 +1303,11 @@ class abstract_value : public Value
}
std::shared_ptr<Value>
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<exceptions::specified_disabled_args>(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<exceptions::specified_disabled_args>(name);
}
//parse the option given
parse_option(opt, name, argu_desc.value);

View File

@ -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<bool>()->implicit_value("true", cxxopts::ImplicitArgPolicy::Disabled))
("s,string", "string with disabled_args", cxxopts::value<std::string>()->implicit_value("value", cxxopts::ImplicitArgPolicy::Disabled))
("x,string2", "string with first disabled and then enabled args",
cxxopts::value<std::string>()->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<std::string>() == "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<std::string>() == "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<bool>() == true);
CHECK(result["string"].as<std::string>() == "value");
CHECK(result["string2"].as<std::string>() == "new_value");
}
}
TEST_CASE("Default values", "[default]")
{
cxxopts::Options options("defaults", "has defaults");