diff --git a/folly/String.h b/folly/String.h index 5347c4d289a..24a4e7b81a7 100644 --- a/folly/String.h +++ b/folly/String.h @@ -762,6 +762,37 @@ inline bool hasSpaceOrCntrlSymbols(folly::StringPiece s) { return detail::simdHasSpaceOrCntrlSymbols(s); } +struct format_string_for_each_named_arg_fn { + template + constexpr void operator()(std::basic_string_view str, Fn fn) const + noexcept(noexcept(fn(str))) { + using view = std::basic_string_view; + while (true) { + auto const pos = str.find('{'); + auto const beg = pos == view::npos ? str.size() : pos + 1; + if (beg == str.size()) { + return; // malformed + } + if (str[beg] == '{') { + str = str.substr(beg + 1); + continue; // escaped + } + auto const end = std::min(str.find('}', pos), str.find(':', pos)); + if (end == view::npos) { + return; // malformed + } + auto const arg = str.substr(beg, end - beg); + if (!arg.empty() && (arg[0] == '_' || std::isalpha(arg[0]))) { + fn(arg); + } + str = str.substr(beg); + } + } +}; + +inline constexpr format_string_for_each_named_arg_fn + format_string_for_each_named_arg{}; + } // namespace folly #include diff --git a/folly/test/BUCK b/folly/test/BUCK index d829bdf762d..aa7ced47867 100644 --- a/folly/test/BUCK +++ b/folly/test/BUCK @@ -1580,6 +1580,7 @@ cpp_unittest( "//folly:fbvector", "//folly:string", "//folly/container:array", + "//folly/portability:gmock", "//folly/portability:gtest", ], external_deps = [ diff --git a/folly/test/StringTest.cpp b/folly/test/StringTest.cpp index 5753b1834eb..79d65de1104 100644 --- a/folly/test/StringTest.cpp +++ b/folly/test/StringTest.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -1466,3 +1467,24 @@ TEST(String, hasSpaceOrCntrlSymbolsTest) { ASSERT_TRUE(std::iscntrl(1)); ASSERT_TRUE(hasSpaceOrCntrlSymbols(hasCntrl)); } + +TEST(String, format_string_for_each_named_arg) { + auto const fn = [](std::string_view str) { + std::vector out; + folly::format_string_for_each_named_arg(str, [&](auto sub) { + out.push_back(std::string(sub)); + }); + return out; + }; + EXPECT_THAT(fn(""), testing::ElementsAre()); + EXPECT_THAT(fn("hello"), testing::ElementsAre()); + EXPECT_THAT(fn("{{}}"), testing::ElementsAre()); + EXPECT_THAT(fn("hello{{}}world"), testing::ElementsAre()); + EXPECT_THAT(fn("hello{}world"), testing::ElementsAre()); + EXPECT_THAT(fn("hello{3}world"), testing::ElementsAre()); + EXPECT_THAT(fn("hello{34}world"), testing::ElementsAre()); + EXPECT_THAT(fn("hello{bob}world"), testing::ElementsAre("bob")); + EXPECT_THAT(fn("hello{3}world{bob}go"), testing::ElementsAre("bob")); + EXPECT_THAT(fn("hello{bob}world{3}go"), testing::ElementsAre("bob")); + EXPECT_THAT(fn("hello{bob}world{sam}go"), testing::ElementsAre("bob", "sam")); +}