diff --git a/src/catch2/internal/catch_clara.hpp b/src/catch2/internal/catch_clara.hpp index 3690baad..8de9a72e 100644 --- a/src/catch2/internal/catch_clara.hpp +++ b/src/catch2/internal/catch_clara.hpp @@ -29,14 +29,17 @@ # endif #endif -#include #include +#include +#include #include #include #include +#include #include #include +#include #include namespace Catch { @@ -53,7 +56,25 @@ namespace Catch { ShortCircuitSame }; + struct accept_many_t {}; + constexpr accept_many_t accept_many {}; + namespace Detail { + struct fake_arg { + template + operator T(); + }; + + template + struct is_unary_function : std::false_type {}; + + template + struct is_unary_function< + F, + Catch::Detail::void_t()( fake_arg() ) ) + > + > : std::true_type {}; // Traits for extracting arg and return type of lambdas (for single // argument lambdas) @@ -393,6 +414,11 @@ namespace Catch { } }; + template struct BoundManyLambda : BoundLambda { + explicit BoundManyLambda( L const& lambda ): BoundLambda( lambda ) {} + bool isContainer() const override { return true; } + }; + template struct BoundFlagLambda : BoundFlagRefBase { L m_lambda; @@ -447,12 +473,23 @@ namespace Catch { m_ref( ref ) {} public: - template + template + ParserRefImpl( accept_many_t, + LambdaT const& ref, + std::string const& hint ): + m_ref( std::make_shared>( ref ) ), + m_hint( hint ) {} + + template ::value>> ParserRefImpl( T& ref, std::string const& hint ): m_ref( std::make_shared>( ref ) ), m_hint( hint ) {} - template + template ::value>> ParserRefImpl( LambdaT const& ref, std::string const& hint ): m_ref( std::make_shared>( ref ) ), m_hint( hint ) {} @@ -513,13 +550,21 @@ namespace Catch { explicit Opt(bool& ref); - template - Opt(LambdaT const& ref, std::string const& hint) : - ParserRefImpl(ref, hint) {} + template ::value>> + Opt( LambdaT const& ref, std::string const& hint ): + ParserRefImpl( ref, hint ) {} - template - Opt(T& ref, std::string const& hint) : - ParserRefImpl(ref, hint) {} + template + Opt( accept_many_t, LambdaT const& ref, std::string const& hint ): + ParserRefImpl( accept_many, ref, hint ) {} + + template ::value>> + Opt( T& ref, std::string const& hint ): + ParserRefImpl( ref, hint ) {} auto operator[](std::string const& optName) -> Opt& { m_optNames.push_back(optName); diff --git a/tests/SelfTest/Baselines/automake.sw.approved.txt b/tests/SelfTest/Baselines/automake.sw.approved.txt index 46945798..c77739c3 100644 --- a/tests/SelfTest/Baselines/automake.sw.approved.txt +++ b/tests/SelfTest/Baselines/automake.sw.approved.txt @@ -102,6 +102,7 @@ Nor would this :test-result: PASS Capture and info messages :test-result: PASS Character pretty printing :test-result: PASS Clara::Arg supports single-arg parse the way Opt does +:test-result: PASS Clara::Opt supports accept-many lambdas :test-result: PASS Combining MatchAllOfGeneric does not nest :test-result: PASS Combining MatchAnyOfGeneric does not nest :test-result: PASS Combining MatchNotOfGeneric does not nest @@ -297,6 +298,7 @@ Message from section two :test-result: FAIL first tag loose text artifact :test-result: FAIL has printf +:test-result: PASS is_unary_function :test-result: FAIL just failure :test-result: FAIL just failure after unscoped info :test-result: FAIL just info diff --git a/tests/SelfTest/Baselines/compact.sw.approved.txt b/tests/SelfTest/Baselines/compact.sw.approved.txt index b28f9ac1..b3bd849e 100644 --- a/tests/SelfTest/Baselines/compact.sw.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.approved.txt @@ -350,6 +350,9 @@ ToStringGeneral.tests.cpp:: passed: c == i for: 4 == 4 ToStringGeneral.tests.cpp:: passed: c == i for: 5 == 5 Clara.tests.cpp:: passed: name.empty() for: true Clara.tests.cpp:: passed: name == "foo" for: "foo" == "foo" +Clara.tests.cpp:: passed: !(parse_result) for: !{?} +Clara.tests.cpp:: passed: parse_result for: {?} +Clara.tests.cpp:: passed: res == std::vector{ "aaa", "bbb" } for: { "aaa", "bbb" } == { "aaa", "bbb" } Matchers.tests.cpp:: passed: with 1 message: 'std::is_same< decltype( ( MatcherA() && MatcherB() ) && MatcherC() ), Catch::Matchers::Detail:: MatchAllOfGeneric>::value' Matchers.tests.cpp:: passed: 1, ( MatcherA() && MatcherB() ) && MatcherC() for: 1 ( equals: (int) 1 or (float) 1.0f and equals: (long long) 1 and equals: (T) 1 ) Matchers.tests.cpp:: passed: with 1 message: 'std::is_same< decltype( MatcherA() && ( MatcherB() && MatcherC() ) ), Catch::Matchers::Detail:: MatchAllOfGeneric>::value' @@ -2111,6 +2114,18 @@ Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: loose text artifact +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +Clara.tests.cpp:: passed: with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +Clara.tests.cpp:: passed: with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' Message.tests.cpp:: failed: explicitly with 1 message: 'Previous info should not be seen' Message.tests.cpp:: failed: explicitly with 1 message: 'previous unscoped info SHOULD not be seen' Misc.tests.cpp:: passed: l == std::numeric_limits::max() for: 9223372036854775807 (0x) diff --git a/tests/SelfTest/Baselines/console.std.approved.txt b/tests/SelfTest/Baselines/console.std.approved.txt index f513b37e..dcc2a39a 100644 --- a/tests/SelfTest/Baselines/console.std.approved.txt +++ b/tests/SelfTest/Baselines/console.std.approved.txt @@ -1426,6 +1426,6 @@ due to unexpected exception with message: Why would you throw a std::string? =============================================================================== -test cases: 374 | 297 passed | 70 failed | 7 failed as expected -assertions: 2127 | 1971 passed | 129 failed | 27 failed as expected +test cases: 376 | 299 passed | 70 failed | 7 failed as expected +assertions: 2142 | 1986 passed | 129 failed | 27 failed as expected diff --git a/tests/SelfTest/Baselines/console.sw.approved.txt b/tests/SelfTest/Baselines/console.sw.approved.txt index e9d9d9a7..f64110e5 100644 --- a/tests/SelfTest/Baselines/console.sw.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.approved.txt @@ -2908,6 +2908,35 @@ Clara.tests.cpp:: PASSED: with expansion: "foo" == "foo" +------------------------------------------------------------------------------- +Clara::Opt supports accept-many lambdas + Parsing fails on multiple options without accept_many +------------------------------------------------------------------------------- +Clara.tests.cpp: +............................................................................... + +Clara.tests.cpp:: PASSED: + CHECK_FALSE( parse_result ) +with expansion: + !{?} + +------------------------------------------------------------------------------- +Clara::Opt supports accept-many lambdas + Parsing succeeds on multiple options with accept_many +------------------------------------------------------------------------------- +Clara.tests.cpp: +............................................................................... + +Clara.tests.cpp:: PASSED: + CHECK( parse_result ) +with expansion: + {?} + +Clara.tests.cpp:: PASSED: + CHECK( res == std::vector{ "aaa", "bbb" } ) +with expansion: + { "aaa", "bbb" } == { "aaa", "bbb" } + ------------------------------------------------------------------------------- Combining MatchAllOfGeneric does not nest ------------------------------------------------------------------------------- @@ -14962,6 +14991,60 @@ Tricky.tests.cpp: No assertions in test case 'has printf' +------------------------------------------------------------------------------- +is_unary_function +------------------------------------------------------------------------------- +Clara.tests.cpp: +............................................................................... + +Clara.tests.cpp:: PASSED: +with message: + Catch::Clara::Detail::is_unary_function::value + +Clara.tests.cpp:: PASSED: +with message: + Catch::Clara::Detail::is_unary_function::value + +Clara.tests.cpp:: PASSED: +with message: + Catch::Clara::Detail::is_unary_function::value + +Clara.tests.cpp:: PASSED: +with message: + Catch::Clara::Detail::is_unary_function::value + +Clara.tests.cpp:: PASSED: +with message: + Catch::Clara::Detail::is_unary_function::value + +Clara.tests.cpp:: PASSED: +with message: + Catch::Clara::Detail::is_unary_function::value + +Clara.tests.cpp:: PASSED: +with message: + !(Catch::Clara::Detail::is_unary_function::value) + +Clara.tests.cpp:: PASSED: +with message: + !(Catch::Clara::Detail::is_unary_function::value) + +Clara.tests.cpp:: PASSED: +with message: + !(Catch::Clara::Detail::is_unary_function::value) + +Clara.tests.cpp:: PASSED: +with message: + !(Catch::Clara::Detail::is_unary_function::value) + +Clara.tests.cpp:: PASSED: +with message: + !(Catch::Clara::Detail::is_unary_function::value) + +Clara.tests.cpp:: PASSED: +with message: + !(Catch::Clara::Detail::is_unary_function::value) + ------------------------------------------------------------------------------- just failure ------------------------------------------------------------------------------- @@ -17144,6 +17227,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 374 | 281 passed | 86 failed | 7 failed as expected -assertions: 2144 | 1971 passed | 146 failed | 27 failed as expected +test cases: 376 | 283 passed | 86 failed | 7 failed as expected +assertions: 2159 | 1986 passed | 146 failed | 27 failed as expected diff --git a/tests/SelfTest/Baselines/junit.sw.approved.txt b/tests/SelfTest/Baselines/junit.sw.approved.txt index 21e3dbd2..dd6a6e92 100644 --- a/tests/SelfTest/Baselines/junit.sw.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -382,6 +382,8 @@ Exception.tests.cpp: + + @@ -1601,6 +1603,7 @@ Misc.tests.cpp: + FAILED: diff --git a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt index e83a3532..aaa3493f 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt @@ -3,6 +3,9 @@ > + + + diff --git a/tests/SelfTest/Baselines/tap.sw.approved.txt b/tests/SelfTest/Baselines/tap.sw.approved.txt index b306f5cb..1e4e6b34 100644 --- a/tests/SelfTest/Baselines/tap.sw.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.approved.txt @@ -698,6 +698,12 @@ ok {test-number} - c == i for: 5 == 5 ok {test-number} - name.empty() for: true # Clara::Arg supports single-arg parse the way Opt does ok {test-number} - name == "foo" for: "foo" == "foo" +# Clara::Opt supports accept-many lambdas +ok {test-number} - !(parse_result) for: !{?} +# Clara::Opt supports accept-many lambdas +ok {test-number} - parse_result for: {?} +# Clara::Opt supports accept-many lambdas +ok {test-number} - res == std::vector{ "aaa", "bbb" } for: { "aaa", "bbb" } == { "aaa", "bbb" } # Combining MatchAllOfGeneric does not nest ok {test-number} - with 1 message: 'std::is_same< decltype( ( MatcherA() && MatcherB() ) && MatcherC() ), Catch::Matchers::Detail:: MatchAllOfGeneric>::value' # Combining MatchAllOfGeneric does not nest @@ -3786,6 +3792,30 @@ ok {test-number} - # even more nested SECTION tests ok {test-number} - loose text artifact +# is_unary_function +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +# is_unary_function +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +# is_unary_function +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +# is_unary_function +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +# is_unary_function +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +# is_unary_function +ok {test-number} - with 1 message: 'Catch::Clara::Detail::is_unary_function::value' +# is_unary_function +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +# is_unary_function +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +# is_unary_function +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +# is_unary_function +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +# is_unary_function +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' +# is_unary_function +ok {test-number} - with 1 message: '!(Catch::Clara::Detail::is_unary_function::value)' # just failure not ok {test-number} - explicitly with 1 message: 'Previous info should not be seen' # just failure after unscoped info @@ -4290,5 +4320,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..2144 +1..2159 diff --git a/tests/SelfTest/Baselines/teamcity.sw.approved.txt b/tests/SelfTest/Baselines/teamcity.sw.approved.txt index 6b3336d6..933ac413 100644 --- a/tests/SelfTest/Baselines/teamcity.sw.approved.txt +++ b/tests/SelfTest/Baselines/teamcity.sw.approved.txt @@ -237,6 +237,8 @@ Exception.tests.cpp:|nunexpected exception with message:|n "unexpe ##teamcity[testFinished name='Character pretty printing' duration="{duration}"] ##teamcity[testStarted name='Clara::Arg supports single-arg parse the way Opt does'] ##teamcity[testFinished name='Clara::Arg supports single-arg parse the way Opt does' duration="{duration}"] +##teamcity[testStarted name='Clara::Opt supports accept-many lambdas'] +##teamcity[testFinished name='Clara::Opt supports accept-many lambdas' duration="{duration}"] ##teamcity[testStarted name='Combining MatchAllOfGeneric does not nest'] ##teamcity[testFinished name='Combining MatchAllOfGeneric does not nest' duration="{duration}"] ##teamcity[testStarted name='Combining MatchAnyOfGeneric does not nest'] @@ -723,6 +725,8 @@ Misc.tests.cpp:|nexpression failed|n REQUIRE( testCheckedIf( false ##teamcity[testStarted name='has printf'] loose text artifact ##teamcity[testFinished name='has printf' duration="{duration}"] +##teamcity[testStarted name='is_unary_function'] +##teamcity[testFinished name='is_unary_function' duration="{duration}"] ##teamcity[testStarted name='just failure'] Message.tests.cpp:|nexplicit failure with message:|n "Previous info should not be seen"'] ##teamcity[testFinished name='just failure' duration="{duration}"] diff --git a/tests/SelfTest/Baselines/xml.sw.approved.txt b/tests/SelfTest/Baselines/xml.sw.approved.txt index 071b3649..b922d3b5 100644 --- a/tests/SelfTest/Baselines/xml.sw.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.approved.txt @@ -3156,6 +3156,39 @@ Nor would this + +
+ + + !(parse_result) + + + !{?} + + + +
+
+ + + parse_result + + + {?} + + + + + res == std::vector<std::string>{ "aaa", "bbb" } + + + { "aaa", "bbb" } == { "aaa", "bbb" } + + + +
+ +
@@ -17633,6 +17666,9 @@ There is no extra whitespace here loose text artifact + + + Previous info should not be seen @@ -20147,6 +20183,6 @@ loose text artifact - - + + diff --git a/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp b/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp index 1259e7ff..054cda55 100644 --- a/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp @@ -4,19 +4,70 @@ // https://www.boost.org/LICENSE_1_0.txt) // SPDX-License-Identifier: BSL-1.0 - + #include #include #include +TEST_CASE("is_unary_function", "[clara][compilation]") { + auto unary1 = [](int) {}; + auto unary2 = [](std::string const&) {}; + auto const unary3 = [](std::string const&) {}; + auto unary4 = [](int) { return 42; }; + void unary5(char); + double unary6(long); + + double binary1(long, int); + auto binary2 = [](int, char) {}; + auto nullary1 = []() {}; + auto nullary2 = []() {return 42;}; + + STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function::value); + STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function::value); + STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function::value); + STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function::value); + STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function::value); + STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function::value); + + STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function::value); + STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function::value); + STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function::value); + STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function::value); + STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function::value); + STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function::value); +} + + TEST_CASE("Clara::Arg supports single-arg parse the way Opt does", "[clara][arg][compilation]") { std::string name; auto p = Catch::Clara::Arg(name, "just one arg"); - + CHECK(name.empty()); - + p.parse( Catch::Clara::Args{ "UnitTest", "foo" } ); REQUIRE(name == "foo"); } + +TEST_CASE("Clara::Opt supports accept-many lambdas", "[clara][opt]") { + std::string name; + using namespace Catch::Clara; + std::vector res; + const auto push_to_res = [&](std::string const& s) { + res.push_back(s); + return ParserResult::ok(); + }; + + SECTION("Parsing fails on multiple options without accept_many") { + auto p = Parser() | Opt(push_to_res, "value")["-o"]; + auto parse_result = p.parse( Args{ "UnitTest", "-o", "aaa", "-o", "bbb" } ); + CHECK_FALSE(parse_result); + } + SECTION("Parsing succeeds on multiple options with accept_many") { + auto p = Parser() | Opt(accept_many, push_to_res, "value")["-o"]; + auto parse_result = p.parse( Args{ "UnitTest", "-o", "aaa", "-o", "bbb" } ); + CHECK(parse_result); + CHECK(res == std::vector{ "aaa", "bbb" }); + } +}