Lector v1.0.0
C++ library for parsing command line arguments.
arguments.hpp
1// Copyright © 2026, Alexandre Coderre-Chabot.
2
3// This file is part of Lector (https://github.com/acodcha/lector), a C++ library for parsing
4// command line arguments. Lector is licensed under the MIT License (https://mit-license.org).
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
7// associated documentation files (the "Software"), to deal in the Software without restriction,
8// including without limitation the rights to use, copy, modify, merge, publish, distribute,
9// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11// - The above copyright notice and this permission notice shall be included in all copies or
12// substantial portions of the Software.
13// - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
14// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
16// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM
17// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18
19#ifndef LECTOR_ARGUMENTS_HPP
20#define LECTOR_ARGUMENTS_HPP
21
22#include <algorithm>
23#include <array>
24#include <cstddef>
25#include <cstdint>
26#include <filesystem>
27#include <limits>
28#include <optional>
29#include <stdexcept>
30#include <string>
31#include <string_view>
32#include <tuple>
33#include <type_traits>
34#include <unordered_set>
35#include <utility>
36#include <vector>
37
38#include "lector/parse.hpp"
39#include "lector/print.hpp"
40#include "lector/text.hpp"
41
42/// @brief The Lector library's namespace.
43namespace lector {
44
45/// @brief Arity of a command line argument.
46enum class Arity : std::int8_t {
47 /// @brief Unknown, unspecified, or invalid command line argument arity.
48 Unknown = 0,
49
50 /// @brief The command line argument has singular arity; it can only appear once on the command
51 /// line.
52 Singular = 1,
53
54 /// @brief The command line argument has repeatable arity; it can appear multiple times on the
55 /// command line. If the argument is a named repeatable argument, each appearance must include
56 /// both its key and its value. If the argument is a positional repeatable argument, its multiple
57 /// values must appear in an uninterrupted sequence.
58 Repeatable = 2,
59};
60
61/// @brief Specialization of the lector::Names constant for the lector::Arity enumeration.
62template <>
63inline constexpr std::array<lector::Name<lector::Arity>, 3> Names<lector::Arity>{
64 {
65 {lector::Arity::Unknown, "Unknown"},
66 {lector::Arity::Singular, "Singular"},
67 {lector::Arity::Repeatable, "Repeatable"},
68 }
69};
70
71/// @brief Specialization of the lector::Spellings constant for the lector::Arity enumeration.
72template <>
73inline constexpr std::array<lector::Spelling<lector::Arity>, 9> Spellings<lector::Arity>{
74 {
75 {"Unknown", lector::Arity::Unknown},
76 {"Singular", lector::Arity::Singular},
77 {"Repeatable", lector::Arity::Repeatable},
78 {"unknown", lector::Arity::Unknown},
79 {"singular", lector::Arity::Singular},
80 {"repeatable", lector::Arity::Repeatable},
81 {"UNKNOWN", lector::Arity::Unknown},
82 {"SINGULAR", lector::Arity::Singular},
83 {"REPEATABLE", lector::Arity::Repeatable},
84 }
85};
86
87/// @brief Form of a command line argument.
88enum class Form : std::int8_t {
89 /// @brief Unknown, unspecified, or invalid command line argument form.
90 Unknown = 0,
91
92 /// @brief The command line argument is a positional argument; it does not define any keys and
93 /// must be specified in a specific order on the command line.
94 Positional = 1,
95
96 /// @brief The command line argument is a named argument; it defines one or more keys and is
97 /// specified on the command line by one of its keys. Named arguments can be specified in any
98 /// order on the command line.
99 Named = 2,
100};
101
102/// @brief Specialization of the lector::Names constant for the lector::Form enumeration.
103template <>
104inline constexpr std::array<lector::Name<lector::Form>, 3> Names<lector::Form>{
105 {
106 {lector::Form::Unknown, "Unknown"},
107 {lector::Form::Positional, "Positional"},
108 {lector::Form::Named, "Named"},
109 }
110};
111
112/// @brief Specialization of the lector::Spellings constant for the lector::Form enumeration.
113template <>
114inline constexpr std::array<lector::Spelling<lector::Form>, 9> Spellings<lector::Form>{
115 {
116 {"Unknown", lector::Form::Unknown},
117 {"Positional", lector::Form::Positional},
118 {"Named", lector::Form::Named},
119 {"unknown", lector::Form::Unknown},
120 {"positional", lector::Form::Positional},
121 {"named", lector::Form::Named},
122 {"UNKNOWN", lector::Form::Unknown},
123 {"POSITIONAL", lector::Form::Positional},
124 {"NAMED", lector::Form::Named},
125 }
126};
127
128/// @brief Importance of a command line argument.
129enum class Importance : std::int8_t {
130 /// @brief Unknown, unspecified, or invalid command line argument importance.
131 Unknown = 0,
132
133 /// @brief The command line argument is optional; it may or may not be provided by the user.
134 Optional = 1,
135
136 /// @brief The command line argument is required; it must be provided by the user.
137 Required = 2,
138};
139
140/// @brief Specialization of the lector::Names constant for the lector::Importance enumeration.
141template <>
142inline constexpr std::array<lector::Name<lector::Importance>, 3> Names<lector::Importance>{
143 {
144 {lector::Importance::Unknown, "Unknown"},
145 {lector::Importance::Optional, "Optional"},
146 {lector::Importance::Required, "Required"},
147 }
148};
149
150/// @brief Specialization of the lector::Spellings constant for the lector::Importance enumeration.
151template <>
152inline constexpr std::array<lector::Spelling<lector::Importance>, 9> Spellings<lector::Importance>{
153 {
154 {"Unknown", lector::Importance::Unknown},
155 {"Optional", lector::Importance::Optional},
156 {"Required", lector::Importance::Required},
157 {"unknown", lector::Importance::Unknown},
158 {"optional", lector::Importance::Optional},
159 {"required", lector::Importance::Required},
160 {"UNKNOWN", lector::Importance::Unknown},
161 {"OPTIONAL", lector::Importance::Optional},
162 {"REQUIRED", lector::Importance::Required},
163 }
164};
165
166/// @brief A singular command line argument.
167/// @tparam LabelValue Value of this command line argument's label. The label is used to uniquely
168/// identify this command line argument in a collection of command line arguments.
169/// @tparam Type The type of the value stored in this command line argument.
170template <auto LabelValue, typename Type>
171class SingularArgument final {
172public:
173 using ValueType = Type;
174
175 /// @brief Default constructor. Initializes the singular command line argument with no keys, an
176 /// empty description, required importance, and no default value.
177 SingularArgument() noexcept = default;
178
179 /// @brief Constructor for a singular positional required command line argument. No default value
180 /// is needed.
181 /// @param[in] description The description of the command line argument.
182 /// @throws std::invalid_argument if this argument's type is boolean or if its description is
183 /// empty.
184 explicit SingularArgument(const std::string_view description) : description_{description} {
185 validate_non_boolean_positional();
186 validate_description();
187 }
188
189 /// @brief Constructor for a singular named required command line argument or a singular named
190 /// boolean command line argument. No default value is needed. Singular named boolean command line
191 /// arguments are always optional and always default to false.
192 /// @param[in] keys The keys used to specify the command line argument.
193 /// @param[in] description The description of the command line argument.
194 /// @throws std::invalid_argument if the keys are invalid or if the description is empty.
195 SingularArgument(const std::vector<std::string>& keys, const std::string_view description)
196 : keys_{keys}, description_{description},
197 importance_{
198 std::is_same_v<Type, bool> ? lector::Importance::Optional : lector::Importance::Required} {
199 set_default_value_to_false_if_boolean();
200 validate_keys();
201 validate_description();
202 }
203
204 /// @brief Constructor for a singular positional optional non-boolean command line argument. A
205 /// default value must be provided.
206 /// @param[in] description The description of the command line argument.
207 /// @param[in] default_value The default value of the command line argument.
208 /// @throws std::invalid_argument if this argument's type is boolean or if its description is
209 /// empty.
210 SingularArgument(const std::string_view description, const Type& default_value)
211 : description_{description}, default_value_{default_value},
212 importance_{lector::Importance::Optional} {
213 validate_non_boolean_default_value();
214 validate_description();
215 }
216
217 /// @brief Constructor for a singular named optional non-boolean command line argument. A default
218 /// value must be provided.
219 /// @param[in] keys The keys used to specify the command line argument.
220 /// @param[in] description The description of the command line argument.
221 /// @param[in] default_value The default value of the command line argument.
222 /// @throws std::invalid_argument if this argument's type is boolean, if its keys are invalid, or
223 /// if its description is empty.
224 SingularArgument(const std::vector<std::string>& keys, const std::string_view description,
225 const Type& default_value)
226 : keys_{keys}, description_{description}, default_value_{default_value},
227 importance_{lector::Importance::Optional} {
228 validate_non_boolean_default_value();
229 validate_keys();
230 validate_description();
231 }
232
233 /// @brief Destructor. Destroys this singular command line argument.
234 ~SingularArgument() noexcept = default;
235
236 /// @brief Copy constructor. Constructs a singular command line argument by copying another one.
237 SingularArgument(const lector::SingularArgument<LabelValue, Type>&) = default;
238
239 /// @brief Copy assignment operator. Assigns this singular command line argument by copying
240 /// another one.
241 /// @return This singular command line argument after the assignment.
242 lector::SingularArgument<LabelValue, Type>& operator=(
243 const lector::SingularArgument<LabelValue, Type>&) = default;
244
245 /// @brief Move constructor. Constructs a singular command line argument by moving another one.
246 SingularArgument(lector::SingularArgument<LabelValue, Type>&&) noexcept = default;
247
248 /// @brief Move assignment operator. Assigns this singular command line argument by moving another
249 /// one.
250 /// @return This singular command line argument after the assignment.
251 lector::SingularArgument<LabelValue, Type>& operator=(
252 lector::SingularArgument<LabelValue, Type>&&) noexcept = default;
253
254 /// @brief Label of this command line argument. Used to uniquely identify this command line
255 /// argument in a collection of command line arguments. Set at construction.
256 /// @return The label of this command line argument.
257 [[nodiscard]] static constexpr auto label() noexcept {
258 return LabelValue;
259 }
260
261 /// @brief Keys that can be used to specify this argument on the command line if it is a named
262 /// argument, or an empty collection if this argument is a positional argument. Set at
263 /// construction.
264 /// @return The keys that can be used to specify this command line argument.
265 [[nodiscard]] const std::vector<std::string>& keys() const noexcept {
266 return keys_;
267 }
268
269 /// @brief Description of this command line argument. Set at construction.
270 /// @return The description of this command line argument.
271 [[nodiscard]] std::string_view description() const noexcept {
272 return description_;
273 }
274
275 /// @brief Returns whether this singular command line argument has a default value.
276 /// @return True if this singular command line argument has a default value; false if it does not.
277 [[nodiscard]] bool has_default() const noexcept {
278 return default_value_.has_value();
279 }
280
281 /// @brief Default value of this singular command line argument if it is optional and non-boolean,
282 /// or std::nullopt otherwise. Set at construction.
283 /// @return The default value of this singular command line argument.
284 [[nodiscard]] const std::optional<Type>& default_value() const noexcept {
285 return default_value_;
286 }
287
288 /// @brief Returns whether this singular command line argument has a parsed value.
289 /// @return True if this singular command line argument has a parsed value; false if it does not.
290 [[nodiscard]] bool has_parsed() const noexcept {
291 return parsed_value_.has_value();
292 }
293
294 /// @brief Parsed value of this singular command line argument. Set when this argument is parsed
295 /// from the command line.
296 /// @return The parsed value of this singular command line argument.
297 [[nodiscard]] const std::optional<Type>& parsed_value() const noexcept {
298 return parsed_value_;
299 }
300
301 /// @brief Importance of this command line argument. A required argument must be provided by the
302 /// user on the command line, whereas an optional argument may or may not be provided by the user
303 /// on the command line. Set at construction.
304 /// @return The importance of this command line argument.
305 [[nodiscard]] lector::Importance importance() const noexcept {
306 return importance_;
307 }
308
309 /// @brief Form of this command line argument. A positional argument does not define any keys and
310 /// must be specified in a specific order on the command line, whereas a named argument defines
311 /// one or more keys and is specified on the command line by one of its keys. Named arguments can
312 /// be specified in any order on the command line.
313 /// @return The form of this command line argument.
314 [[nodiscard]] lector::Form form() const noexcept {
315 if (keys_.empty()) {
317 }
318 return lector::Form::Named;
319 }
320
321 /// @brief Arity of this command line argument. A singular argument can only appear once on the
322 /// command line, whereas a repeatable argument can appear multiple times.
323 /// @return The arity of this command line argument.
324 [[nodiscard]] static lector::Arity arity() noexcept {
326 }
327
328 /// @brief Value of this singular command line argument. Returns the parsed value if it exists;
329 /// otherwise, returns the default value.
330 /// @return The value of this singular command line argument.
331 /// @throws std::logic_error if this singular command line argument is missing both its parsed
332 /// value and its default value. Cannot occur in practice due to checks done at construction.
333 [[nodiscard]] const Type& parsed_or_default_value() const {
334 if (parsed_value_.has_value()) {
335 return parsed_value_.value();
336 }
337 if (default_value_.has_value()) {
338 return default_value_.value();
339 }
340 throw std::logic_error(
341 "No parsed or default value for argument '" + longest_key_with_value_type() + "'.");
342 }
343
344 /// @brief Sets the parsed value of this singular command line argument.
345 /// @param[in] value The parsed value to set.
346 /// @throws std::logic_error if this singular command line argument is default-constructed or if a
347 /// parsed value has already been set for this singular command line argument.
348 /// @throws std::invalid_argument if this singular command line argument is boolean and the parsed
349 /// value is false.
350 void set_parsed_value(const Type& value) {
351 if (description_.empty()) {
352 throw std::logic_error("Default-constructed arguments cannot parse values.");
353 }
354 if constexpr (std::is_same_v<Type, bool>) {
355 if (!value) {
356 throw std::invalid_argument("Boolean arguments can only be parsed as true.");
357 }
358 }
359 if (parsed_value_.has_value()) {
360 throw std::logic_error("A singular argument cannot have more than one parsed value.");
361 }
362 parsed_value_ = value;
363 }
364
365 /// @brief Prints the longest key of this command line argument with its associated value type as
366 /// a string of text.
367 /// @return The string of text that contains the longest key of this command line argument with
368 /// its associated value type.
369 [[nodiscard]] std::string longest_key_with_value_type() const {
370 if (keys_.empty()) {
371 return std::string{value_type()};
372 }
373 std::string result{longest_key()};
374 const std::string type{value_type()};
375 if (!type.empty()) {
376 result.push_back(' ');
377 result.append(type);
378 }
379 return result;
380 }
381
382 /// @brief Prints the keys and value type of this command line argument as a string of text.
383 /// @return The string of text that contains the keys and value type of this command line
384 /// argument.
385 [[nodiscard]] std::string keys_with_value_type() const {
386 if (keys_.empty()) {
387 return std::string{value_type()};
388 }
389 std::string result;
390 for (std::size_t index{0UL}; index < keys_.size(); ++index) {
391 const std::string type{value_type()};
392 result.append(keys_.at(index));
393 if (!type.empty()) {
394 result.push_back(' ');
395 result.append(type);
396 }
397 if (index + 1 < keys_.size()) {
398 result.append(", ");
399 }
400 }
401 return result;
402 }
403
404 /// @brief Prints the usage information of this command line argument as a string of text. The
405 /// usage information consists of this command line argument's longest key and value type,
406 /// enclosed in square braces if this command line argument is optional.
407 /// @return The string of text that contains the usage information of this command line argument.
408 [[nodiscard]] std::string usage() const {
410 return "[" + longest_key_with_value_type() + "]";
411 }
413 }
414
415 /// @brief Prints the options information of this command line argument as a string of text. The
416 /// options information consists of this command line argument's keys, value type, and
417 /// description.
418 /// @return The string of text that contains the options information of this command line
419 /// argument.
420 [[nodiscard]] std::string options() const {
421 std::string keys_with_value_type_{keys_with_value_type()};
422 if (keys_with_value_type_.empty()) {
423 return description_;
424 }
425 if (description_.empty()) {
426 return keys_with_value_type_;
427 }
428 return keys_with_value_type_ + " " + description_;
429 }
430
431 /// @brief Prints the execution of this command line argument as a string of text. The execution
432 /// consists of this command line argument's longest key and parsed value, if any.
433 /// @return The string of text that contains the execution of this command line argument.
434 [[nodiscard]] std::string execution() const {
435 if constexpr (std::is_same_v<Type, bool>) {
436 return execution_boolean();
437 } else {
438 return execution_non_boolean();
439 }
440 }
441
442private:
443 /// @brief If this command line argument is boolean, sets its default value to false. Boolean
444 /// command line arguments are always optional and always default to false.
445 void set_default_value_to_false_if_boolean() {
446 if constexpr (std::is_same_v<Type, bool>) {
447 default_value_ = false;
448 }
449 }
450
451 /// @brief Validates that this command line argument is not boolean. Called by constructors that
452 /// do not take keys. Boolean command line arguments must always specify one or more keys.
453 void validate_non_boolean_positional() const {
454 if constexpr (std::is_same_v<Type, bool>) {
455 throw std::invalid_argument("Boolean arguments must specify one or more keys.");
456 }
457 }
458
459 /// @brief Validates that this command line argument is not boolean. Called by constructors that
460 /// take default values. Boolean command line arguments are always optional and always default to
461 /// false, so they cannot specify default values.
462 void validate_non_boolean_default_value() const {
463 if constexpr (std::is_same_v<Type, bool>) {
464 throw std::invalid_argument(
465 "Boolean arguments cannot specify default values; they are always false by default.");
466 }
467 }
468
469 /// @brief Validates the keys of this command line argument.
470 /// @throws std::logic_error if the keys are missing or invalid.
471 void validate_keys() const {
472 if (keys_.empty()) {
473 throw std::logic_error("All named arguments must each have at least one key.");
474 }
475 for (const std::string& key : keys_) {
476 if (key.empty()) {
477 if (longest_key().empty()) {
478 throw std::logic_error("Named arguments cannot have empty keys.");
479 }
480 throw std::logic_error("Empty key in named argument '" + longest_key_with_value_type()
481 + "'. Named arguments cannot have empty keys.");
482 }
483 }
484 const std::size_t keys_size{keys_.size()};
485 for (std::size_t first{0UL}; first < keys_size; ++first) {
486 for (std::size_t second{first + static_cast<std::size_t>(1UL)}; second < keys_size;
487 ++second) {
488 if (keys_[first] == keys_[second]) {
489 throw std::logic_error(
490 "Duplicated key '" + keys_[first] + "' in named argument '"
491 + longest_key_with_value_type() + "'. Named arguments cannot have duplicate keys.");
492 }
493 }
494 }
495 };
496
497 /// @brief Validates the description of this command line argument.
498 /// @throws std::logic_error if the description of this command line argument is empty.
499 void validate_description() const {
500 if (description_.empty()) {
501 throw std::logic_error("Empty description in argument '" + longest_key_with_value_type()
502 + "'. All arguments must have descriptions.");
503 }
504 };
505
506 /// @brief Prints the value type of this command line argument as a string of text.
507 /// @return The string of text that contains the value type of this command line argument.
508 [[nodiscard]] constexpr std::string_view value_type() const {
509 if constexpr (std::is_same_v<Type, bool>) {
510 return "";
511 } else if constexpr (std::numeric_limits<Type>::is_integer) {
512 return "<number>";
513 } else if constexpr (std::is_floating_point_v<Type>) {
514 return "<value>";
515 } else if constexpr (
516 std::is_same_v<Type, std::string> || std::is_same_v<Type, std::string_view>) {
517 return "<text>";
518 } else if constexpr (std::is_same_v<Type, std::filesystem::path>) {
519 return "<path>";
520 } else {
521 return "<value>";
522 }
523 }
524
525 /// @brief Returns the longest key of this command line argument.
526 /// @return The longest key of this command line argument.
527 /// @throws std::logic_error if this command line argument has no keys.
528 [[nodiscard]] const std::string& longest_key() const {
529 if (keys_.empty()) {
530 throw std::logic_error("A named argument must have at least one key.");
531 }
532 std::size_t longest_key_index{0UL};
533 for (std::size_t index{1UL}; index < keys_.size(); ++index) {
534 if (keys_[index].size() > keys_[longest_key_index].size()) {
535 longest_key_index = index;
536 }
537 }
538 return keys_[longest_key_index];
539 }
540
541 /// @brief Prints the execution of this boolean command line argument as a string of text. The
542 /// execution consists of this boolean command line argument's longest key.
543 /// @return The string of text that contains the execution of this boolean command line argument.
544 [[nodiscard]] std::string execution_boolean() const {
545 if (parsed_value_.has_value() && parsed_value_.value()) {
546 return longest_key();
547 }
548 return std::string{};
549 }
550
551 /// @brief Prints the execution of this non-boolean command line argument as a string of text. The
552 /// execution consists of this non-boolean command line argument's longest key and parsed value,
553 /// if any.
554 /// @return The string of text that contains the execution of this non-boolean command line
555 /// argument.
556 [[nodiscard]] std::string execution_non_boolean() const {
557 if (parsed_value_.has_value()) {
558 if (keys_.empty()) {
559 return lector::quote_if_contains_whitespace(lector::print<Type>(parsed_value_.value()));
560 }
561 return longest_key() + " "
562 + lector::quote_if_contains_whitespace(lector::print<Type>(parsed_value_.value()));
563 }
564 return std::string{};
565 }
566
567 /// @brief Keys that can be used to specify this argument on the command line if it is a named
568 /// argument, or an empty collection if this argument is a positional argument. Set at
569 /// construction.
570 std::vector<std::string> keys_;
571
572 /// @brief Description of this command line argument. Set at construction.
573 std::string description_;
574
575 /// @brief Default value of this singular command line argument if it is optional and non-boolean,
576 /// or std::nullopt otherwise. Set at construction.
577 std::optional<Type> default_value_;
578
579 /// @brief Parsed value of this singular command line argument. Set when this argument is parsed
580 /// from the command line.
581 std::optional<Type> parsed_value_;
582
583 /// @brief Importance of this command line argument. A required argument must be provided by the
584 /// user on the command line, whereas an optional argument may or may not be provided by the user
585 /// on the command line. Set at construction.
587};
588
589/// @brief A repeatable command line argument.
590/// @tparam LabelValue Value of this command line argument's label. The label is used to uniquely
591/// identify this command line argument in a collection of command line arguments.
592/// @tparam Type The type of the value stored in this command line argument.
593template <auto LabelValue, typename Type>
595public:
596 using ValueType = Type;
597
598 /// @brief Default constructor. Initializes the repeatable command line argument with no keys, an
599 /// empty description, required importance, and no default values.
600 RepeatableArgument() noexcept = default;
601
602 /// @brief Constructor for a repeatable positional required command line argument. No default
603 /// values are needed.
604 /// @param[in] description The description of the command line argument.
605 /// @throws std::invalid_argument if the type is boolean or if the description is empty.
606 explicit RepeatableArgument(const std::string_view description) : description_{description} {
607 validate_non_boolean_positional();
608 validate_description();
609 }
610
611 /// @brief Constructor for a repeatable named required command line argument or a repeatable named
612 /// optional boolean command line argument. No default value is needed. Repeatable named boolean
613 /// command line arguments are always optional and always default to false.
614 /// @param[in] keys The keys used to specify the command line argument.
615 /// @param[in] description The description of the command line argument.
616 /// @throws std::invalid_argument if the keys are invalid or if the description is empty.
617 RepeatableArgument(const std::vector<std::string>& keys, const std::string_view description)
618 : keys_{keys}, description_{description},
619 importance_{
620 std::is_same_v<Type, bool> ? lector::Importance::Optional : lector::Importance::Required} {
621 validate_keys();
622 validate_description();
623 }
624
625 /// @brief Constructor for a repeatable positional optional non-boolean command line argument.
626 /// @param[in] description The description of the command line argument.
627 /// @param[in] default_values The default values of the command line argument, if any.
628 /// @throws std::invalid_argument if the type is boolean or if the description is empty.
629 RepeatableArgument(const std::string_view description, const std::vector<Type>& default_values)
630 : description_{description}, default_values_{default_values},
631 importance_{lector::Importance::Optional} {
632 validate_non_boolean_default_values();
633 validate_description();
634 }
635
636 /// @brief Constructor for a repeatable named optional non-boolean command line argument.
637 /// @param[in] keys The keys used to specify the command line argument.
638 /// @param[in] description The description of the command line argument.
639 /// @param[in] default_values The default values of the command line argument, if any.
640 /// @throws std::invalid_argument if the type is boolean, if the keys are invalid, or if the
641 /// description is empty.
642 RepeatableArgument(const std::vector<std::string>& keys, const std::string_view description,
643 const std::vector<Type>& default_values)
644 : keys_{keys}, description_{description}, default_values_{default_values},
645 importance_{lector::Importance::Optional} {
646 validate_non_boolean_default_values();
647 validate_keys();
648 validate_description();
649 }
650
651 /// @brief Destructor. Destroys this repeatable command line argument.
652 ~RepeatableArgument() noexcept = default;
653
654 /// @brief Copy constructor. Constructs a repeatable command line argument by copying another one.
655 RepeatableArgument(const lector::RepeatableArgument<LabelValue, Type>&) = default;
656
657 /// @brief Copy assignment operator. Assigns this repeatable command line argument by copying
658 /// another one.
659 /// @return This repeatable command line argument after the assignment.
660 lector::RepeatableArgument<LabelValue, Type>& operator=(
661 const lector::RepeatableArgument<LabelValue, Type>&) = default;
662
663 /// @brief Move constructor. Constructs a repeatable command line argument by moving another one.
664 RepeatableArgument(lector::RepeatableArgument<LabelValue, Type>&&) noexcept = default;
665
666 /// @brief Move assignment operator. Assigns this repeatable command line argument by moving
667 /// another one.
668 /// @return This repeatable command line argument after the assignment.
669 lector::RepeatableArgument<LabelValue, Type>& operator=(
670 lector::RepeatableArgument<LabelValue, Type>&&) noexcept = default;
671
672 /// @brief Label of this command line argument. Used to uniquely identify this command line
673 /// argument in a collection of command line arguments. Set at construction.
674 /// @return The label of this command line argument.
675 [[nodiscard]] static constexpr auto label() noexcept {
676 return LabelValue;
677 }
678
679 /// @brief Keys that can be used to specify this argument on the command line if it is a named
680 /// argument, or an empty collection if this argument is a positional argument. Set at
681 /// construction.
682 /// @return The keys that can be used to specify this command line argument.
683 [[nodiscard]] const std::vector<std::string>& keys() const noexcept {
684 return keys_;
685 }
686
687 /// @brief Description of this command line argument. Set at construction.
688 /// @return The description of this command line argument.
689 [[nodiscard]] std::string_view description() const noexcept {
690 return description_;
691 }
692
693 /// @brief Returns whether this repeatable command line argument has one or more default values.
694 /// @return True if this repeatable command line argument has one or more default values; false if
695 /// it has none.
696 [[nodiscard]] bool has_default() const noexcept {
697 return !default_values_.empty();
698 }
699
700 /// @brief Default values of this repeatable command line argument. Set at construction.
701 /// @return The default values of this repeatable command line argument.
702 [[nodiscard]] const std::vector<Type>& default_values() const noexcept {
703 return default_values_;
704 }
705
706 /// @brief Returns whether this repeatable command line argument has one or more parsed values.
707 /// @return True if this repeatable command line argument has one or more parsed values; false if
708 /// it has none.
709 [[nodiscard]] bool has_parsed() const noexcept {
710 return !parsed_values_.empty();
711 }
712
713 /// @brief Parsed values of this repeatable command line argument. Set when this argument is
714 /// parsed from the command line.
715 /// @return The parsed values of this repeatable command line argument.
716 [[nodiscard]] const std::vector<Type>& parsed_values() const noexcept {
717 return parsed_values_;
718 }
719
720 /// @brief Importance of this command line argument. A required argument must be provided by the
721 /// user on the command line, whereas an optional argument may or may not be provided by the user
722 /// on the command line. Set at construction.
723 /// @return The importance of this command line argument.
724 [[nodiscard]] lector::Importance importance() const noexcept {
725 return importance_;
726 }
727
728 /// @brief Form of this command line argument. A positional argument does not define any keys and
729 /// must be specified in a specific order on the command line, whereas a named argument defines
730 /// one or more keys and is specified on the command line by one of its keys. Named arguments can
731 /// be specified in any order on the command line.
732 /// @return The form of this command line argument.
733 [[nodiscard]] lector::Form form() const noexcept {
734 if (keys_.empty()) {
736 }
737 return lector::Form::Named;
738 }
739
740 /// @brief Arity of this command line argument. A repeatable argument can only appear once on the
741 /// command line, whereas a repeatable argument can appear multiple times.
742 /// @return The arity of this command line argument.
743 [[nodiscard]] static lector::Arity arity() noexcept {
745 }
746
747 /// @brief Values of this repeatable command line argument. Returns the parsed values if they
748 /// exist; otherwise, returns the default values.
749 /// @return The values of this repeatable command line argument.
750 [[nodiscard]] const std::vector<Type>& parsed_or_default_values() const {
751 if (!parsed_values_.empty()) {
752 return parsed_values_;
753 }
754 return default_values_;
755 }
756
757 /// @brief Inserts an additional parsed value into this repeatable command line argument.
758 /// @param[in] value The parsed value to insert.
759 /// @throws std::logic_error if this repeatable command line argument is default-constructed.
760 /// @throws std::invalid_argument if this repeatable command line argument is boolean and the
761 /// parsed value is false.
762 void set_parsed_value(const Type& value) {
763 if (description_.empty()) {
764 throw std::logic_error("Default-constructed arguments cannot parse values.");
765 }
766 if constexpr (std::is_same_v<Type, bool>) {
767 if (!value) {
768 throw std::invalid_argument("Boolean arguments can only be parsed as true.");
769 }
770 }
771 parsed_values_.push_back(value);
772 }
773
774 /// @brief Prints the longest key of this command line argument with its associated value type as
775 /// a string of text.
776 /// @return The string of text that contains the longest key of this command line argument with
777 /// its associated value type.
778 [[nodiscard]] std::string longest_key_with_value_type() const {
779 if (keys_.empty()) {
780 return std::string{value_type()};
781 }
782 std::string result{longest_key()};
783 const std::string type{value_type()};
784 if (!type.empty()) {
785 result.push_back(' ');
786 result.append(type);
787 }
788 return result;
789 }
790
791 /// @brief Prints the keys and value type of this command line argument as a string of text.
792 /// @return The string of text that contains the keys and value type of this command line
793 /// argument.
794 [[nodiscard]] std::string keys_with_value_type() const {
795 if (keys_.empty()) {
796 return std::string{value_type()};
797 }
798 std::string result;
799 for (std::size_t index{0UL}; index < keys_.size(); ++index) {
800 const std::string type{value_type()};
801 result.append(keys_.at(index));
802 if (!type.empty()) {
803 result.push_back(' ');
804 result.append(type);
805 }
806 if (index + 1 < keys_.size()) {
807 result.append(", ");
808 }
809 }
810 return result;
811 }
812
813 /// @brief Prints the usage information of this command line argument as a string of text. The
814 /// usage information consists of this command line argument's longest key and value type,
815 /// enclosed in square braces if this command line argument is optional.
816 /// @return The string of text that contains the usage information of this command line argument.
817 [[nodiscard]] std::string usage() const {
818 const std::string longest_key_with_value_type_{longest_key_with_value_type()};
820 return "[" + longest_key_with_value_type_ + "] ...";
821 }
822 if (longest_key_with_value_type_.empty()) {
823 return "...";
824 }
825 return longest_key_with_value_type_ + " ...";
826 }
827
828 /// @brief Prints the options information of this command line argument as a string of text. The
829 /// options information consists of this command line argument's keys, value type, and
830 /// description.
831 /// @return The string of text that contains the options information of this command line
832 /// argument.
833 [[nodiscard]] std::string options() const {
834 std::string keys_with_value_type_{keys_with_value_type()};
835 if (keys_with_value_type_.empty()) {
836 return description_;
837 }
838 if (description_.empty()) {
839 return keys_with_value_type_;
840 }
841 return keys_with_value_type_ + " " + description_;
842 }
843
844 /// @brief Prints the execution of this command line argument as a string of text. The execution
845 /// consists of this command line argument's longest key and parsed values, if any.
846 /// @return The string of text that contains the execution of this command line argument.
847 [[nodiscard]] std::string execution() const {
848 if constexpr (std::is_same_v<Type, bool>) {
849 return execution_boolean();
850 } else {
851 return execution_non_boolean();
852 }
853 }
854
855private:
856 /// @brief Validates that this command line argument is not boolean. Called by constructors that
857 /// do not take keys. Boolean command line arguments must always specify one or more keys.
858 void constexpr validate_non_boolean_positional() const {
859 if constexpr (std::is_same_v<Type, bool>) {
860 throw std::invalid_argument("Boolean arguments must specify one or more keys.");
861 }
862 }
863
864 /// @brief Validates that this command line argument is not boolean. Called by constructors that
865 /// take default values. Boolean command line arguments are always optional and always default to
866 /// false, so they cannot specify default values.
867 void constexpr validate_non_boolean_default_values() const {
868 if constexpr (std::is_same_v<Type, bool>) {
869 throw std::invalid_argument(
870 "Boolean arguments cannot specify default values; they are always false by default.");
871 }
872 }
873
874 /// @brief Validates the keys of this command line argument.
875 /// @throws std::logic_error if the keys are missing or invalid.
876 void validate_keys() const {
877 if (keys_.empty()) {
878 throw std::logic_error("All named arguments must each have at least one key.");
879 }
880 for (const std::string& key : keys_) {
881 if (key.empty()) {
882 if (longest_key().empty()) {
883 throw std::logic_error("Named arguments cannot have empty keys.");
884 }
885 throw std::logic_error("Empty key in named argument '" + longest_key_with_value_type()
886 + "'. Named arguments cannot have empty keys.");
887 }
888 }
889 const std::size_t keys_size{keys_.size()};
890 for (std::size_t first{0UL}; first < keys_size; ++first) {
891 for (std::size_t second{first + static_cast<std::size_t>(1UL)}; second < keys_size;
892 ++second) {
893 if (keys_[first] == keys_[second]) {
894 throw std::logic_error(
895 "Duplicated key '" + keys_[first] + "' in named argument '"
896 + longest_key_with_value_type() + "'. Named arguments cannot have duplicate keys.");
897 }
898 }
899 }
900 };
901
902 /// @brief Validates the description of this command line argument.
903 /// @throws std::logic_error if the description of this command line argument is empty.
904 void validate_description() const {
905 if (description_.empty()) {
906 throw std::logic_error("Empty description in argument '" + longest_key_with_value_type()
907 + "'. All arguments must have descriptions.");
908 }
909 };
910
911 /// @brief Prints the value type of this command line argument as a string of text.
912 /// @return The string of text that contains the value type of this command line argument.
913 [[nodiscard]] constexpr std::string_view value_type() const {
914 if constexpr (std::is_same_v<Type, bool>) {
915 return "";
916 } else if constexpr (std::numeric_limits<Type>::is_integer) {
917 return "<number>";
918 } else if constexpr (std::is_floating_point_v<Type>) {
919 return "<value>";
920 } else if constexpr (
921 std::is_same_v<Type, std::string> || std::is_same_v<Type, std::string_view>) {
922 return "<text>";
923 } else if constexpr (std::is_same_v<Type, std::filesystem::path>) {
924 return "<path>";
925 } else {
926 return "<value>";
927 }
928 }
929
930 /// @brief Returns the longest key of this command line argument.
931 /// @return The longest key of this command line argument.
932 /// @throws std::logic_error if this command line argument has no keys.
933 [[nodiscard]] const std::string& longest_key() const {
934 if (keys_.empty()) {
935 throw std::logic_error("A named argument must have at least one key.");
936 }
937 std::size_t longest_key_index{0UL};
938 for (std::size_t index{1UL}; index < keys_.size(); ++index) {
939 if (keys_[index].size() > keys_[longest_key_index].size()) {
940 longest_key_index = index;
941 }
942 }
943 return keys_[longest_key_index];
944 }
945
946 /// @brief Prints the execution of this boolean command line argument as a string of text. The
947 /// execution consists of this boolean command line argument's longest key for each parsed value.
948 /// @return The string of text that contains the execution of this boolean command line argument.
949 [[nodiscard]] std::string execution_boolean() const {
950 std::string result;
951 for (const Type& parsed_value : parsed_values_) {
952 if (parsed_value) {
953 if (!result.empty()) {
954 result.push_back(' ');
955 }
956 result.append(longest_key());
957 }
958 }
959 return result;
960 }
961
962 /// @brief Prints the execution of this non-boolean command line argument as a string of text. The
963 /// execution consists of this non-boolean command line argument's longest key and parsed values,
964 /// if any.
965 /// @return The string of text that contains the execution of this non-boolean command line
966 /// argument.
967 [[nodiscard]] std::string execution_non_boolean() const {
968 if (keys_.empty()) {
969 std::string result;
970 for (const Type& parsed_value : parsed_values_) {
971 if (!result.empty()) {
972 result.push_back(' ');
973 }
974 result.append(lector::quote_if_contains_whitespace(lector::print<Type>(parsed_value)));
975 }
976 return result;
977 }
978 std::string result;
979 for (const Type& parsed_value : parsed_values_) {
980 if (!result.empty()) {
981 result.push_back(' ');
982 }
983 result.append(longest_key());
984 result.push_back(' ');
985 result.append(lector::quote_if_contains_whitespace(lector::print<Type>(parsed_value)));
986 }
987 return result;
988 }
989
990 /// @brief Keys that can be used to specify this argument on the command line if it is a named
991 /// argument, or an empty collection if this argument is a positional argument. Set at
992 /// construction.
993 std::vector<std::string> keys_;
994
995 /// @brief Description of this command line argument. Set at construction.
996 std::string description_;
997
998 /// @brief Default values of this repeatable command line argument. Set at construction.
999 std::vector<Type> default_values_;
1000
1001 /// @brief Parsed values of this repeatable command line argument. Set when this argument is
1002 /// parsed from the command line.
1003 std::vector<Type> parsed_values_;
1004
1005 /// @brief Importance of this command line argument. A required argument must be provided by the
1006 /// user on the command line, whereas an optional argument may or may not be provided by the user
1007 /// on the command line. Set at construction.
1009};
1010
1011/// @brief Configuration of the help information of a collection of command line arguments.
1012struct Configuration final {
1013public:
1014 /// @brief Title of the application whose command line arguments are to be parsed. When the
1015 /// collection of command line arguments' help information is printed, this title appears first,
1016 /// before its usage information. Optional and empty by default, in which case no title is
1017 /// printed.
1018 std::optional<std::string> title{std::nullopt};
1019
1020 /// @brief Description of the application whose command line arguments are to be parsed. When the
1021 /// collection of command line arguments' help information is printed, this description appears
1022 /// between its usage information and its options information. Optional and empty by default, in
1023 /// which case no description is printed.
1024 std::optional<std::string> description{std::nullopt};
1025
1026 /// @brief Additional notes pertaining to the application whose command line arguments are to be
1027 /// parsed. When the collection of command line arguments' help information is printed, these
1028 /// notes appear last, after its options information. Optional and empty by default, in which case
1029 /// no notes are printed.
1030 std::optional<std::string> notes{std::nullopt};
1031};
1032
1033/// @brief Type trait used to extract a command line argument from a collection of command line
1034/// arguments, using only its Label.
1035/// @tparam Label The label of the command line argument to extract.
1036/// @tparam ...ArgumentTypes The variadic list of argument types in the collection of command line
1037/// arguments.
1038template <auto Label, typename... ArgumentTypes>
1039struct FindArgumentByLabel;
1040
1041/// @brief Helper to provide short-circuit evaluation for lector::FindArgumentByLabel.
1042/// @tparam Label The label of the command line argument to extract.
1043/// @tparam Match Whether the command line argument was found or not.
1044/// @tparam FirstArgument The type of the command line argument to extract.
1045/// @tparam ...RemainingArgumentTypes The variadic list of argument types in the collection of
1046/// command line arguments, excluding the command line argument to extract.
1047template <auto Label, bool Match, typename FirstArgument, typename... RemainingArgumentTypes>
1048struct FindArgumentHelper;
1049
1050/// @brief True branch of the short-circuit evaluation helper. The command line argument has been
1051/// found and will now be returned; the remaining command line arguments do not need to be searched.
1052/// @tparam Label The label of the command line argument to extract.
1053/// @tparam FirstArgument The type of the command line argument to extract.
1054/// @tparam ...RemainingArgumentTypes The variadic list of argument types in the collection of
1055/// command line arguments, excluding the command line argument to extract.
1056template <auto Label, typename FirstArgument, typename... RemainingArgumentTypes>
1057struct FindArgumentHelper<Label, true, FirstArgument, RemainingArgumentTypes...> {
1058 using type = FirstArgument;
1059};
1060
1061/// @brief False branch of the short-circuit evaluation helper. The command line argument has not
1062/// yet been found and the remaining command line arguments should be searched.
1063/// @tparam Label The label of the command line argument to extract.
1064/// @tparam FirstArgument The type of the command line argument to extract.
1065/// @tparam ...RemainingArgumentTypes The variadic list of argument types in the collection of
1066/// command line arguments, excluding the command line argument to extract.
1067template <auto Label, typename FirstArgument, typename... RemainingArgumentTypes>
1068struct FindArgumentHelper<Label, false, FirstArgument, RemainingArgumentTypes...> {
1069 using type = typename FindArgumentByLabel<Label, RemainingArgumentTypes...>::type;
1070};
1071
1072/// @brief Type trait specialization used to extract a command line argument from a collection of
1073/// command line arguments, using its Label and the types of the remaining command line arguments in
1074/// the collection.
1075/// @tparam Label The label of the command line argument to extract.
1076/// @tparam FirstArgument The type of the command line argument to extract.
1077/// @tparam ...RemainingArgumentTypes The variadic list of argument types in the collection of
1078/// command line arguments, excluding the command line argument to extract.
1079template <auto Label, typename FirstArgument, typename... RemainingArgumentTypes>
1080struct FindArgumentByLabel<Label, FirstArgument, RemainingArgumentTypes...> {
1081 using type = typename FindArgumentHelper<Label, (FirstArgument::label() == Label), FirstArgument,
1082 RemainingArgumentTypes...>::type;
1083};
1084
1085/// @brief Data structure that validates at compilation time that a specified variadic list of types
1086/// are unique. Base data structure that contains an empty list of types and returns true.
1087/// @tparam ...Types Variadic list of types to check for uniqueness.
1088template <auto... Types>
1089struct AreUnique : std::true_type {};
1090
1091/// @brief Data structure that validates at compilation time that a specified variadic list of types
1092/// are unique. Recursively compares a first type against the remaining variadic list of types.
1093/// @tparam FirstType The first type to compare.
1094/// @tparam ...RemainingTypes The remaining types in the variadic list of types to compare.
1095template <auto FirstType, auto... RemainingTypes>
1096struct AreUnique<FirstType, RemainingTypes...>
1097 : std::bool_constant<((FirstType != RemainingTypes) && ...)
1098 && AreUnique<RemainingTypes...>::value> {};
1099
1100/// @brief A collection of command line arguments that can be parsed from argc and argv.
1101/// @tparam ...ArgumentTypes Variadic list of the types of the command line arguments in this
1102/// collection.
1103template <typename... ArgumentTypes>
1104class Arguments final {
1105public:
1106 /// @brief Compile-time check that all arguments have unique labels.
1107 static_assert(AreUnique<ArgumentTypes::label()...>::value,
1108 "Duplicate argument labels detected. Each argument must have a unique label.");
1109
1110 /// @brief Constructor. Constructs a collection of command line arguments from a configuration
1111 /// data structure and a variadic list of command line arguments.
1112 /// @param[in] configuration The configuration data structure.
1113 /// @param[in] ...arguments The variadic list of command line arguments.
1114 /// @throws std::logic_error if the command line arguments are invalid.
1115 explicit Arguments(lector::Configuration&& configuration, ArgumentTypes... arguments)
1116 : configuration_{std::move(configuration)}, arguments_{std::move(arguments)...} {
1117 validate_positional_arguments();
1118 validate_keys();
1119 }
1120
1121 /// @brief Constructor. Constructs a collection of command line arguments from a variadic list of
1122 /// command line arguments.
1123 /// @param[in] ...arguments The variadic list of command line arguments.
1124 /// @throws std::logic_error if the command line arguments are invalid.
1125 explicit Arguments(ArgumentTypes... arguments) : arguments_{std::move(arguments)...} {
1126 validate_positional_arguments();
1127 validate_keys();
1128 }
1129
1130 /// @brief Destructor. Destroys this collection of command line arguments.
1131 ~Arguments() noexcept = default;
1132
1133 /// @brief Copy constructor. Constructs a collection of command line argument by copying another
1134 /// one.
1135 Arguments(const lector::Arguments<ArgumentTypes...>&) = default;
1136
1137 /// @brief Copy assignment operator. Assigns this collection of command line argument by copying
1138 /// another one.
1139 /// @return This collection of command line argument after the assignment.
1140 lector::Arguments<ArgumentTypes...>& operator=(
1141 const lector::Arguments<ArgumentTypes...>&) = default;
1142
1143 /// @brief Move constructor. Constructs a collection of command line argument by moving another
1144 /// one.
1145 Arguments(lector::Arguments<ArgumentTypes...>&&) noexcept = default;
1146
1147 /// @brief Move assignment operator. Assigns this collection of command line argument by moving
1148 /// another one.
1149 /// @return This collection of command line argument after the assignment.
1150 lector::Arguments<ArgumentTypes...>& operator=(
1151 lector::Arguments<ArgumentTypes...>&&) noexcept = default;
1152
1153 /// @brief Parses argc and argv to populate the parsed values of the command line arguments in
1154 /// this collection. This method should be called before calling the validate() method.
1155 /// @param[in] argc The number of command line arguments, including the executable path.
1156 /// @param[in] argv The array of C-strings that represents the command line arguments, starting
1157 /// with the executable path.
1158 /// @throws std::invalid_argument if an invalid, unknown, duplicated, or missing argument is
1159 /// encountered.
1160 void parse(const int argc, char* argv[]) {
1161 parse_executable_path(argc, argv);
1162 const std::vector<std::string_view> positional_tokens{parse_named_arguments(argc, argv)};
1163 parse_positional_arguments(positional_tokens);
1164 }
1165
1166 /// @brief Validates that all required arguments have each successfully parsed a value from the
1167 /// command line. Should only be called after the parse() method has been called. If any command
1168 /// line arguments require special consideration, such as --version or --help flags, they should
1169 /// be handled before calling this method.
1170 /// @throws std::invalid_argument if any required arguments are lacking parsed values.
1171 void validate() const {
1172 std::apply(
1173 [&](const auto&... argument) {
1174 (..., [&] {
1175 if (argument.importance() == lector::Importance::Required && !argument.has_parsed()) {
1176 throw std::invalid_argument(
1177 "Missing required argument '" + argument.longest_key_with_value_type() + "'.");
1178 }
1179 }());
1180 },
1181 arguments_);
1182 }
1183
1184 /// @brief Returns the configuration of the help information of this collection of command line
1185 /// arguments.
1186 /// @return The configuration of the help information of this collection of command line
1187 /// arguments.
1188 [[nodiscard]] const lector::Configuration& configuration() const {
1189 return configuration_;
1190 }
1191
1192 /// @brief Returns the executable path of this collection of command line arguments. If the
1193 /// command line arguments have not yet been parsed from argc and argv, this path is empty.
1194 /// @return The executable path of this collection of command line arguments.
1195 [[nodiscard]] const std::filesystem::path& executable_path() const {
1196 return executable_path_;
1197 }
1198
1199 /// @brief Returns a specified command line argument from this collection.
1200 /// @tparam Label The label of the command line argument to return.
1201 /// @return The specified command line argument.
1202 template <auto Label>
1203 [[nodiscard]] const auto& get() const {
1204 using Type = typename lector::FindArgumentByLabel<Label, ArgumentTypes...>::type;
1205 return std::get<Type>(arguments_);
1206 }
1207
1208 /// @brief Prints the usage information of this collection of command line arguments as a string
1209 /// of text. The usage information consists of each argument's longest key and value type,
1210 /// enclosed in square braces for optional command line arguments.
1211 /// @return The string of text that contains the usage information of this collection of command
1212 /// line arguments.
1213 [[nodiscard]] std::string usage() const {
1214 return usage(std::numeric_limits<std::size_t>::max());
1215 }
1216
1217 /// @brief Prints the usage information of this collection of command line arguments as a string
1218 /// of text. The usage information consists of each argument's longest key and value type,
1219 /// enclosed in square braces for optional command line arguments. Lines are wrapped.
1220 /// @param[in] line_length The desired line length to use when wrapping. Must be strictly greater
1221 /// than zero. The recommended value is 80. The actual line length may be longer if the string of
1222 /// text contains very long words whose lengths exceed the desired line length.
1223 /// @return The string of text that contains the usage information of this collection of command
1224 /// line arguments.
1225 /// @throws std::invalid_argument if the desired line length is zero.
1226 [[nodiscard]] std::string usage(const std::size_t line_length) const {
1227 validate_line_length(line_length);
1228 std::string result;
1229 result.append(executable_path_.filename().string());
1230 std::apply(
1231 [&](const auto&... argument) {
1232 (..., [&] {
1233 result.push_back(' ');
1234 result.append(argument.usage());
1235 }());
1236 },
1237 arguments_);
1238 return result;
1239 }
1240
1241 /// @brief Prints the options information of this collection of command line argument as a string
1242 /// of text. The options information consists of the list of each command line argument's keys,
1243 /// value type, and description.
1244 /// @return The string of text that contains the options information of this collection of command
1245 /// line arguments.
1246 [[nodiscard]] std::string options() const {
1247 return options(std::numeric_limits<std::size_t>::max());
1248 }
1249
1250 /// @brief Prints the options information of this collection of command line argument as a string
1251 /// of text. The options information consists of the list of each command line argument's keys,
1252 /// value type, and description. Lines are wrapped.
1253 /// @param[in] line_length The desired line length to use when wrapping. Must be strictly greater
1254 /// than zero. The recommended value is 80. The actual line length may be longer if the string of
1255 /// text contains very long words whose lengths exceed the desired line length.
1256 /// @return The string of text that contains the options information of this collection of command
1257 /// line arguments.
1258 /// @throws std::invalid_argument if the desired line length is zero.
1259 [[nodiscard]] std::string options(const std::size_t line_length) const {
1260 validate_line_length(line_length);
1261 // Compute the text formatting dimensions.
1262 constexpr std::size_t gutter_width{2UL};
1263 const std::size_t maximum_first_column_width{
1264 (line_length - gutter_width) / static_cast<std::size_t>(2UL)};
1265 const std::size_t maximum_length_of_keys_with_value_types_{
1266 maximum_length_of_keys_with_value_type()};
1267 const std::size_t first_column_width{
1268 std::min(maximum_length_of_keys_with_value_types_, maximum_first_column_width)};
1269 const std::size_t second_column_width{line_length - gutter_width - first_column_width};
1270 // Obtain the number of command line arguments.
1271 constexpr std::size_t argument_count{std::tuple_size_v<decltype(arguments_)>};
1272 // Iterate through the command line arguments and update the resulting string of text.
1273 std::string result;
1274 std::size_t argument_index{0UL};
1275 std::apply(
1276 [&](const auto&... argument) {
1277 (..., [&] {
1278 result.append(lector::collate_and_left_align(
1279 argument.keys_with_value_type(), first_column_width, argument.description(),
1280 second_column_width));
1281 ++argument_index;
1282 if (argument_index < argument_count) {
1283 result.push_back('\n');
1284 }
1285 }());
1286 },
1287 arguments_);
1288 return result;
1289 }
1290
1291 /// @brief Prints the help information of this collection of command line arguments as a string of
1292 /// text. The help information consists of this collection of command line arguments' title, usage
1293 /// information, description, options information, and notes.
1294 /// @return The string of text that contains the help information of this collection of command
1295 /// line arguments.
1296 [[nodiscard]] std::string help() const {
1297 return help(std::numeric_limits<std::size_t>::max());
1298 }
1299
1300 /// @brief Prints the help information of this collection of command line arguments as a string of
1301 /// text. The help information consists of this collection of command line arguments' title, usage
1302 /// information, description, options information, and notes. Lines are wrapped.
1303 /// @param[in] line_length The desired line length to use when wrapping. Must be strictly greater
1304 /// than zero. The recommended value is 80. The actual line length may be longer if the string of
1305 /// text contains very long words whose lengths exceed the desired line length.
1306 /// @return The string of text that contains the help information of this collection of command
1307 /// line arguments.
1308 /// @throws std::invalid_argument if the desired line length is zero.
1309 [[nodiscard]] std::string help(const std::size_t line_length) const {
1310 validate_line_length(line_length);
1311 std::string result;
1312 if (configuration_.title.has_value() && !configuration_.title.value().empty()) {
1313 result.append(lector::wrap_and_left_align(configuration_.title.value(), line_length));
1314 }
1315 const std::string usage_{lector::wrap_and_left_align(usage(), line_length)};
1316 if (!usage_.empty()) {
1317 if (!result.empty()) {
1318 result.push_back('\n');
1319 result.push_back('\n');
1320 }
1321 result.append("Usage:\n");
1322 result.append(usage_);
1323 }
1324 if (configuration_.description.has_value() && !configuration_.description.value().empty()) {
1325 if (!result.empty()) {
1326 result.push_back('\n');
1327 result.push_back('\n');
1328 }
1329 result.append(lector::wrap_and_left_align(configuration_.description.value(), line_length));
1330 }
1331 const std::string options_{options(line_length)};
1332 if (!options_.empty()) {
1333 if (!result.empty()) {
1334 result.push_back('\n');
1335 result.push_back('\n');
1336 }
1337 result.append("Options:\n");
1338 result.append(options_);
1339 }
1340 if (configuration_.notes.has_value() && !configuration_.notes.value().empty()) {
1341 if (!result.empty()) {
1342 result.push_back('\n');
1343 result.push_back('\n');
1344 }
1345 result.append(lector::wrap_and_left_align(configuration_.notes.value(), line_length));
1346 }
1347 return result;
1348 }
1349
1350 /// @brief Prints the execution of this collection of command line argument as a string of text.
1351 /// The execution consists of the executable path followed by each argument's longest key and
1352 /// corresponding parsed value, if any.
1353 /// @return The string of text that contains the execution of this collection of command line
1354 /// argument.
1355 [[nodiscard]] std::string execution() const {
1356 return execution(std::numeric_limits<std::size_t>::max());
1357 }
1358
1359 /// @brief Prints the execution of this collection of command line argument as a string of text.
1360 /// The execution consists of the executable path followed by each argument's longest key and
1361 /// corresponding parsed value, if any. Lines are wrapped.
1362 /// @param[in] line_length The desired line length to use when wrapping. Must be strictly greater
1363 /// than zero. The recommended value is 80. The actual line length may be longer if the string of
1364 /// text contains very long words whose lengths exceed the desired line length.
1365 /// @return The string of text that contains the execution of this collection of command line
1366 /// argument.
1367 /// @throws std::invalid_argument if the desired line length is zero.
1368 [[nodiscard]] std::string execution(const std::size_t line_length) const {
1369 validate_line_length(line_length);
1370 std::string printed_execution_arguments;
1371 std::apply(
1372 [&](const auto&... argument) {
1373 (..., [&] {
1374 const std::string argument_execution{argument.execution()};
1375 if (!printed_execution_arguments.empty() && !argument_execution.empty()) {
1376 printed_execution_arguments.push_back(' ');
1377 }
1378 printed_execution_arguments.append(argument_execution);
1379 }());
1380 },
1381 arguments_);
1382 if (printed_execution_arguments.empty()) {
1383 return executable_path_.string();
1384 }
1386 executable_path_.string() + " " + printed_execution_arguments, line_length);
1387 }
1388
1389private:
1390 /// @brief Default length to use when wrapping lines when printing usage information, options
1391 /// information, or help information as a string of text. The actual line length may be longer if
1392 /// the string of text contains very long words whose lengths exceed the desired line length.
1393 static constexpr std::size_t default_line_length_{80UL};
1394
1395 /// @brief The best matching argument for a command line argument token during parsing. The best
1396 /// matching argument is the argument with the longest matching key, and if there are multiple
1397 /// arguments with keys of the same length that match, then the best matching argument is the one
1398 /// that is matched by a non-inline key rather than an inline key. Used to avoid shadowing when
1399 /// multiple arguments have keys that are prefixes of each other, and to prefer non-inline matches
1400 /// over inline matches when the key lengths are equal.
1401 struct BestArgument final {
1402 /// @brief Index of this argument in the tuple of arguments. Used to identify this argument
1403 /// during parsing.
1404 std::size_t index{0UL};
1405
1406 /// @brief Length of the longest matching key for this argument. Used to avoid shadowing when
1407 /// multiple arguments have keys that are prefixes of each other. For example, if one argument
1408 /// has the key "key" and another argument has the key "key_long", then the argument with the
1409 /// key "key_long" should be matched for the command line argument "key_long=value", not the
1410 /// argument with the key "key".
1411 std::size_t key_length{0UL};
1412
1413 /// @brief Whether this argument was matched by an inline key of the form "key=value" rather
1414 /// than a whitespace-separated key-value pair of the form "key value". Used to prefer
1415 /// non-inline matches over inline matches when the key lengths are equal. For example, if one
1416 /// argument has the key "key" and another argument has the key "key_long", then the argument
1417 /// with the key "key" should be matched for the command line argument "key=value", not the
1418 /// argument with the key "key_long".
1419 bool is_inline{false};
1420 };
1421
1422 /// @brief Validates that a specified line length is strictly greater than zero.
1423 /// @param[in] line_length The desired line length to use when wrapping. Must be strictly greater
1424 /// than zero.
1425 /// @throws std::invalid_argument if the desired line length is zero.
1426 static void validate_line_length(const std::size_t line_length) {
1427 if (line_length <= static_cast<std::size_t>(0UL)) {
1428 throw std::invalid_argument("Invalid line length. Must be strictly greater than zero.");
1429 }
1430 }
1431
1432 /// @brief Parses the executable path from argc and argv. Called by the lector::Arguments::parse
1433 /// method.
1434 /// @param[in] argc The number of command line arguments, including the executable path.
1435 /// @param[in] argv The array of C-strings that represents the command line arguments, starting
1436 /// with the executable path.
1437 void parse_executable_path(const int argc, char* argv[]) {
1438 if (argc > 0) {
1439 executable_path_ = argv[0];
1440 }
1441 }
1442
1443 /// @brief Parses argc and argv, except for the executable path, and attempts to match them to the
1444 /// named arguments. Starts at the second argument in argv. Called by the lector::Arguments::parse
1445 /// method. Remaining arguments that could not be matched to named arguments are treated as
1446 /// positional arguments and returned.
1447 /// @param[in] argc The number of command line arguments, including the executable path.
1448 /// @param[in] argv The array of C-strings that represents the command line arguments, starting
1449 /// with the executable path.
1450 /// @return Collection of the remaining command line arguments that could not be matched to named
1451 /// arguments, which are treated as positional arguments.
1452 /// @throws std::invalid_argument if an invalid, unknown, duplicated, or missing argument is
1453 /// encountered.
1454 [[nodiscard]] std::vector<std::string_view> parse_named_arguments(const int argc, char* argv[]) {
1455 std::vector<std::string_view> positional_tokens;
1456 const std::size_t count{static_cast<std::size_t>(argc)};
1457 for (std::size_t argv_index{1UL}; argv_index < count; ++argv_index) {
1458 const std::string_view token{argv[argv_index]};
1459 const std::optional<BestArgument> best_argument{find_best_argument(token)};
1460 if (best_argument.has_value()) {
1461 std::size_t current_index{0UL};
1462 std::apply(
1463 [&](auto&... argument) {
1464 (..., [&] {
1465 if (current_index == best_argument->index) {
1466 populate_argument(argument, best_argument.value(), argc, argv, argv_index);
1467 }
1468 ++current_index;
1469 }());
1470 },
1471 arguments_);
1472 } else {
1473 // If no named argument key matches the current argv token, treat it as the value of a
1474 // positional argument.
1475 positional_tokens.push_back(token);
1476 }
1477 }
1478 return positional_tokens;
1479 }
1480
1481 /// @brief Parses the remaining command line arguments that could not be matched to named
1482 /// arguments as positional arguments. Called by the lector::Arguments::parse method.
1483 /// @param[in] positional_tokens Collection of the remaining command line arguments that could not
1484 /// be matched to named arguments, which are treated as positional arguments.
1485 /// @throws std::invalid_argument if too many positional arguments are provided or if a positional
1486 /// argument cannot be parsed.
1487 void parse_positional_arguments(const std::vector<std::string_view>& positional_tokens) {
1488 std::size_t positional_token_index{0UL};
1489 std::apply(
1490 [&](auto&... argument) {
1491 (..., [&] {
1492 if (argument.form() == lector::Form::Positional) {
1493 using Type = typename std::decay_t<decltype(argument)>::ValueType;
1494 if (argument.arity() == lector::Arity::Singular) {
1495 if (positional_token_index < positional_tokens.size()) {
1496 const std::string raw_value{positional_tokens[positional_token_index]};
1497 const std::optional<Type> parsed_value{lector::parse<Type>(raw_value)};
1498 if (parsed_value.has_value()) {
1499 argument.set_parsed_value(parsed_value.value());
1500 } else {
1501 throw std::invalid_argument("Invalid value '" + raw_value + "' for argument '"
1502 + argument.longest_key_with_value_type() + "'.");
1503 }
1504 ++positional_token_index;
1505 }
1506 } else {
1507 // Repeatable arity: consume all remaining tokens.
1508 while (positional_token_index < positional_tokens.size()) {
1509 const std::string raw_value{positional_tokens[positional_token_index]};
1510 const std::optional<Type> parsed_value{lector::parse<Type>(raw_value)};
1511 if (parsed_value.has_value()) {
1512 argument.set_parsed_value(parsed_value.value());
1513 } else {
1514 throw std::invalid_argument("Invalid value '" + raw_value + "' for argument '"
1515 + argument.longest_key_with_value_type() + "'.");
1516 }
1517 ++positional_token_index;
1518 }
1519 }
1520 }
1521 }());
1522 },
1523 arguments_);
1524 validate_all_positional_tokens_matched(positional_tokens, positional_token_index);
1525 }
1526
1527 /// @brief Finds the best matching argument for a command line argument token.
1528 /// @param[in] token The command line argument token to match.
1529 /// @return The best matching argument.
1530 /// @throws std::invalid_argument if an unknown argument is encountered.
1531 [[nodiscard]] std::optional<BestArgument> find_best_argument(const std::string_view token) const {
1532 std::optional<BestArgument> best;
1533 std::size_t argument_index{0UL};
1534 std::apply(
1535 [&](const auto&... argument) {
1536 (..., [&] {
1537 for (const std::string& argument_key : argument.keys()) {
1538 const std::optional<BestArgument> exact_match{
1539 try_exact_match(token, argument_index, argument_key)};
1540 if (exact_match.has_value()) {
1541 if (!best.has_value() || exact_match->key_length > best->key_length
1542 || (exact_match->key_length == best->key_length && best->is_inline)) {
1543 best = exact_match;
1544 }
1545 continue;
1546 }
1547 const std::optional<BestArgument> inline_match{
1548 try_inline_match<decltype(argument)>(token, argument_index, argument_key)};
1549 if (inline_match.has_value()
1550 && (!best.has_value() || inline_match->key_length > best->key_length)) {
1551 best = inline_match;
1552 }
1553 }
1554 ++argument_index;
1555 }());
1556 },
1557 arguments_);
1558 return best;
1559 }
1560
1561 /// @brief Checks whether a token is the exact match of an argument's key. Called by
1562 /// lector::Arguments::find_best_argument().
1563 /// @param[in] token The token to check.
1564 /// @param[in] argument_index The index of the argument that has the key.
1565 /// @param[in] argument_key The argument key against which to compare.
1566 /// @return A populated lector::Arguments::BestArgument data structure if the specified token is
1567 /// an exact match for the key, or std::nullopt otherwise.
1568 [[nodiscard]] static std::optional<BestArgument> try_exact_match(
1569 const std::string_view token, const std::size_t argument_index,
1570 const std::string_view argument_key) noexcept {
1571 if (token == argument_key) {
1572 return BestArgument{argument_index, argument_key.size(), false};
1573 }
1574 return std::nullopt;
1575 }
1576
1577 /// @brief Checks whether a token contains an inline match of an argument's key of the form
1578 /// "key=value". Called by lector::Arguments::find_best_argument().
1579 /// @tparam SingularArgument The type of the argument that has the key to be used in the
1580 /// comparison.
1581 /// @param[in] token The token to check.
1582 /// @param[in] argument_index The index of the argument that has the key to be used in the
1583 /// comparison.
1584 /// @param[in] argument_key The argument key against which to compare.
1585 /// @return A populated lector::Arguments::BestArgument data structure if the specified token
1586 /// contains an inline match for the key, or std::nullopt otherwise.
1587 template <typename SingularArgument>
1588 [[nodiscard]] static std::optional<BestArgument> try_inline_match(
1589 const std::string_view token, const std::size_t argument_index,
1590 const std::string_view argument_key) noexcept {
1591 using ArgumentType = typename std::decay_t<SingularArgument>::ValueType;
1592 // Inline matching is strictly disabled for boolean arguments because they are key-only flags
1593 // that do not have values.
1594 if constexpr (!std::is_same_v<ArgumentType, bool>) {
1595 if (token.size() > argument_key.size()
1596 && token.compare(0, argument_key.size(), argument_key) == 0
1597 && token[argument_key.size()] == '=') {
1598 return BestArgument{argument_index, argument_key.size(), true};
1599 }
1600 }
1601 return std::nullopt;
1602 }
1603
1604 /// @brief Populates an argument with its parsed value. Called by
1605 /// lector::Arguments::parse_named_arguments().
1606 /// @tparam ArgumentType The type of the argument to be populated.
1607 /// @param[in,out] argument The argument to be populated.
1608 /// @param[in] best_argument The lector::Arguments::BestArgument data structure that corresponds
1609 /// to the argument to be populated.
1610 /// @param[in] argc The number of command line arguments, including the executable path.
1611 /// @param[in] argv The array of C-strings that represents the command line arguments, starting
1612 /// with the executable path.
1613 /// @param[in,out] argv_index The index of the argument in the array of C-string command line
1614 /// arguments whose value is to be extracted and used to populate the argument.
1615 /// @throws std::invalid_argument if the parsed value is invalid for this argument type.
1616 template <typename ArgumentType>
1617 static void populate_argument(ArgumentType& argument, const BestArgument& best_argument,
1618 const int argc, char* argv[], std::size_t& argv_index) {
1619 using Type = typename std::decay_t<ArgumentType>::ValueType;
1620 if constexpr (std::is_same_v<Type, bool>) {
1621 // Boolean arguments are key-only flags; their presence implies true.
1622 argument.set_parsed_value(true);
1623 } else {
1624 // This is a non-boolean argument. Extract and parse its value.
1625 const std::string raw_value{extract_raw_value(
1626 best_argument, argument.longest_key_with_value_type(), argc, argv, argv_index)};
1627 const std::optional<Type> parsed_value{lector::parse<Type>(raw_value)};
1628 if (parsed_value.has_value()) {
1629 argument.set_parsed_value(parsed_value.value());
1630 } else {
1631 throw std::invalid_argument("Invalid value '" + raw_value + "' for argument '"
1632 + argument.longest_key_with_value_type() + "'.");
1633 }
1634 }
1635 }
1636
1637 /// @brief Extracts the raw string value from an argv token for a non-boolean best argument.
1638 /// Called by lector::Arguments::populate_argument().
1639 /// @param[in] best_argument The best argument.
1640 /// @param[in] best_argument_longest_key_with_value_type The longest key with value type of the
1641 /// best argument.
1642 /// @param[in] argc The number of command line arguments, including the executable path.
1643 /// @param[in] argv The array of C-strings that represents the command line arguments, starting
1644 /// with the executable path.
1645 /// @param[in,out] argv_index The index of the argument in the array of C-string command line
1646 /// arguments whose raw string value is to be extracted.
1647 /// @return The extracted raw string value.
1648 /// @throws std::invalid_argument if the command line arguments are missing the value for this
1649 /// best argument.
1650 [[nodiscard]] static std::string extract_raw_value(
1651 const BestArgument& best_argument,
1652 const std::string& best_argument_longest_key_with_value_type, const int argc, char* argv[],
1653 std::size_t& argv_index) {
1654 const std::string_view token{argv[argv_index]};
1655 if (best_argument.is_inline) {
1656 // The token contains an inline value of the form "key=value". Extract the last portion of the
1657 // token.
1658 return std::string{token.substr(best_argument.key_length + 1)};
1659 }
1660 if (argv_index + 1 < static_cast<std::size_t>(argc)) {
1661 // The token is a standalone value of the form "key", and the next token is of the form
1662 // "value". Advance the argv index to consume the next argv element that contains the value.
1663 ++argv_index;
1664 return std::string{argv[argv_index]};
1665 }
1666 throw std::invalid_argument(
1667 "Missing value for argument '" + best_argument_longest_key_with_value_type + "'.");
1668 }
1669
1670 /// @brief Validates that this collection of command line arguments does not mix a repeated
1671 /// positional command line argument with other positional arguments.
1672 void validate_positional_arguments() const {
1673 bool has_repeated_positional_argument{false};
1674 std::size_t positional_argument_count{0UL};
1675 std::apply(
1676 [&](const auto&... argument) {
1677 (..., [&] {
1678 if (argument.form() == lector::Form::Positional) {
1679 ++positional_argument_count;
1680 if (argument.arity() == lector::Arity::Repeatable) {
1681 has_repeated_positional_argument = true;
1682 }
1683 }
1684 }());
1685 },
1686 arguments_);
1687 if (has_repeated_positional_argument
1688 && positional_argument_count >= static_cast<std::size_t>(2UL)) {
1689 throw std::logic_error(
1690 "A repeated positional argument cannot be mixed with any other positional arguments.");
1691 }
1692 }
1693
1694 /// @brief Validates that the same key is never duplicated across two or more arguments. Called by
1695 /// the constructor.
1696 /// @throws std::logic_error if the same key is duplicated across two or more arguments.
1697 void validate_keys() const {
1698 std::unordered_set<std::string> unique_keys;
1699 std::apply(
1700 [&](const auto&... argument) {
1701 (..., [&] {
1702 for (const std::string& key : argument.keys()) {
1703 const std::pair<std::unordered_set<std::string>::const_iterator, bool> result{
1704 unique_keys.insert(key)};
1705 if (!result.second) {
1706 throw std::logic_error("Duplicate key '" + key + "' across two arguments.");
1707 }
1708 }
1709 }());
1710 },
1711 arguments_);
1712 }
1713
1714 /// @brief Validates that all raw positional tokens have been consumed by positional command line
1715 /// arguments.
1716 /// @param[in] positional_tokens The raw positional command line tokens.
1717 /// @param[in] positional_token_index The parsed index in the collection of raw positional command
1718 /// line tokens.
1719 /// @throws std::invalid_argument if any raw positional command line tokens were not consumed by
1720 /// positional command line arguments.
1721 void validate_all_positional_tokens_matched(
1722 const std::vector<std::string_view>& positional_tokens,
1723 const std::size_t positional_token_index) {
1724 if (positional_token_index < positional_tokens.size()) {
1725 const std::size_t unexpected_count{positional_tokens.size() - positional_token_index};
1726 std::string unexpected_tokens;
1727 for (std::size_t unexpected_token_index{positional_token_index};
1728 unexpected_token_index < positional_tokens.size(); ++unexpected_token_index) {
1729 if (unexpected_token_index > positional_token_index) {
1730 unexpected_tokens.append(", ");
1731 }
1732 unexpected_tokens.push_back('\'');
1733 unexpected_tokens.append(std::string{positional_tokens[unexpected_token_index]});
1734 unexpected_tokens.push_back('\'');
1735 }
1736 throw std::invalid_argument(std::to_string(unexpected_count)
1737 + " unexpected command line tokens: " + unexpected_tokens + ".");
1738 }
1739 }
1740
1741 /// @brief Computes and returns the maximum length of the printed keys and value type across all
1742 /// arguments in this collection of command line arguments.
1743 /// @return The maximum length of the printed keys and value type across all arguments in this
1744 /// collection.
1745 [[nodiscard]] std::size_t maximum_length_of_keys_with_value_type() const {
1746 std::size_t maximum_length{0UL};
1747 std::apply(
1748 [&](const auto&... argument) {
1749 (..., [&] {
1750 const std::size_t length{lector::code_points(argument.keys_with_value_type())};
1751 maximum_length = std::max(maximum_length, length);
1752 }());
1753 },
1754 arguments_);
1755 return maximum_length;
1756 }
1757
1758 /// @brief Configuration of the help information of this collection of command line arguments.
1759 lector::Configuration configuration_{};
1760
1761 /// @brief Variadic collection of command line arguments.
1762 std::tuple<ArgumentTypes...> arguments_;
1763
1764 /// @brief Executable path of this collection of command line arguments. If the command line
1765 /// arguments have not yet been parsed from argc and argv, this path is empty.
1766 std::filesystem::path executable_path_;
1767};
1768
1769} // namespace lector
1770
1771#endif // LECTOR_ARGUMENTS_HPP
A collection of command line arguments that can be parsed from argc and argv.
Definition arguments.hpp:1104
std::string usage(const std::size_t line_length) const
Prints the usage information of this collection of command line arguments as a string of text....
Definition arguments.hpp:1226
std::string options() const
Prints the options information of this collection of command line argument as a string of text....
Definition arguments.hpp:1246
const std::filesystem::path & executable_path() const
Returns the executable path of this collection of command line arguments. If the command line argumen...
Definition arguments.hpp:1195
std::string execution(const std::size_t line_length) const
Prints the execution of this collection of command line argument as a string of text....
Definition arguments.hpp:1368
Arguments(ArgumentTypes... arguments)
Constructor. Constructs a collection of command line arguments from a variadic list of command line a...
Definition arguments.hpp:1125
const lector::Configuration & configuration() const
Returns the configuration of the help information of this collection of command line arguments.
Definition arguments.hpp:1188
std::string help() const
Prints the help information of this collection of command line arguments as a string of text....
Definition arguments.hpp:1296
std::string execution() const
Prints the execution of this collection of command line argument as a string of text....
Definition arguments.hpp:1355
void validate() const
Validates that all required arguments have each successfully parsed a value from the command line....
Definition arguments.hpp:1171
~Arguments() noexcept=default
Destructor. Destroys this collection of command line arguments.
const auto & get() const
Returns a specified command line argument from this collection.
Definition arguments.hpp:1203
void parse(const int argc, char *argv[])
Parses argc and argv to populate the parsed values of the command line arguments in this collection....
Definition arguments.hpp:1160
std::string usage() const
Prints the usage information of this collection of command line arguments as a string of text....
Definition arguments.hpp:1213
std::string help(const std::size_t line_length) const
Prints the help information of this collection of command line arguments as a string of text....
Definition arguments.hpp:1309
Arguments(lector::Configuration &&configuration, ArgumentTypes... arguments)
Compile-time check that all arguments have unique labels.
Definition arguments.hpp:1115
std::string options(const std::size_t line_length) const
Prints the options information of this collection of command line argument as a string of text....
Definition arguments.hpp:1259
A repeatable command line argument.
Definition arguments.hpp:594
const std::vector< Type > & default_values() const noexcept
Default values of this repeatable command line argument. Set at construction.
Definition arguments.hpp:702
static lector::Arity arity() noexcept
Arity of this command line argument. A repeatable argument can only appear once on the command line,...
Definition arguments.hpp:743
~RepeatableArgument() noexcept=default
Destructor. Destroys this repeatable command line argument.
const std::vector< std::string > & keys() const noexcept
Keys that can be used to specify this argument on the command line if it is a named argument,...
Definition arguments.hpp:683
const std::vector< Type > & parsed_or_default_values() const
Values of this repeatable command line argument. Returns the parsed values if they exist; otherwise,...
Definition arguments.hpp:750
std::string keys_with_value_type() const
Prints the keys and value type of this command line argument as a string of text.
Definition arguments.hpp:794
const std::vector< Type > & parsed_values() const noexcept
Parsed values of this repeatable command line argument. Set when this argument is parsed from the com...
Definition arguments.hpp:716
std::string_view description() const noexcept
Description of this command line argument. Set at construction.
Definition arguments.hpp:689
RepeatableArgument() noexcept=default
Default constructor. Initializes the repeatable command line argument with no keys,...
bool has_default() const noexcept
Returns whether this repeatable command line argument has one or more default values.
Definition arguments.hpp:696
lector::Form form() const noexcept
Form of this command line argument. A positional argument does not define any keys and must be specif...
Definition arguments.hpp:733
std::string longest_key_with_value_type() const
Prints the longest key of this command line argument with its associated value type as a string of te...
Definition arguments.hpp:778
static constexpr auto label() noexcept
Label of this command line argument. Used to uniquely identify this command line argument in a collec...
Definition arguments.hpp:675
RepeatableArgument(const std::vector< std::string > &keys, const std::string_view description)
Constructor for a repeatable named required command line argument or a repeatable named optional bool...
Definition arguments.hpp:617
std::string execution() const
Prints the execution of this command line argument as a string of text. The execution consists of thi...
Definition arguments.hpp:847
lector::Importance importance() const noexcept
Importance of this command line argument. A required argument must be provided by the user on the com...
Definition arguments.hpp:724
std::string options() const
Prints the options information of this command line argument as a string of text. The options informa...
Definition arguments.hpp:833
void set_parsed_value(const Type &value)
Inserts an additional parsed value into this repeatable command line argument.
Definition arguments.hpp:762
bool has_parsed() const noexcept
Returns whether this repeatable command line argument has one or more parsed values.
Definition arguments.hpp:709
RepeatableArgument(const std::string_view description, const std::vector< Type > &default_values)
Constructor for a repeatable positional optional non-boolean command line argument.
Definition arguments.hpp:629
std::string usage() const
Prints the usage information of this command line argument as a string of text. The usage information...
Definition arguments.hpp:817
RepeatableArgument(const std::vector< std::string > &keys, const std::string_view description, const std::vector< Type > &default_values)
Constructor for a repeatable named optional non-boolean command line argument.
Definition arguments.hpp:642
A singular command line argument.
Definition arguments.hpp:171
std::string longest_key_with_value_type() const
Prints the longest key of this command line argument with its associated value type as a string of te...
Definition arguments.hpp:369
bool has_parsed() const noexcept
Returns whether this singular command line argument has a parsed value.
Definition arguments.hpp:290
SingularArgument() noexcept=default
Default constructor. Initializes the singular command line argument with no keys, an empty descriptio...
std::string keys_with_value_type() const
Prints the keys and value type of this command line argument as a string of text.
Definition arguments.hpp:385
bool has_default() const noexcept
Returns whether this singular command line argument has a default value.
Definition arguments.hpp:277
static lector::Arity arity() noexcept
Arity of this command line argument. A singular argument can only appear once on the command line,...
Definition arguments.hpp:324
const std::optional< Type > & parsed_value() const noexcept
Parsed value of this singular command line argument. Set when this argument is parsed from the comman...
Definition arguments.hpp:297
std::string options() const
Prints the options information of this command line argument as a string of text. The options informa...
Definition arguments.hpp:420
const std::vector< std::string > & keys() const noexcept
Keys that can be used to specify this argument on the command line if it is a named argument,...
Definition arguments.hpp:265
const Type & parsed_or_default_value() const
Value of this singular command line argument. Returns the parsed value if it exists; otherwise,...
Definition arguments.hpp:333
static constexpr auto label() noexcept
Label of this command line argument. Used to uniquely identify this command line argument in a collec...
Definition arguments.hpp:257
SingularArgument(const std::string_view description, const Type &default_value)
Constructor for a singular positional optional non-boolean command line argument. A default value mus...
Definition arguments.hpp:210
const std::optional< Type > & default_value() const noexcept
Default value of this singular command line argument if it is optional and non-boolean,...
Definition arguments.hpp:284
std::string usage() const
Prints the usage information of this command line argument as a string of text. The usage information...
Definition arguments.hpp:408
~SingularArgument() noexcept=default
Destructor. Destroys this singular command line argument.
lector::Importance importance() const noexcept
Importance of this command line argument. A required argument must be provided by the user on the com...
Definition arguments.hpp:305
std::string_view description() const noexcept
Description of this command line argument. Set at construction.
Definition arguments.hpp:271
SingularArgument(const std::vector< std::string > &keys, const std::string_view description, const Type &default_value)
Constructor for a singular named optional non-boolean command line argument. A default value must be ...
Definition arguments.hpp:224
SingularArgument(const std::vector< std::string > &keys, const std::string_view description)
Constructor for a singular named required command line argument or a singular named boolean command l...
Definition arguments.hpp:195
void set_parsed_value(const Type &value)
Sets the parsed value of this singular command line argument.
Definition arguments.hpp:350
std::string execution() const
Prints the execution of this command line argument as a string of text. The execution consists of thi...
Definition arguments.hpp:434
lector::Form form() const noexcept
Form of this command line argument. A positional argument does not define any keys and must be specif...
Definition arguments.hpp:314
The Lector library's namespace.
Definition arguments.hpp:43
std::string quote_if_contains_whitespace(const std::string_view text)
Encloses a string of text in quotes if it contains any whitespace. Either single or double quotes are...
Definition text.hpp:198
std::string wrap_and_left_align(const std::string_view text, const std::size_t line_length)
Left-aligns and wraps a string of text to a line length.
Definition text.hpp:516
std::size_t code_points(const std::string_view text)
Counts and returns the number of UTF-8 code points in a string of text. The number of UTF-8 code poin...
Definition text.hpp:102
Form
Form of a command line argument.
Definition arguments.hpp:88
@ Named
The command line argument is a named argument; it defines one or more keys and is specified on the co...
@ Unknown
Unknown, unspecified, or invalid command line argument form.
@ Positional
The command line argument is a positional argument; it does not define any keys and must be specified...
Importance
Importance of a command line argument.
Definition arguments.hpp:129
@ Unknown
Unknown, unspecified, or invalid command line argument importance.
@ Required
The command line argument is required; it must be provided by the user.
@ Optional
The command line argument is optional; it may or may not be provided by the user.
std::string collate_and_left_align(const std::string_view first_column_text, const std::size_t first_column_width, const std::string_view second_column_text, const std::size_t second_column_width)
Collates two strings of text, each representing a column, into a single string that contains newline-...
Definition text.hpp:567
Arity
Arity of a command line argument.
Definition arguments.hpp:46
@ Repeatable
The command line argument has repeatable arity; it can appear multiple times on the command line....
@ Unknown
Unknown, unspecified, or invalid command line argument arity.
@ Singular
The command line argument has singular arity; it can only appear once on the command line.
Configuration of the help information of a collection of command line arguments.
Definition arguments.hpp:1012
std::optional< std::string > title
Title of the application whose command line arguments are to be parsed. When the collection of comman...
Definition arguments.hpp:1018
std::optional< std::string > description
Description of the application whose command line arguments are to be parsed. When the collection of ...
Definition arguments.hpp:1024
std::optional< std::string > notes
Additional notes pertaining to the application whose command line arguments are to be parsed....
Definition arguments.hpp:1030