Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions include/argon/argument.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -703,10 +703,40 @@ class argument : public detail::typed_argument_base<T> {
));
}

if (not this->_is_valid_choice(value))
throw parsing_failure(std::format(
"Value `{}` is not a valid choice for argument [{}].", str_value, this->_name.str()
));
if (not this->_is_valid_choice(value)) {
if constexpr (traits::c_writable<value_type>) {
constexpr std::size_t max_display_choices = 5ull;

std::string choices_str = util::join(
this->_choices | std::views::take(max_display_choices)
| std::views::transform([](const auto& c) {
return std::format("'{}'", util::as_string(c));
})
);

if (this->_choices.size() > max_display_choices) {
choices_str = std::format(
"{}, ... and {} more",
choices_str,
this->_choices.size() - max_display_choices
);
}

throw parsing_failure(std::format(
"Value `{}` is not a valid choice for argument [{}]. Valid choices are: {}",
str_value,
this->_name.str(),
choices_str
));
}
else {
throw parsing_failure(std::format(
"Value `{}` is not a valid choice for argument [{}].",
str_value,
this->_name.str()
));
}
}

const auto apply_visitor = action::util::apply_visitor<value_type>{value};
for (const auto& action : this->_value_actions)
Expand Down
2 changes: 1 addition & 1 deletion include/argon/argument_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1338,7 +1338,7 @@ class argument_parser {
* @param value The value to be set for the current argument.
* @param state The current parsing state.
*/
void _set_argument_value(const std::string_view value, parsing_state& state) noexcept {
void _set_argument_value(const std::string_view value, parsing_state& state) {
if (state.curr_arg->set_value(std::string(value)))
return; // argument still accepts values

Expand Down
4 changes: 2 additions & 2 deletions include/argon/util/string.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ namespace argon::util {
* @ingroup util
*/
template <traits::c_writable T>
[[nodiscard]] std::string as_string(const T& value) noexcept {
[[nodiscard]] std::string as_string(T&& value) noexcept {
std::ostringstream oss;
oss << value;
return oss.str();
Expand All @@ -45,7 +45,7 @@ template <traits::c_writable T>
*/
template <std::ranges::range R>
requires(traits::c_writable<std::ranges::range_value_t<R>>)
[[nodiscard]] std::string join(const R& range, const std::string_view delimiter = ", ") {
[[nodiscard]] std::string join(R&& range, const std::string_view delimiter = ", ") {
std::ostringstream oss;

auto it = std::ranges::begin(range);
Expand Down
3 changes: 2 additions & 1 deletion tests/source/test_optional_argument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -526,9 +526,10 @@ TEST_CASE_FIXTURE(

REQUIRE_THROWS_WITH_AS(
set_value(sut, invalid_choice),
invalid_choice_msg(arg_name_primary, as_string(invalid_choice)).c_str(),
doctest::Contains(invalid_choice_msg(arg_name_primary, as_string(invalid_choice)).c_str()),
parsing_failure
);

CHECK_FALSE(has_value(sut));
}

Expand Down
2 changes: 1 addition & 1 deletion tests/source/test_positional_argument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ TEST_CASE_FIXTURE(

REQUIRE_THROWS_WITH_AS(
set_value(sut, invalid_choice),
invalid_choice_msg(arg_name, std::to_string(invalid_choice)).c_str(),
doctest::Contains(invalid_choice_msg(arg_name, as_string(invalid_choice)).c_str()),
parsing_failure
);
CHECK_FALSE(has_value(sut));
Expand Down
Loading