diff --git a/README.md b/README.md
index a48f3fb..6955067 100644
--- a/README.md
+++ b/README.md
@@ -296,14 +296,13 @@ For the complete code see [examples/libenvpp_range_example.cpp](examples/libenvp
### Option Variables
-Another frequent use-case is that a value is one of a given set of options. For this an environment variable can be registered with `register_[required]_option`, which takes a list of valid options, against which the value is checked. For example:
+Another frequent use-case is that a value is one of a given set of options. For this an environment variable can be registered with `register_[required]_option`, which takes a list of either pairs of strings and corresponding options, or just valid options, against which the value is checked. For example:
```cpp
enum class option {
- first_choice,
- second_choice,
- third_choice,
- default_choice,
+ first,
+ second,
+ fallback,
};
int main()
@@ -311,29 +310,29 @@ int main()
auto pre = env::prefix("OPTION");
const auto option_id =
- pre.register_option("CHOICE", {option::first_choice, option::second_choice, option::third_choice});
+ pre.register_option ("CHOICE", {{"first", option::first}, {"second", option::second}});
const auto parsed_and_validated_pre = pre.parse_and_validate();
if (parsed_and_validated_pre.ok()) {
- const auto opt = parsed_and_validated_pre.get_or(option_id, option::default_choice);
+ const auto opt = parsed_and_validated_pre.get_or(option_id, option::fallback);
}
}
```
-This registers an `enum class` option, where only a subset of all possible values is considered valid, so that `option::default_choice` can be used as the value if the variable is not set.
+This registers an `enum class` option, where only a subset of all possible values is considered valid, so that `option::fallback` can be used as the value if the variable is not set.
_Note:_ The list of options provided when registering must not be empty, and must not contain duplicates.
_Note:_ As with range variables, the default value given with `get_or` is not enforced to be within the list of options given when registering the option variable.
-_Note:_ Since C++ does not provide any way to automatically parse `enum class` types from string, the example above additionally requires a specialized `default_parser` for the `enum class` type.
+_Note:_ For the variant where no mapping to strings is provided, a specialized `default_parser` for the `enum class` type must exist.
_Note:_ Options are mostly intended to be used with `enum class` types, but this is in no way a requirement. Any type can be used as an option, and `enum class` types can also just be normal environment variables.
#### Option Variables - Code
-For the full code, including the parser for the enum class, see [examples/libenvpp_option_example.cpp](examples/libenvpp_option_example.cpp).
+For the full code, which features both the simple case shown above and a case with a custom parser, see [examples/libenvpp_option_example.cpp](examples/libenvpp_option_example.cpp).
### Deprecated Variables
diff --git a/examples/libenvpp_option_example.cpp b/examples/libenvpp_option_example.cpp
index 6c44733..e8e65a8 100644
--- a/examples/libenvpp_option_example.cpp
+++ b/examples/libenvpp_option_example.cpp
@@ -36,6 +36,12 @@ struct default_parser {
};
} // namespace env
+enum class simple_option {
+ opt_a,
+ opt_b,
+ opt_c,
+};
+
int main()
{
auto pre = env::prefix("OPTION");
@@ -43,6 +49,9 @@ int main()
const auto option_id =
pre.register_option ("CHOICE", {option::first_choice, option::second_choice, option::third_choice});
+ const auto simple_option_id = pre.register_option(
+ "SIMPLE", {{"opt_a", simple_option::opt_a}, {"opt_b", simple_option::opt_b}, {"opt_c", simple_option::opt_c}});
+
const auto parsed_and_validated_pre = pre.parse_and_validate();
if (parsed_and_validated_pre.ok()) {
@@ -63,6 +72,21 @@ int main()
std::cout << "default_choice" << std::endl;
break;
}
+
+ const auto simple_opt = parsed_and_validated_pre.get_or(simple_option_id, simple_option::opt_a);
+
+ std::cout << "Simple option: ";
+ switch (simple_opt) {
+ case simple_option::opt_a:
+ std::cout << "opt_a" << std::endl;
+ break;
+ case simple_option::opt_b:
+ std::cout << "opt_b" << std::endl;
+ break;
+ case simple_option::opt_c:
+ std::cout << "opt_c" << std::endl;
+ break;
+ }
} else {
std::cout << parsed_and_validated_pre.warning_message();
std::cout << parsed_and_validated_pre.error_message();
diff --git a/include/libenvpp/env.hpp b/include/libenvpp/env.hpp
index 2540bc6..7edc8f0 100644
--- a/include/libenvpp/env.hpp
+++ b/include/libenvpp/env.hpp
@@ -14,6 +14,7 @@
#include
#include
+#include
#include
#include
@@ -58,6 +59,20 @@ class variable_data {
friend class ::env::parsed_and_validated_prefix;
};
+template
+std::pair, std::vector>
+extract_options(const std::initializer_list>& options)
+{
+ std::vector option_strings;
+ std::vector option_values;
+ option_strings.reserve(options.size());
+ option_values.reserve(options.size());
+ for (const auto& [str, val] : options) {
+ option_strings.push_back(str);
+ option_values.push_back(val);
+ }
+ return {option_strings, option_values};
+}
} // namespace detail
template
@@ -325,10 +340,26 @@ class prefix {
return registration_option_helper(name, options);
}
+ template
+ [[nodiscard]] auto register_option(const std::string_view name,
+ const std::initializer_list> options)
+ {
+ const auto [option_strings, option_values] = detail::extract_options(options);
+ return registration_option_helper(name, option_values, option_strings);
+ }
+
template
[[nodiscard]] auto register_required_option(const std::string_view name, const std::initializer_list options)
{
- return registration_option_helper(name, options);
+ return registration_option_helper(name, options);
+ }
+
+ template
+ [[nodiscard]] auto register_required_option(const std::string_view name,
+ const std::initializer_list> options)
+ {
+ const auto [option_strings, option_values] = detail::extract_options(options);
+ return registration_option_helper(name, option_values, option_strings);
}
void register_deprecated(const std::string_view name, const std::string_view deprecation_message)
@@ -426,8 +457,9 @@ class prefix {
return registration_helper(name, std::move(parser_and_validator));
}
- template
- [[nodiscard]] auto registration_option_helper(const std::string_view name, const std::initializer_list options)
+ template
+ [[nodiscard]] auto registration_option_helper(const std::string_view name, const std::vector options,
+ const std::vector option_strings = {})
{
if (options.size() == 0) {
throw empty_option{fmt::format("No options provided for '{}'", get_full_env_var_name(name))};
@@ -437,8 +469,24 @@ class prefix {
if (options_set.size() != options.size()) {
throw duplicate_option{fmt::format("Duplicate option specified for '{}'", get_full_env_var_name(name))};
}
- const auto parser_and_validator = [options = std::move(options_set)](const std::string_view str) {
- const auto value = default_parser{}(str);
+ const auto parser_and_validator = [options = std::move(options),
+ strings = std::move(option_strings)](const std::string_view str) {
+ const auto value = [&]() {
+ if constexpr(SimpleParsing) {
+ if(strings.size() != options.size()) {
+ throw option_error{fmt::format("Option strings must be provided for simple option parsing")};
+ }
+ const auto it = std::find(strings.begin(), strings.end(), str);
+ if (it != strings.end()) {
+ return options.at(std::distance(strings.begin(), it));
+ } else {
+ throw option_error{fmt::format("Unrecognized option '{}', should be one of [{}]", str,
+ fmt::join(strings, ", "))};
+ }
+ } else {
+ return default_parser{}(str);
+ }
+ }();
default_validator{}(value);
if (std::all_of(options.begin(), options.end(), [&value](const auto& option) { return option != value; })) {
throw option_error{fmt::format("Unrecognized option '{}'", str)};
diff --git a/test/libenvpp_test.cpp b/test/libenvpp_test.cpp
index 4d0d6cf..bec02e0 100644
--- a/test/libenvpp_test.cpp
+++ b/test/libenvpp_test.cpp
@@ -65,12 +65,18 @@ struct default_parser {
}
};
+enum class testing_simple_option {
+ OPT_A,
+ OPT_B,
+};
+
class option_var_fixture {
public:
- option_var_fixture() : m_var("LIBENVPP_TESTING_OPTION", "SECOND_OPTION") {}
+ option_var_fixture() : m_var("LIBENVPP_TESTING_OPTION", "SECOND_OPTION"), m_simple("LIBENVPP_TESTING_SIMPLE_OPTION", "OPT_A") {}
private:
detail::set_scoped_environment_variable m_var;
+ detail::set_scoped_environment_variable m_simple;
};
TEST_CASE_METHOD(int_var_fixture, "Retrieving integer environment variable", "[libenvpp]")
@@ -110,11 +116,27 @@ TEST_CASE_METHOD(option_var_fixture, "Retrieving option environment variable", "
{
auto pre = env::prefix("LIBENVPP_TESTING");
const auto option_id = pre.register_variable("OPTION");
+ const auto simple_option_id = pre.register_option("SIMPLE_OPTION", {{"OPT_A", testing_simple_option::OPT_A}, {"OPT_B", testing_simple_option::OPT_B}});
auto parsed_and_validated_pre = pre.parse_and_validate();
REQUIRE(parsed_and_validated_pre.ok());
const auto option_val = parsed_and_validated_pre.get(option_id);
REQUIRE(option_val.has_value());
CHECK(*option_val == testing_option::SECOND_OPTION);
+ const auto simple_option_val = parsed_and_validated_pre.get(simple_option_id);
+ REQUIRE(simple_option_val.has_value());
+ CHECK(*simple_option_val == testing_simple_option::OPT_A);
+}
+
+TEST_CASE("Parsing failure with 'simple' option handling", "[libenvpp]")
+{
+ const auto _ = detail::set_scoped_environment_variable{"LIBENVPP_TESTING_SIMPLE_OPTION", "INVALID_OPTION"};
+
+ auto pre = env::prefix("LIBENVPP_TESTING");
+ (void)pre.register_option("SIMPLE_OPTION", {{"OPT_A", testing_simple_option::OPT_A}, {"OPT_B", testing_simple_option::OPT_B}});
+ auto parsed_and_validated_pre = pre.parse_and_validate();
+ REQUIRE_FALSE(parsed_and_validated_pre.ok());
+ CHECK_THAT(parsed_and_validated_pre.error_message(),
+ ContainsSubstring("'LIBENVPP_TESTING_SIMPLE_OPTION': Unrecognized option 'INVALID_OPTION', should be one of [OPT_A, OPT_B]"));
}
struct user_parsable_type {