diff --git a/include/argparse.hpp b/include/argparse.hpp index c506085..dd6249c 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -40,6 +40,7 @@ SOFTWARE. #include #include #include +#include #include #include #include @@ -73,7 +74,23 @@ using enable_if_container = std::enable_if_t, T>; template using enable_if_not_container = std::enable_if_t, T>; -} // namespace + +template +constexpr decltype(auto) apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x, + std::index_sequence) { + return std::invoke(std::forward(f), std::get(std::forward(t))..., + std::forward(x)); +} + +template +constexpr decltype(auto) apply_plus_one(F &&f, Tuple &&t, Extra &&x) { + return details::apply_plus_one_impl( + std::forward(f), std::forward(t), std::forward(x), + std::make_index_sequence< + std::tuple_size_v>>{}); +} + +} // namespace details class ArgumentParser; @@ -115,14 +132,22 @@ public: return *this; } - template - auto action(F &&aAction) - -> std::enable_if_t, + template + auto action(F &&aAction, Args &&... aBound) + -> std::enable_if_t, Argument &> { - if constexpr (std::is_void_v>) - mAction.emplace(std::forward(aAction)); + using action_type = std::conditional_t< + std::is_void_v>, + void_action, valued_action>; + if constexpr (sizeof...(Args) == 0) + mAction.emplace(std::forward(aAction)); else - mAction.emplace(std::forward(aAction)); + mAction.emplace( + [f = std::forward(aAction), + tup = std::make_tuple(std::forward(aBound)...)]( + std::string const &opt) mutable { + return details::apply_plus_one(f, tup, opt); + }); return *this; } diff --git a/test/test_actions.hpp b/test/test_actions.hpp index 920a500..0a259bd 100644 --- a/test/test_actions.hpp +++ b/test/test_actions.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include TEST_CASE("Users can use default value inside actions", "[actions]") { argparse::ArgumentParser program("test"); @@ -39,3 +40,82 @@ TEST_CASE("Users can add actions that return nothing", "[actions]") { REQUIRE(program.get("button") == 42); } } + +class Image { +public: + int w = 0, h = 0; + + void resize(std::string_view geometry) { + std::stringstream s; + s << geometry; + s >> w; + s.ignore(); + s >> h; + } + + static auto create(int w, int h, std::string_view format) -> Image { + auto factor = [=] { + if (format == "720p") + return std::min(1280. / w, 720. / h); + else if (format == "1080p") + return std::min(1920. / w, 1080. / h); + else + return 1.; + }(); + + return {static_cast(w * factor), static_cast(h * factor)}; + } +}; + +TEST_CASE("Users can bind arguments to actions", "[actions]") { + argparse::ArgumentParser program("test"); + + GIVEN("an default initialized object bounded by reference") { + Image img; + program.add_argument("--size").action(&Image::resize, std::ref(img)); + + WHEN("provided no command-line arguments") { + program.parse_args({"test"}); + + THEN("the object is not updated") { + REQUIRE(img.w == 0); + REQUIRE(img.h == 0); + } + } + + WHEN("provided command-line arguments") { + program.parse_args({"test", "--size", "320x98"}); + + THEN("the object is updated accordingly") { + REQUIRE(img.w == 320); + REQUIRE(img.h == 98); + } + } + } + + GIVEN("a command-line option waiting for the last argument in its action") { + program.add_argument("format").action(Image::create, 400, 300); + + WHEN("provided such an argument") { + program.parse_args({"test", "720p"}); + + THEN("the option object is created as if providing all arguments") { + auto img = program.get("format"); + + REQUIRE(img.w == 960); + REQUIRE(img.h == 720); + } + } + + WHEN("provided a different argument") { + program.parse_args({"test", "1080p"}); + + THEN("a different option object is created") { + auto img = program.get("format"); + + REQUIRE(img.w == 1440); + REQUIRE(img.h == 1080); + } + } + } +}