diff --git a/CMakeLists.txt b/CMakeLists.txt
index 19bcd720..c349b3fa 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,7 +7,7 @@ else()
endif()
project(cpp-ap
- VERSION 3.0.0.8
+ VERSION 3.0.0
DESCRIPTION "Command-line argument parser for C++20"
HOMEPAGE_URL "https://github.com/SpectraL519/cpp-ap"
LANGUAGES CXX
diff --git a/Doxyfile b/Doxyfile
index fb9fd682..c2cbab5f 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = CPP-AP
# could be handy for archiving the generated documentation or if some version
# control system is used.
-PROJECT_NUMBER = 3.0.0.8
+PROJECT_NUMBER = 3.0.0
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
diff --git a/MODULE.bazel b/MODULE.bazel
index 597d9bb6..91d3db86 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -1,4 +1,4 @@
module(
name = "cpp-ap",
- version = "3.0.0.8",
+ version = "3.0.0",
)
diff --git a/README.md b/README.md
index 0527f171..60bddaea 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ Command-line argument parser for C++20
> [!NOTE]
>
-> [v1.0](https://github.com/SpectraL519/cpp-ap/commit/9a9e5360766b732f322ae2efe3cf5ec5f9268eef) of the library has been developed for the *Team Programming* course at the *Wrocław University of Science and Technology*.
+> [v1.0](https://github.com/SpectraL519/cpp-ap/releases/tag/v1.0) of the library has been developed for the *Team Programming* course at the *Wrocław University of Science and Technology*.
>
> Faculty: *W04N - Faculty of Information and Communication Technology*
>
@@ -62,6 +62,11 @@ Command-line argument parser for C++20
- [Parameters Specific for Optional Arguments](/docs/tutorial.md#parameters-specific-for-optional-arguments)
- [Default Arguments](/docs/tutorial.md#default-arguments)
- [Argument Groups](/docs/tutorial.md#argument-groups)
+ - [Creating New Groups](/docs/tutorial.md#creating-new-groups)
+ - [Adding Arguments to Groups](/docs/tutorial.md#adding-arguments-to-groups)
+ - [Group Attributes](/docs/tutorial.md#group-attributes)
+ - [Complete Example](/docs/tutorial.md#complete-example)
+ - [Suppressing Argument Group Checks](/docs/tutorial.md#suppressing-argument-group-checks)
- [Parsing Arguments](/docs/tutorial.md#parsing-arguments)
- [Basic Argument Parsing Rules](/docs/tutorial.md#basic-argument-parsing-rules)
- [Compound Arguments](/docs/tutorial.md#compound-arguments)
@@ -72,6 +77,7 @@ Command-line argument parser for C++20
- [Using Multiple Subparsers](/docs/tutorial.md#using-multiple-subparsers)
- [Parsing Arguments with Subparsers](/docs/tutorial.md#parsing-arguments-with-subparsers)
- [Tracking Parser State](/docs/tutorial.md#tracking-parser-state)
+ - [Suppressing Argument Group Checks](/docs/tutorial.md#suppressing-argument-group-checks)
- [Examples](/docs/tutorial.md#examples)
- [Common Utility](/docs/tutorial.md#common-utility)
- [Dev notes](/docs/dev_notes.md#dev-notes)
diff --git a/cpp-ap-demo b/cpp-ap-demo
index bb13a211..cf3f2ad8 160000
--- a/cpp-ap-demo
+++ b/cpp-ap-demo
@@ -1 +1 @@
-Subproject commit bb13a2111f075e388d48e0cc4bba1bf62dfaad45
+Subproject commit cf3f2ad8e9c06af9adcde99288eea517bfb6ecae
diff --git a/docs/tutorial.md b/docs/tutorial.md
index 87567155..63142626 100644
--- a/docs/tutorial.md
+++ b/docs/tutorial.md
@@ -12,18 +12,18 @@
- [Boolean Flags](#boolean-flags)
- [Argument Parameters](#argument-parameters)
- [Common Parameters](#common-parameters)
- - [help](#1-help---the-arguments-description-which-will-be-printed-when-printing-the-parser-class-instance)
- - [hidden](#2-hidden---if-this-option-is-set-for-an-argument-then-it-will-not-be-included-in-the-program-description)
- - [required](#3-required---if-this-option-is-set-for-an-argument-and-its-value-is-not-passed-in-the-command-line-an-exception-will-be-thrown)
- - [bypass required](#4-bypass_required---if-this-option-is-set-for-an-argument-the-required-option-for-other-arguments-will-be-discarded-if-the-bypassing-argument-is-used-in-the-command-line)
- - [nargs](#5-nargs---sets-the-allowed-number-of-values-to-be-parsed-for-an-argument)
- - [greedy](#6-greedy---if-this-option-is-set-the-argument-will-consume-all-command-line-values-until-its-upper-nargs-bound-is-reached)
- - [choices](#7-choices---a-list-of-valid-argument-values)
- - [value actions](#8-value-actions---functions-that-are-called-after-parsing-an-arguments-value)
- - [default values](#9-default_values---a-list-of-values-which-will-be-used-if-no-values-for-an-argument-have-been-parsed)
+ - [help](#1-help---the-arguments-description-which-will-be-printed-when-printing-the-parser-class-instance) - the text shown in the help message to describe an argument
+ - [hidden](#2-hidden---if-this-option-is-set-for-an-argument-then-it-will-not-be-included-in-the-program-description) - hides the argument from the generated program description and help output
+ - [required](#3-required---if-this-option-is-set-for-an-argument-and-its-value-is-not-passed-in-the-command-line-an-exception-will-be-thrown) - marks the argument as mandatory; not using it will cause an error
+ - [suppress arg checks](#4-suppress_arg_checks---using-a-suppressing-argument-results-in-suppressing-requirement-checks-for-other-arguments) - if a suppressing argument is used, other requirement validation will be skipped for other arguments
+ - [nargs](#5-nargs---sets-the-allowed-number-of-values-to-be-parsed-for-an-argument) - defines how many values an argument can or must accept
+ - [greedy](#6-greedy---if-this-option-is-set-the-argument-will-consume-all-command-line-values-until-its-upper-nargs-bound-is-reached) - makes the argument consume all following values until its limit is reached
+ - [choices](#7-choices---a-list-of-valid-argument-values) - restricts the valid inputs to a predefined set of values
+ - [value actions](#8-value-actions---functions-that-are-called-after-parsing-an-arguments-value) - allows you to run custom code after the argument’s value is parsed
+ - [default values](#9-default_values---a-list-of-values-which-will-be-used-if-no-values-for-an-argument-have-been-parsed) - specifies fallback values to use if none are provided
- [Parameters Specific for Optional Arguments](#parameters-specific-for-optional-arguments)
- - [on-flag actions](#1-on-flag-actions---functions-that-are-called-immediately-after-parsing-an-arguments-flag)
- - [implicit values](#2-implicit_values---a-list-of-values-which-will-be-set-for-an-argument-if-only-its-flag-but-no-values-are-parsed-from-the-command-line)
+ - [on-flag actions](#1-on-flag-actions---functions-that-are-called-immediately-after-parsing-an-arguments-flag) - executes custom code immediately when the argument’s flag is present
+ - [implicit values](#2-implicit_values---a-list-of-values-which-will-be-set-for-an-argument-if-only-its-flag-but-no-values-are-parsed-from-the-command-line) - automatically assigns a value if an argument flag is used without an explicit value
- [Predefined Parameter Values](#predefined-parameter-values)
- [Default Arguments](#default-arguments)
- [Argument Groups](#argument-groups)
@@ -31,6 +31,7 @@
- [Adding Arguments to Groups](#adding-arguments-to-groups)
- [Group Attributes](#group-attributes)
- [Complete Example](#complete-example)
+ - [Suppressing Argument Group Checks](#suppressing-argument-group-checks)
- [Parsing Arguments](#parsing-arguments)
- [Basic Argument Parsing Rules](#basic-argument-parsing-rules)
- [Compound Arguments](#compound-arguments)
@@ -318,9 +319,7 @@ Optional arguments:
> [!WARNING]
>
> - If a positional argument is defined as non-required, then no required positional argument can be defined after (only other non-required positional arguments and optional arguments will be allowed).
-> - For both positional and optional arguments:
-> - enabling the `required` option disables the `bypass_required` option
-> - disabling the `required` option has no effect on the `bypass_required` option.
+> - If an argument is suppressing (see [suppress arg checks](#4-suppress_arg_checks---using-a-suppressing-argument-results-in-suppressing-requirement-checks-for-other-arguments) and [Suppressing Argument Group Checks](#suppressing-argument-group-checks)), then it cannot be required (an exception will be thrown).
```cpp
// example: positional arguments
@@ -377,24 +376,27 @@ Command Result
-#### 4. `bypass_required` - If this option is set for an argument, the `required` option for other arguments will be discarded if the bypassing argument is used in the command-line.
+#### 4. `suppress_arg_checks` - Using a suppressing argument results in suppressing requirement checks for other arguments.
+
+If an argument is defined with the `suppress_arg_checks` option enabled and such argument is explicitly used in the command-line, then requirement validation will be suppressed/skipped for other arguments. This includes validating whether:
+- a required argument has been parsed
+- the number of values parsed for an argument matches the specified [nargs](#5-nargs---sets-the-allowed-number-of-values-to-be-parsed-for-an-argument) range.
> [!NOTE]
>
-> - Both all arguments have the `bypass_required` option disabled.
-> - The default value of the value parameter of the `argument::bypass_required(bool)` method is `true` for all arguments.
+> - All arguments have the `suppress_arg_checks` option disabled by default.
+> - The default value of the value parameter of the `argument::suppress_arg_checks(bool)` method is `true` for all arguments.
> [!WARNING]
>
-> For both positional and optional arguments:
-> - enabling the `bypass_required` option disables the `required` option
-> - disabling the `bypass_required` option has no effect on the `required` option.
+> - Enabling the `suppress_arg_checks` option has no effect on [argument group](#argument-groups) requirements validation.
+> - Enabling argument checks suppressing is not possible for required arguments (an exception will be thrown).
```cpp
// example: optional arguments
parser.add_positional_argument("input");
parser.add_optional_argument("output", "o").required();
-parser.add_optional_argument("version", "v").bypass_required();
+parser.add_optional_argument("version", "v").suppress_arg_checks();
parser.parse_args(argc, argv);
@@ -522,7 +524,7 @@ Actions are represented as functions, which take the argument's value as an argu
```cpp
void is_valid_user_tag(const std::string& tag) {
if (tag.empty() or tag.front() != '@')
- throw std::runtime_error(std::format("Invalid user tag: `{}` — must start with '@'", tag));
+ throw std::runtime_error(std::format("Invalid user tag: `{}` - must start with '@'", tag));
}
parser.add_optional_argument("user", "u")
@@ -934,6 +936,26 @@ Output Options: (required, mutually exclusive)
--print, -p : Print output to the console
```
+### Suppressing Argument Group Checks
+
+Similarly to [suppressing argument checks](#4-suppress_arg_checks---using-a-suppressing-argument-results-in-suppressing-requirement-checks-for-other-arguments), an argument can suppress the requirement checks of argument groups:
+
+```c++
+argument.suppress_group_checks();
+```
+
+If such argument is used the requirement checks associated with the [group attributes](#group-attributes) will not be validated.
+
+> [!NOTE]
+>
+> - All arguments have the `suppress_group_checks` option disabled by default.
+> - The default value of the value parameter of the `argument::suppress_group_checks(bool)` method is `true` for all arguments.
+
+> [!WARNING]
+>
+> - Enabling the `suppress_group_checks` option has no effect on argument requirements validation.
+> - Enabling argument group checks suppressing is not possible for required arguments (an exception will be thrown).
+
diff --git a/include/ap/argument.hpp b/include/ap/argument.hpp
index 9fcdc472..a2d2c4bd 100644
--- a/include/ap/argument.hpp
+++ b/include/ap/argument.hpp
@@ -117,10 +117,14 @@ class argument : public detail::argument_base {
return this->_required;
}
- /// @return `true` if required argument bypassing is enabled for the argument, `false` otherwise.
- /// @note Required argument bypassing is enabled only if the argument is not required.
- [[nodiscard]] bool is_bypass_required_enabled() const noexcept override {
- return not this->_required and this->_bypass_required;
+ /// @return `true` if argument checks suppressing is enabled for the argument, `false` otherwise.
+ [[nodiscard]] bool suppresses_arg_checks() const noexcept override {
+ return this->_suppress_arg_checks;
+ }
+
+ /// @return `true` if argument group checks suppressing is enabled for the argument, `false` otherwise.
+ [[nodiscard]] bool suppresses_group_checks() const noexcept override {
+ return this->_suppress_group_checks;
}
/// @return `true` if the argument is greedy, `false` otherwise.
@@ -152,40 +156,59 @@ class argument : public detail::argument_base {
/**
* @brief Set the `required` attribute of the argument
- * @param r The attribute value.
+ * @param value The attribute value (default: `true`).
* @return Reference to the argument instance.
- * @attention Setting the `required` attribute to `true` disables the `bypass_required` attribute.
*/
- argument& required(const bool r = true) noexcept {
- this->_required = r;
- if (this->_required)
- this->_bypass_required = false;
+ argument& required(const bool value = true) {
+ if (value and (this->_suppress_arg_checks or this->_suppress_group_checks))
+ throw invalid_configuration(
+ std::format("A suppressing argument [{}] cannot be required!", this->_name.str())
+ );
+
+ this->_required = value;
return *this;
}
/**
- * @brief Enable/disable bypassing the `required` attribute for the argument.
- * @param br The attribute value.
+ * @brief Enable/disable suppressing argument checks for other arguments.
+ * @param value The attribute value (default: `true`).
* @return Reference to the argument instance.
- * @attention Setting the `bypass_required` option to `true` disables the `required` attribute.
*/
- argument& bypass_required(const bool br = true) noexcept {
- this->_bypass_required = br;
- if (this->_bypass_required)
- this->_required = false;
+ argument& suppress_arg_checks(const bool value = true) {
+ if (value and this->_required)
+ throw invalid_configuration(std::format(
+ "A required argument [{}] cannot suppress argument checks!", this->_name.str()
+ ));
+
+ this->_suppress_arg_checks = value;
+ return *this;
+ }
+
+ /**
+ * @brief Enable/disable suppressing argument group checks.
+ * @param value The attribute value (default: `true`).
+ * @return Reference to the argument instance.
+ */
+ argument& suppress_group_checks(const bool value = true) {
+ if (value and this->_required)
+ throw invalid_configuration(std::format(
+ "A required argument [{}] cannot suppress argument group checks!", this->_name.str()
+ ));
+
+ this->_suppress_group_checks = value;
return *this;
}
/**
* @brief Set the `greedy` attribute of the argument.
- * @param g The attribute value.
+ * @param value The attribute value (default: `true`).
* @return Reference to the argument instance.
* @note The method is enabled only if `value_type` is not `none_type`.
*/
- argument& greedy(const bool g = true) noexcept
+ argument& greedy(const bool value = true) noexcept
requires(not util::c_is_none)
{
- this->_greedy = g;
+ this->_greedy = value;
return *this;
}
@@ -438,8 +461,10 @@ class argument : public detail::argument_base {
bld.params.reserve(6ull);
if (this->_required != _default_required)
bld.add_param("required", std::format("{}", this->_required));
- if (this->is_bypass_required_enabled())
- bld.add_param("bypass required", "true");
+ if (this->_suppress_arg_checks)
+ bld.add_param("suppress arg checks", "true");
+ if (this->_suppress_group_checks)
+ bld.add_param("suppress group checks", "true");
if (this->_nargs_range != _default_nargs_range)
bld.add_param("nargs", this->_nargs_range);
if constexpr (util::c_writable) {
@@ -670,11 +695,15 @@ class argument : public detail::argument_base {
}
else {
if (not (std::istringstream(str_value) >> value))
- throw parsing_failure::invalid_value(this->_name, str_value);
+ throw parsing_failure(std::format(
+ "Cannot parse value `{}` for argument [{}].", str_value, this->_name.str()
+ ));
}
if (not this->_is_valid_choice(value))
- throw parsing_failure::invalid_choice(this->_name, str_value);
+ throw parsing_failure(std::format(
+ "Value `{}` is not a valid choice for argument [{}].", str_value, this->_name.str()
+ ));
const auto apply_visitor = action::util::apply_visitor{value};
for (const auto& action : this->_value_actions)
@@ -700,7 +729,10 @@ class argument : public detail::argument_base {
_value_actions; ///< The argument's value actions collection.
bool _required : 1; ///< The argument's `required` attribute value.
- bool _bypass_required : 1 = false; ///< The argument's `bypass_required` attribute value.
+ bool _suppress_arg_checks : 1 =
+ false; ///< The argument's `suppress_arg_checks` attribute value.
+ bool _suppress_group_checks : 1 =
+ false; ///< The argument's `suppress_group_checks` attribute value.
bool _greedy : 1 = false; ///< The argument's `greedy` attribute value.
bool _hidden : 1 = false; ///< The argument's `hidden` attribute value.
diff --git a/include/ap/argument_parser.hpp b/include/ap/argument_parser.hpp
index 099f2524..88830b0a 100644
--- a/include/ap/argument_parser.hpp
+++ b/include/ap/argument_parser.hpp
@@ -558,7 +558,9 @@ class argument_parser {
this->_parse_args_impl(std::ranges::begin(argv_rng), std::ranges::end(argv_rng), state);
if (not state.unknown_args.empty())
- throw parsing_failure::argument_deduction_failure(state.unknown_args);
+ throw parsing_failure(std::format(
+ "Failed to deduce the argument for values [{}]", util::join(state.unknown_args)
+ ));
}
/**
@@ -1066,10 +1068,8 @@ class argument_parser {
// process command-line arguments within the current parser
this->_validate_argument_configuration();
- std::ranges::for_each(
- this->_tokenize(args_begin, args_end, state),
- std::bind_front(&argument_parser::_parse_token, this, std::ref(state))
- );
+ for (const auto& tok : this->_tokenize(args_begin, args_end, state))
+ this->_parse_token(tok, state);
this->_verify_final_state();
this->_finalized = true;
}
@@ -1090,10 +1090,12 @@ class argument_parser {
}
if (non_required_arg and arg->is_required())
- // TODO: remove static builder in v3 release commit
- throw invalid_configuration::positional::required_after_non_required(
- arg->name(), non_required_arg->name()
- );
+ throw invalid_configuration(std::format(
+ "Required positional argument [{}] cannot be defined after a non-required "
+ "positional argument [{}].",
+ arg->name().str(),
+ non_required_arg->name().str()
+ ));
}
}
@@ -1103,6 +1105,7 @@ class argument_parser {
* @note `AIt` must be a `std::forward_iterator` with a value type convertible to `std::string`.
* @param args_begin The begin iterator for the command-line argument value range.
* @param args_end The end iterator for the command-line argument value range.
+ * @param state The current parsing state.
* @return A list of preprocessed command-line argument tokens.
*/
template AIt>
@@ -1111,23 +1114,20 @@ class argument_parser {
) {
arg_token_vec_t toks;
toks.reserve(static_cast(std::ranges::distance(args_begin, args_end)));
-
- std::ranges::for_each(
- args_begin,
- args_end,
- std::bind_front(&argument_parser::_tokenize_arg, this, std::ref(state), std::ref(toks))
- );
-
+ std::ranges::for_each(args_begin, args_end, [&](const auto& arg_value) {
+ this->_tokenize_arg(arg_value, toks, state);
+ });
return toks;
}
/**
* @brief Appends an argument token(s) created from `arg_value` to the `toks` vector.
- * @param toks The argument token list to which the processed token(s) will be appended.
* @param arg_value The command-line argument's value to be processed.
+ * @param toks The argument token list to which the processed token(s) will be appended.
+ * @param state The current parsing state.
*/
void _tokenize_arg(
- const parsing_state& state, arg_token_vec_t& toks, const std::string_view arg_value
+ const std::string_view arg_value, arg_token_vec_t& toks, const parsing_state& state
) {
detail::argument_token tok{
.type = this->_deduce_token_type(arg_value), .value = std::string(arg_value)
@@ -1275,29 +1275,29 @@ class argument_parser {
/**
* @brief Parse a single command-line argument token.
- * @param state The current parsing state.
* @param tok The token to be parsed.
+ * @param state The current parsing state.
* @throws ap::parsing_failure
*/
- void _parse_token(parsing_state& state, const detail::argument_token& tok) {
+ void _parse_token(const detail::argument_token& tok, parsing_state& state) {
if (state.curr_arg and state.curr_arg->is_greedy()) {
- this->_set_argument_value(state, tok.value);
+ this->_set_argument_value(tok.value, state);
return;
}
if (tok.is_flag_token())
- this->_parse_flag_token(state, tok);
+ this->_parse_flag_token(tok, state);
else
- this->_parse_value_token(state, tok);
+ this->_parse_value_token(tok, state);
}
/**
* @brief Parse a single command-line argument *flag* token.
- * @param state The current parsing state.
* @param tok The token to be parsed.
+ * @param state The current parsing state.
* @throws ap::parsing_failure
*/
- void _parse_flag_token(parsing_state& state, const detail::argument_token& tok) {
+ void _parse_flag_token(const detail::argument_token& tok, parsing_state& state) {
if (not tok.is_valid_flag_token()) {
if (state.parse_known_only) {
state.curr_arg.reset();
@@ -1320,11 +1320,11 @@ class argument_parser {
/**
* @brief Parse a single command-line argument *value* token.
- * @param state The current parsing state.
* @param tok The token to be parsed.
+ * @param state The current parsing state.
* @throws ap::parsing_failure
*/
- void _parse_value_token(parsing_state& state, const detail::argument_token& tok) {
+ void _parse_value_token(const detail::argument_token& tok, parsing_state& state) {
if (not state.curr_arg) {
if (state.curr_pos_arg_it == this->_positional_args.end()) {
state.unknown_args.emplace_back(tok.value);
@@ -1334,16 +1334,16 @@ class argument_parser {
state.curr_arg = *state.curr_pos_arg_it;
}
- this->_set_argument_value(state, tok.value);
+ this->_set_argument_value(tok.value, state);
}
/**
* @brief Set the value for the currently processed argument.
* @attention This function assumes that the current argument is set (i.e. `state.curr_arg != nullptr`).
- * @param state The current parsing state.
* @param value The value to be set for the current argument.
+ * @param state The current parsing state.
*/
- void _set_argument_value(parsing_state& state, const std::string_view value) noexcept {
+ void _set_argument_value(const std::string_view value, parsing_state& state) noexcept {
if (state.curr_arg->set_value(std::string(value)))
return; // argument still accepts values
@@ -1360,86 +1360,98 @@ class argument_parser {
* @throws ap::parsing_failure if the state of the parsed arguments is invalid.
*/
void _verify_final_state() const {
- const bool are_required_args_bypassed = this->_are_required_args_bypassed();
+ const auto [supress_group_checks, suppress_arg_checks] = this->_are_checks_suppressed();
for (const auto& group : this->_argument_groups)
- this->_verify_group_requirements(*group, are_required_args_bypassed);
+ this->_verify_group_requirements(*group, supress_group_checks, suppress_arg_checks);
}
/**
- * @brief Check whether required argument bypassing is enabled
- * @return true if at least one argument with enabled required argument bypassing is used, false otherwise.
+ * @brief Check whether required argument group checks or argument checks suppressing is enabled.
+ * @return A pair of boolean flags indicating whether suppressing is enabled.
+ * @note The first flag of the returned pair indicates whetehr argument group check suppressing is enabled,
+ * @note while the second flag indicated whether argument check suppressing is enabled.
*/
- [[nodiscard]] bool _are_required_args_bypassed() const noexcept {
+ [[nodiscard]] std::pair _are_checks_suppressed() const noexcept {
// TODO: use std::views::join after the transition to C++23
- return std::ranges::any_of(
- this->_positional_args,
- [](const arg_ptr_t& arg) {
- return arg->is_used() and arg->is_bypass_required_enabled();
- }
- )
- or std::ranges::any_of(this->_optional_args, [](const arg_ptr_t& arg) {
- return arg->is_used() and arg->is_bypass_required_enabled();
- });
+ bool suppress_group_checks = false;
+ bool suppress_arg_checks = false;
+
+ auto check_arg = [&](const arg_ptr_t& arg) {
+ if (arg->is_used()) {
+ if (arg->suppresses_group_checks())
+ suppress_group_checks = true;
+ if (arg->suppresses_arg_checks())
+ suppress_arg_checks = true;
+ }
+ };
+
+ std::ranges::for_each(this->_positional_args, check_arg);
+ std::ranges::for_each(this->_optional_args, check_arg);
+ return {suppress_group_checks, suppress_arg_checks};
}
/**
* @brief Verifies whether the requirements of the given argument group are satisfied.
* @param group The argument group to verify.
- * @param are_required_args_bypassed A flag indicating whether required argument bypassing is enabled.
+ * @param suppress_arg_checks A flag indicating whether argument checks are suppressed.
* @throws ap::parsing_failure if the requirements are not satistied.
*/
void _verify_group_requirements(
- const argument_group& group, const bool are_required_args_bypassed
+ const argument_group& group,
+ const bool suppress_group_checks,
+ const bool suppress_arg_checks
) const {
if (group._arguments.empty())
return;
- const auto n_used_args = static_cast(
- std::ranges::count_if(group._arguments, [](const auto& arg) { return arg->is_used(); })
- );
+ if (not suppress_group_checks) {
+ const auto n_used_args = static_cast(std::ranges::count_if(
+ group._arguments, [](const auto& arg) { return arg->is_used(); }
+ ));
- if (group._mutually_exclusive) {
- if (n_used_args > 1ull)
- throw parsing_failure(std::format(
- "At most one argument from the mutually exclusive group '{}' can be used",
- group._name
- ));
+ if (group._mutually_exclusive) {
+ if (n_used_args > 1ull)
+ throw parsing_failure(std::format(
+ "At most one argument from the mutually exclusive group '{}' can be used",
+ group._name
+ ));
- const auto used_arg_it = std::ranges::find_if(group._arguments, [](const auto& arg) {
- return arg->is_used();
- });
+ const auto used_arg_it = std::ranges::find_if(
+ group._arguments, [](const auto& arg) { return arg->is_used(); }
+ );
- if (used_arg_it != group._arguments.end()) {
- // only the one used argument has to be validated
- this->_verify_argument_requirements(*used_arg_it, are_required_args_bypassed);
- return;
+ if (used_arg_it != group._arguments.end()) {
+ // only the one used argument has to be validated
+ this->_verify_argument_requirements(*used_arg_it, suppress_arg_checks);
+ return;
+ }
}
- }
- if (group._required and n_used_args == 0ull)
- throw parsing_failure(std::format(
- "At least one argument from the required group '{}' must be used", group._name
- ));
+ if (group._required and n_used_args == 0ull)
+ throw parsing_failure(std::format(
+ "At least one argument from the required group '{}' must be used", group._name
+ ));
+ }
// all arguments in the group have to be validated
for (const auto& arg : group._arguments)
- this->_verify_argument_requirements(arg, are_required_args_bypassed);
+ this->_verify_argument_requirements(arg, suppress_arg_checks);
}
/**
* @brief Verifies whether the requirements of the given argument are satisfied.
* @param arg The argument to verify.
- * @param are_required_args_bypassed A flag indicating whether required argument bypassing is enabled.
+ * @param suppress_arg_checks A flag indicating whether argument checks are suppressed.
* @throws ap::parsing_failure if the requirements are not satistied.
*/
- void _verify_argument_requirements(const arg_ptr_t& arg, const bool are_required_args_bypassed)
- const {
- if (are_required_args_bypassed)
+ void _verify_argument_requirements(const arg_ptr_t& arg, const bool suppress_arg_checks) const {
+ if (suppress_arg_checks)
return;
if (arg->is_required() and not arg->has_value())
- throw parsing_failure::required_argument_not_parsed(arg->name());
-
+ throw parsing_failure(
+ std::format("No values parsed for a required argument [{}]", arg->name().str())
+ );
if (const auto nv_ord = arg->nvalues_ordering(); not std::is_eq(nv_ord))
throw parsing_failure::invalid_nvalues(arg->name(), nv_ord);
}
diff --git a/include/ap/detail/argument_base.hpp b/include/ap/detail/argument_base.hpp
index c01a5fcf..e3f1de6e 100644
--- a/include/ap/detail/argument_base.hpp
+++ b/include/ap/detail/argument_base.hpp
@@ -45,8 +45,11 @@ class argument_base {
/// @return `true` if the argument is required, `false` otherwise.
virtual bool is_required() const noexcept = 0;
- /// @return `true` if the argument is allowed to bypass the required check, `false` otherwise.
- virtual bool is_bypass_required_enabled() const noexcept = 0;
+ /// @return `true` if argument checks suppressing is enabled for the argument, `false` otherwise.
+ virtual bool suppresses_arg_checks() const noexcept = 0;
+
+ /// @return `true` if argument group checks suppressing is enabled for the argument, `false` otherwise.
+ virtual bool suppresses_group_checks() const noexcept = 0;
/// @return `true` if the argument is greedy, `false` otherwise.
virtual bool is_greedy() const noexcept = 0;
diff --git a/include/ap/exceptions.hpp b/include/ap/exceptions.hpp
index b9ceb461..9670b285 100644
--- a/include/ap/exceptions.hpp
+++ b/include/ap/exceptions.hpp
@@ -37,20 +37,6 @@ struct invalid_configuration : public argument_parser_exception {
) noexcept {
return invalid_configuration(std::format("Given name [{}] already used.", arg_name.str()));
}
-
- struct positional {
- static invalid_configuration required_after_non_required(
- const detail::argument_name& required_arg_name,
- const detail::argument_name& non_required_arg_name
- ) noexcept {
- return invalid_configuration(std::format(
- "Required positional argument [{}] cannot be defined after a non-required "
- "positional argument [{}].",
- required_arg_name.str(),
- non_required_arg_name.str()
- ));
- }
- };
};
/// @brief Exception type used for errors encountered during the argument parsing operation.
@@ -61,42 +47,6 @@ struct parsing_failure : public argument_parser_exception {
return parsing_failure(std::format("Unknown argument [{}].", arg_name));
}
- static parsing_failure value_already_set(const detail::argument_name& arg_name) noexcept {
- return parsing_failure(
- std::format("Value for argument [{}] has already been set.", arg_name.str())
- );
- }
-
- static parsing_failure invalid_value(
- const detail::argument_name& arg_name, const std::string& value
- ) noexcept {
- return parsing_failure(
- std::format("Cannot parse value `{}` for argument [{}].", value, arg_name.str())
- );
- }
-
- static parsing_failure invalid_choice(
- const detail::argument_name& arg_name, const std::string& value
- ) noexcept {
- return parsing_failure(std::format(
- "Value `{}` is not a valid choice for argument [{}].", value, arg_name.str()
- ));
- }
-
- static parsing_failure required_argument_not_parsed(const detail::argument_name& arg_name
- ) noexcept {
- return parsing_failure(
- std::format("No values parsed for a required argument [{}]", arg_name.str())
- );
- }
-
- static parsing_failure argument_deduction_failure(const std::vector& values
- ) noexcept {
- return parsing_failure(
- std::format("Failed to deduce the argument for values [{}]", util::join(values))
- );
- }
-
static parsing_failure invalid_nvalues(
const detail::argument_name& arg_name, const std::weak_ordering ordering
) noexcept {
diff --git a/tests/include/argument_parser_test_fixture.hpp b/tests/include/argument_parser_test_fixture.hpp
index f2e7924a..564a938f 100644
--- a/tests/include/argument_parser_test_fixture.hpp
+++ b/tests/include/argument_parser_test_fixture.hpp
@@ -191,6 +191,11 @@ struct argument_parser_test_fixture {
return this->sut._get_argument(arg_name);
}
+ // exception message builders
+ std::string required_argument_not_parsed_msg(const argument_name& arg_name) const {
+ return std::format("No values parsed for a required argument [{}]", arg_name.str());
+ }
+
ap::argument_parser sut{program_name};
parsing_state state{sut};
diff --git a/tests/include/argument_test_fixture.hpp b/tests/include/argument_test_fixture.hpp
index 8580b376..df15671c 100644
--- a/tests/include/argument_test_fixture.hpp
+++ b/tests/include/argument_test_fixture.hpp
@@ -31,16 +31,6 @@ struct argument_test_fixture {
return arg.count();
}
- template
- bool set_required(argument& arg, const bool r) const {
- return arg._required = r;
- }
-
- template
- bool set_bypass_required(argument& arg, const bool br) const {
- return arg._bypass_required = br;
- }
-
template
bool set_value(argument& arg, const T& value) const {
return set_value(arg, as_string(value));
@@ -126,6 +116,17 @@ struct argument_test_fixture {
[[nodiscard]] bool is_bypass_required_enabled(const argument& arg) const {
return arg.is_bypass_required_enabled();
}
+
+ // exception message builders
+ std::string invalid_value_msg(const argument_name& arg_name, const std::string& value) const {
+ return std::format("Cannot parse value `{}` for argument [{}].", value, arg_name.str());
+ }
+
+ std::string invalid_choice_msg(const argument_name& arg_name, const std::string& value) const {
+ return std::format(
+ "Value `{}` is not a valid choice for argument [{}].", value, arg_name.str()
+ );
+ }
};
} // namespace ap_testing
diff --git a/tests/source/test_argument_parser_parse_args.cpp b/tests/source/test_argument_parser_parse_args.cpp
index 19dbe5b3..5a540101 100644
--- a/tests/source/test_argument_parser_parse_args.cpp
+++ b/tests/source/test_argument_parser_parse_args.cpp
@@ -155,8 +155,7 @@ TEST_CASE_FIXTURE(
CHECK_THROWS_WITH_AS(
sut.parse_args(argc, argv),
- parsing_failure::required_argument_not_parsed({init_arg_name_primary(last_pos_arg_idx)})
- .what(),
+ required_argument_not_parsed_msg({init_arg_name_primary(last_pos_arg_idx)}).c_str(),
parsing_failure
);
@@ -179,10 +178,13 @@ TEST_CASE_FIXTURE(
CHECK_THROWS_WITH_AS(
sut.parse_args(argc, argv),
- invalid_configuration::positional::required_after_non_required(
- {required_arg_name}, {non_required_arg_name}
+ std::format(
+ "Required positional argument [{}] cannot be defined after a non-required positional "
+ "argument [{}].",
+ required_arg_name,
+ non_required_arg_name
)
- .what(),
+ .c_str(),
invalid_configuration
);
@@ -206,7 +208,8 @@ TEST_CASE_FIXTURE(
CHECK_THROWS_WITH_AS(
sut.parse_args(argc, argv),
- parsing_failure::argument_deduction_failure(unknown_args).what(),
+ std::format("Failed to deduce the argument for values [{}]", ap::util::join(unknown_args))
+ .c_str(),
parsing_failure
);
@@ -230,7 +233,7 @@ TEST_CASE_FIXTURE(
CHECK_THROWS_WITH_AS(
sut.parse_args(argc, argv),
- parsing_failure::required_argument_not_parsed(required_arg_name).what(),
+ required_argument_not_parsed_msg(required_arg_name).c_str(),
parsing_failure
);
@@ -366,12 +369,13 @@ TEST_CASE_FIXTURE(
TEST_CASE_FIXTURE(
test_argument_parser_parse_args,
- "parse_args should not throw if there is a positional argument which has the bypass_required "
+ "parse_args should not throw if there is a positional argument which has the "
+ "suppress_arg_checks "
"option enabled and is used"
) {
const std::size_t n_positional_args = 1ull;
const auto bypass_required_arg_name = init_arg_name(n_positional_args - 1ull).primary.value();
- sut.add_positional_argument(bypass_required_arg_name).bypass_required();
+ sut.add_positional_argument(bypass_required_arg_name).required(false).suppress_arg_checks();
const std::string bypass_required_arg_value = "bypass_required_arg_value";
for (std::size_t i = 0ull; i < n_optional_args; ++i)
@@ -389,7 +393,8 @@ TEST_CASE_FIXTURE(
TEST_CASE_FIXTURE(
test_argument_parser_parse_args,
- "parse_args should not throw if there is an optional argument which has the bypass_required "
+ "parse_args should not throw if there is an optional argument which has the "
+ "suppress_arg_checks "
"option enabled and is used"
) {
add_arguments(n_positional_args, n_optional_args);
@@ -400,7 +405,7 @@ TEST_CASE_FIXTURE(
)
.default_values(false)
.implicit_values(true)
- .bypass_required();
+ .suppress_arg_checks();
std::string arg_flag;
@@ -1459,6 +1464,28 @@ TEST_CASE_FIXTURE(
free_argv(argc, argv);
}
+TEST_CASE_FIXTURE(
+ test_argument_parser_parse_args,
+ "parse_args should not throw when the group requirements are not satisfied but a group check "
+ "suppressing argument is used"
+) {
+ const std::string suppressing_arg_name = "suppress";
+ sut.add_flag(suppressing_arg_name).suppress_group_checks();
+
+ const std::string req_me_gr_name = "Required & Mutually Exclusive Group";
+ auto& req_me_gr = sut.add_group(req_me_gr_name).mutually_exclusive();
+
+ for (std::size_t i = 0ull; i < n_optional_args; ++i)
+ sut.add_optional_argument(req_me_gr, init_arg_name_primary(i));
+
+ std::vector argv_vec{std::format("--{}", suppressing_arg_name)};
+
+ REQUIRE_NOTHROW(sut.parse_args(argv_vec));
+ CHECK(sut.value(suppressing_arg_name));
+ for (std::size_t i = 0ull; i < n_optional_args; ++i)
+ CHECK_FALSE(sut.is_used(init_arg_name_primary(i)));
+}
+
// subparsers
TEST_CASE_FIXTURE(
diff --git a/tests/source/test_optional_argument.cpp b/tests/source/test_optional_argument.cpp
index 91880983..b1e456b1 100644
--- a/tests/source/test_optional_argument.cpp
+++ b/tests/source/test_optional_argument.cpp
@@ -129,7 +129,9 @@ TEST_CASE_FIXTURE(
CHECK_EQ(required_it->value, "true");
// other parameters
- sut.bypass_required();
+ sut.required(false); // required for argument check suppressing
+ sut.suppress_arg_checks();
+ sut.suppress_group_checks();
sut.nargs(non_default_range);
sut.choices(choices);
sut.default_values(default_value);
@@ -138,10 +140,15 @@ TEST_CASE_FIXTURE(
// check the descriptor parameters
bld = get_help_builder(sut, verbose);
- const auto bypass_required_it =
- std::ranges::find(bld.params, "bypass required", ¶meter_descriptor::name);
- REQUIRE_NE(bypass_required_it, bld.params.end());
- CHECK_EQ(bypass_required_it->value, "true");
+ const auto suppress_arg_checks_it =
+ std::ranges::find(bld.params, "suppress arg checks", ¶meter_descriptor::name);
+ REQUIRE_NE(suppress_arg_checks_it, bld.params.end());
+ CHECK_EQ(suppress_arg_checks_it->value, "true");
+
+ const auto suppress_group_checks_it =
+ std::ranges::find(bld.params, "suppress group checks", ¶meter_descriptor::name);
+ REQUIRE_NE(suppress_group_checks_it, bld.params.end());
+ CHECK_EQ(suppress_group_checks_it->value, "true");
const auto nargs_it = std::ranges::find(bld.params, "nargs", ¶meter_descriptor::name);
REQUIRE_NE(nargs_it, bld.params.end());
@@ -192,66 +199,79 @@ TEST_CASE_FIXTURE(
}
TEST_CASE_FIXTURE(
- argument_test_fixture,
- "bypass_required() should return the value set using the `bypass_required` param setter"
+ argument_test_fixture, "required(true) should throw if an argument is supressing"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
+ sut.required(false);
- sut.bypass_required(true);
- CHECK(sut.is_bypass_required_enabled());
+ SUBCASE("suppressing argument checks") {
+ sut.suppress_arg_checks();
+ }
+ SUBCASE("suppressing argument group checks") {
+ sut.suppress_group_checks();
+ }
+ SUBCASE("suppressing all checks") {
+ sut.suppress_arg_checks();
+ sut.suppress_group_checks();
+ }
+
+ CAPTURE(sut);
- sut.bypass_required(false);
- CHECK_FALSE(sut.is_bypass_required_enabled());
+ CHECK_THROWS_WITH_AS(
+ sut.required(true),
+ std::format("A suppressing argument [{}] cannot be required!", arg_name.str()).c_str(),
+ ap::invalid_configuration
+ );
}
TEST_CASE_FIXTURE(
argument_test_fixture,
- "is_bypass_required_enabled() should return true only if the `required` flag is set to false "
- "and "
- "the `bypass_required` flags is set to true"
+ "suppress_arg_checks() should return the value set using the `suppress_arg_checks` param "
+ "setter if the argument is not required"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
- // disabled
- set_required(sut, false);
- set_bypass_required(sut, false);
- CHECK_FALSE(sut.is_bypass_required_enabled());
+ sut.required(true);
+ CHECK_THROWS_WITH_AS(
+ sut.suppress_arg_checks(true),
+ std::format("A required argument [{}] cannot suppress argument checks!", arg_name.str())
+ .c_str(),
+ ap::invalid_configuration
+ );
- set_required(sut, true);
- set_bypass_required(sut, false);
- CHECK_FALSE(sut.is_bypass_required_enabled());
+ sut.required(false);
- set_required(sut, true);
- set_bypass_required(sut, true);
- CHECK_FALSE(sut.is_bypass_required_enabled());
+ sut.suppress_arg_checks(true);
+ CHECK(sut.suppresses_arg_checks());
- // enabled
- set_required(sut, false);
- set_bypass_required(sut, true);
- CHECK(sut.is_bypass_required_enabled());
+ sut.suppress_arg_checks(false);
+ CHECK_FALSE(sut.suppresses_arg_checks());
}
TEST_CASE_FIXTURE(
argument_test_fixture,
- "required(true) should disable `bypass_required` option and bypass_required(true) should "
- "disable the `required` option"
+ "suppresses_group_checks() should return the value set using the `suppress_group_checks` param "
+ "setter if the argument is not required"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
- REQUIRE_FALSE(sut.is_required());
- REQUIRE_FALSE(sut.is_bypass_required_enabled());
+ sut.required(true);
+ CHECK_THROWS_WITH_AS(
+ sut.suppress_group_checks(true),
+ std::format(
+ "A required argument [{}] cannot suppress argument group checks!", arg_name.str()
+ )
+ .c_str(),
+ ap::invalid_configuration
+ );
- sut.bypass_required();
- CHECK(sut.is_bypass_required_enabled());
- CHECK_FALSE(sut.is_required());
+ sut.required(false);
- sut.required();
- CHECK(sut.is_required());
- CHECK_FALSE(sut.is_bypass_required_enabled());
+ sut.suppress_group_checks(true);
+ CHECK(sut.suppresses_group_checks());
- sut.bypass_required();
- CHECK(sut.is_bypass_required_enabled());
- CHECK_FALSE(sut.is_required());
+ sut.suppress_group_checks(false);
+ CHECK_FALSE(sut.suppresses_group_checks());
}
TEST_CASE_FIXTURE(argument_test_fixture, "is_used() should return false by default") {
@@ -482,7 +502,7 @@ TEST_CASE_FIXTURE(
SUBCASE("given string is empty") {
REQUIRE_THROWS_WITH_AS(
set_value(sut, empty_str),
- parsing_failure::invalid_value(arg_name_primary, empty_str).what(),
+ invalid_value_msg(arg_name_primary, empty_str).c_str(),
parsing_failure
);
CHECK_FALSE(has_value(sut));
@@ -490,7 +510,7 @@ TEST_CASE_FIXTURE(
SUBCASE("given string is non-convertible to value_type") {
REQUIRE_THROWS_WITH_AS(
set_value(sut, invalid_value_str),
- parsing_failure::invalid_value(arg_name_primary, invalid_value_str).what(),
+ invalid_value_msg(arg_name_primary, invalid_value_str).c_str(),
parsing_failure
);
CHECK_FALSE(has_value(sut));
@@ -506,7 +526,7 @@ TEST_CASE_FIXTURE(
REQUIRE_THROWS_WITH_AS(
set_value(sut, invalid_choice),
- parsing_failure::invalid_choice(arg_name_primary, as_string(invalid_choice)).what(),
+ invalid_choice_msg(arg_name_primary, as_string(invalid_choice)).c_str(),
parsing_failure
);
CHECK_FALSE(has_value(sut));
diff --git a/tests/source/test_positional_argument.cpp b/tests/source/test_positional_argument.cpp
index 1a403cbc..759892c6 100644
--- a/tests/source/test_positional_argument.cpp
+++ b/tests/source/test_positional_argument.cpp
@@ -14,14 +14,8 @@ namespace {
constexpr std::string_view help_msg = "test help msg";
-constexpr std::string_view primary_name = "test";
-const auto primary_name_opt = std::make_optional(primary_name);
-
-constexpr std::string_view secondary_name = "t";
-const auto secondary_name_opt = std::make_optional(secondary_name);
-
-const argument_name arg_name(primary_name_opt, secondary_name_opt);
-const argument_name arg_name_primary(primary_name_opt, std::nullopt);
+constexpr std::string_view name_value = "test";
+const argument_name arg_name(std::make_optional(name_value), std::nullopt);
using sut_value_type = int;
using sut_type = positional_argument;
@@ -40,30 +34,17 @@ const range non_default_range = range{1ull, choices.size()};
} // namespace
TEST_CASE_FIXTURE(argument_test_fixture, "name() should return the proper argument_name instance") {
- SUBCASE("initialized with the primary name only") {
- const auto sut = sut_type(arg_name_primary);
- const auto name = get_name(sut);
-
- CHECK(name.match(primary_name));
- CHECK_FALSE(name.match(secondary_name));
- }
-
- SUBCASE("initialized with the primary and secondary names") {
- const auto sut = sut_type(arg_name);
- const auto name = get_name(sut);
-
- CHECK(name.match(primary_name));
- CHECK(name.match(secondary_name));
- }
+ const auto sut = sut_type(arg_name);
+ CHECK_EQ(get_name(sut), arg_name);
}
TEST_CASE_FIXTURE(argument_test_fixture, "help() should return nullopt by default") {
- const auto sut = sut_type(arg_name_primary);
+ const auto sut = sut_type(arg_name);
CHECK_FALSE(get_help(sut));
}
TEST_CASE_FIXTURE(argument_test_fixture, "help() should return a massage set for the argument") {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
sut.help(help_msg);
const auto stored_help_msg = get_help(sut);
@@ -116,7 +97,9 @@ TEST_CASE_FIXTURE(
CHECK(bld.params.empty());
// other parameters
- sut.bypass_required();
+ sut.required(false);
+ sut.suppress_arg_checks();
+ sut.suppress_group_checks();
sut.nargs(non_default_range);
sut.choices(choices);
sut.default_values(default_value);
@@ -124,16 +107,20 @@ TEST_CASE_FIXTURE(
// check the descriptor parameters
bld = get_help_builder(sut, verbose);
- const auto bypass_required_it =
- std::ranges::find(bld.params, "bypass required", ¶meter_descriptor::name);
- REQUIRE_NE(bypass_required_it, bld.params.end());
- CHECK_EQ(bypass_required_it->value, "true");
-
- // automatically set to false with bypass_required
const auto required_it = std::ranges::find(bld.params, "required", ¶meter_descriptor::name);
REQUIRE_NE(required_it, bld.params.end());
CHECK_EQ(required_it->value, "false");
+ const auto suppress_arg_checks_it =
+ std::ranges::find(bld.params, "suppress arg checks", ¶meter_descriptor::name);
+ REQUIRE_NE(suppress_arg_checks_it, bld.params.end());
+ CHECK_EQ(suppress_arg_checks_it->value, "true");
+
+ const auto suppress_group_checks_it =
+ std::ranges::find(bld.params, "suppress group checks", ¶meter_descriptor::name);
+ REQUIRE_NE(suppress_group_checks_it, bld.params.end());
+ CHECK_EQ(suppress_group_checks_it->value, "true");
+
const auto nargs_it = std::ranges::find(bld.params, "nargs", ¶meter_descriptor::name);
REQUIRE_NE(nargs_it, bld.params.end());
CHECK_EQ(nargs_it->value, ap::util::as_string(non_default_range));
@@ -152,7 +139,7 @@ TEST_CASE_FIXTURE(
argument_test_fixture,
"is_hidden() should return false by default or the value passed in the attribute setter"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
REQUIRE_FALSE(sut.is_hidden());
sut.hidden();
@@ -160,7 +147,7 @@ TEST_CASE_FIXTURE(
}
TEST_CASE_FIXTURE(argument_test_fixture, "is_required() should return true by default") {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
CHECK(sut.is_required());
}
@@ -168,7 +155,7 @@ TEST_CASE_FIXTURE(
argument_test_fixture,
"is_required() should return the value set using the `required` param setter"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
sut.required(false);
CHECK_FALSE(sut.is_required());
@@ -178,73 +165,88 @@ TEST_CASE_FIXTURE(
}
TEST_CASE_FIXTURE(
- argument_test_fixture,
- "bypass_required() should return the value set using the `bypass_required` param setter"
+ argument_test_fixture, "required(true) should throw if an argument is supressing"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
+ sut.required(false);
- sut.bypass_required(true);
- CHECK(sut.is_bypass_required_enabled());
+ SUBCASE("suppressing argument checks") {
+ sut.suppress_arg_checks();
+ }
+ SUBCASE("suppressing argument group checks") {
+ sut.suppress_group_checks();
+ }
+ SUBCASE("suppressing all checks") {
+ sut.suppress_arg_checks();
+ sut.suppress_group_checks();
+ }
+
+ CAPTURE(sut);
- sut.bypass_required(false);
- CHECK_FALSE(sut.is_bypass_required_enabled());
+ CHECK_THROWS_WITH_AS(
+ sut.required(true),
+ std::format("A suppressing argument [{}] cannot be required!", arg_name.str()).c_str(),
+ ap::invalid_configuration
+ );
}
TEST_CASE_FIXTURE(
argument_test_fixture,
- "is_bypass_required_enabled() should return true only if the `required` flag is set to false "
- "and "
- "the `bypass_required` flags is set to true"
+ "suppresses_arg_checks() should return the value set using the `suppress_arg_checks` param "
+ "setter if the argument is not required"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
- // disabled
- set_required(sut, false);
- set_bypass_required(sut, false);
- CHECK_FALSE(sut.is_bypass_required_enabled());
+ CHECK_THROWS_WITH_AS(
+ sut.suppress_arg_checks(true),
+ std::format("A required argument [{}] cannot suppress argument checks!", arg_name.str())
+ .c_str(),
+ ap::invalid_configuration
+ );
- set_required(sut, true);
- set_bypass_required(sut, false);
- CHECK_FALSE(sut.is_bypass_required_enabled());
+ sut.required(false);
- set_required(sut, true);
- set_bypass_required(sut, true);
- CHECK_FALSE(sut.is_bypass_required_enabled());
+ sut.suppress_arg_checks(true);
+ CHECK(sut.suppresses_arg_checks());
- // enabled
- set_required(sut, false);
- set_bypass_required(sut, true);
- CHECK(sut.is_bypass_required_enabled());
+ sut.suppress_arg_checks(false);
+ CHECK_FALSE(sut.suppresses_arg_checks());
}
TEST_CASE_FIXTURE(
argument_test_fixture,
- "required(true) should disable `bypass_required` option and bypass_required(true) should "
- "disable the `required` option"
+ "suppresses_group_checks() should return the value set using the `suppress_group_checks` param "
+ "setter if the argument is not required"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
- REQUIRE(sut.is_required());
- REQUIRE_FALSE(sut.is_bypass_required_enabled());
+ CHECK_THROWS_WITH_AS(
+ sut.suppress_group_checks(true),
+ std::format(
+ "A required argument [{}] cannot suppress argument group checks!", arg_name.str()
+ )
+ .c_str(),
+ ap::invalid_configuration
+ );
- sut.bypass_required();
- CHECK(sut.is_bypass_required_enabled());
- CHECK_FALSE(sut.is_required());
+ sut.required(false);
- sut.required();
- CHECK(sut.is_required());
- CHECK_FALSE(sut.is_bypass_required_enabled());
+ sut.suppress_group_checks(true);
+ CHECK(sut.suppresses_group_checks());
+
+ sut.suppress_group_checks(false);
+ CHECK_FALSE(sut.suppresses_group_checks());
}
TEST_CASE_FIXTURE(argument_test_fixture, "is_used() should return false by default") {
- const auto sut = sut_type(arg_name_primary);
+ const auto sut = sut_type(arg_name);
CHECK_FALSE(is_used(sut));
}
TEST_CASE_FIXTURE(
argument_test_fixture, "is_used() should return true when argument contains a value"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
REQUIRE_FALSE(is_used(sut));
set_value(sut, valid_value);
@@ -252,24 +254,24 @@ TEST_CASE_FIXTURE(
}
TEST_CASE_FIXTURE(argument_test_fixture, "count() should return 0 by default") {
- const auto sut = sut_type(arg_name_primary);
+ const auto sut = sut_type(arg_name);
CHECK_EQ(get_count(sut), 0ull);
}
TEST_CASE_FIXTURE(argument_test_fixture, "count() should return 1 when argument contains a value") {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
set_value(sut, valid_value);
CHECK_EQ(get_count(sut), 1ull);
}
TEST_CASE_FIXTURE(argument_test_fixture, "has_value() should return false by default") {
- const auto sut = sut_type(arg_name_primary);
+ const auto sut = sut_type(arg_name);
CHECK_FALSE(has_value(sut));
}
TEST_CASE_FIXTURE(argument_test_fixture, "has_value() should return true if the value is set") {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
set_value(sut, valid_value);
CHECK(has_value(sut));
@@ -278,14 +280,14 @@ TEST_CASE_FIXTURE(argument_test_fixture, "has_value() should return true if the
TEST_CASE_FIXTURE(
argument_test_fixture, "has_value() should return true if the default value is set"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
sut.default_values(default_value);
CHECK(has_value(sut));
}
TEST_CASE_FIXTURE(argument_test_fixture, "has_parsed_values() should return false by default") {
- const auto sut = sut_type(arg_name_primary);
+ const auto sut = sut_type(arg_name);
CHECK_FALSE(has_parsed_values(sut));
}
@@ -293,28 +295,28 @@ TEST_CASE_FIXTURE(
argument_test_fixture,
"has_parsed_values() should return false regardless of the default value parameter"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
sut.default_values(default_value);
CHECK_FALSE(has_parsed_values(sut));
}
TEST_CASE_FIXTURE(argument_test_fixture, "has_parsed_values() should true if the value is set") {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
set_value(sut, valid_value);
CHECK(has_parsed_values(sut));
}
TEST_CASE_FIXTURE(argument_test_fixture, "has_predefined_values() should return false by default") {
- const auto sut = sut_type(arg_name_primary);
+ const auto sut = sut_type(arg_name);
CHECK_FALSE(has_predefined_values(sut));
}
TEST_CASE_FIXTURE(
argument_test_fixture, "has_predefined_values() should return true if the default value is set"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
sut.default_values(default_value);
CHECK(has_predefined_values(sut));
@@ -323,7 +325,7 @@ TEST_CASE_FIXTURE(
TEST_CASE_FIXTURE(
argument_test_fixture, "value() should throw if the argument's value has not been set"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
REQUIRE_FALSE(has_value(sut));
CHECK_THROWS_AS(static_cast(get_value(sut)), std::logic_error);
@@ -332,7 +334,7 @@ TEST_CASE_FIXTURE(
TEST_CASE_FIXTURE(
argument_test_fixture, "value() should return the argument's value if it has been set"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
set_value(sut, valid_value);
REQUIRE(has_value(sut));
@@ -344,7 +346,7 @@ TEST_CASE_FIXTURE(
"value() should return the default argument's default value if it has been set and no values "
"were parsed"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
sut.default_values(default_value);
REQUIRE(has_value(sut));
@@ -354,7 +356,7 @@ TEST_CASE_FIXTURE(
TEST_CASE_FIXTURE(
argument_test_fixture, "value() should return the argument's parsed value if it has been set"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
sut.default_values(default_value);
set_value(sut, valid_value);
@@ -367,12 +369,12 @@ TEST_CASE_FIXTURE(
"set_value(any) should throw when the given string cannot be converted to an instance of "
"value_type"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
SUBCASE("given string is empty") {
REQUIRE_THROWS_WITH_AS(
set_value(sut, empty_str),
- parsing_failure::invalid_value(arg_name_primary, empty_str).what(),
+ invalid_value_msg(arg_name, empty_str).c_str(),
parsing_failure
);
CHECK_FALSE(has_value(sut));
@@ -381,7 +383,7 @@ TEST_CASE_FIXTURE(
SUBCASE("given string is non-convertible to value_type") {
REQUIRE_THROWS_WITH_AS(
set_value(sut, invalid_value_str),
- parsing_failure::invalid_value(arg_name_primary, invalid_value_str).what(),
+ invalid_value_msg(arg_name, invalid_value_str).c_str(),
parsing_failure
);
CHECK_FALSE(has_value(sut));
@@ -392,12 +394,12 @@ TEST_CASE_FIXTURE(
argument_test_fixture,
"set_value(any) should throw when the choices set does not contain the parsed value"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
sut.choices(choices);
REQUIRE_THROWS_WITH_AS(
set_value(sut, invalid_choice),
- parsing_failure::invalid_choice(arg_name_primary, std::to_string(invalid_choice)).what(),
+ invalid_choice_msg(arg_name, std::to_string(invalid_choice)).c_str(),
parsing_failure
);
CHECK_FALSE(has_value(sut));
@@ -408,7 +410,7 @@ TEST_CASE_FIXTURE(
"set_value(any) should throw when adding the given value would result in exceeding the maximum "
"number of values specified by nargs"
) {
- auto sut = sut_type(arg_name_primary).nargs(non_default_range);
+ auto sut = sut_type(arg_name).nargs(non_default_range);
for (const auto value : choices)
REQUIRE_NOTHROW(set_value(sut, value));
@@ -420,13 +422,13 @@ TEST_CASE_FIXTURE(
CHECK_THROWS_WITH_AS(
set_value(sut, valid_value),
- parsing_failure::invalid_nvalues(arg_name_primary, std::weak_ordering::greater).what(),
+ parsing_failure::invalid_nvalues(arg_name, std::weak_ordering::greater).what(),
parsing_failure
);
}
TEST_CASE_FIXTURE(argument_test_fixture, "set_value(any) should perform the specified action") {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
SUBCASE("observe action") {
const auto is_power_of_two = [](const sut_value_type n) {
@@ -469,7 +471,7 @@ TEST_CASE_FIXTURE(argument_test_fixture, "set_value(any) should perform the spec
TEST_CASE_FIXTURE(
argument_test_fixture, "nvalues_ordering() should return less for default nargs (1)"
) {
- const auto sut = sut_type(arg_name_primary);
+ const auto sut = sut_type(arg_name);
CHECK(std::is_lt(nvalues_ordering(sut)));
}
@@ -477,7 +479,7 @@ TEST_CASE_FIXTURE(
argument_test_fixture,
"nvalues_ordering() should return equivalent if a default value has been set"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
sut.nargs(non_default_range);
sut.default_values(default_value);
@@ -490,7 +492,7 @@ TEST_CASE_FIXTURE(
"nvalues_ordering() should return equivalent only when the number of values "
"is in the specified range"
) {
- auto sut = sut_type(arg_name_primary);
+ auto sut = sut_type(arg_name);
sut.nargs(non_default_range);
REQUIRE(std::is_lt(nvalues_ordering(sut)));