diff --git a/docs/tutorial.md b/docs/tutorial.md index 13745142..5d5da2c2 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -31,6 +31,9 @@ - [Creating New Groups](#creating-new-groups) - [Adding Arguments to Groups](#adding-arguments-to-groups) - [Group Attributes](#group-attributes) + - [Validation Rules](#validation-rules) + - [Naming Modifiers](#naming-modifiers) + - [Visibility](#visibility) - [Complete Example](#complete-example) - [Suppressing Argument Group Checks](#suppressing-argument-group-checks) - [Parsing Arguments](#parsing-arguments) @@ -42,6 +45,9 @@ - [Compound Arguments](#compound-arguments) - [Parsing Known Arguments](#parsing-known-arguments) - [Retrieving Argument Values](#retrieving-argument-values) + - [Using the Parser](#using-the-parser) + - [Using Argument References](#using-argument-references) + - [Using Argument Groups](#using-argument-groups) - [Subparsers](#subparsers) - [Creating Subparsers](#creating-subparsers) - [Using Multiple Subparsers](#using-multiple-subparsers) @@ -894,12 +900,14 @@ parser.add_flag(out_opts, "print", "p") ### Group Attributes -User-defined groups can be configured with special attributes that change how the parser enforces their usage: +User-defined groups can be configured with special attributes that change how the parser enforces their usage, modifies their arguments' names, or handles their visibility in the help output: + +#### Validation Rules - `required()` – at least one argument from the group must be provided by the user, otherwise parsing will fail. - `mutually_exclusive()` – at most one argument from the group can be provided; using more than one at the same time results in an error. -Both attributes are **off by default**, and they can be combined (e.g., a group can require that exactly one argument is chosen). +Both attributes are **off by default**, and they can be combined (i.e., a group can require that exactly one argument is chosen). ```cpp auto& out_opts = parser.add_group("Output Options") @@ -911,16 +919,34 @@ auto& out_opts = parser.add_group("Output Options") > > If a group is defined as **mutually exclusive** and an argument from this group is used, then the `required` and `nargs` attribute requirements of other arguments from the group **will NOT be verified**. > -> Consider the example in the section below. Normally the `--output, -o` argument would expect a value to be given in the command-line. However, if the `--print, -p` flag is used, then the `nargs` requirement of the `--output, -o` argument will not be verified, and therefore no exception will be thrown, even though the `nargs` requirement is not satisfied. +> Consider the example in the [Complete Example](#complete-example) section. Normally the `--out-file, -out-f` argument would expect a value to be given in the command-line due to the `.nargs(1)` parameter. However, if the `--out-console, -out-c` flag is used, then the `nargs` requirement of the `--out-file, -out-f` argument will not be verified, and therefore no exception will be thrown, even though the `nargs` requirement is not satisfied. + +#### Naming Modifiers -Additionally, argument groups can be marked `hidden` - a hidden group will not be visible in the parser's help output (even if it has visible arguments). +Groups can automatically apply modifiers to the names of all arguments registered to them. This is especially useful for preventing name collisions or grouping related arguments under a common namespace. + +* `with_prefix("str")` – prepends the specified string to the argument's base name. +* `with_suffix("str")` – appends the specified string to the argument's base name. + +```cpp +auto& net_opts = parser.add_group("Network Options").with_prefix("net-").with_suffix("-cfg"); +net_opts.add_optional_argument("port"); // Registered in the parser as "net-port-cfg" +``` + +> [!NOTE] +> +> When using naming modifiers, the argument is registered in the main parser under its fully modified name. To learn how to easily retrieve values using only the base names, see [Retrieving Values from Groups](https://www.google.com/search?q=%2523using-argument-groups&utm_source=gemini). + +#### Visibility + +* `hidden()` – If this option is set, the entire group (including all of its visible arguments) will be hidden from the program's help description. ```cpp auto& hidden_opts = parser.add_group("Hidden Options").hidden(); -parser.add_optional_argument("visible").help("A visible arg"); +parser.add_optional_argument(hidden_opts, "visible").help("A visible arg"); ``` -In the example above, neither the `Hidden Options` group nor the `visible` arg will be visible in the parser's help output. +In the example above, neither the `Hidden Options` group nor the `visible` arg will be printed in the parser's help output. ### Complete Example @@ -935,15 +961,16 @@ int main(int argc, char* argv[]) { // create the argument group auto& out_opts = parser.add_group("Output Options") + .with_prefix("out-") .required() .mutually_exclusive(); // add arguments to the custom group - parser.add_optional_argument(out_opts, "output", "o") + parser.add_optional_argument(out_opts, "file", "f") .nargs(1) .help("Print output to a given file"); - parser.add_flag(out_opts, "print", "p") + parser.add_flag(out_opts, "console", "c") .help("Print output to the console"); parser.try_parse_args(argc, argv); @@ -957,10 +984,14 @@ When invoked with the `--help` flag, the above program produces a help message t ``` Program: myprog +Optional Arguments: + + --help, -h : Display the help message + Output Options: (required, mutually exclusive) - --output, -o : Print output to a given file - --print, -p : Print output to the console + --out-file, -out-f : Print output to a given file + --out-console, -out-c : Print output to the console ``` ### Suppressing Argument Group Checks @@ -1402,40 +1433,97 @@ Now all the values, that caused an exception for the `parse_args` example, are c ## Retrieving Argument Values -You can retrieve the argument's value(s) with: +Once parsing is complete, you can extract your data using three different interfaces depending on how you organized your arguments: via the parser, via direct argument references, or via argument groups. + +### Using the Parser + +You can retrieve an argument's value(s) using the parser instance by providing the argument's registered name: ```cpp -/*const*/ value_type value = parser.value("argument_name"); // (1) +/*const*/ value_type value /*&*/ = parser.value("argument_name"); // (1) /*const*/ value_type value = parser.value_or("argument_name", fallback_value); // (2) const std::vector& values = parser.values("argument_name"); // (3) + ``` 1. Returns the given argument's value. +* Returns the argument's parsed value if it has one. +* If more than one value has been parsed for the argument, this function will return the first parsed value. +* Returns the argument's predefined value if no value has been parsed for the argument. +> [!NOTE] +> For simple/small types (e.g. booleans, integers) this method returns by value. Otherwise, the argument's value is returned by reference. - - Returns the argument's parsed value if it has one. - - If more than one value has been parsed for the argument, this function will return the first parsed value. - - Returns the argument's predefined value if no value has been parsed for the argument. - -2. Returns the given argument's value or the specified fallback value if the argument has no values. +1. Returns the given argument's value or the specified fallback value if the argument has no values. +* If the argument has a value (parsed or predefined), the behavior is the same as in case **(1)**. +* If the argument has no values, this will return `value_type{std::forward(fallback_value)}` (where `U` is the deduced type of `fallback_value`). +> [!NOTE] +> Because of the fallback value, the function always returns by value, even if the argument's value type is large. - - If the argument has a value (parsed or predefined), the behavior is the same as in case **(1)**. - - If the argument has no values, this will return `value_type{std::forward(fallback_value)}` (where `U` is the deduced type of `fallback_value`). +1. Returns a vector of the given argument's values. +* If the argument has any values (parsed or predefined), they will be returned as a `std::vector`. +* If the argument has no values an empty vector will be returned. -3. Returns a vector of the given argument's values. - - If the argument has any values (parsed or predefined), they will be returned as a `std::vector`. - - If the argument has no values an empty vector will be returned. > [!NOTE] -> > The argument value getter functions might throw an exception if: -> - An argument with the given name does not exist -> - The argument does not contain any values - parsed or predefined (only getter function `(1)`) -> - The specified `value_type` does not match the value type of the argument +> * An argument with the given name does not exist +> * The argument does not contain any values - parsed or predefined (only getter function `(1)`) +> * The specified `value_type` does not match the value type of the argument -
-
-
+### Using Argument References + +When you define an argument, the parser returns a strongly-typed reference to the argument object. You can use this reference to retrieve the argument's state and values directly. + +Because the argument reference is inherently aware of its own `value_type`, you **do not** need to provide template parameters when calling these methods, making it entirely type-safe. + +```cpp +// Capture the returned references +auto& port_arg = parser.add_optional_argument("port", "p"); +auto& verbose_arg = parser.add_optional_argument("verbose", "v") + .help("Set the verbosity level (e.g. -vvv)"); + +parser.try_parse_args(argc, argv); + +// Retrieve values directly without template parameters +int port = port_arg.value_or(8080); + +// Query argument state (count tracks the number of times the flag was used) +std::size_t verbosity_level = verbose_arg.count(); + +if (port_arg.is_used() and verbosity_level >= 2) { + std::cout << "Port explicitly set to " << port << '\n'; +} +``` + +The argument instance provides the exact same API as the parser (`value()`, `value_or()`, `values()`, `has_value()`, `is_used()`, `count()`), but tied directly to itself. + +### Using Argument Groups + +If you organize your arguments into an `argument_group` configured with a `prefix` or `suffix`, retrieving values via the `argument_parser` requires you to use the fully formatted name (e.g., `parser.value("mod_output_mod")`). + +To simplify this, the `argument_group` class provides its own value and state getters. These methods automatically apply the group's naming modifiers, allowing you to query arguments using their simple base names. + +```cpp +auto& out_opts = parser.add_group("Output").with_prefix("out-"); + +out_opts.add_optional_argument("file"); // Registered in the parser as "out-file" +out_opts.add_optional_argument("verbose", "v"); // Registered as "out-verbose" + +parser.try_parse_args(argc, argv); + +// Querying the parser requires the full name: +std::size_t verbosity_level = parser.count("out-verbose"); + +// Querying the group requires ONLY the base name: +std::size_t verbosity_level_grp = out_opts.count("verbose"); +std::string out_file = out_opts.value_or("file", "default.txt"); + +// State getters are also available: +if (out_opts.is_used("file")) { + std::cout << "Writing to " << out_file << " at verbosity level " << verbosity_level_grp << '\n'; +} +```

diff --git a/include/argon/argument.hpp b/include/argon/argument.hpp index 8bc60f32..a81145f9 100644 --- a/include/argon/argument.hpp +++ b/include/argon/argument.hpp @@ -417,6 +417,72 @@ class argument : public detail::typed_argument_base { return *this; } + // --- value and state getters --- + + /// @return `true` if the argument is used, `false` otherwise. + [[nodiscard]] bool is_used() const noexcept override { + return this->count() > 0ull; + } + + /** + * @return The number of times the argument has been used. + * @note - For positional arguments, the count is either `0` (not used) or `1` (used). + * @note - For optional arguments, the count reflects the number of times the argument's flag has been used. + */ + [[nodiscard]] std::size_t count() const noexcept override { + if constexpr (type == argument_type::optional) + return this->_count; + else + return static_cast(this->has_parsed_values()); + } + + /// @return `true` if the argument has a value, `false` otherwise. + /// @note An argument is considered to have a value if it has parsed values or predefined values (default/implicit). + [[nodiscard]] bool has_value() const noexcept override { + return this->has_parsed_values() or this->_has_predefined_values_impl(); + } + + /** + * @return Reference to the stored value of the argument. + * @note If multiple values are available, the first one is returned. + * @throws std::logic_error if no values are available. + */ + [[nodiscard]] traits::argument_result_type value() const override { + if (this->has_parsed_values()) + return this->_values.front(); + + if constexpr (traits::c_is_none) + throw std::logic_error( + std::format("No values parsed for argument '{}'.", this->_name.str()) + ); + else + return this->_predefined_values().front(); + } + + /** + * @brief Get the value of the argument, if it has any, or a fallback value, if not. + * @tparam U The fallback value type. + * @param fallback_value The fallback value. + * @return The value of the argument. + */ + template U> + [[nodiscard]] value_type value_or(U&& fallback_value) const + requires(not traits::c_is_none) + { + try { + return this->value(); + } + catch (const std::logic_error&) { + // No parsed values and no predefined (default/implicit) values + return value_type{std::forward(fallback_value)}; + } + } + + /// @return Reference to the vector of parsed values for the argument. + [[nodiscard]] const std::vector& values() const override { + return this->_values_impl(); + } + #ifdef AP_TESTING friend struct ::argon_testing::argument_test_fixture; #endif @@ -495,23 +561,6 @@ class argument : public detail::typed_argument_base { return this->_accepts_further_values(); } - /// @return `true` if the argument is used, `false` otherwise. - [[nodiscard]] bool is_used() const noexcept override { - return this->count() > 0ull; - } - - /** - * @return The number of times the argument has been used. - * @note - For positional arguments, the count is either `0` (not used) or `1` (used). - * @note - For optional arguments, the count reflects the number of times the argument's flag has been used. - */ - [[nodiscard]] std::size_t count() const noexcept override { - if constexpr (type == argument_type::optional) - return this->_count; - else - return static_cast(this->has_parsed_values()); - } - /** * @brief Set the value for the optional argument. * @param str_value The string value to use. @@ -522,12 +571,6 @@ class argument : public detail::typed_argument_base { return this->_set_value_impl(str_value); } - /// @return `true` if the argument has a value, `false` otherwise. - /// @note An argument is considered to have a value if it has parsed values or predefined values (default/implicit). - [[nodiscard]] bool has_value() const noexcept override { - return this->has_parsed_values() or this->_has_predefined_values_impl(); - } - /// @return `true` if parsed values are available for the argument, `false` otherwise. [[nodiscard]] bool has_parsed_values() const noexcept override { return not this->_values.empty(); @@ -546,28 +589,6 @@ class argument : public detail::typed_argument_base { return this->_values.size() <=> this->_nargs_range; } - /** - * @return Reference to the stored value of the argument. - * @note If multiple values are available, the first one is returned. - * @throws std::logic_error if no values are available. - */ - [[nodiscard]] traits::argument_result_type value() const override { - if (this->has_parsed_values()) - return this->_values.front(); - - if constexpr (traits::c_is_none) - throw std::logic_error( - std::format("No values parsed for argument '{}'.", this->_name.str()) - ); - else - return this->_predefined_values().front(); - } - - /// @return Reference to the vector of parsed values for the argument. - [[nodiscard]] const std::vector& values() const override { - return this->_values_impl(); - } - /// @return Reference to the vector of parsed values for the argument. /// @note For none-type arguments, the method always returns an empty vector. [[nodiscard]] const std::vector& _values_impl() const noexcept diff --git a/include/argon/argument_group.hpp b/include/argon/argument_group.hpp index b6fe0e00..2be40660 100644 --- a/include/argon/argument_group.hpp +++ b/include/argon/argument_group.hpp @@ -112,6 +112,21 @@ class argument_group { return *this; } + // --- argument value and state getters --- + + [[nodiscard]] bool is_used(std::string_view arg_base_name) const noexcept; + [[nodiscard]] std::size_t count(std::string_view arg_base_name) const noexcept; + [[nodiscard]] bool has_value(std::string_view arg_base_name) const noexcept; + + template + [[nodiscard]] traits::argument_result_type value(std::string_view arg_base_name) const; + + template U> + [[nodiscard]] T value_or(std::string_view arg_base_name, U&& fallback_value) const; + + template + [[nodiscard]] const std::vector& values(std::string_view arg_base_name) const; + friend class argument_parser; private: diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index f1d556f2..ea40c1ee 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -1586,6 +1586,37 @@ class argument_parser { static constexpr std::uint8_t _indent_width = 2u; }; +// --- argument_group method implementations --- + +inline bool argument_group::is_used(std::string_view arg_base_name) const noexcept { + return this->_parser->is_used(this->_format_arg_name(arg_base_name)); +} + +inline std::size_t argument_group::count(std::string_view arg_base_name) const noexcept { + return this->_parser->count(this->_format_arg_name(arg_base_name)); +} + +inline bool argument_group::has_value(std::string_view arg_base_name) const noexcept { + return this->_parser->has_value(this->_format_arg_name(arg_base_name)); +} + +template +inline traits::argument_result_type argument_group::value(std::string_view arg_base_name) const { + return this->_parser->value(this->_format_arg_name(arg_base_name)); +} + +template U> +inline T argument_group::value_or(std::string_view arg_base_name, U&& fallback_value) const { + return this->_parser->value_or( + this->_format_arg_name(arg_base_name), std::forward(fallback_value) + ); +} + +template +inline const std::vector& argument_group::values(std::string_view arg_base_name) const { + return this->_parser->values(this->_format_arg_name(arg_base_name)); +} + namespace detail { /** diff --git a/tests/include/argument_test_fixture.hpp b/tests/include/argument_test_fixture.hpp index e2f5b2b4..4ea3e860 100644 --- a/tests/include/argument_test_fixture.hpp +++ b/tests/include/argument_test_fixture.hpp @@ -22,16 +22,6 @@ struct argument_test_fixture { return arg.mark_used(); } - template - bool is_used(const argument& arg) const { - return arg.is_used(); - } - - template - std::size_t get_count(const argument& arg) const { - return arg.count(); - } - template bool set_value(argument& arg, const T& value) const { return set_value(arg, as_string(value)); @@ -52,11 +42,6 @@ struct argument_test_fixture { arg._values.clear(); } - template - [[nodiscard]] bool has_value(const argument& arg) const { - return arg.has_value(); - } - template [[nodiscard]] bool has_parsed_values(const argument& arg) const { return arg.has_parsed_values(); @@ -72,16 +57,6 @@ struct argument_test_fixture { return arg.nvalues_ordering(); } - template - [[nodiscard]] argument_result_type get_value(const argument& arg) const { - return arg.value(); - } - - template - [[nodiscard]] const std::vector& get_values(const argument& arg) const { - return arg.values(); - } - template [[nodiscard]] const argument_name& get_name(const argument& arg) const { return arg.name(); diff --git a/tests/source/test_argument_parser_parse_args.cpp b/tests/source/test_argument_parser_parse_args.cpp index e642af12..b4bca471 100644 --- a/tests/source/test_argument_parser_parse_args.cpp +++ b/tests/source/test_argument_parser_parse_args.cpp @@ -1580,6 +1580,81 @@ TEST_CASE_FIXTURE( free_argv(argc, argv); } +TEST_CASE_FIXTURE( + test_argument_parser_parse_args, + "argument_group's argument value and state getters should automatically apply prefixes and " + "suffixes and return correct values" +) { + const std::string prefix = "mod_"; + const std::string suffix = "_mod"; + auto& group = sut.add_group("Modified Group").with_prefix(prefix).with_suffix(suffix); + + const std::string pos_base_name = "pos_arg"; + const std::string opt_base_name = "opt_arg"; + const std::string flag_base_name = "flag"; + const std::string unused_opt_base = "unused_opt"; + + // Add arguments to the group + sut.add_positional_argument(group, pos_base_name); + sut.add_optional_argument(group, opt_base_name).nargs(argon::nargs::any()); + sut.add_flag(group, flag_base_name); + sut.add_optional_argument(group, unused_opt_base); + + const std::string expected_opt_name = prefix + opt_base_name + suffix; + const std::string expected_flag_name = prefix + flag_base_name + suffix; + + const std::string pos_val = "positional-value"; + const std::string opt_val1 = "opt1"; + const std::string opt_val2 = "opt2"; + const std::string fallback_str = "fallback_value"; + + std::vector argv_vec{ + "program", + pos_val, + std::format("--{}", expected_opt_name), + opt_val1, + opt_val2, + std::format("--{}", expected_flag_name), + }; + + const int argc = static_cast(argv_vec.size()); + auto argv = to_char_2d_array(argv_vec); + + REQUIRE_NOTHROW(sut.parse_args(argc, argv)); + + // Verify group getters for a positional argument + CHECK(group.has_value(pos_base_name)); + CHECK(group.is_used(pos_base_name)); + CHECK_EQ(group.count(pos_base_name), 1ull); + CHECK_EQ(group.value(pos_base_name), pos_val); + CHECK_EQ(group.value_or(pos_base_name, fallback_str), pos_val); + CHECK_EQ(group.values(pos_base_name), std::vector{pos_val}); + + // Verify group getters for a multi-value optional argument + CHECK(group.has_value(opt_base_name)); + CHECK(group.is_used(opt_base_name)); + CHECK_EQ(group.count(opt_base_name), 1ull); // Used once (one flag provided) + CHECK_EQ(group.value(opt_base_name), opt_val1); + CHECK_EQ(group.value_or(opt_base_name, fallback_str), opt_val1); + CHECK_EQ(group.values(opt_base_name), (std::vector{opt_val1, opt_val2})); + + // Verify group getters for a boolean flag + CHECK(group.has_value(flag_base_name)); + CHECK(group.is_used(flag_base_name)); + CHECK_EQ(group.count(flag_base_name), 1ull); + CHECK(group.value(flag_base_name)); + CHECK(group.value_or(flag_base_name, false)); + + // Verify group getters for an unused optional argument + CHECK_FALSE(group.has_value(unused_opt_base)); + CHECK_FALSE(group.is_used(unused_opt_base)); + CHECK_EQ(group.count(unused_opt_base), 0ull); + CHECK_EQ(group.value_or(unused_opt_base, fallback_str), fallback_str); + CHECK_THROWS_AS(discard(group.value(unused_opt_base)), std::logic_error); + + free_argv(argc, argv); +} + // subparsers TEST_CASE_FIXTURE( diff --git a/tests/source/test_optional_argument.cpp b/tests/source/test_optional_argument.cpp index 321d10a6..b8cb3c93 100644 --- a/tests/source/test_optional_argument.cpp +++ b/tests/source/test_optional_argument.cpp @@ -276,22 +276,22 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE(argument_test_fixture, "is_used() should return false by default") { const auto sut = sut_type(arg_name_primary); - CHECK_FALSE(is_used(sut)); + CHECK_FALSE(sut.is_used()); } TEST_CASE_FIXTURE( argument_test_fixture, "is_used() should return true if the argument's flag has been used" ) { auto sut = sut_type(arg_name_primary); - REQUIRE_FALSE(is_used(sut)); + REQUIRE_FALSE(sut.is_used()); mark_used(sut); - CHECK(is_used(sut)); + CHECK(sut.is_used()); } TEST_CASE_FIXTURE(argument_test_fixture, "count() should return 0 by default") { const auto sut = sut_type(arg_name_primary); - CHECK_EQ(get_count(sut), 0ull); + CHECK_EQ(sut.count(), 0ull); } TEST_CASE_FIXTURE( @@ -305,7 +305,7 @@ TEST_CASE_FIXTURE( for (std::size_t n = 0ull; n < count; ++n) mark_used(sut); - CHECK_EQ(get_count(sut), count); + CHECK_EQ(sut.count(), count); } TEST_CASE_FIXTURE(argument_test_fixture, "argument flag usage should trigger the on-flag actions") { @@ -319,13 +319,13 @@ TEST_CASE_FIXTURE(argument_test_fixture, "argument flag usage should trigger the TEST_CASE_FIXTURE(argument_test_fixture, "has_value() should return false by default") { const auto sut = sut_type(arg_name_primary); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } TEST_CASE_FIXTURE(argument_test_fixture, "has_value() should return true if value is set") { auto sut = sut_type(arg_name_primary); set_value(sut, arbitrary_value); - CHECK(has_value(sut)); + CHECK(sut.has_value()); } TEST_CASE_FIXTURE( @@ -336,8 +336,8 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name_primary); sut.default_values(default_value); - REQUIRE(has_value(sut)); - CHECK_EQ(get_value(sut), default_value); + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value(), default_value); } TEST_CASE_FIXTURE( @@ -347,7 +347,7 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name_primary); sut.implicit_values(implicit_value); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } TEST_CASE_FIXTURE( @@ -359,8 +359,8 @@ TEST_CASE_FIXTURE( mark_used(sut); - REQUIRE(has_value(sut)); - CHECK_EQ(get_value(sut), implicit_value); + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value(), implicit_value); } TEST_CASE_FIXTURE(argument_test_fixture, "has_parsed_values() should return false by default") { @@ -452,8 +452,8 @@ TEST_CASE_FIXTURE( ) { auto sut = sut_type(arg_name_primary); - REQUIRE_FALSE(has_value(sut)); - CHECK_THROWS_AS(discard(get_value(sut)), std::logic_error); + REQUIRE_FALSE(sut.has_value()); + CHECK_THROWS_AS(discard(sut.value()), std::logic_error); } TEST_CASE_FIXTURE( @@ -463,8 +463,8 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name_primary); sut.default_values(arbitrary_value); - REQUIRE(has_value(sut)); - CHECK_EQ(get_value(sut), arbitrary_value); + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value(), arbitrary_value); } TEST_CASE_FIXTURE( @@ -476,8 +476,8 @@ TEST_CASE_FIXTURE( mark_used(sut); - REQUIRE(has_value(sut)); - CHECK_EQ(get_value(sut), implicit_value); + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value(), implicit_value); } TEST_CASE_FIXTURE( @@ -488,8 +488,58 @@ TEST_CASE_FIXTURE( sut.implicit_values(implicit_value); set_value(sut, arbitrary_value); - REQUIRE(has_value(sut)); - CHECK_EQ(get_value(sut), arbitrary_value); + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value(), arbitrary_value); +} + +TEST_CASE_FIXTURE( + argument_test_fixture, + "value_or() should return the fallback value if the argument's value has not been set" +) { + auto sut = sut_type(arg_name_primary); + constexpr sut_value_type fallback_value = 999; + + REQUIRE_FALSE(sut.has_value()); + CHECK_EQ(sut.value_or(fallback_value), fallback_value); +} + +TEST_CASE_FIXTURE( + argument_test_fixture, + "value_or() should return the default value if one has been provided and argument is not used" +) { + auto sut = sut_type(arg_name_primary); + sut.default_values(arbitrary_value); + constexpr sut_value_type fallback_value = 999; + + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value_or(fallback_value), arbitrary_value); +} + +TEST_CASE_FIXTURE( + argument_test_fixture, + "value_or() should return the implicit value if one has been provided and argument is used" +) { + auto sut = sut_type(arg_name_primary); + sut.implicit_values(implicit_value); + constexpr sut_value_type fallback_value = 999; + + mark_used(sut); + + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value_or(fallback_value), implicit_value); +} + +TEST_CASE_FIXTURE( + argument_test_fixture, "value_or() should return the argument's value if it has been set" +) { + auto sut = sut_type(arg_name_primary); + sut.default_values(default_value); + sut.implicit_values(implicit_value); + set_value(sut, arbitrary_value); + constexpr sut_value_type fallback_value = 999; + + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value_or(fallback_value), arbitrary_value); } TEST_CASE_FIXTURE( @@ -505,7 +555,7 @@ TEST_CASE_FIXTURE( invalid_value_msg(arg_name_primary, empty_str).c_str(), parsing_failure ); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } SUBCASE("given string is non-convertible to value_type") { REQUIRE_THROWS_WITH_AS( @@ -513,7 +563,7 @@ TEST_CASE_FIXTURE( invalid_value_msg(arg_name_primary, invalid_value_str).c_str(), parsing_failure ); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } } @@ -530,7 +580,7 @@ TEST_CASE_FIXTURE( parsing_failure ); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } TEST_CASE_FIXTURE( @@ -543,7 +593,7 @@ TEST_CASE_FIXTURE( for (const auto value : choices) REQUIRE_NOTHROW(set_value(sut, value)); - REQUIRE_EQ(get_values(sut), choices); + REQUIRE_EQ(sut.values(), choices); CHECK_THROWS_WITH_AS( set_value(sut, arbitrary_value), @@ -570,7 +620,7 @@ TEST_CASE_FIXTURE( sut_value_type valid_value = 16; REQUIRE_NOTHROW(set_value(sut, valid_value)); - CHECK_EQ(get_value(sut), valid_value); + CHECK_EQ(sut.value(), valid_value); } SUBCASE("transform action") { @@ -579,7 +629,7 @@ TEST_CASE_FIXTURE( set_value(sut, arbitrary_value); - CHECK_EQ(get_value(sut), double_action(arbitrary_value)); + CHECK_EQ(sut.value(), double_action(arbitrary_value)); } SUBCASE("modify action") { @@ -591,7 +641,7 @@ TEST_CASE_FIXTURE( set_value(sut, test_value); double_action(test_value); - CHECK_EQ(get_value(sut), test_value); + CHECK_EQ(sut.value(), test_value); } } diff --git a/tests/source/test_positional_argument.cpp b/tests/source/test_positional_argument.cpp index 9c8a6af9..a6e7afef 100644 --- a/tests/source/test_positional_argument.cpp +++ b/tests/source/test_positional_argument.cpp @@ -242,41 +242,41 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE(argument_test_fixture, "is_used() should return false by default") { const auto sut = sut_type(arg_name); - CHECK_FALSE(is_used(sut)); + CHECK_FALSE(sut.is_used()); } TEST_CASE_FIXTURE( argument_test_fixture, "is_used() should return true when argument contains a value" ) { auto sut = sut_type(arg_name); - REQUIRE_FALSE(is_used(sut)); + REQUIRE_FALSE(sut.is_used()); set_value(sut, valid_value); - CHECK(is_used(sut)); + CHECK(sut.is_used()); } TEST_CASE_FIXTURE(argument_test_fixture, "count() should return 0 by default") { const auto sut = sut_type(arg_name); - CHECK_EQ(get_count(sut), 0ull); + CHECK_EQ(sut.count(), 0ull); } TEST_CASE_FIXTURE(argument_test_fixture, "count() should return 1 when argument contains a value") { auto sut = sut_type(arg_name); set_value(sut, valid_value); - CHECK_EQ(get_count(sut), 1ull); + CHECK_EQ(sut.count(), 1ull); } TEST_CASE_FIXTURE(argument_test_fixture, "has_value() should return false by default") { const auto sut = sut_type(arg_name); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } TEST_CASE_FIXTURE(argument_test_fixture, "has_value() should return true if the value is set") { auto sut = sut_type(arg_name); set_value(sut, valid_value); - CHECK(has_value(sut)); + CHECK(sut.has_value()); } TEST_CASE_FIXTURE( @@ -285,7 +285,7 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name); sut.default_values(default_value); - CHECK(has_value(sut)); + CHECK(sut.has_value()); } TEST_CASE_FIXTURE(argument_test_fixture, "has_parsed_values() should return false by default") { @@ -329,8 +329,8 @@ TEST_CASE_FIXTURE( ) { auto sut = sut_type(arg_name); - REQUIRE_FALSE(has_value(sut)); - CHECK_THROWS_AS(discard(get_value(sut)), std::logic_error); + REQUIRE_FALSE(sut.has_value()); + CHECK_THROWS_AS(discard(sut.value()), std::logic_error); } TEST_CASE_FIXTURE( @@ -339,8 +339,8 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name); set_value(sut, valid_value); - REQUIRE(has_value(sut)); - CHECK_EQ(get_value(sut), valid_value); + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value(), valid_value); } TEST_CASE_FIXTURE( @@ -351,8 +351,8 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name); sut.default_values(default_value); - REQUIRE(has_value(sut)); - CHECK_EQ(get_value(sut), default_value); + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value(), default_value); } TEST_CASE_FIXTURE( @@ -362,8 +362,44 @@ TEST_CASE_FIXTURE( sut.default_values(default_value); set_value(sut, valid_value); - REQUIRE(has_value(sut)); - CHECK_EQ(get_value(sut), valid_value); + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value(), valid_value); +} + +TEST_CASE_FIXTURE( + argument_test_fixture, + "value_or() should return the fallback value if the argument's value has not been set" +) { + auto sut = sut_type(arg_name); + constexpr sut_value_type fallback_value = 999; + + REQUIRE_FALSE(sut.has_value()); + CHECK_EQ(sut.value_or(fallback_value), fallback_value); +} + +TEST_CASE_FIXTURE( + argument_test_fixture, "value_or() should return the argument's value if it has been set" +) { + auto sut = sut_type(arg_name); + set_value(sut, valid_value); + constexpr sut_value_type fallback_value = 999; + + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value_or(fallback_value), valid_value); +} + +TEST_CASE_FIXTURE( + argument_test_fixture, + "value_or() should return the default argument's default value if it has been set and no " + "values " + "were parsed" +) { + auto sut = sut_type(arg_name); + sut.default_values(default_value); + constexpr sut_value_type fallback_value = 999; + + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value_or(fallback_value), default_value); } TEST_CASE_FIXTURE( @@ -379,7 +415,7 @@ TEST_CASE_FIXTURE( invalid_value_msg(arg_name, empty_str).c_str(), parsing_failure ); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } SUBCASE("given string is non-convertible to value_type") { @@ -388,7 +424,7 @@ TEST_CASE_FIXTURE( invalid_value_msg(arg_name, invalid_value_str).c_str(), parsing_failure ); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } } @@ -404,7 +440,7 @@ TEST_CASE_FIXTURE( doctest::Contains(invalid_choice_msg(arg_name, as_string(invalid_choice)).c_str()), parsing_failure ); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } TEST_CASE_FIXTURE( @@ -417,7 +453,7 @@ TEST_CASE_FIXTURE( for (const auto value : choices) REQUIRE_NOTHROW(set_value(sut, value)); - REQUIRE_EQ(get_values(sut), choices); + REQUIRE_EQ(sut.values(), choices); CHECK_THROWS_WITH_AS( set_value(sut, valid_value), @@ -442,7 +478,7 @@ TEST_CASE_FIXTURE(argument_test_fixture, "set_value(any) should perform the spec sut_value_type valid_value = 16; REQUIRE_NOTHROW(set_value(sut, valid_value)); - CHECK_EQ(get_value(sut), valid_value); + CHECK_EQ(sut.value(), valid_value); } SUBCASE("transform action") { @@ -451,7 +487,7 @@ TEST_CASE_FIXTURE(argument_test_fixture, "set_value(any) should perform the spec set_value(sut, valid_value); - CHECK_EQ(get_value(sut), double_action(valid_value)); + CHECK_EQ(sut.value(), double_action(valid_value)); } SUBCASE("modify action") { @@ -463,7 +499,7 @@ TEST_CASE_FIXTURE(argument_test_fixture, "set_value(any) should perform the spec set_value(sut, test_value); double_action(test_value); - CHECK_EQ(get_value(sut), test_value); + CHECK_EQ(sut.value(), test_value); } }