Skip to content

Commit 7366309

Browse files
authored
YT-CPPAP-52: Optional arguments with secondary names only
- Aligned the `argument_name` class to store both primary and secondary names as optionals - Added the `argument_name_discriminator` enum for the purpose of argument initialization with specific name member being set - Aligned the `add_optional_argument` and `add_flag` methods of `argument_parser` to take a discriminator member, specifying which name member should be set for the new argument
1 parent 89e11e5 commit 7366309

13 files changed

+247
-140
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 2.5.2
10+
VERSION 2.5.3
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 = 2.5.2
51+
PROJECT_NUMBER = 2.5.3
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 = "2.5.2",
3+
version = "2.5.3",
44
)

docs/tutorial.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,15 @@ parser.add_optional_argument<bool>("disable_another_option", "dao")
178178
*/
179179
```
180180

181+
> [!NOTE]
182+
>
183+
> 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:
184+
>
185+
> ```cpp
186+
> parser.add_optional_argument("a", ap::n_secondary);
187+
> parser.add_flag("f", ap::n_secondary);
188+
> ```
189+
181190
<br/>
182191
<br/>
183192
<br/>

include/ap/argument/positional.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ class positional : public detail::argument_base {
276276
*/
277277
[[nodiscard]] const std::vector<std::any>& values() const override {
278278
throw std::logic_error(
279-
std::format("Positional argument `{}` has only 1 value.", this->_name.primary)
279+
std::format("Positional argument `{}` has only 1 value.", this->_name.str())
280280
);
281281
}
282282

include/ap/argument_parser.hpp

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,10 @@ class argument_parser {
142142
* \todo Check forbidden characters (after adding the assignment character).
143143
*/
144144
template <detail::c_argument_value_type T = std::string>
145-
argument::positional<T>& add_positional_argument(std::string_view primary_name) {
145+
argument::positional<T>& add_positional_argument(const std::string_view primary_name) {
146146
this->_verify_arg_name_pattern(primary_name);
147147

148-
const detail::argument_name arg_name(primary_name);
148+
const detail::argument_name arg_name(std::make_optional<std::string>(primary_name));
149149
if (this->_is_arg_name_used(arg_name))
150150
throw invalid_configuration::argument_name_used(arg_name);
151151

@@ -165,12 +165,15 @@ class argument_parser {
165165
*/
166166
template <detail::c_argument_value_type T = std::string>
167167
argument::positional<T>& add_positional_argument(
168-
std::string_view primary_name, std::string_view secondary_name
168+
const std::string_view primary_name, const std::string_view secondary_name
169169
) {
170170
this->_verify_arg_name_pattern(primary_name);
171171
this->_verify_arg_name_pattern(secondary_name);
172172

173-
const detail::argument_name arg_name(primary_name, secondary_name);
173+
const detail::argument_name arg_name{
174+
std::make_optional<std::string>(primary_name),
175+
std::make_optional<std::string>(secondary_name)
176+
};
174177
if (this->_is_arg_name_used(arg_name))
175178
throw invalid_configuration::argument_name_used(arg_name);
176179

@@ -181,17 +184,27 @@ class argument_parser {
181184
/**
182185
* @brief Adds a positional argument to the parser's configuration.
183186
* @tparam T Type of the argument value.
184-
* @param primary_name The primary name of the argument.
187+
* @param name The name of the argument.
188+
* @param name_discr The discriminator value specifying whether the given name should be treated as primary or secondary.
185189
* @return Reference to the added optional argument.
186190
* @throws ap::invalid_configuration
187191
*
188192
* \todo Check forbidden characters (after adding the assignment character).
189193
*/
190194
template <detail::c_argument_value_type T = std::string>
191-
argument::optional<T>& add_optional_argument(std::string_view primary_name) {
192-
this->_verify_arg_name_pattern(primary_name);
193-
194-
const detail::argument_name arg_name(primary_name, std::nullopt, this->_flag_prefix_char);
195+
argument::optional<T>& add_optional_argument(
196+
const std::string_view name,
197+
const detail::argument_name_discriminator name_discr = n_primary
198+
) {
199+
this->_verify_arg_name_pattern(name);
200+
201+
const auto arg_name =
202+
name_discr == n_primary
203+
? detail::
204+
argument_name{std::make_optional<std::string>(name), std::nullopt, this->_flag_prefix_char}
205+
: detail::argument_name{
206+
std::nullopt, std::make_optional<std::string>(name), this->_flag_prefix_char
207+
};
195208
if (this->_is_arg_name_used(arg_name))
196209
throw invalid_configuration::argument_name_used(arg_name);
197210

@@ -211,12 +224,16 @@ class argument_parser {
211224
*/
212225
template <detail::c_argument_value_type T = std::string>
213226
argument::optional<T>& add_optional_argument(
214-
std::string_view primary_name, std::string_view secondary_name
227+
const std::string_view primary_name, const std::string_view secondary_name
215228
) {
216229
this->_verify_arg_name_pattern(primary_name);
217230
this->_verify_arg_name_pattern(secondary_name);
218231

219-
const detail::argument_name arg_name(primary_name, secondary_name, this->_flag_prefix_char);
232+
const detail::argument_name arg_name(
233+
std::make_optional<std::string>(primary_name),
234+
std::make_optional<std::string>(secondary_name),
235+
this->_flag_prefix_char
236+
);
220237
if (this->_is_arg_name_used(arg_name))
221238
throw invalid_configuration::argument_name_used(arg_name);
222239

@@ -227,12 +244,16 @@ class argument_parser {
227244
/**
228245
* @brief Adds a boolean flag argument (an optional argument with `value_type = bool`) to the parser's configuration.
229246
* @tparam StoreImplicitly A boolean value used as the `implicit_value` parameter of the argument.
230-
* @param primary_name The primary name of the flag.
247+
* @param name The primary name of the flag.
248+
* @param name_discr The discriminator value specifying whether the given name should be treated as primary or secondary.
231249
* @return Reference to the added boolean flag argument.
232250
*/
233251
template <bool StoreImplicitly = true>
234-
argument::optional<bool>& add_flag(std::string_view primary_name) {
235-
return this->add_optional_argument<bool>(primary_name)
252+
argument::optional<bool>& add_flag(
253+
const std::string_view name,
254+
const detail::argument_name_discriminator name_discr = n_primary
255+
) {
256+
return this->add_optional_argument<bool>(name, name_discr)
236257
.default_value(not StoreImplicitly)
237258
.implicit_value(StoreImplicitly)
238259
.nargs(0ull);
@@ -247,7 +268,7 @@ class argument_parser {
247268
*/
248269
template <bool StoreImplicitly = true>
249270
argument::optional<bool>& add_flag(
250-
std::string_view primary_name, std::string_view secondary_name
271+
const std::string_view primary_name, const std::string_view secondary_name
251272
) {
252273
return this->add_optional_argument<bool>(primary_name, secondary_name)
253274
.default_value(not StoreImplicitly)

include/ap/detail/argument_name.hpp

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
#include <string>
1313
#include <string_view>
1414

15-
namespace ap::detail {
15+
namespace ap {
16+
17+
namespace detail {
1618

1719
/// @brief Structure holding the argument's name.
1820
struct argument_name {
@@ -39,11 +41,17 @@ struct argument_name {
3941
* @param flag_char The flag character (used for optional argument names).
4042
*/
4143
argument_name(
42-
std::string_view primary,
43-
std::optional<std::string_view> secondary = std::nullopt,
44+
std::optional<std::string> primary,
45+
std::optional<std::string> secondary = std::nullopt,
4446
std::optional<char> flag_char = std::nullopt
4547
)
46-
: primary(primary), secondary(std::move(secondary)), flag_char(std::move(flag_char)) {}
48+
: primary(std::move(primary)),
49+
secondary(std::move(secondary)),
50+
flag_char(std::move(flag_char)) {
51+
if (not (this->primary or this->secondary))
52+
throw std::logic_error("An argument name cannot be empty! At least one of "
53+
"primary/secondary must be specified");
54+
}
4755

4856
/// @brief Class destructor.
4957
~argument_name() = default;
@@ -54,13 +62,7 @@ struct argument_name {
5462
* @return Equality of argument names.
5563
*/
5664
bool operator==(const argument_name& other) const noexcept {
57-
if (not (this->secondary and other.secondary) and (this->secondary or other.secondary))
58-
return false; // only one of the compared argument names has a secondary name
59-
60-
if (this->primary != other.primary)
61-
return false;
62-
63-
return this->secondary ? this->secondary.value() == other.secondary.value() : true;
65+
return this->primary == other.primary and this->secondary == other.secondary;
6466
}
6567

6668
/**
@@ -73,11 +75,11 @@ struct argument_name {
7375
const noexcept {
7476
switch (m_type) {
7577
case m_any:
76-
return this->match_primary(arg_name) or this->match_secondary(arg_name);
78+
return this->primary == arg_name or this->secondary == arg_name;
7779
case m_primary:
78-
return this->match_primary(arg_name);
80+
return this->primary == arg_name;
7981
case m_secondary:
80-
return this->match_secondary(arg_name);
82+
return this->secondary == arg_name;
8183
}
8284

8385
return false;
@@ -92,7 +94,7 @@ struct argument_name {
9294
[[nodiscard]] bool match(
9395
const argument_name& arg_name, [[maybe_unused]] const match_type m_type = m_any
9496
) const noexcept {
95-
if (this->match(arg_name.primary))
97+
if (arg_name.primary and this->match(arg_name.primary.value()))
9698
return true;
9799

98100
if (arg_name.secondary)
@@ -101,34 +103,21 @@ struct argument_name {
101103
return false;
102104
}
103105

104-
/**
105-
* @brief Matches the given string to the primary name of an argument_name instance.
106-
* @param arg_name The name string to match.
107-
* @return True if name is equal to either the primary name of the argument_name instance.
108-
*/
109-
[[nodiscard]] bool match_primary(std::string_view arg_name) const noexcept {
110-
return arg_name == this->primary;
111-
}
112-
113-
/**
114-
* @brief Matches the given string to the secondary name of an argument_name instance.
115-
* @param arg_name The name string to match.
116-
* @return True if name is equal to either the secondary name of the argument_name instance.
117-
*/
118-
[[nodiscard]] bool match_secondary(std::string_view arg_name) const noexcept {
119-
return this->secondary and arg_name == this->secondary.value();
120-
}
121-
122106
/**
123107
* @brief Get a string representation of the argument_name.
124108
* @param flag_char The character used for the argument flag prefix.
125109
*/
126110
[[nodiscard]] std::string str() const noexcept {
127111
// if flag_char = nullopt, then the fallback character doesn't matter - the string will be empty
128112
const std::string fc(this->flag_char.has_value(), this->flag_char.value_or(char()));
129-
return this->secondary
130-
? std::format("{}{}{}, {}{}", fc, fc, this->primary, fc, this->secondary.value())
131-
: std::format("{}{}{}", fc, fc, this->primary);
113+
114+
std::string primary_str =
115+
this->primary ? std::format("{}{}{}", fc, fc, this->primary.value()) : "";
116+
std::string separator = this->primary and this->secondary ? ", " : "";
117+
std::string secondary_str =
118+
this->secondary ? std::format("{}{}", fc, this->secondary.value()) : "";
119+
120+
return std::format("{}{}{}", primary_str, separator, secondary_str);
132121
}
133122

134123
/**
@@ -142,9 +131,23 @@ struct argument_name {
142131
return os;
143132
}
144133

145-
const std::string primary; ///< The primary name of the argument.
134+
const std::optional<std::string> primary; ///< The primary name of the argument.
146135
const std::optional<std::string> secondary; ///< The optional (short) name of the argument.
147136
const std::optional<char> flag_char; ///< The flag character (used for optional argument names).
148137
};
149138

150-
} // namespace ap::detail
139+
/**
140+
* @brief Argument name member discriminator.
141+
*
142+
* This discriminator type is used in the argument initializing methods of the `argument_parser` class.
143+
*/
144+
enum class argument_name_discriminator : bool {
145+
n_primary, ///< Represents the primary name (used with a long flag prefix --).
146+
n_secondary ///< Represents the secondary name (used with a short flag prefix --).
147+
};
148+
149+
} // namespace detail
150+
151+
using enum detail::argument_name_discriminator;
152+
153+
} // namespace ap

tests/include/argument_parser_test_fixture.hpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,13 @@ struct argument_parser_test_fixture {
9898
void add_arguments(std::size_t n_positional_args, std::size_t n_optional_args) {
9999
for (std::size_t i = 0ull; i < n_positional_args; ++i) {
100100
const auto arg_name = init_arg_name(i);
101-
sut.add_positional_argument<T>(arg_name.primary, arg_name.secondary.value());
101+
sut.add_positional_argument<T>(arg_name.primary.value(), arg_name.secondary.value());
102102
}
103103

104104
for (std::size_t i = 0ull; i < n_optional_args; ++i) {
105105
const auto arg_idx = n_positional_args + i;
106106
const auto arg_name = init_arg_name(arg_idx);
107-
sut.add_optional_argument<T>(arg_name.primary, arg_name.secondary.value());
107+
sut.add_optional_argument<T>(arg_name.primary.value(), arg_name.secondary.value());
108108
}
109109
}
110110

@@ -119,9 +119,9 @@ struct argument_parser_test_fixture {
119119

120120
for (std::size_t i = 0ull; i < n_optional_args; ++i) {
121121
const auto arg_idx = n_positional_args + i;
122-
arg_tokens.push_back(
123-
argument_token{argument_token::t_flag_primary, init_arg_name(arg_idx).primary}
124-
);
122+
arg_tokens.push_back(argument_token{
123+
argument_token::t_flag_primary, init_arg_name(arg_idx).primary.value()
124+
});
125125
arg_tokens.push_back(argument_token{argument_token::t_value, init_arg_value(arg_idx)});
126126
}
127127

0 commit comments

Comments
 (0)