Skip to content

Commit 4fe32f0

Browse files
committed
YT-CPPAP-45: Add argument groups support
- Added an `argument_group` class with the following attribute setters: - `required` - at least one argument from the group must be used - `mutually_exclusive` - at most one argument from the group can be used - Added an `add_group` method to `argument_parser` - Added new overloads for the `add_optional_argument` and `add_flag` functions with an additional `group` parameter - Aligned the final parsing validation logic to properly verify the requirements of defined argument groups - Removed the `program_name` attribute setter from `argument_parser` and added the `name` parameter to the class constructor to enforce the program name's presence in a parser instance
1 parent c8d84e7 commit 4fe32f0

14 files changed

+636
-146
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ else()
77
endif()
88

99
project(cpp-ap
10-
VERSION 3.0.0.6
10+
VERSION 3.0.0.7
1111
DESCRIPTION "Command-line argument parser for C++20"
1212
HOMEPAGE_URL "https://github.com/SpectraL519/cpp-ap"
1313
LANGUAGES CXX

Doxyfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ PROJECT_NAME = CPP-AP
4848
# could be handy for archiving the generated documentation or if some version
4949
# control system is used.
5050

51-
PROJECT_NUMBER = 3.0.0.6
51+
PROJECT_NUMBER = 3.0.0.7
5252

5353
# Using the PROJECT_BRIEF tag one can provide an optional one line description
5454
# for a project that appears at the top of each page and should give viewer a

MODULE.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
module(
22
name = "cpp-ap",
3-
version = "3.0.0.6",
3+
version = "3.0.0.7",
44
)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Command-line argument parser for C++20
5757
- [Common Parameters](/docs/tutorial.md#common-parameters)
5858
- [Parameters Specific for Optional Arguments](/docs/tutorial.md#parameters-specific-for-optional-arguments)
5959
- [Default Arguments](/docs/tutorial.md#default-arguments)
60+
- [Argument Groups](/docs/tutorial.md#argument-groups)
6061
- [Parsing Arguments](/docs/tutorial.md#parsing-arguments)
6162
- [Basic Argument Parsing Rules](/docs/tutorial.md#basic-argument-parsing-rules)
6263
- [Compound Arguments](/docs/tutorial.md#compound-arguments)

docs/tutorial.md

Lines changed: 142 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
- [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)
2323
- [Predefined Parameter Values](#predefined-parameter-values)
2424
- [Default Arguments](#default-arguments)
25+
- [Argument Groups](#argument-groups)
26+
- [Creating New Groups](#creating-new-groups)
27+
- [Adding Arguments to Groups](#adding-arguments-to-groups)
28+
- [Group Attributes](#group-attributes)
29+
- [Complete Example](#complete-example)
2530
- [Parsing Arguments](#parsing-arguments)
2631
- [Basic Argument Parsing Rules](#basic-argument-parsing-rules)
2732
- [Compound Arguments](#compound-arguments)
@@ -118,21 +123,26 @@ If you do not use CMake you can dowload the desired [library release](https://gi
118123

119124
To use the argument parser in your code you need to use the `ap::argument_parser` class.
120125

121-
The parameters you can specify for a parser's instance are:
122-
123-
- The program's name, version and description - used in the parser's configuration output (`std::cout << parser`).
124-
- 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.
125-
- [Arguments](#adding-arguments) - specify the values/options accepted by the program.
126-
- [The unknown argument flags handling policy](#4-unknown-argument-flag-handling).
127-
128126
```cpp
129-
ap::argument_parser parser;
130-
parser.program_name("Name of the program")
131-
.program_version("alhpa")
127+
ap::argument_parser parser("program");
128+
parser.program_version("alhpa")
132129
.program_description("Description of the program")
133130
.verbose();
134131
```
135132
133+
> [!IMPORTANT]
134+
>
135+
> - When creating an argument parser instance, you must provide a program name to the constructor.
136+
>
137+
> The program name given to the parser cannot contain whitespace characters.
138+
>
139+
> - Additional parameters you can specify for a parser's instance incldue:
140+
> - The program's version and description - used in the parser's configuration output (`std::cout << parser`).
141+
> - 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.
142+
> - [Arguments](#adding-arguments) - specify the values/options accepted by the program.
143+
> - [Argument Groups](#argument-groups) - organize related optional arguments into sections and optionally enforce usage rules.
144+
> - [The unknown argument flags handling policy](#4-unknown-argument-flag-handling).
145+
136146
> [!TIP]
137147
>
138148
> You can specify the program version using a string (like in the example above) or using the `ap::version` structure:
@@ -256,8 +266,8 @@ parser.add_positional_argument<std::size_t>("number", "n")
256266
By default all arguments are visible, but this can be modified using the `hidden(bool)` setter as follows:
257267
258268
```cpp
259-
parser.program_name("hidden-test")
260-
.program_description("A simple program")
269+
ap::argument_parser("hidden-test")
270+
parser.program_description("A simple test program for argument hiding")
261271
.default_arguments(ap::default_argument::o_help);
262272
263273
parser.add_optional_argument("hidden")
@@ -272,7 +282,7 @@ parser.try_parse_args(argc, argv);
272282
> ./hidden-test --help
273283
Program: hidden-test
274284
275-
A simple program
285+
A simple test program for argument hiding
276286
277287
Optional arguments:
278288
@@ -445,9 +455,8 @@ The `nargs` parameter can be set as:
445455
Consider a simple example:
446456
447457
```cpp
448-
ap::argument_parser parser;
449-
parser.program_name("run-script")
450-
.default_arguments(ap::default_argument::o_help);
458+
ap::argument_parser parser("run-script");
459+
parser.default_arguments(ap::default_argument::o_help);
451460
452461
parser.add_positional_argument("script")
453462
.help("The name of the script to run");
@@ -807,6 +816,106 @@ parser.default_arguments(<args>);
807816
<br/>
808817
<br/>
809818
819+
## Argument Groups
820+
821+
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**.
822+
823+
By default, every parser comes with two predefined groups:
824+
825+
- **Positional Arguments** – contains all arguments added via `add_positional_argument`.
826+
- **Optional Arguments** – contains all arguments added via `add_optional_argument` or `add_flag` without explicitly specifying an argument group.
827+
828+
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".
829+
830+
831+
### Creating New Groups
832+
833+
A new group can be created by calling the `add_group` method of an argument parser:
834+
835+
```cpp
836+
ap::argument_parser parser("myprog");
837+
auto& out_opts = parser.add_group("Output Options");
838+
```
839+
840+
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.
841+
842+
> [!NOTE]
843+
>
844+
> If a group has no visible arguments, it will not be included in the parser's help message output at all.
845+
846+
### Adding Arguments to Groups
847+
848+
Arguments are added to a group by passing the group reference as the first parameter to the `add_optional_argument` and `add_flag` functions:
849+
850+
```cpp
851+
parser.add_optional_argument(out_opts, "output", "o")
852+
.nargs(1)
853+
.help("Print output to the given file");
854+
855+
parser.add_flag(out_opts, "print", "p")
856+
.help("Print output to the console");
857+
```
858+
859+
### Group Attributes
860+
861+
User-defined groups can be configured with special attributes that change how the parser enforces their usage:
862+
863+
- `required()` – at least one argument from the group must be provided by the user, otherwise parsing will fail.
864+
- `mutually_exclusive()` – at most one argument from the group can be provided; using more than one at the same time results in an error.
865+
866+
Both attributes are **off by default**, and they can be combined (e.g., a group can require that exactly one argument is chosen).
867+
868+
```cpp
869+
auto& out_opts = parser.add_group("Output Options")
870+
.required() // at least one option is required
871+
.mutually_exclusive(); // but at most one can be chosen
872+
```
873+
874+
### Complete Example
875+
876+
Below is a small program that demonstrates how to use a mutually exclusive group of required arguments:
877+
878+
```cpp
879+
#include <ap/argument_parser.hpp>
880+
881+
int main(int argc, char* argv[]) {
882+
ap::argument_parser parser("myprog");
883+
parser.default_arguments(ap::default_argument::o_help);
884+
885+
// create the argument group
886+
auto& out_opts = parser.add_group("Output Options")
887+
.required()
888+
.mutually_exclusive();
889+
890+
// add arguments to the custom group
891+
parser.add_optional_argument(out_opts, "output", "o")
892+
.nargs(1)
893+
.help("Print output to a given file");
894+
895+
parser.add_flag(out_opts, "print", "p")
896+
.help("Print output to the console");
897+
898+
parser.try_parse_args(argc, argv);
899+
900+
return 0;
901+
}
902+
```
903+
904+
When invoked with the `--help` flag, the above program produces a help message that clearly shows the group and its rules:
905+
906+
```
907+
Program: myprog
908+
909+
Output Options: (required, mutually exclusive)
910+
911+
--output, -o : Print output to a given file
912+
--print, -p : Print output to the console
913+
```
914+
915+
<br/>
916+
<br/>
917+
<br/>
918+
810919
## Parsing Arguments
811920
812921
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`.
@@ -845,19 +954,18 @@ The simple example below demonstrates how (in terms of the program's structure)
845954
846955
int main(int argc, char* argv[]) {
847956
// create the parser class instance
848-
ap::argument_parser parser;
957+
ap::argument_parser parser("some-program");
849958
850-
// define the parser's attributes
851-
parser.program_name("some-program")
852-
.program_description("The program does something with command-line arguments");
959+
// define the parser's attributes and default arguments
960+
parser.program_version({0u, 0u, 0u})
961+
.program_description("The program does something with command-line arguments")
962+
.default_arguments(ap::default_argument::o_help);
853963
854964
// define the program arguments
855965
parser.add_positional_argument("positional").help("A positional argument");
856966
parser.add_optional_argument("optional", "o").help("An optional argument");
857967
parser.add_flag("flag", "f").help("A boolean flag");
858968
859-
parser.default_arguments(ap::default_argument::o_help);
860-
861969
// parse command-line arguments
862970
parser.try_parse_args(argc, argv);
863971
@@ -1009,10 +1117,9 @@ This behavior can be modified using the `unknown_arguments_policy` method of the
10091117
#include <ap/argument_parser.hpp>
10101118
10111119
int main(int argc, char* argv[]) {
1012-
ap::argument_parser parser;
1120+
ap::argument_parser parser("unknown-policy-test");
10131121
1014-
parser.program_name("test")
1015-
.program_description("A simple test program")
1122+
parser.program_description("A simple test program for unknwon argument handling policies")
10161123
.default_arguments(ap::default_argument::o_help)
10171124
// set the unknown argument flags handling policy
10181125
.unknown_arguments_policy(ap::unknown_policy::<policy>);
@@ -1031,12 +1138,12 @@ int main(int argc, char* argv[]) {
10311138
The available policies are:
10321139
- `ap::unknown_policy::fail` (default) - throws an exception if an unknown argument flag is encountered:
10331140
1034-
```bash
1035-
> ./test --known --unknown
1141+
```txt
1142+
> ./unknown-policy-test --known --unknown
10361143
[ap::error] Unknown argument [--unknown].
1037-
Program: test
1144+
Program: unknown-policy-test
10381145
1039-
A simple test program
1146+
A simple test program for unknwon argument handling policies
10401147
10411148
Optional arguments:
10421149
@@ -1046,23 +1153,23 @@ The available policies are:
10461153
10471154
- `ap::unknown_policy::warn` - prints a warning message to the standard error stream and continues parsing the remaining arguments:
10481155
1049-
```bash
1050-
> ./test --known --unknown
1156+
```txt
1157+
> ./unknown-policy-test --known --unknown
10511158
[ap::warning] Unknown argument '--unknown' will be ignored.
10521159
known =
10531160
```
10541161
10551162
- `ap::unknown_policy::ignore` - ignores unknown argument flags and continues parsing the remaining arguments:
10561163
1057-
```shell
1058-
./test --known --unknown
1164+
```txt
1165+
./unknown-policy-test --known --unknown
10591166
known =
10601167
```
10611168
10621169
- `ap::unknown_policy::as_values` - treats unknown argument flags as values:
10631170
1064-
```shell
1065-
> ./test --known --unknown
1171+
```txt
1172+
> ./unknown-policy-test --known --unknown
10661173
known = --unknown
10671174
```
10681175

0 commit comments

Comments
 (0)