From 236bacc2445ab93106bc9ed8c53299db9282f174 Mon Sep 17 00:00:00 2001 From: Brad King Date: Tue, 26 Jul 2022 16:25:29 -0400 Subject: cmArgumentParser: Offer bindings for positional arguments --- Source/cmArgumentParser.cxx | 27 ++++++++++++++- Source/cmArgumentParser.h | 64 +++++++++++++++++++++++++++++------ Tests/CMakeLib/testArgumentParser.cxx | 7 ++++ 3 files changed, 87 insertions(+), 11 deletions(-) diff --git a/Source/cmArgumentParser.cxx b/Source/cmArgumentParser.cxx index bd31b46..05f8ce8 100644 --- a/Source/cmArgumentParser.cxx +++ b/Source/cmArgumentParser.cxx @@ -34,6 +34,25 @@ auto KeywordActionMap::Find(cm::string_view name) const -> const_iterator return (it != this->end() && it->first == name) ? it : this->end(); } +auto PositionActionMap::Emplace(std::size_t pos, PositionAction action) + -> std::pair +{ + auto const it = std::lower_bound( + this->begin(), this->end(), pos, + [](value_type const& elem, std::size_t k) { return elem.first < k; }); + return (it != this->end() && it->first == pos) + ? std::make_pair(it, false) + : std::make_pair(this->emplace(it, pos, std::move(action)), true); +} + +auto PositionActionMap::Find(std::size_t pos) const -> const_iterator +{ + auto const it = std::lower_bound( + this->begin(), this->end(), pos, + [](value_type const& elem, std::size_t k) { return elem.first < k; }); + return (it != this->end() && it->first == pos) ? it : this->end(); +} + void Instance::Bind(std::function f, ExpectAtLeast expect) { @@ -99,7 +118,7 @@ void Instance::Bind(std::vector>& multiVal) ExpectAtLeast{ 0 }); } -void Instance::Consume(cm::string_view arg) +void Instance::Consume(std::size_t pos, cm::string_view arg) { auto const it = this->Bindings.Keywords.Find(arg); if (it != this->Bindings.Keywords.end()) { @@ -125,6 +144,12 @@ void Instance::Consume(cm::string_view arg) return; } + auto const pit = this->Bindings.Positions.Find(pos); + if (pit != this->Bindings.Positions.end()) { + pit->second(*this, pos, arg); + return; + } + if (this->UnparsedArguments != nullptr) { this->UnparsedArguments->emplace_back(arg); } diff --git a/Source/cmArgumentParser.h b/Source/cmArgumentParser.h index fd7b69a..c25e2f6 100644 --- a/Source/cmArgumentParser.h +++ b/Source/cmArgumentParser.h @@ -5,6 +5,7 @@ #include "cmConfigure.h" // IWYU pragma: keep #include +#include #include #include #include @@ -81,6 +82,8 @@ struct ExpectAtLeast class Instance; using KeywordAction = std::function; using KeywordNameAction = std::function; +using PositionAction = + std::function; // using KeywordActionMap = cm::flat_map; class KeywordActionMap @@ -92,12 +95,22 @@ public: const_iterator Find(cm::string_view name) const; }; +// using PositionActionMap = cm::flat_map; +class PositionActionMap + : public std::vector> +{ +public: + std::pair Emplace(std::size_t pos, PositionAction action); + const_iterator Find(std::size_t pos) const; +}; + class ActionMap { public: KeywordActionMap Keywords; KeywordNameAction KeywordMissingValue; KeywordNameAction ParsedKeyword; + PositionActionMap Positions; }; class Base @@ -133,6 +146,14 @@ public: assert(!this->Bindings.KeywordMissingValue); this->Bindings.KeywordMissingValue = std::move(action); } + + void Bind(std::size_t pos, PositionAction action) + { + bool const inserted = + this->Bindings.Positions.Emplace(pos, std::move(action)).second; + assert(inserted); + static_cast(inserted); + } }; class Instance @@ -166,10 +187,10 @@ public: } template - void Parse(Range const& args) + void Parse(Range const& args, std::size_t pos = 0) { for (cm::string_view arg : args) { - this->Consume(arg); + this->Consume(pos++, arg); } this->FinishKeyword(); } @@ -185,7 +206,7 @@ private: std::size_t KeywordValuesExpected = 0; std::function KeywordValueFunc; - void Consume(cm::string_view arg); + void Consume(std::size_t pos, cm::string_view arg); void FinishKeyword(); template @@ -273,6 +294,18 @@ public: return *this; } + cmArgumentParser& Bind(std::size_t position, + cm::optional Result::*member) + { + this->Base::Bind( + position, + [member](Instance& instance, std::size_t, cm::string_view arg) { + Result* result = static_cast(instance.Result); + result->*member = arg; + }); + return *this; + } + cmArgumentParser& BindParsedKeywords( std::vector Result::*member) { @@ -285,22 +318,23 @@ public: template bool Parse(Result& result, Range const& args, - std::vector* unparsedArguments) const + std::vector* unparsedArguments, + std::size_t pos = 0) const { using ArgumentParser::AsParseResultPtr; ParseResult* parseResultPtr = AsParseResultPtr(result); Instance instance(this->Bindings, parseResultPtr, unparsedArguments, &result); - instance.Parse(args); + instance.Parse(args, pos); return parseResultPtr ? static_cast(*parseResultPtr) : true; } template - Result Parse(Range const& args, - std::vector* unparsedArguments) const + Result Parse(Range const& args, std::vector* unparsedArguments, + std::size_t pos = 0) const { Result result; - this->Parse(result, args, unparsedArguments); + this->Parse(result, args, unparsedArguments, pos); return result; } }; @@ -343,6 +377,15 @@ public: return *this; } + cmArgumentParser& Bind(std::size_t position, cm::optional& ref) + { + this->Base::Bind(position, + [&ref](Instance&, std::size_t, cm::string_view arg) { + ref = std::string(arg); + }); + return *this; + } + cmArgumentParser& BindParsedKeywords(std::vector& ref) { this->Base::BindParsedKeyword( @@ -352,11 +395,12 @@ public: template ParseResult Parse(Range const& args, - std::vector* unparsedArguments) const + std::vector* unparsedArguments, + std::size_t pos = 0) const { ParseResult parseResult; Instance instance(this->Bindings, &parseResult, unparsedArguments); - instance.Parse(args); + instance.Parse(args, pos); return parseResult; } diff --git a/Tests/CMakeLib/testArgumentParser.cxx b/Tests/CMakeLib/testArgumentParser.cxx index d5533f8..496b873 100644 --- a/Tests/CMakeLib/testArgumentParser.cxx +++ b/Tests/CMakeLib/testArgumentParser.cxx @@ -40,6 +40,8 @@ struct Result : public ArgumentParser::ParseResult cm::optional>> Multi3; cm::optional>> Multi4; + cm::optional Pos1; + bool Func0_ = false; ArgumentParser::Continue Func0(cm::string_view) { @@ -93,6 +95,7 @@ std::initializer_list const args = { /* clang-format off */ "OPTION_1", // option // "OPTION_2", // option that is not present + "pos1", // position index 1 "STRING_1", // string arg missing value "STRING_2", "foo", "bar", // string arg + unparsed value, presence captured // "STRING_3", // string arg that is not present @@ -201,6 +204,8 @@ bool verifyResult(Result const& result, ASSERT_TRUE((*result.Multi3)[1] == barfoo); ASSERT_TRUE(!result.Multi4); + ASSERT_TRUE(result.Pos1 == "pos1"); + ASSERT_TRUE(result.Func0_ == false); ASSERT_TRUE(result.Func1_ == "foo"); ASSERT_TRUE(result.Func2_ == func2map); @@ -254,6 +259,7 @@ bool testArgumentParserDynamic() .Bind("MULTI_2"_s, result.Multi2) .Bind("MULTI_3"_s, result.Multi3) .Bind("MULTI_4"_s, result.Multi4) + .Bind(1, result.Pos1) .Bind("FUNC_0"_s, [&result](cm::string_view arg) -> ArgumentParser::Continue { return result.Func0(arg); @@ -303,6 +309,7 @@ static auto const parserStatic = // .Bind("MULTI_2"_s, &Result::Multi2) .Bind("MULTI_3"_s, &Result::Multi3) .Bind("MULTI_4"_s, &Result::Multi4) + .Bind(1, &Result::Pos1) .Bind("FUNC_0"_s, &Result::Func0) .Bind("FUNC_1"_s, &Result::Func1) .Bind("FUNC_2a"_s, &Result::Func2) -- cgit v0.12