From 1f2eb63d1c77279f69e18144daa3d0eb0d035b01 Mon Sep 17 00:00:00 2001 From: Brad King Date: Mon, 20 Jun 2022 10:15:46 -0400 Subject: cmArgumentParser: Add callback bindings --- Source/cmArgumentParser.h | 91 +++++++++++++++++++++++++++ Tests/CMakeLib/testArgumentParser.cxx | 113 +++++++++++++++++++++++++++++++++- 2 files changed, 201 insertions(+), 3 deletions(-) diff --git a/Source/cmArgumentParser.h b/Source/cmArgumentParser.h index d0406e4..fd7b69a 100644 --- a/Source/cmArgumentParser.h +++ b/Source/cmArgumentParser.h @@ -209,6 +209,70 @@ public: return *this; } + cmArgumentParser& Bind(cm::static_string_view name, + Continue (Result::*member)(cm::string_view), + ExpectAtLeast expect = { 1 }) + { + this->Base::Bind(name, [member, expect](Instance& instance) { + Result* result = static_cast(instance.Result); + instance.Bind( + [result, member](cm::string_view arg) -> Continue { + return (result->*member)(arg); + }, + expect); + }); + return *this; + } + + cmArgumentParser& Bind(cm::static_string_view name, + Continue (Result::*member)(cm::string_view, + cm::string_view), + ExpectAtLeast expect = { 1 }) + { + this->Base::Bind(name, [member, expect](Instance& instance) { + Result* result = static_cast(instance.Result); + cm::string_view keyword = instance.Keyword; + instance.Bind( + [result, member, keyword](cm::string_view arg) -> Continue { + return (result->*member)(keyword, arg); + }, + expect); + }); + return *this; + } + + cmArgumentParser& Bind(cm::static_string_view name, + std::function f, + ExpectAtLeast expect = { 1 }) + { + this->Base::Bind(name, [f, expect](Instance& instance) { + Result* result = static_cast(instance.Result); + instance.Bind( + [result, &f](cm::string_view arg) -> Continue { + return f(*result, arg); + }, + expect); + }); + return *this; + } + + cmArgumentParser& Bind( + cm::static_string_view name, + std::function f, + ExpectAtLeast expect = { 1 }) + { + this->Base::Bind(name, [f, expect](Instance& instance) { + Result* result = static_cast(instance.Result); + cm::string_view keyword = instance.Keyword; + instance.Bind( + [result, keyword, &f](cm::string_view arg) -> Continue { + return f(*result, keyword, arg); + }, + expect); + }); + return *this; + } + cmArgumentParser& BindParsedKeywords( std::vector Result::*member) { @@ -252,6 +316,33 @@ public: return *this; } + cmArgumentParser& Bind(cm::static_string_view name, + std::function f, + ExpectAtLeast expect = { 1 }) + { + this->Base::Bind(name, [f, expect](Instance& instance) { + instance.Bind([&f](cm::string_view arg) -> Continue { return f(arg); }, + expect); + }); + return *this; + } + + cmArgumentParser& Bind( + cm::static_string_view name, + std::function f, + ExpectAtLeast expect = { 1 }) + { + this->Base::Bind(name, [f, expect](Instance& instance) { + cm::string_view keyword = instance.Keyword; + instance.Bind( + [keyword, &f](cm::string_view arg) -> Continue { + return f(keyword, arg); + }, + expect); + }); + return *this; + } + cmArgumentParser& BindParsedKeywords(std::vector& ref) { this->Base::BindParsedKeyword( diff --git a/Tests/CMakeLib/testArgumentParser.cxx b/Tests/CMakeLib/testArgumentParser.cxx index dad5b85..d5533f8 100644 --- a/Tests/CMakeLib/testArgumentParser.cxx +++ b/Tests/CMakeLib/testArgumentParser.cxx @@ -1,6 +1,7 @@ /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ +#include #include #include #include @@ -39,6 +40,43 @@ struct Result : public ArgumentParser::ParseResult cm::optional>> Multi3; cm::optional>> Multi4; + bool Func0_ = false; + ArgumentParser::Continue Func0(cm::string_view) + { + Func0_ = true; + return ArgumentParser::Continue::No; + } + + std::string Func1_; + ArgumentParser::Continue Func1(cm::string_view arg) + { + Func1_ = std::string(arg); + return ArgumentParser::Continue::No; + } + + std::map> Func2_; + ArgumentParser::Continue Func2(cm::string_view key, cm::string_view arg) + { + Func2_[std::string(key)].emplace_back(arg); + return key == "FUNC_2b" ? ArgumentParser::Continue::Yes + : ArgumentParser::Continue::No; + } + + std::vector Func3_; + ArgumentParser::Continue Func3(cm::string_view arg) + { + Func3_.emplace_back(arg); + return ArgumentParser::Continue::Yes; + } + + std::map> Func4_; + ArgumentParser::Continue Func4(cm::string_view key, cm::string_view arg) + { + Func4_[std::string(key)].emplace_back(arg); + return key == "FUNC_4b" ? ArgumentParser::Continue::Yes + : ArgumentParser::Continue::No; + } + ArgumentParser::Maybe UnboundMaybe{ 'u', 'n', 'b', 'o', 'u', 'n', 'd' }; ArgumentParser::MaybeEmpty> UnboundMaybeEmpty{ @@ -70,6 +108,13 @@ std::initializer_list const args = { "MULTI_3", "foo", "bar", // multi list with first list with two elems "MULTI_3", "bar", "foo", // multi list with second list with two elems // "MULTI_4", // multi list arg that is not present + "FUNC_0", // callback arg missing value + "FUNC_1", "foo", "ign1", // callback with one arg + unparsed value + "FUNC_2a", "foo", "ign2", // callback with keyword-dependent arg count + "FUNC_2b", "bar", "zot", // callback with keyword-dependent arg count + "FUNC_3", "foo", "bar", // callback with list arg ... + "FUNC_4a", "foo", "ign4", // callback with keyword-dependent arg count + "FUNC_4b", "bar", "zot", // callback with keyword-dependent arg count /* clang-format on */ }; @@ -94,13 +139,29 @@ bool verifyResult(Result const& result, "MULTI_2", "MULTI_3", "MULTI_3", + "FUNC_0", + "FUNC_1", + "FUNC_2a", + "FUNC_2b", + "FUNC_3", + "FUNC_4a", + "FUNC_4b", /* clang-format on */ }; + static std::map> const func2map = { + { "FUNC_2a", { "foo" } }, { "FUNC_2b", { "bar", "zot" } } + }; + static std::map> const func4map = { + { "FUNC_4a", { "foo" } }, { "FUNC_4b", { "bar", "zot" } } + }; static std::map const keywordErrors = { { "STRING_1"_s, " missing required value\n" }, { "LIST_1"_s, " missing required value\n" }, - { "LIST_4"_s, " missing required value\n" } + { "LIST_4"_s, " missing required value\n" }, + { "FUNC_0"_s, " missing required value\n" } }; + static std::vector const unparsed = { "bar", "ign1", "ign2", + "ign4" }; #define ASSERT_TRUE(x) \ do { \ @@ -140,8 +201,13 @@ bool verifyResult(Result const& result, ASSERT_TRUE((*result.Multi3)[1] == barfoo); ASSERT_TRUE(!result.Multi4); - ASSERT_TRUE(unparsedArguments.size() == 1); - ASSERT_TRUE(unparsedArguments[0] == "bar"); + ASSERT_TRUE(result.Func0_ == false); + ASSERT_TRUE(result.Func1_ == "foo"); + ASSERT_TRUE(result.Func2_ == func2map); + ASSERT_TRUE(result.Func3_ == foobar); + ASSERT_TRUE(result.Func4_ == func4map); + + ASSERT_TRUE(unparsedArguments == unparsed); ASSERT_TRUE(result.UnboundMaybe == "unbound"); ASSERT_TRUE(result.UnboundMaybeEmpty == unbound); @@ -164,6 +230,12 @@ bool testArgumentParserDynamic() Result result; std::vector unparsedArguments; + std::function + func4 = [&result](cm::string_view key, + cm::string_view arg) -> ArgumentParser::Continue { + return result.Func4(key, arg); + }; + static_cast(result) = cmArgumentParser{} .Bind("OPTION_1"_s, result.Option1) @@ -182,12 +254,37 @@ bool testArgumentParserDynamic() .Bind("MULTI_2"_s, result.Multi2) .Bind("MULTI_3"_s, result.Multi3) .Bind("MULTI_4"_s, result.Multi4) + .Bind("FUNC_0"_s, + [&result](cm::string_view arg) -> ArgumentParser::Continue { + return result.Func0(arg); + }) + .Bind("FUNC_1"_s, + [&result](cm::string_view arg) -> ArgumentParser::Continue { + return result.Func1(arg); + }) + .Bind("FUNC_2a"_s, + [&result](cm::string_view key, cm::string_view arg) + -> ArgumentParser::Continue { return result.Func2(key, arg); }) + .Bind("FUNC_2b"_s, + [&result](cm::string_view key, cm::string_view arg) + -> ArgumentParser::Continue { return result.Func2(key, arg); }) + .Bind("FUNC_3"_s, + [&result](cm::string_view arg) -> ArgumentParser::Continue { + return result.Func3(arg); + }) + .Bind("FUNC_4a"_s, func4) + .Bind("FUNC_4b"_s, func4) .BindParsedKeywords(result.ParsedKeywords) .Parse(args, &unparsedArguments); return verifyResult(result, unparsedArguments); } +static auto const parserStaticFunc4 = + [](Result& result, cm::string_view key, + cm::string_view arg) -> ArgumentParser::Continue { + return result.Func4(key, arg); +}; static auto const parserStatic = // cmArgumentParser{} .Bind("OPTION_1"_s, &Result::Option1) @@ -206,6 +303,16 @@ static auto const parserStatic = // .Bind("MULTI_2"_s, &Result::Multi2) .Bind("MULTI_3"_s, &Result::Multi3) .Bind("MULTI_4"_s, &Result::Multi4) + .Bind("FUNC_0"_s, &Result::Func0) + .Bind("FUNC_1"_s, &Result::Func1) + .Bind("FUNC_2a"_s, &Result::Func2) + .Bind("FUNC_2b"_s, &Result::Func2) + .Bind("FUNC_3"_s, + [](Result& result, cm::string_view arg) -> ArgumentParser::Continue { + return result.Func3(arg); + }) + .Bind("FUNC_4a"_s, parserStaticFunc4) + .Bind("FUNC_4b"_s, parserStaticFunc4) .BindParsedKeywords(&Result::ParsedKeywords) /* keep semicolon on own line */; -- cgit v0.12