Skip to content

Commit f62bb45

Browse files
committed
Fix positional<values<>> options
1 parent 94dfca2 commit f62bb45

File tree

4 files changed

+79
-8
lines changed

4 files changed

+79
-8
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.idea/
22
test/out/
33
test/tests
4+
cmake-build-*
45

56
# Windows stuff
67
.vs/

include/clopts.hh

+7-6
Original file line numberDiff line numberDiff line change
@@ -811,10 +811,11 @@ private:
811811
size_t max_vals_opt_name_len{};
812812
size_t max_len{};
813813
auto determine_length = [&]<typename opt> {
814+
if constexpr (opt::is_values)
815+
max_vals_opt_name_len = std::max(max_vals_opt_name_len, opt::name.len);
816+
814817
/// Positional options go on the first line, so ignore them here.
815818
if constexpr (not detail::is_positional_v<opt>) {
816-
if constexpr (opt::is_values)
817-
max_vals_opt_name_len = std::max(max_vals_opt_name_len, opt::name.len);
818819
if constexpr (detail::should_print_argument_type<opt>) {
819820
auto n = type_name<typename opt::type>();
820821
max_len = std::max(max_len, opt::name.len + (n.len + sizeof("<> ") - 1));
@@ -892,7 +893,7 @@ public:
892893
private:
893894
/// Handle an option value.
894895
template <typename opt, bool is_multiple>
895-
auto dispatch_option_with_arg(std::string_view opt_str, std::string_view opt_val) {
896+
void dispatch_option_with_arg(std::string_view opt_str, std::string_view opt_val) {
896897
using opt_type = typename opt::type;
897898

898899
/// Mark the option as found.
@@ -1124,16 +1125,16 @@ private:
11241125

11251126
template <typename opt>
11261127
bool handle_positional_impl(std::string_view opt_str) {
1128+
static_assert(not detail::is_callback<typename opt::type>, "positional<>s may not have a callback");
1129+
11271130
/// If we've already encountered this positional option, then return.
11281131
static constexpr bool is_multiple = requires { opt::is_multiple; };
11291132
if constexpr (not is_multiple) {
11301133
if (found<opt::name>()) return false;
11311134
}
11321135

11331136
/// Otherwise, attempt to parse this as the option value.
1134-
set_found<opt::name>();
1135-
if constexpr (is_multiple) ref<opt::name>().push_back(make_arg<typename opt::type>(opt_str));
1136-
else ref<opt::name>() = make_arg<typename opt::type>(opt_str.data());
1137+
dispatch_option_with_arg<opt, is_multiple>(opt::name.sv(), opt_str);
11371138
return true;
11381139
}
11391140

test/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
cmake_minimum_required(VERSION 3.21)
2-
project(clopts VERSION 2.0.0)
2+
project(clopts VERSION 2.1.0)
33

44
set(CMAKE_CXX_STANDARD 26)
55

test/test.cc

+70-1
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,75 @@ TEST_CASE("Positional options are required by default") {
204204
CHECK_THROWS(options::parse(0, nullptr, error_handler));
205205
}
206206

207+
TEST_CASE("Positional values<> work") {
208+
using string_options = clopts<positional<"format", "Output format", values<"foo", "bar">>>;
209+
using int_options = clopts<positional<"format", "Output format", values<0, 1>>>;
210+
211+
SECTION("Correct values are accepted") {
212+
std::array args1 = {"test", "foo"};
213+
std::array args2 = {"test", "bar"};
214+
std::array args3 = {"test", "0"};
215+
std::array args4 = {"test", "1"};
216+
217+
auto opts1 = string_options::parse(args1.size(), args1.data(), error_handler);
218+
auto opts2 = string_options::parse(args2.size(), args2.data(), error_handler);
219+
auto opts3 = int_options::parse(args3.size(), args3.data(), error_handler);
220+
auto opts4 = int_options::parse(args4.size(), args4.data(), error_handler);
221+
222+
REQUIRE(opts1.get<"format">());
223+
REQUIRE(opts2.get<"format">());
224+
REQUIRE(opts3.get<"format">());
225+
REQUIRE(opts4.get<"format">());
226+
227+
CHECK(*opts1.get<"format">() == "foo");
228+
CHECK(*opts2.get<"format">() == "bar");
229+
CHECK(*opts3.get<"format">() == 0);
230+
CHECK(*opts4.get<"format">() == 1);
231+
}
232+
233+
SECTION("Invalid values raise an error") {
234+
std::array args1 = {"test", "baz"};
235+
std::array args2 = {"test", "2"};
236+
237+
CHECK_THROWS(string_options::parse(args1.size(), args1.data(), error_handler));
238+
CHECK_THROWS(int_options::parse(args2.size(), args2.data(), error_handler));
239+
}
240+
}
241+
242+
TEST_CASE("Multiple positional values<> work") {
243+
using string_options = clopts<multiple<positional<"format", "Output format", values<"foo", "bar">>>>;
244+
using int_options = clopts<multiple<positional<"format", "Output format", values<0, 1>>>>;
245+
246+
SECTION("Correct values are accepted") {
247+
std::array args1 = {"test", "foo", "bar", "foo"};
248+
std::array args2 = {"test", "0", "1", "1"};
249+
250+
auto opts1 = string_options::parse(args1.size(), args1.data(), error_handler);
251+
auto opts2 = int_options::parse(args2.size(), args2.data(), error_handler);
252+
253+
REQUIRE(opts1.get<"format">());
254+
REQUIRE(opts2.get<"format">());
255+
256+
REQUIRE(opts1.get<"format">()->size() == 3);
257+
REQUIRE(opts2.get<"format">()->size() == 3);
258+
259+
CHECK(opts1.get<"format">()->at(0) == "foo");
260+
CHECK(opts1.get<"format">()->at(1) == "bar");
261+
CHECK(opts1.get<"format">()->at(2) == "foo");
262+
CHECK(opts2.get<"format">()->at(0) == 0);
263+
CHECK(opts2.get<"format">()->at(1) == 1);
264+
CHECK(opts2.get<"format">()->at(2) == 1);
265+
}
266+
267+
SECTION("Invalid values raise an error") {
268+
std::array args1 = {"test", "foo", "baz", "foo"};
269+
std::array args2 = {"test", "0", "2", "1"};
270+
271+
CHECK_THROWS(string_options::parse(args1.size(), args1.data(), error_handler));
272+
CHECK_THROWS(int_options::parse(args2.size(), args2.data(), error_handler));
273+
}
274+
}
275+
207276
TEST_CASE("Short option options are parsed properly") {
208277
using options = clopts<
209278
experimental::short_option<"s", "A string", std::string>,
@@ -360,7 +429,7 @@ TEST_CASE("Calling from main() works as expected") {
360429
}
361430

362431
TEST_CASE("File option can map a file properly") {
363-
auto run = [] <typename file> {
432+
auto run = []<typename file> {
364433
using options = clopts<option<"file", "A file", file>>;
365434

366435
std::array args = {

0 commit comments

Comments
 (0)