From df870e17f597ebaa46ae89c6539fe077a9e8eeed Mon Sep 17 00:00:00 2001 From: Vito Gamberini Date: Mon, 14 Apr 2025 16:24:09 -0400 Subject: [PATCH] cmArgumentParser: Implement trailing positional args --- Source/cmArgumentParser.cxx | 28 +++++--- Source/cmArgumentParser.h | 27 ++++++++ Tests/CMakeLib/testArgumentParser.cxx | 96 ++++++++++++++++++++++++++- 3 files changed, 139 insertions(+), 12 deletions(-) diff --git a/Source/cmArgumentParser.cxx b/Source/cmArgumentParser.cxx index 5c4fc0d92c..4862de1d49 100644 --- a/Source/cmArgumentParser.cxx +++ b/Source/cmArgumentParser.cxx @@ -147,6 +147,24 @@ void Instance::Consume(std::size_t pos, cm::string_view arg) return; } + if (!this->DoneWithPositional) { + auto const pit = this->Bindings.Positions.Find(pos); + if (pit != this->Bindings.Positions.end()) { + pit->second(*this, pos, arg); + return; + } + + if (this->Bindings.TrailingArgs) { + this->Keyword = ""_s; + this->KeywordValuesSeen = 0; + this->DoneWithPositional = true; + this->Bindings.TrailingArgs(*this); + if (!this->KeywordValueFunc) { + return; + } + } + } + if (this->KeywordValueFunc) { switch (this->KeywordValueFunc(arg)) { case Continue::Yes: @@ -159,14 +177,6 @@ void Instance::Consume(std::size_t pos, cm::string_view arg) return; } - if (!this->DoneWithPositional) { - auto const pit = this->Bindings.Positions.Find(pos); - if (pit != this->Bindings.Positions.end()) { - pit->second(*this, pos, arg); - return; - } - } - if (this->UnparsedArguments) { this->UnparsedArguments->emplace_back(arg); } @@ -174,7 +184,7 @@ void Instance::Consume(std::size_t pos, cm::string_view arg) void Instance::FinishKeyword() { - if (this->Keyword.empty()) { + if (!this->DoneWithPositional) { return; } if (this->KeywordValuesSeen < this->KeywordValuesExpected) { diff --git a/Source/cmArgumentParser.h b/Source/cmArgumentParser.h index e8cd78ee43..160dda0520 100644 --- a/Source/cmArgumentParser.h +++ b/Source/cmArgumentParser.h @@ -113,6 +113,7 @@ public: KeywordNameAction KeywordMissingValue; KeywordNameAction ParsedKeyword; PositionActionMap Positions; + KeywordAction TrailingArgs; }; class Base @@ -149,6 +150,12 @@ public: this->Bindings.KeywordMissingValue = std::move(action); } + void BindTrailingArgs(KeywordAction action) + { + assert(!this->Bindings.TrailingArgs); + this->Bindings.TrailingArgs = std::move(action); + } + void Bind(std::size_t pos, PositionAction action) { bool const inserted = @@ -354,6 +361,18 @@ public: return *this; } + template , + typename mT = cm::remove_member_pointer_t, + typename = cm::enable_if_t::value>, + typename = cm::enable_if_t::value>> + cmArgumentParser& BindTrailingArgs(T member) + { + this->Base::BindTrailingArgs([member](Instance& instance) { + instance.Bind(static_cast(instance.Result)->*member); + }); + return *this; + } + template bool Parse(Result& result, Range const& args, std::vector* unparsedArguments, @@ -431,6 +450,14 @@ public: return *this; } + template + cmArgumentParser& BindTrailingArgs(T& ref) + { + this->Base::BindTrailingArgs( + [&ref](Instance& instance) { instance.Bind(ref); }); + return *this; + } + template ParseResult Parse(Range const& args, std::vector* unparsedArguments, diff --git a/Tests/CMakeLib/testArgumentParser.cxx b/Tests/CMakeLib/testArgumentParser.cxx index 1bdad599f4..95df43e828 100644 --- a/Tests/CMakeLib/testArgumentParser.cxx +++ b/Tests/CMakeLib/testArgumentParser.cxx @@ -44,6 +44,7 @@ struct Result : public ArgumentParser::ParseResult cm::optional Pos0; cm::optional Pos1; cm::optional Pos2; + ArgumentParser::MaybeEmpty> TrailingPos; bool Func0_ = false; ArgumentParser::Continue Func0(cm::string_view) @@ -133,6 +134,31 @@ std::initializer_list const args = { /* clang-format on */ }; +struct ResultTrailingPos : public ArgumentParser::ParseResult +{ + bool Option1 = false; + ArgumentParser::NonEmpty> List1; + + cm::optional Pos0; + cm::optional Pos1; + ArgumentParser::NonEmpty> TrailingPos; +}; + +struct DerivedTrailingPos : ResultTrailingPos +{ +}; + +std::initializer_list const args_trailingpos = { + /* clang-format off */ + "pos0", // position index 0 + "pos1", // position index 1 + "pos_trailing0", // trailing positional 0 + "pos_trailing1", // trailing positional 1 + "OPTION_1", // option + "LIST_1", "foo", "bar", // list arg with 2 elems + /* clang-format on */ +}; + bool verifyResult(Result const& result, std::vector const& unparsedArguments) { @@ -216,6 +242,7 @@ bool verifyResult(Result const& result, ASSERT_TRUE(result.Pos0 == "pos0"); ASSERT_TRUE(!result.Pos1); ASSERT_TRUE(!result.Pos2); + ASSERT_TRUE(result.TrailingPos.empty()); ASSERT_TRUE(result.Func0_ == false); ASSERT_TRUE(result.Func1_ == "foo"); @@ -242,6 +269,26 @@ bool verifyResult(Result const& result, return true; } +bool verifyResult(ResultTrailingPos const& result, + std::vector const& unparsedArguments) +{ + static std::vector const foobar = { "foo", "bar" }; + static std::vector const trailing = { "pos_trailing0", + "pos_trailing1" }; + + ASSERT_TRUE(result); + ASSERT_TRUE(unparsedArguments.empty()); + + ASSERT_TRUE(result.Option1); + ASSERT_TRUE(result.List1 == foobar); + + ASSERT_TRUE(result.Pos0 == "pos0"); + ASSERT_TRUE(result.Pos1 == "pos1"); + ASSERT_TRUE(result.TrailingPos == trailing); + + return true; +} + bool testArgumentParserDynamic() { Result result; @@ -258,6 +305,7 @@ bool testArgumentParserDynamic() .Bind(0, result.Pos0) .Bind(1, result.Pos1) .Bind(2, result.Pos2) + .BindTrailingArgs(result.TrailingPos) .Bind("OPTION_1"_s, result.Option1) .Bind("OPTION_2"_s, result.Option2) .Bind("STRING_1"_s, result.String1) @@ -299,7 +347,23 @@ bool testArgumentParserDynamic() .BindParsedKeywords(result.ParsedKeywords) .Parse(args, &unparsedArguments); - return verifyResult(result, unparsedArguments); + if (!verifyResult(result, unparsedArguments)) { + return false; + } + + unparsedArguments.clear(); + + ResultTrailingPos result_trailing; + static_cast(result_trailing) = + cmArgumentParser{} + .Bind(0, result_trailing.Pos0) + .Bind(1, result_trailing.Pos1) + .BindTrailingArgs(result_trailing.TrailingPos) + .Bind("OPTION_1"_s, result_trailing.Option1) + .Bind("LIST_1"_s, result_trailing.List1) + .Parse(args_trailingpos, &unparsedArguments); + + return verifyResult(result_trailing, unparsedArguments); } static auto const parserStaticFunc4 = @@ -314,6 +378,7 @@ static auto const parserStaticFunc4 = .Bind(0, &resultType::Pos0) \ .Bind(1, &resultType::Pos1) \ .Bind(2, &resultType::Pos2) \ + .BindTrailingArgs(&resultType::TrailingPos) \ .Bind("OPTION_1"_s, &resultType::Option1) \ .Bind("OPTION_2"_s, &resultType::Option2) \ .Bind("STRING_1"_s, &resultType::String1) \ @@ -346,18 +411,43 @@ static auto const parserStaticFunc4 = BIND_ALL(parserStatic, Result); BIND_ALL(parserDerivedStatic, Derived); +#define BIND_TRAILING(name, resultType) \ + static auto const name = cmArgumentParser{} \ + .Bind(0, &resultType::Pos0) \ + .Bind(1, &resultType::Pos1) \ + .BindTrailingArgs(&resultType::TrailingPos) \ + .Bind("OPTION_1"_s, &resultType::Option1) \ + .Bind("LIST_1"_s, &resultType::List1) + +BIND_TRAILING(parserTrailingStatic, ResultTrailingPos); +BIND_TRAILING(parserTrailingDerivedStatic, DerivedTrailingPos); + bool testArgumentParserStatic() { std::vector unparsedArguments; Result const result = parserStatic.Parse(args, &unparsedArguments); - return verifyResult(result, unparsedArguments); + if (!verifyResult(result, unparsedArguments)) { + return false; + } + + unparsedArguments.clear(); + ResultTrailingPos const result_trailing = + parserTrailingStatic.Parse(args_trailingpos, &unparsedArguments); + return verifyResult(result_trailing, unparsedArguments); } bool testArgumentParserDerivedStatic() { std::vector unparsedArguments; Derived const result = parserDerivedStatic.Parse(args, &unparsedArguments); - return verifyResult(result, unparsedArguments); + if (!verifyResult(result, unparsedArguments)) { + return false; + } + + unparsedArguments.clear(); + ResultTrailingPos const result_trailing = + parserTrailingDerivedStatic.Parse(args_trailingpos, &unparsedArguments); + return verifyResult(result_trailing, unparsedArguments); } bool testArgumentParserStaticBool()