diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2055e2d1..c349b3fa 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,7 +7,7 @@ else()
endif()
project(cpp-ap
- VERSION 2.7.0
+ 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 585f767e..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 = 2.7.0
+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
@@ -989,7 +989,7 @@ INPUT_FILE_ENCODING =
# be provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
-FILE_PATTERNS = *.c *.cpp *.h *.hpp *.md *.py
+FILE_PATTERNS = *.c *.cpp *.h *.hpp *.md *.py *.dox
# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
diff --git a/MODULE.bazel b/MODULE.bazel
index c7bf023c..91d3db86 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -1,4 +1,4 @@
module(
name = "cpp-ap",
- version = "2.7.0",
+ version = "3.0.0",
)
diff --git a/README.md b/README.md
index e793034b..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*
>
@@ -53,14 +53,33 @@ Command-line argument parser for C++20
- [Downloading the Library](/docs/tutorial.md#downloading-the-library)
- [The Parser Class](/docs/tutorial.md#the-parser-class)
- [Adding Arguments](/docs/tutorial.md#adding-arguments)
+ - [Syntax](/docs/tutorial.md#syntax)
+ - [Names](/docs/tutorial.md#names)
+ - [Value Types](/docs/tutorial.md#value-types)
+ - [Boolean Flags](/docs/tutorial.md#boolean-flags)
- [Argument Parameters](/docs/tutorial.md#argument-parameters)
+ - [Common Parameters](/docs/tutorial.md#common-parameters)
+ - [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)
- - [Argument Parsing Rules](/docs/tutorial.md#argument-parsing-rules)
+ - [Basic Argument Parsing Rules](/docs/tutorial.md#basic-argument-parsing-rules)
- [Compound Arguments](/docs/tutorial.md#compound-arguments)
- [Parsing Known Arguments](/docs/tutorial.md#parsing-known-arguments)
- [Retrieving Argument Values](/docs/tutorial.md#retrieving-argument-values)
+ - [Subparsers](/docs/tutorial.md#subparsers)
+ - [Creating Subparsers](/docs/tutorial.md#creating-subparsers)
+ - [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)
- [Building and testing](/docs/dev_notes.md#building-and-testing)
- [Formatting](/docs/dev_notes.md#formatting)
diff --git a/cpp-ap-demo b/cpp-ap-demo
index 76d1d7a4..cf3f2ad8 160000
--- a/cpp-ap-demo
+++ b/cpp-ap-demo
@@ -1 +1 @@
-Subproject commit 76d1d7a49ca14ed7595739c118c07b6d98160f83
+Subproject commit cf3f2ad8e9c06af9adcde99288eea517bfb6ecae
diff --git a/docs/groups.dox b/docs/groups.dox
new file mode 100644
index 00000000..129940d1
--- /dev/null
+++ b/docs/groups.dox
@@ -0,0 +1,9 @@
+/**
+ * @file groups.dox
+ * @brief Defines custom groups for documentation.
+ */
+
+/**
+ * @defgroup util Utility
+ * @brief Helper functions, types, concepts, etc.
+ */
diff --git a/docs/tutorial.md b/docs/tutorial.md
index 850d8975..c45e46f8 100644
--- a/docs/tutorial.md
+++ b/docs/tutorial.md
@@ -6,15 +6,44 @@
- [Downloading the Library](#downloading-the-library)
- [The Parser Class](#the-parser-class)
- [Adding Arguments](#adding-arguments)
+ - [Syntax](#syntax)
+ - [Names](#names)
+ - [Value Types](#value-types)
+ - [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) - 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) - 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)
+ - [Creating New Groups](#creating-new-groups)
+ - [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)
- - [Argument Parsing Rules](#argument-parsing-rules)
+ - [Basic Argument Parsing Rules](#basic-argument-parsing-rules)
- [Compound Arguments](#compound-arguments)
- [Parsing Known Arguments](#parsing-known-arguments)
- [Retrieving Argument Values](#retrieving-argument-values)
+- [Subparsers](#subparsers)
+ - [Creating Subparsers](#creating-subparsers)
+ - [Using Multiple Subparsers](#using-multiple-subparsers)
+ - [Parsing Arguments with Subparsers](#parsing-arguments-with-subparsers)
+ - [Tracking Parser State](#tracking-parser-state)
- [Examples](#examples)
+- [Common Utility](#common-utility)
@@ -104,20 +133,26 @@ If you do not use CMake you can dowload the desired [library release](https://gi
To use the argument parser in your code you need to use the `ap::argument_parser` class.
-The parameters you can specify for a parser's instance are:
-
-- The program's name, version and description - used in the parser's configuration output (`std::cout << parser`).
-- Verbosity mode - `false` by default; if set to `true` the parser's configuration output will include more detailed info about arguments' parameters in addition to their names and help messages.
-- [Arguments](#adding-arguments) - specify the values/options accepted by the program.
-
```cpp
-ap::argument_parser parser;
-parser.program_name("Name of the program")
- .program_version("alhpa")
+ap::argument_parser parser("program");
+parser.program_version("alhpa")
.program_description("Description of the program")
.verbose();
```
+> [!IMPORTANT]
+>
+> - When creating an argument parser instance, you must provide a program name to the constructor.
+>
+> The program name given to the parser cannot be empty and cannot contain whitespace characters.
+>
+> - Additional parameters you can specify for a parser's instance incldue:
+> - The program's version and description - used in the parser's configuration output (`std::cout << parser`).
+> - Verbosity mode - `false` by default; if set to `true` the parser's configuration output will include more detailed info about arguments' parameters in addition to their names and help messages.
+> - [Arguments](#adding-arguments) - specify the values/options accepted by the program.
+> - [Argument Groups](#argument-groups) - organize related optional arguments into sections and optionally enforce usage rules.
+> - [The unknown argument flags handling policy](#4-unknown-argument-flag-handling).
+
> [!TIP]
>
> You can specify the program version using a string (like in the example above) or using the `ap::version` structure:
@@ -140,72 +175,89 @@ parser.program_name("Name of the program")
## Adding Arguments
-The parser supports both positional and optional arguments. Both argument types are identified by their names represented as strings. Arguments can be defined with only a primary name or with a primary and a secondary (short) name.
+The parser supports **positional** and **optional** arguments.
> [!NOTE]
>
-> The basic rules of parsing positional and optional arguments are described in the [Parsing arguments](#parsing-arguments) section.
+> The general rules for parsing arguments are described in the [Parsing arguments](#parsing-arguments) section.
+
+### Syntax
-To add an argument to the parameter's configurations use the following syntax:
+To add an argument, use:
```cpp
-parser.add__argument("argument");
+parser.add_positional_argument("name");
+parser.add_optional_argument("name");
```
-or
+For **optional arguments**, you may also specify a secondary (short) name:
```cpp
-parser.add__argument("argument", "a");
+parser.add_optional_argument("name", "n")
```
+or use only the secondary name:
+
+```cpp
+parser.add_optional_argument("n", ap::n_secondary);
+```
+
+### Names
+
+- Positional arguments must have exactly one name (no secondary/short names allowed).
+- Optional arguments can have:
+ - only a primary (long) name,
+ - only a secondary (short) name, or
+ - both a primary and a secondary name.
+
+### Value Types
+
> [!IMPORTANT]
+> An argument's value type must be `ap::none_type` **or** satisfy all of the following requirements:
>
-> The library supports any argument value types which meet the following requirements:
->
-> - The type is [constructible from](https://en.cppreference.com/w/cpp/concepts/constructible_from) `const std::string&` or the stream extraction operator - `std::istream& operator>>` is defined for the type.
->
-> **IMPORTANT:** The argument parser will always use direct initialization from `std::string` and will use the extraction operator only if an argument's value type cannot be initialized from `std::string`.
->
-> - The type satisfies the [`std::semiregular`](https://en.cppreference.com/w/cpp/concepts/semiregular.html) concept - is default initializable and copyable.
+> - [Constructible from](https://en.cppreference.com/w/cpp/concepts/constructible_from) `const std::string&` or overload `std::istream& operator>>`.
+> - The parser will always try direct initialization from std::string first, and only fall back to the extraction operator if direct initialization fails.
+> - Satisfy the [`std::semiregular`](https://en.cppreference.com/w/cpp/concepts/semiregular.html) concept (default-initializable and copyable).
> [!NOTE]
>
-> The default value type of any argument is `std::string`.
+> - The default value type of any argument is `std::string`.
+> - If the argument's value type is `ap::none_type`, the argument will not accept any values and therefore no value-related parameters can be set for such argument. This includes:
+> - [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)
+> - [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)
+
+### Boolean Flags
-You can also add boolean flags:
+Flags are essentialy optional arguments with a boolean value type.
```cpp
parser.add_flag("enable_some_option", "eso").help("enables option: some option");
/* equivalent to:
parser.add_optional_argument("enable_some_option", "eso")
- .default_value(false)
- .implicit_value(true)
+ .default_values(false)
+ .implicit_values(true)
.nargs(0)
.help("enables option: some option");
*/
```
-Boolean flags store `true` by default but you can specify whether the flag should store `true` or `false` when used:
+By default, flags store `true` when parsed from the command-line. You can invert this behavior:
```cpp
parser.add_flag("disable_another_option", "dao").help("disables option: another option");
/* equivalent to:
parser.add_optional_argument("disable_another_option", "dao")
- .default_value(true)
- .implicit_value(false)
+ .default_values(true)
+ .implicit_values(false)
.nargs(0)
.help("disables option: another option");
*/
-```
-> [!NOTE]
->
-> While passing a primary name is required for creating positional arguments, optional arguments (and flags) can be initialized using only a secondary name as follows:
->
-> ```cpp
-> parser.add_optional_argument("a", ap::n_secondary);
-> parser.add_flag("f", ap::n_secondary);
-> ```
+```
@@ -224,14 +276,16 @@ parser.add_positional_argument("number", "n")
.help("a positive integer value");
```
+
+
#### 2. `hidden` - If this option is set for an argument, then it will not be included in the program description.
By default all arguments are visible, but this can be modified using the `hidden(bool)` setter as follows:
```cpp
-parser.program_name("hidden-test")
- .program_description("A simple program")
- .default_optional_arguments({ap::argument::default_optional::help});
+ap::argument_parser("hidden-test")
+parser.program_description("A simple test program for argument hiding")
+ .default_arguments(ap::default_argument::o_help);
parser.add_optional_argument("hidden")
.hidden()
@@ -245,7 +299,7 @@ parser.try_parse_args(argc, argv);
> ./hidden-test --help
Program: hidden-test
- A simple program
+ A simple test program for argument hiding
Optional arguments:
@@ -253,9 +307,11 @@ Optional arguments:
--visible : A simple visible argument
```
+
+
#### 3. `required` - If this option is set for an argument and it's value is not passed in the command-line, an exception will be thrown.
-> [!NOTE]
+> [!IMPORTANT]
>
> - By default positional arguments are set to be required, while optional arguments have this option disabled by default.
> - The default value of the value parameter of the `required(bool)` function is `true` for both positional and optional arguments.
@@ -263,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
@@ -320,24 +374,29 @@ 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 positional and optional arguments have the `bypass_required` option disabled.
-> - The default value of the value parameter of the `bypass_required(bool)` function is `true` for both positional and optional 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);
@@ -356,74 +415,108 @@ std::ofstream os(parser.value("output"));
os << data << std::endl;
```
-#### 5. `default_value` - The default value for an argument which will be used if no values for this argument are parsed
+
+
+#### 5. `nargs` - Sets the allowed number of values to be parsed for an argument.
-> [!WARNING]
+The `nargs` parameter can be set as:
+
+- Specific number:
+
+ ```cpp
+ parser.add_optional_argument("input", "i").nargs(1);
+ ```
+
+- Fully bound range:
+
+ ```cpp
+ parser.add_optional_argument("input", "i").nargs(1, 3);
+ ```
+
+- Partially bound range:
+
+ ```cpp
+ parser.add_optional_argument("input", "i").nargs(ap::nargs::at_least(1)); // n >= 1
+ parser.add_optional_argument("input", "i").nargs(ap::nargs::more_than(1)); // n > 1
+ parser.add_optional_argument("input", "i").nargs(ap::nargs::less_than(5)); // n < 5
+ parser.add_optional_argument("input", "i").nargs(ap::nargs::up_to(5)); // n <= 5
+ ```
+
+- Unbound range:
+
+ ```cpp
+ parser.add_optional_argument("input", "i").nargs(ap::nargs::any());
+ ```
+
+> [!IMPORTANT]
>
-> For both positional and optional arguments, setting the `default_value` parameter disables the `required` option.
+> The default `nargs` parameter value is:
+>
+> - `ap::nargs::range(1ull)` for positional arguments
+>
+> - `ap::nargs::any()` for optional arguments
-```cpp
-// example: positional arguments
-parser.add_positional_argument("input");
-parser.add_positional_argument("output").default_value("output.txt");
+
-parser.parse_args(argc, argv);
+#### 6. `greedy` - If this option is set, the argument will consume ALL command-line values until it's upper nargs bound is reached.
-// `input` is guaranteed to have a value if parsing was successfull
-const auto data = read_data(parser.value("input"));
+> [!NOTE]
+>
+> - By default the `greedy` option is disabled for all arguments.
+> - The default value of the parameter of the `argument::greedy(bool)` method is true for all arguments.
-// `output` is guaranteed to have a value even if one was not specified in the command-line
-std::ofstream os(parser.value("output"));
-os << data << std::endl;
+> [!TIP]
+>
+> - Enabling the `greedy` option for an argument only makes sense for arguments with string-like value types.
+> - If no explicit `nargs` bound is set for a greedy argument, once it starts being parsed, it will consume all remaining command-line arguments.
-/*
-Command Result
------------------------------------------------------------------------------------------
-./program Parsing error (no value for the input argument)
-./program input.txt Parsing success; Printing data to `output.txt`
-./program input.txt myfile.txt Parsing success; Printing data to the `myfile.txt` file
-```
+Consider a simple example:
```cpp
-// example: optional arguments
-parser.add_optional_argument("input", "i").required();
-parser.add_optional_argument("output", "o").default_value("output.txt");
+ap::argument_parser parser("run-script");
+parser.default_arguments(ap::default_argument::o_help);
-parser.parse_args(argc, argv);
+parser.add_positional_argument("script")
+ .help("The name of the script to run");
+parser.add_optional_argument("args")
+ .greedy()
+ .help("Set the execution option");
-// `input` is guaranteed to have a value if parsing was successfull
-const auto data = read_data(parser.value("input"));
+parser.try_parse_args(argc, argv);
-// `output` is guaranteed to have a value even if one was not specified in the command-line
-std::ofstream os(parser.value("output"));
-os << data << std::endl;
+// Application logic here
+std::cout << "Executing: " << parser.value("script") << " " << ap::util::join(parser.values("args")) << std::endl;
+```
-/*
-Command Result
------------------------------------------------------------------------------------------------
-./program Parsing error (no value for the input argument)
-./program --input input.txt Parsing success; Printing data to `output.txt`
-./program -i input.txt -o myfile.txt Parsing success; Printing data to the `myfile.txt` file
+Here the program execution should look something like this:
+
+```txt
+> ./run-script remove-comments --args module.py -v --type py
+Executing: remove-comments module.py -v --type py
```
-> [!TIP]
->
-> The setter of the `default_value` parameter accepts any type that is convertible to the argument's value type.
+Notice that even though the `-v` and `--type` command-line arguments have flag prefixes and are not defined in the program, they are not treated as unknown arguments (and therefore no exception is thrown) because the `--args` argument is marked as `greedy` and it consumes these command-line arguments as its values.
-#### 6. `choices` - A list of valid argument values.
+
-The `choices` parameter takes as an argument an instance of `std::initializer_list` or any `std::ranges::range` type such that its value type is convertible to the argument's `value_type`.
+#### 7. `choices` - A list of valid argument values.
```cpp
-parser.add_optional_argument("method", "m").choices({'a', 'b', 'c'});
+parser.add_optional_argument("method", "m").choices('a', 'b', 'c');
+// equivalent to: parser.add_optional_argument("method", "m").choices({'a', 'b', 'c'});
// passing a value other than a, b or c for the `method` argument will result in an error
```
> [!IMPORTANT]
>
-> The `choices` function can be used only if the argument's `value_type` is equality comparable (defines the `==` operator).
+> - The `choices` function can be used only if the argument's `value_type` is equality comparable (defines the `==` operator)
+> - The `choices` function can be called with:
+> - A variadic number of values [convertible to](https://en.cppreference.com/w/cpp/concepts/convertible_to.html) the argument's value type
+> - An arbitrary [`std::ranges::range`](https://en.cppreference.com/w/cpp/ranges/range.html) type with a value type [convertible to](https://en.cppreference.com/w/cpp/concepts/convertible_to.html) the argument's value type
-#### 7. Value actions - Function performed after parsing an argument's value.
+
+
+#### 8. value actions - Functions that are called after parsing an argument's value.
Actions are represented as functions, which take the argument's value as an argument. The available action types are:
- `observe` actions | `void(const value_type&)` - applied to the parsed value. No value is returned - this action type is used to perform some logic on the parsed value without modifying it.
@@ -431,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")
@@ -466,50 +559,96 @@ Actions are represented as functions, which take the argument's value as an argu
>
> A single argument can have multiple value actions. Instead of writing complex logic in one action, consider composing several simple, focused actions for better readability and reusability.
-
+
-### Parameters Specific for Optional Arguments
+#### 9. `default_values` - A list of values which will be used if no values for an argument have been parsed
-Apart from the common parameters listed above, for optional arguments you can also specify the following parameters:
+> [!WARNING]
+>
+> For both positional and optional arguments, setting the `default_values` parameter disables the `required` option.
-#### 1. `nargs` - Sets the allowed number of values to be parsed for an argument. This can be set as a:
+```cpp
+// example: positional arguments
+parser.add_positional_argument("input");
+parser.add_positional_argument("output").default_values("output.txt");
-- Specific number:
+parser.parse_args(argc, argv);
- ```cpp
- parser.add_optional_argument("input", "i").nargs(1);
- ```
+// `input` is guaranteed to have a value if parsing was successfull
+const auto data = read_data(parser.value("input"));
-- Fully bound range:
+// `output` is guaranteed to have a value even if one was not specified in the command-line
+std::ofstream os(parser.value("output"));
+os << data << std::endl;
- ```cpp
- parser.add_optional_argument("input", "i").nargs(1, 3);
- ```
+/*
+Command Result
+-----------------------------------------------------------------------------------------
+./program Parsing error (no value for the input argument)
+./program input.txt Parsing success; Printing data to `output.txt`
+./program input.txt myfile.txt Parsing success; Printing data to the `myfile.txt` file
+```
-- Partially bound range:
+```cpp
+// example: optional arguments
+parser.add_optional_argument("input", "i").required();
+parser.add_optional_argument("output", "o").default_values("output.txt");
- ```cpp
- parser.add_optional_argument("input", "i").nargs(ap::nargs::at_least(1)); // n >= 1
- parser.add_optional_argument("input", "i").nargs(ap::nargs::more_than(1)); // n > 1
- parser.add_optional_argument("input", "i").nargs(ap::nargs::less_than(5)); // n < 5
- parser.add_optional_argument("input", "i").nargs(ap::nargs::up_to(5)); // n <= 5
- ```
+parser.parse_args(argc, argv);
-- Unbound range:
+// `input` is guaranteed to have a value if parsing was successfull
+const auto data = read_data(parser.value("input"));
- ```cpp
- parser.add_optional_argument("input", "i").nargs(ap::nargs::any());
- ```
+// `output` is guaranteed to have a value even if one was not specified in the command-line
+std::ofstream os(parser.value("output"));
+os << data << std::endl;
-> [!IMPORTANT]
+/*
+Command Result
+-----------------------------------------------------------------------------------------------
+./program Parsing error (no value for the input argument)
+./program --input input.txt Parsing success; Printing data to `output.txt`
+./program -i input.txt -o myfile.txt Parsing success; Printing data to the `myfile.txt` file
+```
+
+> [!NOTE]
>
-> The default `nargs` parameter value is `ap::nargs::any()`.
+> The `default_values` function can be called with:
+> - A variadic number of values [convertible to](https://en.cppreference.com/w/cpp/concepts/convertible_to.html) the argument's value type
+> - An arbitrary [`std::ranges::range`](https://en.cppreference.com/w/cpp/ranges/range.html) type with a value type [convertible to](https://en.cppreference.com/w/cpp/concepts/convertible_to.html) the argument's value type
+
+
+
+
+### Parameters Specific for Optional Arguments
+
+Apart from the common parameters listed above, for optional arguments you can also specify the following parameters:
+
+#### 1. on-flag actions - Functions that are called immediately after parsing an argument's flag.
+
+```cpp
+void print_debug_info() noexcept {
+#ifdef NDEBUG
+ std::cout << "Running in release mode.\n";
+#else
+ std::cout << "Running in debug mode.\n";
+#endif
+ std::exit(EXIT_SUCCESS);
+};
+
+parser.add_optional_argument("--debug-info")
+ .action(print_debug_info);
+```
+
+Here the `print_debug_info` function will be called right after parsing the `--debug-info` flag and the program will exit, even if there are more arguments after this flag.
+
+
-#### 2. `implicit_value` - A value which will be set for an argument if only it's flag is parsed from the command-line but no values follow.
+#### 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.
```cpp
// example
-parser.add_optional_argument("save", "s").implicit_value("output.txt");
+parser.add_optional_argument("save", "s").implicit_values("output.txt");
parser.parse_args(argc, argv);
@@ -529,28 +668,15 @@ Command Result
./program --save myfile.txt The data will be saved to `myfile.txt`
```
-> [!TIP]
+> [!NOTE]
>
-> - The `implicit_value` parameter is extremely useful when combined with default value (e.g. in case of boolean flags - see [Adding Arguments](#adding-arguments)).
-> - The setter of the `implicit_value` parameter accepts any type that is convertible to the argument's value type.
-
-#### 4. On-flag actions - For optional arguments, apart from value actions, you can specify on-flag actions which are executed immediately after parsing an argument's flag.
+> The `implicit_values` function can be called with:
+> - A variadic number of values [convertible to](https://en.cppreference.com/w/cpp/concepts/convertible_to.html) the argument's value type
+> - An arbitrary [`std::ranges::range`](https://en.cppreference.com/w/cpp/ranges/range.html) type with a value type [convertible to](https://en.cppreference.com/w/cpp/concepts/convertible_to.html) the argument's value type
-```cpp
-void print_debug_info() noexcept {
-#ifdef NDEBUG
- std::cout << "Running in release mode.\n";
-#else
- std::cout << "Running in debug mode.\n";
-#endif
- std::exit(EXIT_SUCCESS);
-};
-
-parser.add_optional_argument("--debug-info")
- .action(print_debug_info);
-```
-
-Here the `print_debug_info` function will be called right after parsing the `--debug-info` flag and the program will exit, even if there are more arguments after this flag.
+> [!TIP]
+>
+> The `implicit_values` parameter is extremely useful when combined with default value (e.g. in case of boolean flags - see [Adding Arguments](#adding-arguments)).
@@ -560,12 +686,12 @@ Here the `print_debug_info` function will be called right after parsing the `--d
### Actions
-- `print_config` | on-flag
+- `print_help` | on-flag
- Prints the configuration of the parser to the output stream and optionally exits with the given code.
+ Prints the parser's help message to the output stream and optionally exits with the given code.
```cpp
- typename ap::action_type::on_flag::type print_config(
+ typename ap::action_type::on_flag::type print_help(
const ap::argument_parser& parser,
const std::optional exit_code = std::nullopt,
std::ostream& os = std::cout
@@ -577,7 +703,7 @@ Here the `print_debug_info` function will be called right after parsing the `--d
Throws if the provided file path does not exist.
```cpp
- detail::callable_type check_file_exists() noexcept;
+ ap::action::util::callable_type check_file_exists() noexcept;
```
- `gt` | observe (value type: [arithmetic](https://en.cppreference.com/w/cpp/types/is_arithmetic))
@@ -585,8 +711,8 @@ Here the `print_debug_info` function will be called right after parsing the `--d
Validates that the value is strictly greater than `lower_bound`.
```cpp
- template
- detail::callable_type gt(const T lower_bound) noexcept;
+ template
+ ap::action::util::callable_type gt(const T lower_bound) noexcept;
```
- `geq` | observe (value type: [arithmetic](https://en.cppreference.com/w/cpp/types/is_arithmetic))
@@ -594,8 +720,8 @@ Here the `print_debug_info` function will be called right after parsing the `--d
Validates that the value is greater than or equal to `lower_bound`.
```cpp
- template
- detail::callable_type geq(const T lower_bound) noexcept;
+ template
+ ap::action::util::callable_type geq(const T lower_bound) noexcept;
```
- `lt` | observe (value type: [arithmetic](https://en.cppreference.com/w/cpp/types/is_arithmetic))
@@ -603,8 +729,8 @@ Here the `print_debug_info` function will be called right after parsing the `--d
Validates that the value is strictly less than `upper_bound`.
```cpp
- template
- detail::callable_type lt(const T upper_bound) noexcept
+ template
+ ap::action::util::callable_type lt(const T upper_bound) noexcept
```
- `leq` | observe (value type: [arithmetic](https://en.cppreference.com/w/cpp/types/is_arithmetic))
@@ -612,8 +738,8 @@ Here the `print_debug_info` function will be called right after parsing the `--d
Validates that the value is less than or equal to `upper_bound`.
```cpp
- template
- detail::callable_type leq(const T upper_bound) noexcept
+ template
+ ap::action::util::callable_type leq(const T upper_bound) noexcept
```
- `within` | observe (value type: [arithmetic](https://en.cppreference.com/w/cpp/types/is_arithmetic))
@@ -621,8 +747,8 @@ Here the `print_debug_info` function will be called right after parsing the `--d
Checks if the value is within the given interval. Bound inclusivity is customizable using template parameters.
```cpp
- template
- detail::callable_type within(
+ template
+ ap::action::util::callable_type within(
const T lower_bound, const T upper_bound
) noexcept
```
@@ -636,20 +762,16 @@ Here the `print_debug_info` function will be called right after parsing the `--d
The `CPP-AP` library defines several default arguments, which can be added to the parser's configuration as follows.
```cpp
-parser.default_positional_arguments({...});
-// here `...` represents a collection of ap::argument::default_positional values
-
-parser.default_positional_arguments({...});
-// here `...` represents a collection of ap::argument::default_optional values
+parser.default_arguments();
```
> [!NOTE]
>
-> These functions work with `std::initializer_list` and all other `std::ranges::range` types with the correct value type - `ap::argument::default_{positional/optional}`
-
-The available default arguments are:
+> The `default_arguments` function can be called with:
+> - A variadic number of `ap::default_argument` values
+> - An arbitrary [`std::ranges::range`](https://en.cppreference.com/w/cpp/ranges/range.html) type with the `ap::default_argument` value type
-- `default_positional::input`:
+- `p_input`:
```cpp
// equivalent to:
@@ -658,23 +780,23 @@ The available default arguments are:
.help("Input file path");
```
-- `default_positional::output`:
+- `p_output`:
```cpp
// equivalent to:
parser.add_positional_argument("output").help("Output file path");
```
-- `default_optional::help`:
+- `o_help`:
```cpp
// equivalent to:
- parser.add_flag("help", "h")
- .action(action::print_config(arg_parser, EXIT_SUCCESS))
+ parser.add_optional_argument("help", "h")
+ .action(ap::action::print_help(parser, EXIT_SUCCESS))
.help("Display the help message");
```
-- `default_optional::input` and `default_optional::multi_input`:
+- `o_input` and `o_multi_input`:
```cpp
// input - equivalent to:
@@ -692,7 +814,7 @@ The available default arguments are:
.help("Input files paths");
```
-- `default_optional::output` and `default_optional::multi_output`:
+- `o_output` and `o_multi_output`:
```cpp
// output - equivalent to:
@@ -712,52 +834,149 @@ The available default arguments are:
-## Parsing Arguments
+## Argument Groups
-To parse the command-line arguments use the `void argument_parser::parse_args(const AR& argv)` method, where `AR` must be a type that satisfies `std::ranges::range` and its value type is convertible to `std::string`.
+Argument groups provide a way to organize related optional arguments into logical sections. They make the command-line interface easier to read in help messages, and can enforce rules such as **mutual exclusivity** or **required usage**.
-The `argument_parser` class also defines the `void parse_args(int argc, char* argv[])` overload, which works directly with the `argc` and `argv` arguments of the `main` function.
+By default, every parser comes with two predefined groups:
-> [!IMPORTANT]
->
-> The `parse_args(argc, argv)` method ignores the first argument (the program name) and is equivalent to calling:
+- **Positional Arguments** – contains all arguments added via `add_positional_argument`.
+- **Optional Arguments** – contains all arguments added via `add_optional_argument` or `add_flag` without explicitly specifying an argument group.
+
+User-defined groups can only contain optional arguments (including flags). This allows you to structure your command-line interface into meaningful sections such as "Input Options", "Output Options", or "Debug Settings".
+
+
+### Creating New Groups
+
+A new group can be created by calling the `add_group` method of an argument parser:
+
+```cpp
+ap::argument_parser parser("myprog");
+auto& out_opts = parser.add_group("Output Options");
+```
+
+The group’s name will appear as a dedicated section in the help message and arguments added to this group will be listed under `Output Options` instead of the default `Optional Arguments` section.
+
+> [!NOTE]
>
-> ```cpp
-> parse_args(std::span(argv + 1, argc - 1));
-> ```
+> If a group has no visible arguments, it will not be included in the parser's help message output at all.
-> [!WARNING]
+### Adding Arguments to Groups
+
+Arguments are added to a group by passing the group reference as the first parameter to the `add_optional_argument` and `add_flag` functions:
+
+```cpp
+parser.add_optional_argument(out_opts, "output", "o")
+ .nargs(1)
+ .help("Print output to the given file");
+
+parser.add_flag(out_opts, "print", "p")
+ .help("Print output to the console");
+```
+
+### Group Attributes
+
+User-defined groups can be configured with special attributes that change how the parser enforces their usage:
+
+- `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).
+
+```cpp
+auto& out_opts = parser.add_group("Output Options")
+ .required() // at least one option is required
+ .mutually_exclusive(); // but at most one can be chosen
+```
+
+> [!IMPORANT]
>
-> By default the `argument_parser` class treats *all\** command-line arguments beggining with a `--` or `-` prefix as optional argument flags and if the flag's value does not match any of the specified arguments, then such flag is considered *unknown* and an exception will be thrown.
+> 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**.
>
-> > [*all\**] If a command-line argument begins with a flag prefix, but contains whitespaces (e.g. `"--flag value"`), then it is treated as a value and not a flag.
+> 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.
+
+### Complete Example
+
+Below is a small program that demonstrates how to use a mutually exclusive group of required arguments:
+
+```cpp
+#include
+
+int main(int argc, char* argv[]) {
+ ap::argument_parser parser("myprog");
+ parser.default_arguments(ap::default_argument::o_help);
+
+ // create the argument group
+ auto& out_opts = parser.add_group("Output Options")
+ .required()
+ .mutually_exclusive();
+
+ // add arguments to the custom group
+ parser.add_optional_argument(out_opts, "output", "o")
+ .nargs(1)
+ .help("Print output to a given file");
+
+ parser.add_flag(out_opts, "print", "p")
+ .help("Print output to the console");
+
+ parser.try_parse_args(argc, argv);
+
+ return 0;
+}
+```
+
+When invoked with the `--help` flag, the above program produces a help message that clearly shows the group and its rules:
+
+```
+Program: myprog
+
+Output Options: (required, mutually exclusive)
+
+ --output, -o : Print output to a given file
+ --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]
>
-> This behavior can be altered so that the unknown argument flags will be treated as values, not flags.
+> - 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]
>
-> Example:
-> ```cpp
-> parser.add_optional_argument("option", "o");
-> parser.try_parse_args(argc, argv);
-> std::cout << "option: " << parser.value("option");
+> - 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).
+
+
+
+
+
+## Parsing Arguments
+
+To parse the command-line arguments use the `void argument_parser::parse_args(const AR& argv)` method, where `AR` must be a type that satisfies the [`std::ranges::forward_range`](https://en.cppreference.com/w/cpp/ranges/forward_range.html) concept and its value type is convertible to `std::string`.
+
+The `argument_parser` class also defines the `void parse_args(int argc, char* argv[])` overload, which works directly with the `argc` and `argv` arguments of the `main` function.
+
+> [!IMPORTANT]
>
-> /*
-> ./program --option --unknown-flag
-> option: --unknown-flag
-> ```
+> The `parse_args(argc, argv)` method ignores the first argument (the program name) and is equivalent to calling:
>
-> To do this add the following in you `CMakeLists.txt` file:
-> ```cmake
-> target_compile_definitions(cpp-ap PRIVATE AP_UNKNOWN_FLAGS_AS_VALUES)
-> ```
-> or simply add:
> ```cpp
-> #define AP_UNKNOWN_FLAGS_AS_VALUES
+> parse_args(std::span(argv + 1, argc - 1));
> ```
-> before the `#include ` statement.
> [!TIP]
>
-> The `parse_args` function may throw an `ap::argument_parser_exception` (specifically the `ap::parsing_failure` derived exception) if the provided command-line arguments do not match the expected configuration. To simplify error handling, the `argument_parser` class provides `try_parse_args` methods, which will automatically catch these exceptions, print the error message, and exit with a failure status.
+> The `parse_args` function may throw an `ap::argument_parser_exception` if the configuration of the defined arguments is invalid or the parsed command-line arguments do not match the expected configuration. To simplify error handling, the `argument_parser` class provides a `try_parse_args` methods, which will automatically catch these exceptions, print the error message as well as the help message of the deepest used parser (see [Subparsers](#subparsers)), and exit with a failure status.
>
> Internally, This is equivalent to:
>
@@ -766,147 +985,257 @@ The `argument_parser` class also defines the `void parse_args(int argc, char* ar
> parser.parse_args(...);
> }
> catch (const ap::argument_parser_exception& err) {
-> std::cerr << "[ERROR] : " << err.what() << std::endl << parser << std::endl;
+> std::cerr << "[ap::error] " << err.what() << std::endl << parser.resolved_parser() << std::endl;
> std::exit(EXIT_FAILURE);
> }
> ```
+The simple example below demonstrates how (in terms of the program's structure) the argument parsing should look like.
+
```cpp
-// power.cpp
+// include the main library header
#include
-#include
-#include
-
int main(int argc, char* argv[]) {
// create the parser class instance
- ap::argument_parser parser;
- parser.program_name("power calculator")
- .program_description("Calculates the value of an expression: base ^ exponent");
+ ap::argument_parser parser("some-program");
- // add arguments
- parser.add_positional_argument("base").help("the exponentation base value");
- parser.add_optional_argument("exponent", "e")
- .nargs(ap::nargs::any())
- .help("the exponent value");
+ // define the parser's attributes and default arguments
+ parser.program_version({0u, 0u, 0u})
+ .program_description("The program does something with command-line arguments")
+ .default_arguments(ap::default_argument::o_help);
- parser.default_optional_arguments({ap::argument::default_optional::help});
+ // define the program arguments
+ parser.add_positional_argument("positional").help("A positional argument");
+ parser.add_optional_argument("optional", "o").help("An optional argument");
+ parser.add_flag("flag", "f").help("A boolean flag");
// parse command-line arguments
parser.try_parse_args(argc, argv);
- // check if any values for the `exponent` argument have been parsed
- if (not parser.has_value("exponent")) {
- std::cout << "no exponent values given" << std::endl;
- std::exit(EXIT_SUCCESS);
- }
-
- const double base = parser.value("base");
- const std::vector exponent_values = parser.values("exponent");
-
- for (const int exponent : exponent_values) {
- std::cout << base << " ^ " << exponent << " = " << std::pow(base, exponent) << std::endl;
- }
+ // use the program's arguments
+ std::cout << "positional: " << parser.value("positional") << std::endl
+ << "optional: " << ap::util::join(parser.values("optional")) << std::endl
+ << "flag: " << std::boolalpha << parser.value("flag") << std::endl;
return 0;
}
+```
+
+### Basic Argument Parsing Rules
-// compiled with:
-// g++ -o power power.cpp -I -std=c++20
+#### 1. Optional arguments are parsed only with a flag
+
+An optional argument is recognized only when its primary or secondary flag appears in the command-line input. For example:
+
+```cpp
+parser.add_optional_argument("optional", "o");
```
-### Argument Parsing Rules
+Here, the argument is parsed only if either `--optional` (primary flag) or `-o` (secondary flag) is present. If neither flag is given, the argument is ignored.
-- Positional arguments are parsed first, in the order they were defined in and without a flag.
+> [!IMPORTANT]
+>
+> The parser will try to assign the values following such flag to the specified argument until:
+>
+> - A different argument flag is encountered:
+>
+> ```cpp
+> // program.cpp
+> parser.add_optional_argument("first", "f");
+> parser.add_optional_argument("second", "s");
+>
+> parser.try_parse_args(argc, argv);
+>
+> std::cout << "first: " << ap::util::join(parser.values("first")) << std::endl
+> << "second: " << ap::util::join(parser.values("second")) << std::endl;
+>
+> /* Example execution:
+> > ./program --first value1 value2 --second value3 value4
+> first: value1, value2
+> second: value3, value4
+> ```
+>
+> - The upper bound of the argument's [nargs](#1-nargs---sets-the-allowed-number-of-values-to-be-parsed-for-an-argument-this-can-be-set-as-a) parameter is reached:
+>
+> **NOTE:** By default an optional argument accepts an arbitrary number of values (the number of values has no upper bound).
+>
+> ```cpp
+> parser.add_optional_argument("numbers", "n")
+> .nargs(ap::nargs::up_to(3))
+> .help("A list of numbers");
+> ```
+> ```txt
+> > ./program --numbers 1 2 3 4 5
+> [ERROR] : Failed to deduce the argument for values [4, 5]
+> Program: program
+>
+> An example program
+>
+> Optional arguments:
+>
+> --help, -h : Display the help message
+> --numbers, -n : A list of numbers
+> ```
- In the example above the first command-line argument must be the value for the `positional` argument:
+
- ```shell
- ./power 2
- no exponent values given
- ```
+#### 2. Positional arguments are parsed in the order of definition
- ```shell
- ./power
- [ERROR] : No values parsed for a required argument [base]
- Program: power calculator
+Positional arguments are assigned values in the same order they are defined in the program. They are parsed from the command-line input **excluding any values that have already been consumed by optional arguments**. This means positional arguments no longer need to appear at the beginning of the argument list.
- Calculates the value of an expression: base ^ exponent
+For example:
- Positional arguments:
+```cpp
+parser.add_positional_argument("positional1");
+parser.add_positional_argument("positional2");
- base : the exponentation base value
+parser.try_parse_args(argc, argv);
- Optional arguments:
+std::cout << "positional1: " << parser.value("positional1") << std::endl
+ << "positional2: " << parser.value("positional2") << std::endl;
- --exponent, -e : the exponent value
- --help, -h : Display the help message
- ```
+/* Example execution:
+> ./program value1 value2
+positional1: value1
+positional2: value2
+```
> [!IMPORTANT]
>
-> For each positional argument there must be **exactly one value**.
+> - All positional arguments expect **at most one value**.
+> - A positional argument's value doesn't have to be preset in the command-line only if the argument is defined as **not** [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).
-- Optional arguments are parsed only with a flag. The values passed after an argument flag will be treated as the values of the last optional argument that preceeds them. If no argument flag preceeds a value argument, then it will be treated as an **unknown** value.
+
- ```shell
- ./power 2 --exponent 1 2 3 # equivalent to: ./power 2 -e 1 2 3
- 2 ^ 1 = 2
- 2 ^ 2 = 4
- 2 ^ 3 = 8
- ```
+#### 3. Positional arguments consume free values
- You can use the flag for each command-line value:
+A positional argument consumes only those values that cannot be assigned to optional arguments. This allows positional arguments to appear after optional arguments in the command-line input.
- ```shell
- ./power 2 -e 1 -e 2 -e 3
- ```
+```cpp
+parser.add_positional_argument("positional1");
+parser.add_positional_argument("positional2");
+parser.add_optional_argument("optional").nargs(1); // limit the number of arguments
- Not using a flag will result in an error:
+parser.try_parse_args(argc, argv);
+
+std::cout << "positional1: " << parser.value("positional1") << std::endl
+ << "positional2: " << parser.value("positional2") << std::endl
+ << "optional: " << parser.value("optional") << std::endl;
+
+/* Example executions:
+> ./program pos1-value pos2-value --optional opt-value
+positional1: pos1-value
+positional2: pos2-value
+optional: opt-value
+
+> ./program --optional opt-value pos1-value pos2-value
+positional1: pos1-value
+positional2: pos2-value
+optional: opt-value
+
+> ./program pos1-value --optional opt-value pos2-value
+positional1: pos1-value
+positional2: pos2-value
+optional: opt-value
+```
- ```shell
- ./power 2 1 2 3
- [ERROR] : Failed to deduce the argument for values [1, 2, 3]
- Program: power calculator
+> [!TIP]
+>
+> Because of the optional arguments accept an arbitrary number of arguments by default, it is a good practice to set the [nargs](#1-nargs---sets-the-allowed-number-of-values-to-be-parsed-for-an-argument-this-can-be-set-as-a) parameter for optional arguments (where it makes sense).
- Calculates the value of an expression: base ^ exponent
+
- Positional arguments:
+#### 4. Unknown Argument Flag Handling
- base : the exponentation base value
+A command-line argument beginning with a flag prefix (`--` or `-`) that doesn't match any of the specified optional arguments or a compound of optional arguments (only for short flags) is considered **unknown** or **unrecognized**.
- Optional arguments:
+By default an argument parser will throw an exception if an unkown argument flag is encountered.
- --exponent, -e : the exponent value
- --help, -h : Display the help message
- ```
+This behavior can be modified using the `unknown_arguments_policy` method of the `argument_parser` class, which sets the policy for handling unknown argument flags.
-> [!WARNING]
->
-> If an optional argument has the `nargs` parameter set with an upper bound, then the values that succeed this argument's flag will be assigned to this argument only until the specified upper bound is reached. Further values will be treated as **unknown** values.
+
+**Example:**
+
+```cpp
+#include
+
+int main(int argc, char* argv[]) {
+ ap::argument_parser parser("unknown-policy-test");
+
+ parser.program_description("A simple test program for unknwon argument handling policies")
+ .default_arguments(ap::default_argument::o_help)
+ // set the unknown argument flags handling policy
+ .unknown_arguments_policy(ap::unknown_policy::);
+
+ parser.add_optional_argument("known", "k")
+ .help("A known optional argument");
+
+ parser.try_parse_args(argc, argv);
+
+ std::cout << "known = " << ap::util::join(parser.values("known")) << std::endl;
+
+ return 0;
+}
+```
+
+The available policies are:
+- `ap::unknown_policy::fail` (default) - throws an exception if an unknown argument flag is encountered:
+
+ ```txt
+ > ./unknown-policy-test --known --unknown
+ [ap::error] Unknown argument [--unknown].
+ Program: unknown-policy-test
+
+ A simple test program for unknwon argument handling policies
+
+ Optional arguments:
+
+ --help, -h : Display the help message
+ --known, -k : A known optional argument
+ ```
+
+- `ap::unknown_policy::warn` - prints a warning message to the standard error stream and continues parsing the remaining arguments:
+
+ ```txt
+ > ./unknown-policy-test --known --unknown
+ [ap::warning] Unknown argument '--unknown' will be ignored.
+ known =
+ ```
+
+- `ap::unknown_policy::ignore` - ignores unknown argument flags and continues parsing the remaining arguments:
+
+ ```txt
+ ./unknown-policy-test --known --unknown
+ known =
+ ```
+
+- `ap::unknown_policy::as_values` - treats unknown argument flags as values:
+
+ ```txt
+ > ./unknown-policy-test --known --unknown
+ known = --unknown
+ ```
+
+> [!IMPORTANT]
>
-> **Example:**
+> - The unkown argument flags handling polciy only affects the parser's behaviour when calling the `parse_args` or `try_parse_args` methods.
+> - When parsing known args with `parse_known_args` or `try_parse_known_args` all unknown arguments (flags and values) are collected and returned as the parsing result, ignoring the specified policy for handling unknown arguments.
>
+> Consider a similar example as above with only the argument parsing function changed:
> ```cpp
-> parser.add_optional_argument("exponent", "e").nargs(ap::nargs::up_to(3))
+> const auto unknown_args = parser.try_parse_known_args(argc, argv);
+> std::cout << "known = " << ap::util::join(parser.values("known")) << std::endl
+> << "unknown = " << ap::util::join(unknown_args) << std::endl;
> ```
+> This would produce the following output regardless of the specified unknown arguments policy.
> ```shell
-> ./power 2 -e 1 2 3 4 5
-> [ERROR] : Failed to deduce the argument for values [4, 5]
-> Program: power calculator
->
-> Calculates the value of an expression: base ^ exponent
->
-> Positional arguments:
->
-> base : the exponentation base value
->
-> Optional arguments:
->
-> --exponent, -e : the exponent value
-> --help, -h : Display the help message
+> > ./test --known --unknown
+> known =
+> unknown = --unknown
> ```
+
### Compound Arguments
@@ -930,7 +1259,7 @@ parser.try_parse_args(argc, argv);
std::cout << "Verbosity level: " << parser.count("verbose")
<< "\nOption used: " << std::boolalpha << parser.value("use-option")
- << "\nNumbers: " << join(parser.values("numbers"), ", ") // join is an imaginary function :)
+ << "\nNumbers: " << ap::util::join(parser.values("numbers"), ", ")
<< std::endl;
/*
@@ -960,9 +1289,9 @@ parser.add_optional_argument("recognized", "r")
.nargs(ap::nargs::up_to(2))
.help("A recognized optional argument");
-parser.try_parse_args(argc, argv);
+parser.parse_args(argc, argv);
-std::cout << "recognized = " << join(parser.values("recognized")) << std::endl;
+std::cout << "recognized = " << ap::util::join(parser.values("recognized")) << std::endl;
/* Example executions:
> ./program --recognized value1 value2
@@ -994,11 +1323,11 @@ parser.add_optional_argument("recognized", "r")
const auto unknown_args = parser.parse_known_args(argc, argv);
-std::cout << "recognized = " << join(parser.values("recognized")) << std::endl
- << "unkown = " << join(unknown_args) << std::endl;
+std::cout << "recognized = " << ap::util::join(parser.values("recognized")) << std::endl
+ << "unkown = " << ap::util::join(unknown_args) << std::endl;
/* Example execution:
-./program value0 --recognized value1 value2 value3 --unrecognized value
+> ./program value0 --recognized value1 value2 value3 --unrecognized value
recognized = value1, value2
unkown = value0, value3, --unrecognized, value
```
@@ -1007,27 +1336,34 @@ Now all the values, that caused an exception for the `parse_args` example, are c
> [!IMPORTANT]
>
-> If a parser encounters an unrecognized argument flag during *known* args parsing, then the flag will be collected and the currently processed optional argument will be reset. That means that any value following an unrecognized flag will be treated as an unknown argument as well. Let's consider an example:
+> If a parser encounters an unrecognized argument flag during *known* args parsing, then the flag will be collected and the currently processed optional argument will be reset. That means that any value following an unrecognized flag will be used to parse positional arguments or treated as an unknown argument as well (if there are no unparsed positional arguments). Let's consider an example:
>
> ```cpp
+> parser.add_positional_argument("positional")
+> .help("A positinal argument");
> parser.add_optional_argument("recognized", "r")
-> .nargs(ap::nargs::any()) // don't restrict the number of arguments
+> .nargs(ap::nargs::any())
> .help("A recognized optional argument");
>
> const auto unknown_args = parser.parse_known_args(argc, argv);
>
-> std::cout << "recognized = " << join(parser.values("recognized")) << std::endl
-> << "unkown = " << join(unknown_args) << std::endl;
+> std::cout << "positional = " << parser.value("positional") << std::endl
+> << "recognized = " << ap::util::join(parser.values("recognized")) << std::endl
+> << "unkown = " << ap::util::join(unknown_args) << std::endl;
>
> /* Example execution:
-> ./program value0 --recognized value1 value2 value3 --unrecognized value --recognized value4
-> recognized = value1, value2, value3, value4
-> unkown = value0, --unrecognized, value
-> ```
+> > ./program --recognized value1 value2 value3 --unrecognized value4 value5 --recognized value6
+> positional = value4
+> recognized = value1, value2, value3, value6
+> unkown = --unrecognized, value5
>
-> Here `value` is treated as an unknown argument even though the `recognized` optional argument still accepts values and only after a different flag is encountered the parser stops collecting the values to the unknown arguments list.
+> > ./program value0 --recognized value1 value2 value3 --unrecognized value4 --recognized value5
+> positional = value0
+> recognized = value1, value2, value3, value5
+> unkown = --unrecognized, value4
+> ```
>
-> **NOTE:** If the `AP_UNKNOWN_FLAGS_AS_VALUES` is set, the unrecognized argument flags will be treated as values during parsing and therefore they **may** not be collected as unknown arguments, depending on the argument's configuration and the command-line argument list.
+> Here `value` is treated either as the `positional` argument's value or as an unknown argument (depending on the input arguments) even though the `recognized` optional argument still accepts values and only after the `--recognized` argument flag is encountered the parser continues collecting values for this argument.
> [!TIP]
>
@@ -1039,31 +1375,191 @@ 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 with:
+You can retrieve the argument's value(s) with:
```cpp
-(const) auto value = parser.value("argument_name"); // (1)
-(const) auto value = parser.value_or("argument_name", fallback_value); // (2)
+(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. This will return the value parsed for the given argument.
+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.
+
+2. Returns the given argument's value or the specified fallback value if the argument has no values.
- For optional arguments this will return the argument's predefined value if no value has been parsed. Additionaly, if more than one value has been parsed for an optional argument, this function will return the first parsed value.
+ - If the argument has a value (parsed or predefind), 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`).
-2. When a value has been parsed for the argument, the behavior is the same as in case **(1)**. Otherwise, this will return `value_type{std::forward(fallback_value)}` (where `U` is the deducted type of `fallback_value`), if:
+3. Returns a vector of the given argument's values.
- - There is no value parsed for a positional argument
- - There is no parsed values and no predefined values for an optional arrument
+ - If the argument has any values (parsed or predefined), they will be returned as a `std::vector`.
+ - If th 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
+
+
+
+
+
+
+
+## Subparsers
+
+Subparsers allow you to build **hierarchical command-line interfaces**, where a top-level parser delegates parsing to its subcommands. This is particularly useful for creating CLI applications like `git`, where commands such as `git add`, `git commit`, and `git push` each have their own arguments.
+
+### Creating Subparsers
+
+```cpp
+auto& subparser = parser.add_subparser("subprogram");
+```
+
+Each subparser is a separate instance of `ap::argument_parser` and therefore it can have it can have its own parameters, including a description, arguments, argument groups, subparsers, etc.
+
+For example:
-Additionally for optional arguments, you can use:
+```cpp
+// top-level parser
+ap::argument_parser git("ap-git");
+git.program_version({.major = 2u, .minor = 43u, .patch = 0u})
+ .program_description("A version control system built with CPP-AP")
+ .default_arguments(ap::default_argument::o_help, ap::default_argument::o_version);
+
+// subcommand: status
+auto& status = git.add_subparser("status");
+status.default_arguments(ap::default_argument::o_help)
+ .program_description("Show the working tree status");
+status.add_flag("short", "s")
+ .help("Give the output in the short-format");
+```
+
+This defines `git` and `git status` parsers, each with their own sets of arguments.
+
+### Using Multiple Subparsers
+
+You can add as many subparsers as you like, each corresponding to a different command:
+
+```cpp
+auto& init = git.add_subparser("init");
+init.program_description("Create an empty Git repository or reinitialize an existing one");
+
+auto& add = git.add_subparser("add");
+add.program_description("Add file contents to the index");
+
+auto& commit = git.add_subparser("commit");
+commit.program_description("Record changes to the repository");
+
+auto& status = git.add_subparser("status");
+status.program_description("Show the working tree status");
+
+auto& push = git.add_subparser("push");
+push.program_description("Update remote refs along with associated objects");
+```
+
+All defined subparsers will be included in the parent parser's help message:
+
+```txt
+> ap-git --help
+Program: ap-git (v2.43.0)
+
+ A version control system built with CPP-AP
+
+Commands:
+
+ init : Create an empty Git repository or reinitialize an existing one
+ add : Add file contents to the index
+ commit : Record changes to the repository
+ status : Show the working tree status
+ push : Update remote refs along with associated objects
+
+Optional Arguments:
+
+ --help, -h : Display the help message
+ --version, -v : Dsiplay program version info
+```
+
+### Parsing Arguments with Subparsers
+
+When parsing command-line arguments, the parent parser will attempt to match the **first command-line token** against the name of one of its subparsers.
+
+- If a match is found, the parser delegates the remaining arguments to the matched subparser.
+- This process repeats **recursively**, so each subparser may also match one of its own subparsers.
+- Parsing stops when no subparser matches the first token of the *current* argument list. At that point, the parser processes its own arguments.
+
+For example:
```cpp
-(const) std::vector values = parser.values("argument_name");
+ap::argument_parser git("ap-git");
+auto& submodule = git.add_subparser("submodule");
+auto& submodule_init = submodule.add_subparser("init");
```
-which returns a `vector` containing all values parsed for the given argument.
+Running `ap-git submodule init ` will result in `` being parsed by the `submodule_init` parser.
+
+### Tracking Parser State
+
+Each parser tracks its state during parsing. The methods described below let you inspect this state:
+
+- `invoked() -> bool` : Returns `true` if the parser’s name appeared on the command line.
+
+ A parser is *invoked* as soon as the parser is selected during parsing, even if parsing is later delegated to one of its subparsers.
+
+- `finalized() -> bool` : Returns `true` if the parser has processed its own arguments.
+
+ This is distinct from `invoked()`: a parser can be invoked but not finalized if one of its subparsers handled the arguments instead.
+
+- `resolved_parser() -> ap::argument_parser&` : Returns a reference to the *deepest invoked parser*.
+
+ sIf no subparser was invoked, this simply returns the current parser.
+
+
+
+#### Example: Inspecting Parsing States
+
+```cpp
+// define the parser hierarchy
+ap::argument_parser git("ap-git");
+auto& submodule = git.add_subparser("submodule");
+auto& submodule_init = submodule.add_subparser("init");
+
+// parse arguments
+git.try_parse_args(argc, argv);
+
+// print state for each parser
+std::cout << std::boolalpha;
+
+std::cout << "git : invoked=" << git.invoked()
+ << ", finalized=" << git.finalized() << '\n';
+
+std::cout << "submodule : invoked=" << submodule.invoked()
+ << ", finalized=" << submodule.finalized() << '\n';
+
+std::cout << "submodule_init : invoked=" << submodule_init.invoked()
+ << ", finalized=" << submodule_init.finalized() << '\n';
+
+auto& active = git.resolved_parser();
+std::cout << "\nResolved parser : " << active.name() << " (" << active.program_name() << ")\n";
+```
+
+If you run: `./ap-git submodule intit`, you will get the following state:
+
+```txt
+git : invoked=true, finalized=false
+submodule : invoked=true, finalized=false
+submodule_init : invoked=true, finalized=true
+
+Resolved parser : init (ap-git submodule init)
+```
@@ -1071,4 +1567,31 @@ which returns a `vector` containing all values parsed for the given argument.
## Examples
-The library usage examples / demo projects can be found in the [cpp-ap-demo](https://github.com/SpectraL519/cpp-ap-demo) repository.
+The library usage examples and demo projects are included in the `cpp-ap-demo` submodule.
+To fetch the submodule content after cloning the main repository, run:
+
+```bash
+git submodule update --init --recursive
+```
+
+For more detailed information about the demo projects, see the [cpp-ap-demo](https://github.com/SpectraL519/cpp-ap-demo) README.
+
+The following table lists the projects provided in the `cpp-ap-demo` submodule:
+
+| Project | Description |
+| :- | :- |
+| [Power Calculator](https://github.com/SpectraL519/cpp-ap-demo/tree/master/power_calculator/) | Calculates the value of a $b^e$ expression for the given base and exponents.
**Demonstrates:** The basic usage of positional and optional arguments. |
+| [File Merger](https://github.com/SpectraL519/cpp-ap-demo/tree/master/file_merger/) | Merges multiple text files into a single output file.
**Demonstrates:** The usage of default arguments. |
+| [Numbers Converter](https://github.com/SpectraL519/cpp-ap-demo/tree/master/numbers_converter/) | Converts numbers between different bases.
**Demonstrates:** The usage of argument parameters such as *nargs*, *choices*, and *default values*. |
+| [Verbosity](https://github.com/SpectraL519/cpp-ap-demo/tree/master/verbosity/) | Prints messages with varying levels of verbosity.
**Demonstrates:** The usage of `none_type` arguments and compound argument flags. |
+| [Logging Mode](https://github.com/SpectraL519/cpp-ap-demo/tree/master/logging_mode/) | Logs a message depending on the selected logging mode (`quiet`, `normal`, `verbose`).
**Demonstrates:** The usage of custom argument value types (like enums). |
+| [Message Logger](https://github.com/SpectraL519/cpp-ap-demo/tree/master/message_logger/) | Outputs a message to a file, console, or not at all.
**Demonstrates:** The usage of argument groups. |
+| [AP-GIT](https://github.com/SpectraL519/cpp-ap-demo/tree/master/ap_git/) | A minimal Git CLI clone with subcommands (`init`, `add`, `commit`, `status`, `push`).
**Demonstrates:** The usage of subparsers for multi-command CLIs and complex argument configurations. |
+
+
+
+
+
+## Common Utility
+
+The CPP-AP library provides some additional utility, the descriptions of which can be found on the [Utility topic page](https://spectral519.github.io/cpp-ap/latest/group__util.html).
diff --git a/include/ap/action/predefined_actions.hpp b/include/ap/action/predefined.hpp
similarity index 80%
rename from include/ap/action/predefined_actions.hpp
rename to include/ap/action/predefined.hpp
index 405b4071..ecedc037 100644
--- a/include/ap/action/predefined_actions.hpp
+++ b/include/ap/action/predefined.hpp
@@ -2,12 +2,12 @@
// This file is part of the CPP-AP project (https://github.com/SpectraL519/cpp-ap).
// Licensed under the MIT License. See the LICENSE file in the project root for full license information.
-/// @file predefined_actions.hpp
+/// @file ap/action/predefined.hpp
#pragma once
#include "ap/exceptions.hpp"
-#include "detail/utility.hpp"
+#include "util/helpers.hpp"
#include
#include
@@ -20,12 +20,12 @@ std::ostream& operator<<(std::ostream& os, const argument_parser&) noexcept;
namespace action {
/**
- * @brief Returns an *on-flag* action which prints the argument parser's configuration.
- * @param parser The argument parser the configuration of which will be printed.
+ * @brief Returns an *on-flag* action which prints the argument parser's help message.
+ * @param parser Argument parser instance the help message of which will be printed.
* @param exit_code The exit code with which `std::exit` will be called (if not `std::nullopt`).
- * @param os The output stream to which the configuration will be printed.
+ * @param os The output stream to which the help message will be printed.
*/
-inline typename ap::action_type::on_flag::type print_config(
+inline typename ap::action_type::on_flag::type print_help(
const argument_parser& parser,
const std::optional exit_code = std::nullopt,
std::ostream& os = std::cout
@@ -33,12 +33,12 @@ inline typename ap::action_type::on_flag::type print_config(
return [&parser, &os, exit_code]() {
os << parser << std::endl;
if (exit_code)
- std::exit(exit_code.value());
+ std::exit(*exit_code);
};
}
/// @brief Returns an *observe* action which checks whether lower_bound file with the given name exists.
-inline detail::callable_type check_file_exists() noexcept {
+inline util::callable_type check_file_exists() noexcept {
return [](const std::string& file_path) {
if (not std::filesystem::exists(file_path))
throw std::filesystem::filesystem_error(
@@ -54,8 +54,8 @@ inline detail::callable_type check_file_e
* @tparam T The *arithmetic* value type.
* @param lower_bound The exclusive lower bound to validate against.
*/
-template
-detail::callable_type gt(const T lower_bound) noexcept {
+template
+util::callable_type gt(const T lower_bound) noexcept {
return [lower_bound](const T& value) {
if (not (value > lower_bound))
throw std::out_of_range(
@@ -69,8 +69,8 @@ detail::callable_type gt(const T lower_bound) noexc
* @tparam T The *arithmetic* value type.
* @param lower_bound The inclusive lower bound to validate against.
*/
-template
-detail::callable_type geq(const T lower_bound) noexcept {
+template
+util::callable_type geq(const T lower_bound) noexcept {
return [lower_bound](const T& value) {
if (! (value >= lower_bound))
throw std::out_of_range(
@@ -84,8 +84,8 @@ detail::callable_type geq(const T lower_bound) noex
* @tparam T The *arithmetic* value type.
* @param lower_bound The exclusive upper bound to validate against.
*/
-template
-detail::callable_type lt(const T upper_bound) noexcept {
+template
+util::callable_type lt(const T upper_bound) noexcept {
return [upper_bound](const T& value) {
if (! (value < upper_bound))
throw std::out_of_range(
@@ -99,8 +99,8 @@ detail::callable_type lt(const T upper_bound) noexc
* @tparam T The *arithmetic* value type.
* @param lower_bound The inclusive upper bound to validate against.
*/
-template
-detail::callable_type leq(const T upper_bound) noexcept {
+template
+util::callable_type leq(const T upper_bound) noexcept {
return [upper_bound](const T& value) {
if (! (value <= upper_bound))
throw std::out_of_range(
@@ -120,8 +120,8 @@ detail::callable_type leq(const T upper_bound) noex
* @param lower_bound The lower bound of the interval.
* @param upper_bound The upper bound of the interval.
*/
-template
-detail::callable_type within(
+template
+util::callable_type within(
const T lower_bound, const T upper_bound
) noexcept {
return [lower_bound, upper_bound](const T& value) {
diff --git a/include/ap/action/specifiers.hpp b/include/ap/action/types.hpp
similarity index 87%
rename from include/ap/action/specifiers.hpp
rename to include/ap/action/types.hpp
index b7079ac2..05ea9820 100644
--- a/include/ap/action/specifiers.hpp
+++ b/include/ap/action/types.hpp
@@ -3,13 +3,13 @@
// Licensed under the MIT License. See the LICENSE file in the project root for full license information.
/**
- * @file specifiers.hpp
+ * @file ap/action/types.hpp
* @brief Defies the action specifier types.
*/
#pragma once
-#include "ap/detail/concepts.hpp"
+#include "ap/util/concepts.hpp"
#include
@@ -22,7 +22,7 @@ namespace ap::action_type {
* performs some logic on it without modifying it.
*/
struct observe {
- template
+ template
using type = std::function;
};
@@ -33,7 +33,7 @@ struct observe {
* returns a new value with which the argument will be initialized.
*/
struct transform {
- template
+ template
using type = std::function;
};
@@ -44,7 +44,7 @@ struct transform {
* already initialized argument.
*/
struct modify {
- template
+ template
using type = std::function;
};
diff --git a/include/ap/action/util/concepts.hpp b/include/ap/action/util/concepts.hpp
new file mode 100644
index 00000000..d6547ffc
--- /dev/null
+++ b/include/ap/action/util/concepts.hpp
@@ -0,0 +1,44 @@
+// Copyright (c) 2023-2025 Jakub Musiał
+// This file is part of the CPP-AP project (https://github.com/SpectraL519/cpp-ap).
+// Licensed under the MIT License. See the LICENSE file in the project root for full license information.
+
+/**
+ * @file ap/action/util/concepts.hpp
+ * @brief Defines action-related concepts.
+ */
+
+#pragma once
+
+#include "ap/action/types.hpp"
+
+#include
+#include
+
+namespace ap::action::util {
+
+/**
+ * @brief The concept is satisfied when `AS` is a valid *value* action action specifier.
+ * @tparam AS The action specifier type.
+ * @ingroup util
+ */
+template
+concept c_value_action_specifier =
+ ap::util::c_one_of;
+
+/**
+ * @brief The concept is satisfied when `AS` is a valid *on-flag* action action specifier.
+ * @tparam AS The action specifier type.
+ * @ingroup util
+ */
+template
+concept c_flag_action_specifier = ap::util::c_one_of;
+
+/**
+ * @brief The concept is satisfied when `AS` is a valid action action specifier.
+ * @tparam AS The action specifier type.
+ * @ingroup util
+ */
+template
+concept c_action_specifier = c_value_action_specifier or std::same_as;
+
+} // namespace ap::action::util
diff --git a/include/ap/action/detail/utility.hpp b/include/ap/action/util/helpers.hpp
similarity index 66%
rename from include/ap/action/detail/utility.hpp
rename to include/ap/action/util/helpers.hpp
index 03483363..092fba05 100644
--- a/include/ap/action/detail/utility.hpp
+++ b/include/ap/action/util/helpers.hpp
@@ -3,40 +3,26 @@
// Licensed under the MIT License. See the LICENSE file in the project root for full license information.
/**
- * @file utility.hpp
- * @brief Defines general action-related utility.
+ * @file ap/action/util/helpers.hpp
+ * @brief Defines general action-related helper utility.
*/
#pragma once
-#include "ap/action/specifiers.hpp"
+#include "concepts.hpp"
#include
#include
-namespace ap::action::detail {
-
-/**
- * @brief The concept is satisfied when `AS` is a valid *value* action action specifier.
- * @tparam AS The action specifier type.
- */
-template
-concept c_value_action_specifier =
- ap::detail::c_one_of;
-
-/**
- * @brief The concept is satisfied when `AS` is a valid action action specifier.
- * @tparam AS The action specifier type.
- */
-template
-concept c_action_specifier = c_value_action_specifier or std::same_as;
+namespace ap::action::util {
/// @brief Template argument action callable type alias.
-template
+/// @ingroup util
+template
using callable_type = typename AS::template type;
/// @brief Template argument action callabla variant type alias.
-template
+template
using value_action_variant_type = std::variant<
callable_type,
callable_type,
@@ -45,8 +31,9 @@ using value_action_variant_type = std::variant<
/**
* @brief A visitor structure used to apply *value* actions.
* @tparam T The argument's value type
+ * @ingroup util
*/
-template
+template
struct apply_visitor {
using value_type = T;
@@ -77,4 +64,4 @@ struct apply_visitor {
value_type& value; ///< A reference to the argument's value for which the action will be applied.
};
-} // namespace ap::action::detail
+} // namespace ap::action::util
diff --git a/include/ap/argument.hpp b/include/ap/argument.hpp
new file mode 100644
index 00000000..3ddf8462
--- /dev/null
+++ b/include/ap/argument.hpp
@@ -0,0 +1,778 @@
+// Copyright (c) 2023-2025 Jakub Musiał
+// This file is part of the CPP-AP project (https://github.com/SpectraL519/cpp-ap).
+// Licensed under the MIT License. See the LICENSE file in the project root for full license information.
+
+/// @file ap/argument.hpp
+
+#pragma once
+
+#include "action/predefined.hpp"
+#include "action/util/helpers.hpp"
+#include "detail/argument_base.hpp"
+#include "detail/help_builder.hpp"
+#include "nargs/range.hpp"
+#include "types.hpp"
+#include "util/concepts.hpp"
+#include "util/ranges.hpp"
+
+#ifdef AP_TESTING
+
+namespace ap_testing {
+struct argument_test_fixture;
+} // namespace ap_testing
+
+#endif
+
+namespace ap {
+
+/// @brief A discriminator type used to specify the type of an argument within the @ref ap::argument class.
+enum class argument_type : bool { positional, optional };
+
+/**
+ * @brief Represents a command-line argument, either positional or optional.
+ *
+ * This class defines the behaviour of command-line arguments - both positional and optional,
+ * depending on the given type discriminator.
+ *
+ * @note This class is not intended to be constructed directly, but rather throught the
+ * @note - `add_positional_argument`
+ * @note - `add_optional_argument`
+ * @note - `add_flag`
+ * @note methods of @ref ap::argument_parser.
+ * @attention Some member functions are conditionally enabled/disabled depending on the argument type and value type.
+ *
+ * Example usage:
+ * @code{.cpp}
+ * ap::argument_parser parser;
+ * parser.add_positional_argument("input", "i")
+ * .help("An input file path");
+ * parser.add_optional_argument("output", "o")
+ * .default_values("out.txt")
+ * .help("An output file path");
+ * @endcode
+ *
+ * @tparam ArgT The argument type, either @ref ap::argument_type::positional or @ref ap::argument_type::optional.
+ * @tparam T The value type accepted by the argument (defaults to std::string).
+ */
+template
+class argument : public detail::argument_base {
+public:
+ using value_type = T; ///< The argument's value type alias.
+ using count_type = nargs::count_type; ///< The argument's count type alias.
+
+ static constexpr argument_type type = ArgT; ///< The argument's type discriminator.
+
+ argument() = delete;
+
+ /**
+ * @brief Positional argument constructor.
+ * @param name The name of the positional argument.
+ * @note The constructor is enabled only if `type` is `argument_type::positional`.
+ */
+ argument(const detail::argument_name& name)
+ requires(type == argument_type::positional)
+ : _name(name), _nargs_range(_default_nargs_range_actual), _required(_default_required) {}
+
+ /**
+ * @brief Optional argument constructor.
+ * @param name The name of the optional argument.
+ * @note The constructor is enabled only if `type` is `argument_type::optional`.
+ */
+ argument(const detail::argument_name& name)
+ requires(type == argument_type::optional)
+ : _name(name),
+ _nargs_range(_default_nargs_range_actual),
+ _required(_default_required),
+ _count(0ull) {}
+
+ /// @brief Checks if the argument is positional.
+ /// @return `true` if the argument's `type` is `argument_type::positional`, `false` otherwise.
+ [[nodiscard]] bool is_positional() const noexcept override {
+ return type == argument_type::positional;
+ }
+
+ /// @brief Checks if the argument is optional.
+ /// @return `true` if the argument's `type` is `argument_type::optional`, `false` otherwise.
+ [[nodiscard]] bool is_optional() const noexcept override {
+ return type == argument_type::optional;
+ }
+
+ /// @return Reference the name of the positional argument.
+ [[nodiscard]] const ap::detail::argument_name& name() const noexcept override {
+ return this->_name;
+ }
+
+ /// @return Optional help message for the positional argument.
+ [[nodiscard]] const std::optional& help() const noexcept override {
+ return this->_help_msg;
+ }
+
+ /// @return `true` if the argument is hidden, `false` otherwise
+ [[nodiscard]] bool is_hidden() const noexcept override {
+ return this->_hidden;
+ }
+
+ /// @return `true` if the argument is required, `false` otherwise
+ [[nodiscard]] bool is_required() const noexcept override {
+ return this->_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.
+ [[nodiscard]] bool is_greedy() const noexcept override {
+ return this->_greedy;
+ }
+
+ // attribute setters
+
+ /**
+ * @brief Set the help message for the argument.
+ * @param help_msg The help message to set.
+ * @return Reference to the argument instance.
+ */
+ argument& help(std::string_view help_msg) noexcept {
+ this->_help_msg = help_msg;
+ return *this;
+ }
+
+ /**
+ * @brief Set the `hidden` attribute for the argument.
+ * @param h The attribute value.
+ * @return Reference to the argument instance.
+ */
+ argument& hidden(const bool h = true) noexcept {
+ this->_hidden = h;
+ return *this;
+ }
+
+ /**
+ * @brief Set the `required` attribute of the argument
+ * @param value The attribute value (default: `true`).
+ * @return Reference to the argument instance.
+ * @throws ap::invalid_configuration if the argument is configured to suppress argument/group checks.
+ */
+ 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 suppressing argument checks for other arguments.
+ * @param value The attribute value (default: `true`).
+ * @return Reference to the argument instance.
+ * @throws ap::invalid_configuration if the argument is configured to be required.
+ */
+ 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.
+ * @throws ap::invalid_configuration if the argument is configured to be required.
+ */
+ 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 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 value = true) noexcept
+ requires(not util::c_is_none)
+ {
+ this->_greedy = value;
+ return *this;
+ }
+
+ /**
+ * @brief Set the nargs range for the argument.
+ * @param range The attribute value.
+ * @return Reference to the argument instance.
+ * @note The method is enabled only if `value_type` is not `none_type`.
+ */
+ argument& nargs(const nargs::range& range) noexcept
+ requires(not util::c_is_none)
+ {
+ this->_nargs_range = range;
+ return *this;
+ }
+
+ /**
+ * @brief Set the nargs range for the argument.
+ * @param n The exact bound for the nargs range attribute.
+ * @return Reference to the argument instance.
+ * @note The method is enabled only if `value_type` is not `none_type`.
+ */
+ argument& nargs(const count_type n) noexcept
+ requires(not util::c_is_none)
+ {
+ return this->nargs(nargs::range(n));
+ }
+
+ /**
+ * @brief Set the nargs range for the optional argument.
+ * @param lower The lower bound for the nargs range attribute.
+ * @param upper The upper bound for the nargs range attribute.
+ * @return Reference to the argument instance.
+ * @note The method is enabled only if `value_type` is not `none_type`.
+ */
+ argument& nargs(const count_type lower, const count_type upper) noexcept
+ requires(not util::c_is_none)
+ {
+ return this->nargs(nargs::range(lower, upper));
+ }
+
+ /**
+ * @brief Set the *value* action for the argument.
+ * @tparam AS The action specifier type (see @ref ap/action/type.hpp).
+ * @tparam F The type of the action function.
+ * @param action The action callable.
+ * @return Reference to the argument instance.
+ * @note The method is enabled only if:
+ * @note - `value_type` is not `none_type`.
+ * @note - `AS` is a valid value action specifier: `action_type::observe`, `action_type::transform`, `action_type::modify`.
+ */
+ template
+ argument& action(F&& action) noexcept
+ requires(not util::c_is_none)
+ {
+ using callable_type = action::util::callable_type;
+ this->_value_actions.emplace_back(std::forward(action));
+ return *this;
+ }
+
+ /**
+ * @brief Set the *on-flag* action for the argument.
+ * @tparam AS The action specifier type (see @ref ap/action/types.hpp).
+ * @tparam F The type of the action function.
+ * @param action The action callable.
+ * @return Reference to the argument instance.
+ * @note The method is enabled only for optional arguments and if `AS` is `action_type::on_flag`.
+ */
+ template
+ argument& action(F&& action) noexcept
+ requires(type == argument_type::optional)
+ {
+ this->_flag_actions.emplace_back(std::forward(action));
+ return *this;
+ }
+
+ /**
+ * @brief Add the choices for the argument.
+ * @tparam CR The choices range type.
+ * @param choices The range of valid choices for the argument.
+ * @return Reference to the argument instance.
+ * @note The method is enabled only if:
+ * @note - `value_type` must not be `none_type` and must be equality comparable
+ * @note - `CR` must be a range such that its value type is convertible to the argument's `value_type`
+ */
+ template CR>
+ argument& choices(const CR& choices) noexcept
+ requires(not util::c_is_none and std::equality_comparable)
+ {
+ for (const auto& choice : choices)
+ this->_choices.emplace_back(choice);
+ return *this;
+ }
+
+ /**
+ * @brief Add the choices for the argument.
+ * @param choices The list of valid choices for the argument.
+ * @return Reference to the argument instance.
+ * @note The method is enabled only if `value_type` is not `none_type` and is equality comparable.
+ */
+ argument& choices(std::initializer_list choices) noexcept
+ requires(not util::c_is_none and std::equality_comparable)
+ {
+ return this->choices<>(choices);
+ }
+
+ /**
+ * @brief Add the choices for the argument.
+ * @tparam Args The types of the choices.
+ * @param choices The list of valid choices for the argument.
+ * @return Reference to the argument instance.
+ * @note The method is enabled only if `value_type` is not `none_type` and is equality comparable.
+ */
+ argument& choices(const std::convertible_to auto&... choices) noexcept
+ requires(not util::c_is_none and std::equality_comparable)
+ {
+ (this->_choices.emplace_back(choices), ...);
+ return *this;
+ }
+
+ /**
+ * @brief Add default values for the argument.
+ * @param values The default values to add.
+ * @return Reference to the argument instance.
+ * @attention Setting the default values resets the `required` attribute to `false`.
+ * @note The method is enabled only if `value_type` is not `none_type`.
+ */
+ template CR>
+ argument& default_values(const CR& values) noexcept
+ requires(not util::c_is_none and std::equality_comparable)
+ {
+ for (const auto& value : values)
+ this->_default_values.emplace_back(std::make_any(value));
+ this->_required = false;
+ return *this;
+ }
+
+ /**
+ * @brief Add default values for the argument.
+ * @param values The default values to add.
+ * @return Reference to the argument instance.
+ * @attention Setting the default values resets the `required` attribute to `false`.
+ * @note The method is enabled only if `value_type` is not `none_type`.
+ */
+ argument& default_values(std::initializer_list values) noexcept
+ requires(not util::c_is_none and std::equality_comparable)
+ {
+ return this->default_values<>(values);
+ }
+
+ /**
+ * @brief Add default values for the argument.
+ * @param values The default values to add.
+ * @return Reference to the argument instance.
+ * @attention Setting the default values resets the `required` attribute to `false`.
+ * @note The method is enabled only if `value_type` is not `none_type`.
+ */
+ argument& default_values(const std::convertible_to auto&... values) noexcept
+ requires(not util::c_is_none)
+ {
+ (this->_default_values.emplace_back(std::make_any(values)), ...);
+ this->_required = false;
+ return *this;
+ }
+
+ /**
+ * @brief Add implicit values for the optional argument.
+ * @tparam CR The choices range type.
+ * @param values The range of implicit values to set.
+ * @return Reference to the optional argument instance.
+ * @note The method is enabled only for optional arguments and if `value_type` is not `none_type`.
+ */
+ template CR>
+ argument& implicit_values(const CR& values) noexcept
+ requires(not util::c_is_none and type == argument_type::optional)
+ {
+ for (const auto& value : values)
+ this->_implicit_values.emplace_back(std::make_any(value));
+ return *this;
+ }
+
+ /**
+ * @brief Add implicit values for the optional argument.
+ * @param values The initializer list of implicit values to set.
+ * @return Reference to the optional argument instance.
+ * @note The method is enabled only for optional arguments and if `value_type` is not `none_type`.
+ */
+ argument& implicit_values(std::initializer_list values) noexcept
+ requires(not util::c_is_none and type == argument_type::optional)
+ {
+ return this->implicit_values<>(values);
+ }
+
+ /**
+ * @brief Add a implicit values for the optional argument.
+ * @param values The implicit values to set.
+ * @return Reference to the optional argument instance.
+ * @note The method is enabled only for optional arguments and if `value_type` is not `none_type`.
+ */
+ argument& implicit_values(const std::convertible_to auto&... values) noexcept
+ requires(not util::c_is_none and type == argument_type::optional)
+ {
+ (this->_implicit_values.emplace_back(std::make_any(values)), ...);
+ return *this;
+ }
+
+#ifdef AP_TESTING
+ friend struct ::ap_testing::argument_test_fixture;
+#endif
+
+private:
+ /// @brief The argument's value action type alias.
+ using value_action_type = action::util::value_action_variant_type;
+
+ /// @brief The argument's flag action type alias.
+ using flag_action_type = typename action_type::on_flag::type;
+
+ /// @brief The argument's value-argument-specific type alias.
+ /// @tparam _T The actual type used if the argument's `value_type` is not `none_type`.
+ template
+ using value_arg_specific_type = std::conditional_t<
+ util::c_is_none,
+ none_type,
+ _T>; ///< Type alias for value-argument-specific types.
+
+ /// @brief The argument's positional-argument-specific type alias.
+ /// @tparam _T The actual type used if the argument's `type` is `argument_type::positional`.
+ template
+ using positional_specific_type =
+ std::conditional_t;
+
+ /// @brief The argument's optional-argument-specific type alias.
+ /// @tparam _T The actual type used if the argument's `type` is `argument_type::optional`.
+ template
+ using optional_specific_type =
+ std::conditional_t;
+
+ /**
+ * @brief Creates a help message builder object for the argument.
+ * @param verbose The verbosity mode value.
+ * @note If the `verbose` parameter is set to `true` all non-default parameters will be included in the output,
+ * @note otherwise only the argument's name and help message will be included.
+ */
+ [[nodiscard]] detail::help_builder help_builder(const bool verbose) const noexcept override {
+ detail::help_builder bld(this->_name.str(), this->_help_msg);
+
+ if (not verbose)
+ return bld;
+
+ bld.params.reserve(6ull);
+ if (this->_required != _default_required)
+ bld.add_param("required", std::format("{}", this->_required));
+ 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) {
+ if (not this->_choices.empty())
+ bld.add_range_param("choices", this->_choices);
+ if (not this->_default_values.empty())
+ bld.add_range_param(
+ "default value(s)", util::any_range_cast_view(this->_default_values)
+ );
+ if constexpr (type == argument_type::optional) {
+ if (not this->_implicit_values.empty())
+ bld.add_range_param(
+ "implicit value(s)",
+ util::any_range_cast_view(this->_implicit_values)
+ );
+ }
+ }
+
+ return bld;
+ }
+
+ /// @brief Mark the optional argument as used.
+ /// @return `true` if the argument accepts further values, `false` otherwise.
+ bool mark_used() override {
+ if constexpr (type == argument_type::optional) {
+ ++this->_count;
+ for (const auto& action : this->_flag_actions)
+ action();
+ }
+
+ 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.
+ * @return `true` if the argument accepts further values, `false` otherwise.
+ * @throws ap::parsing_failure
+ */
+ bool set_value(const std::string& str_value) override {
+ 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();
+ }
+
+ /// @return `true` if the argument has predefined values, `false` otherwise.
+ [[nodiscard]] bool has_predefined_values() const noexcept override {
+ return this->_has_predefined_values_impl();
+ }
+
+ /// @return The ordering relationship of the argument's values and its nargs range attribute.
+ [[nodiscard]] std::weak_ordering nvalues_ordering() const noexcept override {
+ if (this->_values.empty() and this->_has_predefined_values_impl())
+ return std::weak_ordering::equivalent;
+
+ 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]] const std::any& value() const override {
+ if (this->has_parsed_values())
+ return this->_values.front();
+
+ if constexpr (util::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
+ requires(util::c_is_none)
+ {
+ return this->_values;
+ }
+
+ /**
+ * @return Reference to the vector of parsed values for the argument.
+ * @note If no parsed values are available, the method attempts to return the predefined values (default/implicit).
+ * @note The method is enabled only if `value_type` is not `none_type`.
+ */
+ [[nodiscard]] const std::vector& _values_impl() const noexcept
+ requires(not util::c_is_none)
+ {
+ if (this->has_parsed_values())
+ return this->_values;
+
+ try {
+ return this->_predefined_values();
+ }
+ catch (const std::logic_error&) {
+ return this->_values; // fallback: empty vector
+ }
+ }
+
+ /// @return `true` if the argument has a predefined value, `false` otherwise.
+ [[nodiscard]] bool _has_predefined_values_impl() const noexcept
+ requires(util::c_is_none)
+ {
+ return false;
+ }
+
+ /**
+ * @return `true` if the argument has a predefined value, `false` otherwise.
+ * @note The method is enabled only if `value_type` is not `none_type`.
+ * @note - For positional arguments, a predefined value exists if a default value is set.
+ * @note - For optional arguments, a predefined value exists if either a default value is set or if the argument has been used and an implicit value is set.
+ */
+ [[nodiscard]] bool _has_predefined_values_impl() const noexcept
+ requires(not util::c_is_none)
+ {
+ if constexpr (type == argument_type::positional)
+ return not this->_default_values.empty();
+ else
+ return not this->_default_values.empty()
+ or (this->is_used() and not this->_implicit_values.empty());
+ }
+
+ /**
+ * @return Reference to the argument's predefined value list.
+ * @throws std::logic_error if no predefined values are available.
+ * @note The method is enabled only if `value_type` is not `none_type`.
+ * @note - For positional arguments, the default value list is returned.
+ * @note - For optional arguments, if the argument has been used, the implicit value list is returned, otherwise the default value list is returned.
+ */
+ [[nodiscard]] const std::vector& _predefined_values() const
+ requires(not util::c_is_none)
+ {
+ if constexpr (type == argument_type::optional) {
+ if (this->is_used()) {
+ if (this->_implicit_values.empty())
+ throw(std::logic_error(std::format(
+ "No implicit values specified for argument '{}'.", this->_name.str()
+ )));
+
+ return this->_implicit_values;
+ }
+ }
+
+ if (this->_default_values.empty())
+ throw(std::logic_error(
+ std::format("No default values specified for argument '{}'.", this->_name.str())
+ ));
+
+ return this->_default_values;
+ }
+
+ /// @return `true` if the argument accepts further values, `false` otherwise.
+ [[nodiscard]] bool _accepts_further_values() const noexcept {
+ return not std::is_gt(this->_values.size() + 1ull <=> this->_nargs_range);
+ }
+
+ /// @return `true` if the given value is a valid choice for the argument, `false` otherwise.
+ /// @todo Use std::ranges::contains after the switch to C++23
+ [[nodiscard]] bool _is_valid_choice(const value_type& value) const noexcept
+ requires(not util::c_is_none)
+ {
+ return this->_choices.empty()
+ or std::ranges::find(this->_choices, value) != this->_choices.end();
+ }
+
+ /**
+ * @brief The implementation of the `set_value` method for none-type arguments.
+ * @param str_value The string value to set.
+ * @throws ap::parsing_failure
+ * @attention Always throws! (`set_value` should never be called for a none-type argument).
+ */
+ bool _set_value_impl(const std::string& str_value)
+ requires(util::c_is_none)
+ {
+ throw parsing_failure(std::format(
+ "Cannot set values for a none-type argument '{}' (value: '{}')",
+ this->_name.str(),
+ str_value
+ ));
+ }
+
+ /**
+ * @brief The implementation of the `set_value` method for non-none-type arguments.
+ * @return `true` if the argument accepts further values, `false` otherwise.
+ * @param str_value The string value to set.
+ * @throws ap::parsing_failure if:
+ * @throws - the argument does not accept further values (nargs limit exceeded).
+ * @throws - the value cannot be parsed to the argument's `value_type`.
+ * @throws - the value is not a valid choice for the argument (if choices are defined).
+ * @note The method is enabled only if `value_type` is not `none_type`.
+ */
+ bool _set_value_impl(const std::string& str_value)
+ requires(not util::c_is_none)
+ {
+ if (not this->_accepts_further_values())
+ throw parsing_failure::invalid_nvalues(this->_name, std::weak_ordering::greater);
+
+ value_type value;
+ if constexpr (util::c_trivially_readable) {
+ value = value_type(str_value);
+ }
+ else {
+ if (not (std::istringstream(str_value) >> 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(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)
+ std::visit(apply_visitor, action);
+
+ this->_values.emplace_back(std::move(value));
+ return this->_accepts_further_values();
+ }
+
+ // attributes
+ const ap::detail::argument_name _name; ///< The argument's name.
+ std::optional _help_msg; ///< The argument's help message.
+ nargs::range _nargs_range; ///< The argument's nargs range attribute value.
+ [[no_unique_address]] value_arg_specific_type>
+ _default_values; ///< The argument's default value list.
+ [[no_unique_address]] value_arg_specific_type>>
+ _implicit_values; ///< The optional argument's implicit value list.
+ [[no_unique_address]] value_arg_specific_type>
+ _choices; ///< The argument's valid choices collection.
+ [[no_unique_address]] optional_specific_type>
+ _flag_actions; ///< The optional argument's flag actions collection.
+ [[no_unique_address]] value_arg_specific_type>
+ _value_actions; ///< The argument's value actions collection.
+
+ bool _required : 1; ///< The argument's `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.
+
+ // parsing result
+ [[no_unique_address]] optional_specific_type
+ _count; ///< The argument's value count.
+ std::vector _values; ///< The argument's parsed values.
+
+ // default attribute values
+ static constexpr bool _default_required = (type == argument_type::positional);
+ static constexpr nargs::range _default_nargs_range =
+ (type == argument_type::positional) ? nargs::range(1ull) : nargs::any();
+ static constexpr nargs::range _default_nargs_range_actual =
+ util::c_is_none ? nargs::range(0ull) : _default_nargs_range;
+};
+
+/**
+ * @brief Positional argument alias.
+ * @tparam T The value type accepted by the argument (defaults to std::string).
+ * @see ap::argument
+ */
+template
+using positional_argument = argument;
+
+/**
+ * @brief Optional argument alias.
+ * @tparam T The value type accepted by the argument (defaults to std::string).
+ * @see ap::argument
+ */
+template
+using optional_argument = argument;
+
+} // namespace ap
diff --git a/include/ap/argument/default.hpp b/include/ap/argument/default.hpp
deleted file mode 100644
index cc6a4cc8..00000000
--- a/include/ap/argument/default.hpp
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2023-2025 Jakub Musiał
-// This file is part of the CPP-AP project (https://github.com/SpectraL519/cpp-ap).
-// Licensed under the MIT License. See the LICENSE file in the project root for full license information.
-
-/**
- * @file default.hpp
- * @brief Defines the default argument discriminator types.
- */
-
-#pragma once
-
-#include
-
-namespace ap::argument {
-
-/// @brief Enum class representing positional arguments.
-enum class default_positional : uint8_t { input, output };
-
-/// @brief Enum class representing optional arguments.
-enum class default_optional : uint8_t { help, input, output, multi_input, multi_output };
-
-} // namespace ap::argument
diff --git a/include/ap/argument/optional.hpp b/include/ap/argument/optional.hpp
deleted file mode 100644
index 006de541..00000000
--- a/include/ap/argument/optional.hpp
+++ /dev/null
@@ -1,366 +0,0 @@
-// Copyright (c) 2023-2025 Jakub Musiał
-// This file is part of the CPP-AP project (https://github.com/SpectraL519/cpp-ap).
-// Licensed under the MIT License. See the LICENSE file in the project root for full license information.
-
-/// @file optional.hpp
-
-#pragma once
-
-#include "ap/action/detail/utility.hpp"
-#include "ap/action/predefined_actions.hpp"
-#include "ap/detail/argument_base.hpp"
-#include "ap/detail/argument_descriptor.hpp"
-#include "ap/detail/concepts.hpp"
-#include "ap/nargs/range.hpp"
-
-#ifdef AP_TESTING
-
-namespace ap_testing {
-struct optional_argument_test_fixture;
-} // namespace ap_testing
-
-#endif
-
-namespace ap::argument {
-
-/**
- * @brief The optioanl argument class.
- * @tparam T The argument's value type.
- */
-template
-class optional : public detail::argument_base {
-public:
- using value_type = T; ///< The argument's value type.
- using count_type = nargs::range::count_type; ///< The argument's value count type.
-
- optional() = delete;
-
- /**
- * @brief Constructor for optional argument with the `name` identifier.
- * @param name The `name` identifier of the optional argument.
- */
- optional(const detail::argument_name& name) : argument_base(name) {}
-
- ~optional() = default;
-
- /**
- * @brief Equality comparison operator for optional argument.
- * @param other The optional argument to compare with.
- * @return Equality of comparison.
- */
- bool operator==(const optional& other) const noexcept {
- return this->_name == other._name;
- }
-
- /**
- * @brief Set the help message for the optional argument.
- * @param help_msg The help message to set.
- * @return Reference to the optional argument.
- */
- optional& help(std::string_view help_msg) noexcept {
- this->_help_msg = help_msg;
- return *this;
- }
-
- /**
- * @brief Set the `hidden` attribute for the positional argument.
- * @param h The attribute value.
- * @return Reference to the positional argument.
- */
- optional& hidden(const bool h = true) noexcept {
- this->_hidden = h;
- return *this;
- }
-
- /**
- * @brief Set the `required` attribute of the optional argument
- * @param r The attribute value.
- * @return Reference to the optional argument.
- * @attention Setting the `required` attribute to true disables the `bypass_required` attribute.
- */
- optional& required(const bool r = true) noexcept {
- this->_required = r;
- if (this->_required)
- this->_bypass_required = false;
- return *this;
- }
-
- /**
- * @brief Enable/disable bypassing the `required` attribute for the optional argument.
- * @param br The attribute value.
- * @return Reference to the optional argument.
- * @attention Setting the `bypass_required` option to true disables the `required` attribute.
- */
- optional& bypass_required(const bool br = true) noexcept {
- this->_bypass_required = br;
- if (this->_bypass_required)
- this->_required = false;
- return *this;
- }
-
- /**
- * @brief Set the nargs range for the optional argument.
- * @param range The nargs range to set.
- * @return Reference to the optional argument.
- */
- optional& nargs(const nargs::range& range) noexcept {
- this->_nargs_range = range;
- return *this;
- }
-
- /**
- * @brief Set the nargs range for the optional argument.
- * @param n The exact bound for nargs range.
- * @return Reference to the optional argument.
- */
- optional& nargs(const count_type n) noexcept {
- this->_nargs_range = nargs::range(n);
- return *this;
- }
-
- /**
- * @brief Set the nargs range for the optional argument.
- * @param lower_bound The lower bound for nargs range.
- * @param upper_bound The upper bound for nargs range.
- * @return Reference to the optional argument.
- */
- optional& nargs(const count_type lower_bound, const count_type upper_bound) noexcept {
- this->_nargs_range = nargs::range(lower_bound, upper_bound);
- return *this;
- }
-
- /**
- * @brief Set the action for the optional argument.
- * @tparam AS The action specifier type (see @ref ap/action/specifiers.hpp).
- * @tparam F The type of the action function.
- * @param action The action function to set.
- * @return Reference to the optional argument.
- */
- template
- optional& action(F&& action) noexcept {
- if constexpr (action::detail::c_value_action_specifier) {
- using callable_type = action::detail::callable_type;
- this->_value_actions.emplace_back(std::forward(action));
- }
- else {
- this->_flag_actions.emplace_back(std::forward(action));
- }
-
- return *this;
- }
-
- /**
- * @brief Set the choices for the optional argument.
- * @tparam CR The choices range type.
- * @param choices The range of valid choices for the argument.
- * @return Reference to the optional argument.
- * @note `value_type` must be equality comparable.
- * @note `CR` must be a range such that its value type is convertible to `value_type`.
- */
- template CR>
- optional& choices(const CR& choices) noexcept
- requires(std::equality_comparable)
- {
- for (const auto& choice : choices)
- this->_choices.emplace_back(choice);
- return *this;
- }
-
- /**
- * @brief Set the choices for the optional argument.
- * @param choices The list of valid choices for the argument.
- * @return Reference to the optional argument.
- * @note `value_type` must be equality comparable.
- */
- optional& choices(std::initializer_list choices) noexcept
- requires(std::equality_comparable)
- {
- return this->choices<>(choices);
- }
-
- /**
- * @brief Set the default value for the optional argument.
- * @param default_value The default value to set.
- * @return Reference to the optional argument.
- * @attention Setting the default value disables the `required` attribute.
- */
- optional& default_value(const std::convertible_to auto& default_value) noexcept {
- this->_default_value = std::make_any(default_value);
- this->_required = false;
- return *this;
- }
-
- /**
- * @brief Set the implicit value for the optional argument.
- * @param implicit_value The implicit value to set.
- * @return Reference to the optional argument.
- */
- optional& implicit_value(const std::convertible_to auto& implicit_value) noexcept {
- this->_implicit_value = std::make_any(implicit_value);
- return *this;
- }
-
- /// @brief Friend class declaration for access by argument_parser.
- friend class ::ap::argument_parser;
-
-#ifdef AP_TESTING
- /**
- * @brief Friend struct declaration for testing purposes.
- */
- friend struct ::ap_testing::optional_argument_test_fixture;
-#endif
-
-private:
- using value_action_type =
- action::detail::value_action_variant_type; ///< The argument's value action type.
- using flag_action_type = typename action_type::on_flag::type;
-
- /**
- * @param verbose The verbosity mode value.
- * @return An argument descriptor object for the argument.
- */
- [[nodiscard]] detail::argument_descriptor desc(const bool verbose) const noexcept override {
- detail::argument_descriptor desc(this->_name.str(), this->_help_msg);
-
- if (not verbose)
- return desc;
-
- desc.params.reserve(6);
- if (this->_required)
- desc.add_param("required", "true");
- if (this->bypass_required_enabled())
- desc.add_param("bypass required", "true");
- if (this->_nargs_range.is_bound())
- desc.add_param("nargs", this->_nargs_range);
- if constexpr (detail::c_writable) {
- if (not this->_choices.empty())
- desc.add_range_param("choices", this->_choices);
- if (this->_default_value.has_value())
- desc.add_param("default value", std::any_cast(this->_default_value));
- if (this->_implicit_value.has_value())
- desc.add_param("implicit value", std::any_cast(this->_implicit_value));
- }
-
- return desc;
- }
-
- /// @brief Mark the optional argument as used.
- bool mark_used() override {
- ++this->_count;
- for (const auto& action : this->_flag_actions)
- action();
- return this->_accepts_further_values();
- }
-
- /// @return True if the optional argument is used, false otherwise.
- [[nodiscard]] bool is_used() const noexcept override {
- return this->_count > 0;
- }
-
- /// @return The number of times the optional argument attribute has been used.
- [[nodiscard]] std::size_t count() const noexcept override {
- return this->_count;
- }
-
- /**
- * @brief Set the value for the optional argument.
- * @param str_value The string value to set.
- * @return Reference to the optional argument.
- * @throws ap::parsing_failure
- */
- bool set_value(const std::string& str_value) override {
- if (not this->_accepts_further_values())
- throw parsing_failure::invalid_nvalues(this->_name, std::weak_ordering::greater);
-
- value_type value;
- if constexpr (detail::c_trivially_readable) {
- value = value_type(str_value);
- }
- else {
- if (not (std::istringstream(str_value) >> value))
- throw parsing_failure::invalid_value(this->_name, str_value);
- }
-
- if (not detail::is_valid_choice(value, this->_choices))
- throw parsing_failure::invalid_choice(this->_name, str_value);
-
- const auto apply_visitor = action::detail::apply_visitor{value};
- for (const auto& action : this->_value_actions)
- std::visit(apply_visitor, action);
-
- this->_values.emplace_back(std::move(value));
- return this->_accepts_further_values();
- }
-
- /// @return True if the optional argument has a value, false otherwise.
- [[nodiscard]] bool has_value() const noexcept override {
- return this->has_parsed_values() or this->_has_predefined_value();
- }
-
- /// @return True if parsed values are available for the optional argument, false otherwise.
- [[nodiscard]] bool has_parsed_values() const noexcept override {
- return not this->_values.empty();
- }
-
- /// @return ordering relationship of optional argument range.
- [[nodiscard]] std::weak_ordering nvalues_ordering() const noexcept override {
- if (this->_values.empty() and this->_has_predefined_value())
- return std::weak_ordering::equivalent;
-
- return this->_nargs_range.ordering(this->_values.size());
- }
-
- /// @return Reference to the stored value of the optional argument.
- [[nodiscard]] const std::any& value() const override {
- return this->_values.empty() ? this->_predefined_value() : this->_values.front();
- }
-
- /// @return Reference to the vector of parsed values for the optional argument.
- [[nodiscard]] const std::vector& values() const override {
- return this->_values;
- }
-
- /// @return True if the optional argument has a predefined value, false otherwise.
- [[nodiscard]] bool _has_predefined_value() const noexcept {
- return this->_default_value.has_value()
- or (this->is_used() and this->_implicit_value.has_value());
- }
-
- /**
- * @return Reference to the predefined value of the optional argument.
- * @throws std::logic_error
- */
- [[nodiscard]] const std::any& _predefined_value() const {
- if (this->is_used()) {
- if (not this->_implicit_value.has_value())
- throw(std::logic_error(
- std::format("No implicit value specified for argument `{}`.", this->_name.str())
- ));
-
- return this->_implicit_value;
- }
-
- if (not this->_default_value.has_value())
- throw(std::logic_error(
- std::format("No default value specified for argument `{}`.", this->_name.str())
- ));
-
- return this->_default_value;
- }
-
- [[nodiscard]] bool _accepts_further_values() const noexcept {
- return not std::is_gt(this->_nargs_range.ordering(this->_values.size() + 1ull));
- }
-
- nargs::range _nargs_range = nargs::any();
- std::any _default_value;
- std::any _implicit_value;
- std::vector _choices;
- std::vector _flag_actions;
- std::vector _value_actions;
-
- std::size_t _count = 0ull;
- std::vector _values;
-};
-
-} // namespace ap::argument
diff --git a/include/ap/argument/positional.hpp b/include/ap/argument/positional.hpp
deleted file mode 100644
index df82deb5..00000000
--- a/include/ap/argument/positional.hpp
+++ /dev/null
@@ -1,290 +0,0 @@
-// Copyright (c) 2023-2025 Jakub Musiał
-// This file is part of the CPP-AP project (https://github.com/SpectraL519/cpp-ap).
-// Licensed under the MIT License. See the LICENSE file in the project root for full license information.
-
-/// @file positional.hpp
-
-#pragma once
-
-#include "ap/action/detail/utility.hpp"
-#include "ap/action/predefined_actions.hpp"
-#include "ap/detail/argument_base.hpp"
-#include "ap/detail/concepts.hpp"
-
-#ifdef AP_TESTING
-
-namespace ap_testing {
-struct positional_argument_test_fixture;
-} // namespace ap_testing
-
-#endif
-
-namespace ap::argument {
-
-/**
- * @brief The positional argument class.
- * @tparam T The argument's value type.
- */
-template
-class positional : public detail::argument_base {
-public:
- using value_type = T; ///< The argument's value type.
-
- positional() = delete;
-
- /**
- * @brief Constructor for positional argument with the `name` identifier.
- * @param name The `name` identifier of the positional argument.
- */
- positional(const detail::argument_name& name) : argument_base(name, true) {}
-
- ~positional() = default;
-
- /**
- * @brief Equality operator for positional argument.
- * @param other Another positional argument for comparison.
- * @return Result of equality
- */
- bool operator==(const positional& other) const noexcept {
- return this->_name == other._name;
- }
-
- /**
- * @brief Set the help message for the positional argument.
- * @param help_msg The help message to set.
- * @return Reference to the positional argument.
- */
- positional& help(std::string_view help_msg) noexcept {
- this->_help_msg = help_msg;
- return *this;
- }
-
- /**
- * @brief Set the `hidden` attribute for the positional argument.
- * @param h The attribute value.
- * @return Reference to the positional argument.
- */
- positional& hidden(const bool h = true) noexcept {
- this->_hidden = h;
- return *this;
- }
-
- /**
- * @brief Set the `required` attribute of the positional argument
- * @param r The attribute value.
- * @return Reference to the positional argument.
- * @attention Setting the `required` attribute to true disables the `bypass_required` attribute.
- */
- positional& required(const bool r = true) noexcept {
- this->_required = r;
- if (this->_required)
- this->_bypass_required = false;
- return *this;
- }
-
- /**
- * @brief Enable/disable bypassing the `required` attributeattribute for the positional argument.
- * @param br The attribute value.
- * @return Reference to the positional argument.
- * @attention Setting the `bypass_required` attribute to true disables the `required` attribute.
- */
- positional& bypass_required(const bool br = true) noexcept {
- this->_bypass_required = br;
- if (this->_bypass_required)
- this->_required = false;
- return *this;
- }
-
- /**
- * @brief Set the choices for the positional argument.
- * @tparam CR The choices range type.
- * @param choices The range of valid choices for the argument.
- * @return Reference to the positional argument.
- * @note `value_type` must be equality comparable.
- * @note `CR` must be a range such that its value type is convertible to `value_type`.
- */
- template CR>
- positional& choices(const CR& choices) noexcept
- requires(std::equality_comparable)
- {
- for (const auto& choice : choices)
- this->_choices.emplace_back(choice);
- return *this;
- }
-
- /**
- * @brief Set the choices for the positional argument.
- * @param choices The list of valid choices for the argument.
- * @return Reference to the positional argument.
- * @note `value_type` must be equality comparable.
- */
- positional& choices(std::initializer_list choices) noexcept
- requires(std::equality_comparable)
- {
- return this->choices<>(choices);
- }
-
- /**
- * @brief Set the default value for the positional argument.
- * @param default_value The default value to set.
- * @return Reference to the positional argument.
- * @attention Setting the default value disables the `required` attribute.
- */
- positional& default_value(const std::convertible_to auto& default_value) noexcept {
- this->_default_value = std::make_any(default_value);
- this->_required = false;
- return *this;
- }
-
- /**
- * @brief Set the action for the positional argument.
- * @tparam AS The value action specifier type (valued_action or void_action).
- * @tparam F The type of the action function.
- * @param action The action function to set.
- * @return Reference to the positional argument.
- */
- template F>
- positional& action(F&& action) noexcept {
- using callable_type = action::detail::callable_type;
- this->_value_actions.emplace_back(std::forward