diff --git a/toolbox/util/String.hpp b/toolbox/util/String.hpp index e018ebe1..55ab59fd 100644 --- a/toolbox/util/String.hpp +++ b/toolbox/util/String.hpp @@ -23,6 +23,7 @@ #include #include #include +#include namespace toolbox { inline namespace util { @@ -129,8 +130,8 @@ inline std::string trim_copy(std::string s) noexcept return s; } -TOOLBOX_API std::pair split_pair(std::string_view s, - std::string_view delim) noexcept; +TOOLBOX_API std::pair +split_pair(std::string_view s, std::string_view delim) noexcept; TOOLBOX_API std::pair split_pair(const std::string& s, std::string_view delim); @@ -140,6 +141,26 @@ TOOLBOX_API std::pair split_pair(std::string TOOLBOX_API std::pair split_pair(const std::string& s, char delim); +template +void for_each_csv_item(std::string_view csv, + F f) noexcept(noexcept(split_pair(std::declval(), ',')) + && noexcept(f(std::declval()))) +{ + bool trailing_comma; + do { + std::string_view v; + trailing_comma = (!csv.empty() && csv.back() == ','); + std::tie(v, csv) = split_pair(csv, ','); + f(v); + } while (!csv.empty() || trailing_comma); +} + +static_assert(noexcept(for_each_csv_item(std::string_view{"a,b"}, + [](std::string_view) noexcept {})), + "for_each_csv_item is noexcept(true)"); +static_assert(!noexcept(for_each_csv_item(std::string_view{"a,b"}, [](std::string_view) {})), + "for_each_csv_item is noexcept(false)"); + /// Returns the length of right-padded string. /// \tparam PadC The character used for padding. /// \param src The source string. diff --git a/toolbox/util/String.ut.cpp b/toolbox/util/String.ut.cpp index 15b74569..fa589690 100644 --- a/toolbox/util/String.ut.cpp +++ b/toolbox/util/String.ut.cpp @@ -115,6 +115,72 @@ BOOST_AUTO_TEST_CASE(SplitPairCase) BOOST_CHECK_EQUAL(split_pair(" a = b "s, '='), make_pair(" a "s, " b "s)); } +BOOST_AUTO_TEST_CASE(for_each_csv_item_tests) +{ + std::vector values; + auto f{[&values](std::string_view value) { values.emplace_back(value); }}; + { + values.clear(); + auto csv{""sv}; + for_each_csv_item(csv, f); + BOOST_REQUIRE_EQUAL(values.size(), 1UL); + BOOST_CHECK_EQUAL(values[0], ""); + } + { + values.clear(); + auto csv{","sv}; + for_each_csv_item(csv, f); + BOOST_REQUIRE_EQUAL(values.size(), 2UL); + BOOST_CHECK_EQUAL(values[0], ""); + BOOST_CHECK_EQUAL(values[1], ""); + } + { + values.clear(); + auto csv{",,"sv}; + for_each_csv_item(csv, f); + BOOST_REQUIRE_EQUAL(values.size(), 3UL); + BOOST_CHECK_EQUAL(values[0], ""); + BOOST_CHECK_EQUAL(values[1], ""); + BOOST_CHECK_EQUAL(values[2], ""); + } + { + values.clear(); + auto csv{"steve,,"sv}; + for_each_csv_item(csv, f); + BOOST_REQUIRE_EQUAL(values.size(), 3UL); + BOOST_CHECK_EQUAL(values[0], "steve"); + BOOST_CHECK_EQUAL(values[1], ""); + BOOST_CHECK_EQUAL(values[2], ""); + } + { + values.clear(); + auto csv{",steve,"sv}; + for_each_csv_item(csv, f); + BOOST_REQUIRE_EQUAL(values.size(), 3UL); + BOOST_CHECK_EQUAL(values[0], ""); + BOOST_CHECK_EQUAL(values[1], "steve"); + BOOST_CHECK_EQUAL(values[2], ""); + } + { + values.clear(); + auto csv{",,steve"sv}; + for_each_csv_item(csv, f); + BOOST_REQUIRE_EQUAL(values.size(), 3UL); + BOOST_CHECK_EQUAL(values[0], ""); + BOOST_CHECK_EQUAL(values[1], ""); + BOOST_CHECK_EQUAL(values[2], "steve"); + } + { + values.clear(); + auto csv{"steve,woz,'ere"sv}; + for_each_csv_item(csv, f); + BOOST_REQUIRE_EQUAL(values.size(), 3UL); + BOOST_CHECK_EQUAL(values[0], "steve"); + BOOST_CHECK_EQUAL(values[1], "woz"); + BOOST_CHECK_EQUAL(values[2], "'ere"); + } +} + BOOST_AUTO_TEST_CASE(PstrlenCase) { constexpr char ZeroPad[] = "foo";