feat: Allow implicit values with disabled arguments

This commit is contained in:
Nitin Kumar 2026-02-28 13:40:31 +05:30
parent 370de72bfe
commit a7affdf6d6
3 changed files with 98 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", true)
```
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

@ -394,6 +394,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 +407,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, bool disabled_args = false) = 0;
virtual std::shared_ptr<Value>
no_implicit_value() = 0;
@ -482,6 +485,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 +1283,12 @@ class abstract_value : public Value
return m_implicit;
}
bool
has_disabled_args() const override
{
return m_disabled_args;
}
std::shared_ptr<Value>
default_value(const std::string& value) override
{
@ -1280,10 +1298,11 @@ class abstract_value : public Value
}
std::shared_ptr<Value>
implicit_value(const std::string& value) override
implicit_value(const std::string& value, bool disabled_args = false) override
{
m_implicit = true;
m_implicit_value = value;
m_disabled_args = disabled_args;
return shared_from_this();
}
@ -1328,6 +1347,7 @@ class abstract_value : public Value
bool m_default = false;
bool m_implicit = false;
bool m_disabled_args = false;
std::string m_default_value{};
std::string m_implicit_value{};
@ -2634,6 +2654,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 +2704,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,66 @@ 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", "description", cxxopts::value<bool>()->implicit_value("true", true))
("s,string", "description", cxxopts::value<std::string>()->implicit_value("value", true));
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"};
auto result = options.parse(argv.argc(), argv.argv());
CHECK(result["bool"].as<bool>() == true);
CHECK(result["string"].as<std::string>() == "value");
}
}
TEST_CASE("Default values", "[default]")
{
cxxopts::Options options("defaults", "has defaults");