From 7ca8d9f0f854acd71f2a2134d86a1e182496c4cc Mon Sep 17 00:00:00 2001 From: Brad King Date: Tue, 28 Jun 2022 15:49:48 -0400 Subject: cmArgumentParser: Model non-empty strings with wrapper type Some clients have been explicitly checking whether the string specified after a keyword is empty. Offer them a way to specify that the string must be non-empty as part of the binding type. --- Source/cmArgumentParser.cxx | 14 ++++++++++++++ Source/cmArgumentParser.h | 1 + Source/cmArgumentParserTypes.h | 5 +++++ Tests/CMakeLib/testArgumentParser.cxx | 16 ++++++++++++++++ 4 files changed, 36 insertions(+) diff --git a/Source/cmArgumentParser.cxx b/Source/cmArgumentParser.cxx index 05f8ce8..614d00d 100644 --- a/Source/cmArgumentParser.cxx +++ b/Source/cmArgumentParser.cxx @@ -76,6 +76,20 @@ void Instance::Bind(std::string& val) ExpectAtLeast{ 1 }); } +void Instance::Bind(NonEmpty& val) +{ + this->Bind( + [this, &val](cm::string_view arg) -> Continue { + if (arg.empty() && this->ParseResults) { + this->ParseResults->AddKeywordError(this->Keyword, + " empty string not allowed\n"); + } + val.assign(std::string(arg)); + return Continue::No; + }, + ExpectAtLeast{ 1 }); +} + void Instance::Bind(Maybe& val) { this->Bind( diff --git a/Source/cmArgumentParser.h b/Source/cmArgumentParser.h index c25e2f6..ac78872 100644 --- a/Source/cmArgumentParser.h +++ b/Source/cmArgumentParser.h @@ -171,6 +171,7 @@ public: void Bind(std::function f, ExpectAtLeast expect); void Bind(bool& val); void Bind(std::string& val); + void Bind(NonEmpty& val); void Bind(Maybe& val); void Bind(MaybeEmpty>& val); void Bind(NonEmpty>& val); diff --git a/Source/cmArgumentParserTypes.h b/Source/cmArgumentParserTypes.h index fe8e4ca..7daae09 100644 --- a/Source/cmArgumentParserTypes.h +++ b/Source/cmArgumentParserTypes.h @@ -34,6 +34,11 @@ struct NonEmpty> : public std::vector { using std::vector::vector; }; +template <> +struct NonEmpty : public std::string +{ + using std::string::basic_string; +}; } // namespace ArgumentParser diff --git a/Tests/CMakeLib/testArgumentParser.cxx b/Tests/CMakeLib/testArgumentParser.cxx index 496b873..58c0418 100644 --- a/Tests/CMakeLib/testArgumentParser.cxx +++ b/Tests/CMakeLib/testArgumentParser.cxx @@ -27,6 +27,8 @@ struct Result : public ArgumentParser::ParseResult cm::optional String2; cm::optional String3; ArgumentParser::Maybe String4; + ArgumentParser::NonEmpty String5; + ArgumentParser::NonEmpty String6; ArgumentParser::NonEmpty> List1; ArgumentParser::NonEmpty> List2; @@ -87,6 +89,8 @@ struct Result : public ArgumentParser::ParseResult ArgumentParser::NonEmpty> UnboundNonEmpty{ 1, "unbound" }; + ArgumentParser::NonEmpty UnboundNonEmptyStr{ 'u', 'n', 'b', 'o', + 'u', 'n', 'd' }; std::vector ParsedKeywords; }; @@ -100,6 +104,8 @@ std::initializer_list const args = { "STRING_2", "foo", "bar", // string arg + unparsed value, presence captured // "STRING_3", // string arg that is not present "STRING_4", // string arg allowed to be missing value + "STRING_5", "foo", // string arg that is not empty + "STRING_6", "", // string arg that is empty "LIST_1", // list arg missing values "LIST_2", "foo", "bar", // list arg with 2 elems "LIST_3", "bar", // list arg ... @@ -133,6 +139,8 @@ bool verifyResult(Result const& result, "STRING_1", "STRING_2", "STRING_4", + "STRING_5", + "STRING_6", "LIST_1", "LIST_2", "LIST_3", @@ -159,6 +167,7 @@ bool verifyResult(Result const& result, }; static std::map const keywordErrors = { { "STRING_1"_s, " missing required value\n" }, + { "STRING_6"_s, " empty string not allowed\n" }, { "LIST_1"_s, " missing required value\n" }, { "LIST_4"_s, " missing required value\n" }, { "FUNC_0"_s, " missing required value\n" } @@ -184,6 +193,8 @@ bool verifyResult(Result const& result, ASSERT_TRUE(*result.String2 == "foo"); ASSERT_TRUE(!result.String3); ASSERT_TRUE(result.String4.empty()); + ASSERT_TRUE(result.String5 == "foo"); + ASSERT_TRUE(result.String6.empty()); ASSERT_TRUE(result.List1.empty()); ASSERT_TRUE(result.List2 == foobar); @@ -217,6 +228,7 @@ bool verifyResult(Result const& result, ASSERT_TRUE(result.UnboundMaybe == "unbound"); ASSERT_TRUE(result.UnboundMaybeEmpty == unbound); ASSERT_TRUE(result.UnboundNonEmpty == unbound); + ASSERT_TRUE(result.UnboundNonEmptyStr == "unbound"); ASSERT_TRUE(result.ParsedKeywords == parsedKeywords); @@ -249,6 +261,8 @@ bool testArgumentParserDynamic() .Bind("STRING_2"_s, result.String2) .Bind("STRING_3"_s, result.String3) .Bind("STRING_4"_s, result.String4) + .Bind("STRING_5"_s, result.String5) + .Bind("STRING_6"_s, result.String6) .Bind("LIST_1"_s, result.List1) .Bind("LIST_2"_s, result.List2) .Bind("LIST_3"_s, result.List3) @@ -299,6 +313,8 @@ static auto const parserStatic = // .Bind("STRING_2"_s, &Result::String2) .Bind("STRING_3"_s, &Result::String3) .Bind("STRING_4"_s, &Result::String4) + .Bind("STRING_5"_s, &Result::String5) + .Bind("STRING_6"_s, &Result::String6) .Bind("LIST_1"_s, &Result::List1) .Bind("LIST_2"_s, &Result::List2) .Bind("LIST_3"_s, &Result::List3) -- cgit v0.12