From a7affdf6d6936a43c8a726899e0cbcc67ec53757 Mon Sep 17 00:00:00 2001 From: Nitin Kumar <59679977+lazysegtree@users.noreply.github.com> Date: Sat, 28 Feb 2026 13:40:31 +0530 Subject: [PATCH] feat: Allow implicit values with disabled arguments --- README.md | 10 ++++++++ include/cxxopts.hpp | 30 +++++++++++++++++++++-- test/options.cpp | 60 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b7aedfd..ab5e6e5 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", true) +``` + +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..09b2641 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -394,6 +394,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 +407,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, bool disabled_args = false) = 0; virtual std::shared_ptr 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 default_value(const std::string& value) override { @@ -1280,10 +1298,11 @@ class abstract_value : public Value } std::shared_ptr - 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(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(name); + } //parse the option given parse_option(opt, name, argu_desc.value); diff --git a/test/options.cpp b/test/options.cpp index c028ed8..23ae158 100644 --- a/test/options.cpp +++ b/test/options.cpp @@ -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()->implicit_value("true", true)) + ("s,string", "description", cxxopts::value()->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() == "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"}; + auto result = options.parse(argv.argc(), argv.argv()); + CHECK(result["bool"].as() == true); + CHECK(result["string"].as() == "value"); + } +} + TEST_CASE("Default values", "[default]") { cxxopts::Options options("defaults", "has defaults");