Skip to content

Commit d3fbde3

Browse files
committed
flag detection improved
1 parent 83802cf commit d3fbde3

File tree

3 files changed

+74
-59
lines changed

3 files changed

+74
-59
lines changed

include/ap/argument_parser.hpp

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,12 @@ class argument_parser {
497497
arg_name, "An argument name cannot be empty."
498498
);
499499

500+
// TODO: add tests
501+
if (detail::contains_whitespaces(arg_name))
502+
throw invalid_configuration::invalid_argument_name(
503+
arg_name, "An argument name cannot contain whitespaces."
504+
);
505+
500506
if (arg_name.front() == this->_flag_prefix_char)
501507
throw invalid_configuration::invalid_argument_name(
502508
arg_name,
@@ -570,7 +576,7 @@ class argument_parser {
570576
* 1. No required positional argument can be added after a non-required positional argument.
571577
*/
572578
void _validate_argument_configuration() const {
573-
// Step: 1
579+
// step 1
574580
const_arg_opt_t non_required_arg = std::nullopt;
575581
for (const auto& arg : this->_positional_args) {
576582
if (not arg->is_required()) {
@@ -591,47 +597,74 @@ class argument_parser {
591597
* @param arg_range The command-line argument value range.
592598
* @return A list of preprocessed command-line argument tokens.
593599
*/
594-
template <detail::c_sized_range_of<std::string, detail::type_validator::convertible> AR>
600+
template <detail::c_sized_range_of<std::string_view, detail::type_validator::convertible> AR>
595601
[[nodiscard]] arg_token_list_t _tokenize(const AR& arg_range) const noexcept {
596602
const auto n_args = std::ranges::size(arg_range);
597603
if (n_args == 0ull)
598604
return arg_token_list_t{};
599605

600606
arg_token_list_t toks;
601607
toks.reserve(n_args);
608+
std::ranges::for_each(
609+
arg_range, std::bind_front(&argument_parser::_tokenize_arg, this, std::ref(toks))
610+
);
611+
return toks;
612+
}
602613

603-
for (const auto& arg : arg_range) {
604-
std::string value = static_cast<std::string>(arg);
605-
if (this->_is_flag(value)) {
606-
const auto flag_tok = this->_strip_flag_prefix(value);
607-
toks.emplace_back(flag_tok, std::move(value));
608-
}
609-
else {
610-
toks.emplace_back(detail::argument_token::t_value, std::move(value));
611-
}
614+
/**
615+
* @brief Appends an argument token created from `arg_value` to the `toks` vector.
616+
* @param toks The argument token list to which the processed token(s) will be appended.
617+
* @param arg_value The command-line argument's value to be processed.
618+
*/
619+
void _tokenize_arg(arg_token_list_t& toks, const std::string_view arg_value) const {
620+
auto tok = this->_build_token(arg_value);
621+
if (tok.is_flag_token() and not this->_is_valid_flag(tok)) {
622+
#ifdef AP_UNKNOWN_FLAGS_AS_VALUES
623+
toks.emplace_back(detail::argument_token::t_value, std::string(arg_value));
624+
return;
625+
#else
626+
throw parsing_failure::unknown_argument(arg_value);
627+
#endif
612628
}
613629

614-
return toks;
630+
toks.emplace_back(std::move(tok));
615631
}
616632

617633
/**
618-
* @brief Check if an argument is a flag based on its value.
619-
* @param arg The cmd argument's value.
620-
* @return True if the argument is a flag, false otherwise.
634+
* @brief Builds an argument token from the given value.
635+
* @param arg_value The command-line argument's value to be processed.
636+
* @return An argument token with removed flag prefix (if present) and an adequate token type.
621637
*/
622-
[[nodiscard]] bool _is_flag(const std::string& arg) const noexcept {
623-
if (arg.starts_with(this->_flag_prefix))
624-
return this->_is_arg_name_used(
625-
{arg.substr(this->_primary_flag_prefix_length)}, detail::argument_name::m_primary
626-
);
638+
[[nodiscard]] detail::argument_token _build_token(const std::string_view arg_value
639+
) const noexcept {
640+
if (detail::contains_whitespaces(arg_value))
641+
return {.type = detail::argument_token::t_value, .value = std::string(arg_value)};
627642

628-
if (arg.starts_with(this->_flag_prefix_char))
629-
return this->_is_arg_name_used(
630-
{arg.substr(this->_secondary_flag_prefix_length)},
631-
detail::argument_name::m_secondary
632-
);
643+
if (arg_value.starts_with(this->_flag_prefix))
644+
return {
645+
.type = detail::argument_token::t_flag_primary,
646+
.value = std::string(arg_value.substr(this->_primary_flag_prefix_length))
647+
};
633648

634-
return false;
649+
if (arg_value.starts_with(this->_flag_prefix_char))
650+
return {
651+
.type = detail::argument_token::t_flag_secondary,
652+
.value = std::string(arg_value.substr(this->_secondary_flag_prefix_length))
653+
};
654+
655+
return {.type = detail::argument_token::t_value, .value = std::string(arg_value)};
656+
}
657+
658+
/**
659+
* @brief Check if a flag token is valid based on its value.
660+
* @param tok The processed argument token.
661+
* @return true if the token's value matches an argument name specified within the parser, false otherwise.
662+
*/
663+
[[nodiscard]] bool _is_valid_flag(const detail::argument_token& tok) const noexcept {
664+
if (tok.type == detail::argument_token::t_flag_primary)
665+
return this->_is_arg_name_used({tok.value}, detail::argument_name::m_primary);
666+
else
667+
return this->_is_arg_name_used({tok.value}, detail::argument_name::m_secondary);
635668
}
636669

637670
/**
@@ -658,22 +691,6 @@ class argument_parser {
658691
}
659692
}
660693

661-
/**
662-
* @brief Remove the flag prefix from the argument.
663-
* @param arg_flag The argument flag to strip the prefix from.
664-
* @return A flag argument token representing whether the prefix indicated a primary or a secondary flag.
665-
*/
666-
detail::argument_token::token_type _strip_flag_prefix(std::string& arg_flag) const noexcept {
667-
if (arg_flag.starts_with(this->_flag_prefix)) {
668-
arg_flag.erase(0, this->_primary_flag_prefix_length);
669-
return detail::argument_token::t_flag_primary;
670-
}
671-
else {
672-
arg_flag.erase(0, this->_secondary_flag_prefix_length);
673-
return detail::argument_token::t_flag_secondary;
674-
}
675-
}
676-
677694
/**
678695
* @brief Implementation of parsing command-line arguments.
679696
* @param arg_tokens The list of command-line argument tokens.

include/ap/detail/argument_token.hpp

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,6 @@ struct argument_token {
2121
};
2222
using enum token_type;
2323

24-
argument_token() = delete;
25-
26-
argument_token(const argument_token&) = default;
27-
argument_token(argument_token&&) = default;
28-
29-
argument_token& operator=(const argument_token&) = default;
30-
argument_token& operator=(argument_token&&) = default;
31-
32-
/**
33-
* @brief Constructor of a command-line argument.
34-
* @param type Type type of the token (flag or value).
35-
* @param value The value of the argument.
36-
*/
37-
argument_token(const token_type type, const std::string& value) : type(type), value(value) {}
38-
39-
~argument_token() = default;
40-
4124
/**
4225
* @brief Equality operator for comparing argument_token instances.
4326
* @param other An argument_token instance to compare with.
@@ -47,6 +30,14 @@ struct argument_token {
4730
return this->type == other.type and this->value == other.value;
4831
}
4932

33+
/**
34+
* @brief Checks whether the `type` member is a flag token type.
35+
* @return true if `type` is either `t_flag_primary` or `t_flag_secondary`, false otherwise.
36+
*/
37+
[[nodiscard]] bool is_flag_token() const noexcept {
38+
return this->type == t_flag_primary or this->type == t_flag_secondary;
39+
}
40+
5041
token_type type;
5142
std::string value;
5243
};

include/ap/detail/str_utility.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "concepts.hpp"
1313

14+
#include <algorithm>
1415
#include <sstream>
1516
#include <string_view>
1617

@@ -28,6 +29,12 @@ template <c_writable T>
2829
return oss.str();
2930
}
3031

32+
// TODO: add tests
33+
/// @brief Checks whether a string contains any whitespace characters.
34+
[[nodiscard]] inline bool contains_whitespaces(std::string_view str) noexcept {
35+
return std::ranges::any_of(str, [](unsigned char c) { return std::isspace(c); });
36+
}
37+
3138
/**
3239
* @brief Joins elements of a range into a single string with a delimiter.
3340
* @tparam R The type of the value range. The value type of R must satisfy the @ref c_writable concept.

0 commit comments

Comments
 (0)