diff --git a/README.md b/README.md index f157052..ed2e52b 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,14 @@ completely replaced by calling `options.custom_help`. Note that you might also want to override the positional help by calling `options.positional_help`. +## Value from ENV variable + +When parameter is not set, value will be fetched from an environment variable (if such variable is defined). + +```cpp +cxxopts::value()->env("MY_VAR") +``` + ## Example Putting all together: diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp index 1644692..e912960 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -314,6 +314,15 @@ namespace cxxopts virtual std::shared_ptr implicit_value(const std::string& value) = 0; + virtual std::shared_ptr + env(const std::string& var) = 0; + + virtual bool + has_env() const = 0; + + virtual std::string + get_env_var() const = 0; + virtual std::shared_ptr no_implicit_value() = 0; @@ -888,6 +897,26 @@ namespace cxxopts return m_implicit_value; } + std::shared_ptr + env(const std::string& var) + { + m_env = true; + m_env_var = var; + return shared_from_this(); + } + + bool + has_env() const + { + return m_env; + } + + std::string + get_env_var() const + { + return m_env_var; + } + bool is_boolean() const { @@ -913,9 +942,11 @@ namespace cxxopts bool m_default = false; bool m_implicit = false; + bool m_env = false; std::string m_default_value; std::string m_implicit_value; + std::string m_env_var; }; template @@ -1961,6 +1992,15 @@ ParseResult::parse(int& argc, char**& argv) if(value.has_default() && !store.count() && !store.has_default()){ parse_default(detail); } + + if(value.has_env() && store.count() < 1){ + const char* env = std::getenv(value.get_env_var().c_str()); + if(env){ + std::string str(env); + store.parse(detail, str); + } + } + } if (consume_remaining) diff --git a/test/options.cpp b/test/options.cpp index d3d3c7a..fd11f83 100644 --- a/test/options.cpp +++ b/test/options.cpp @@ -774,3 +774,39 @@ TEST_CASE("Option add with add_option(string, Option)", "[options]") { CHECK(result["aggregate"].as() == 4); CHECK(result["test"].as() == 5); } + +TEST_CASE("Value from ENV variable", "[options]") { + cxxopts::Options options("Reads value from ENV", " - if no parameter value is passed"); + + options.add_options("", { + {"b,bar", "bar option", cxxopts::value()->env("CXXOPTS_BAR")}, + {"f,foo", "foo option", cxxopts::value()->env("CXXOPTS_FOO")}, + {"z,baz", "baz option", cxxopts::value()->env("CXXOPTS_BAZ")->default_value("99")}, + {"e,empty", "empty option", cxxopts::value()->env("CXXOPTS_EMPTY")->default_value("1")}, + }); + + putenv((char*)"CXXOPTS_FOO=7"); + putenv((char*)"CXXOPTS_BAR=8"); + putenv((char*)"CXXOPTS_BAZ=9"); + + Argv argv_({ + "test", + "--foo", + "5" + }); + auto argc = argv_.argc(); + char** argv = argv_.argv(); + auto result = options.parse(argc, argv); + + CHECK(result.arguments().size()==1); + CHECK(options.groups().size() == 1); + CHECK(result.count("foo") == 1); + // value passed as an argument should take precedence + CHECK(result["foo"].as() == 5); + // from ENV variable + CHECK(result["bar"].as() == 8); + // from ENV variable + CHECK(result["baz"].as() == 9); + // env not defined, should fallback to default value + CHECK(result["empty"].as() == 1); +}