浏览代码

feat: add tuple handling

Sam Jaffe 3 年之前
父节点
当前提交
68965f7aec

+ 33 - 30
include/string_utils/cast.h

@@ -13,11 +13,8 @@
 #include <string>
 #include <type_traits>
 #include <utility>
-
-#if __has_include(<variant>) && __cplusplus > 201402L
-#define STRING_UTIL_CAST_STD_VARIANT
 #include <variant>
-#endif
+#include <vector>
 
 #include "any_of.h"
 
@@ -30,7 +27,6 @@
 
 namespace string_utils { namespace traits {
 template <typename T, typename = void> struct is_stringy : std::false_type {};
-#if defined(STRING_UTIL_CAST_STD_VARIANT)
 
 template <typename> struct is_variant : std::false_type {};
 template <typename... Ts>
@@ -40,21 +36,19 @@ template <typename T>
 struct is_stringy<
     T, std::enable_if_t<std::is_constructible<T, std::string const &>{} &&
                         !is_variant<T>{}>> : std::true_type {};
-#else
-template <typename T>
-struct is_stringy<
-    T, std::enable_if_t<std::is_constructible<T, std::string const &>{}>>
-    : std::true_type {};
-#endif
+
 }}
 
 namespace string_utils {
 template <typename T> std::pair<T, bool> cast(std::string const & str);
 template <typename T> bool cast(std::string const & str, std::optional<T> & to);
-#if defined(STRING_UTIL_CAST_STD_VARIANT)
 template <typename... Ts>
 bool cast(std::string const & str, std::variant<Ts...> & to);
-#endif
+
+template <typename... Ts>
+bool cast(std::vector<std::string> const & str, std::tuple<Ts...> & to);
+template <typename K, typename V>
+bool cast(std::vector<std::string> const & str, std::pair<K, V> & to);
 }
 
 namespace string_utils {
@@ -89,42 +83,51 @@ inline bool cast(std::string const & str, bool & to) {
 }
 }
 
-#if defined(STRING_UTIL_CAST_STD_VARIANT)
 namespace string_utils::detail {
-template <size_t I, typename... Ts>
-bool _cast(std::string const & str, std::variant<Ts...> & to) {
-  auto [rval, found] = cast<std::tuple_element_t<I, std::tuple<Ts...>>>(str);
+template <typename T, typename... Ts>
+bool cast_alternative(std::string const & str, std::variant<Ts...> & to) {
+  auto [rval, found] = cast<T>(str);
   if (found) { to = std::move(rval); }
   return found;
 }
 
-// This is needed due to a compiler bug in Clang that fails to properly compile
-// fold-expressions.
-template <size_t I, typename... Ts>
-bool _cast_chain(std::string const & str, std::variant<Ts...> & to) {
-  if constexpr (I == sizeof...(Ts)) {
-    return false;
-  } else {
-    return _cast<I>(str, to) || _cast_chain<I + 1>(str, to);
-  }
+template <typename Tuple, size_t... Is>
+bool cast_tuple(std::vector<std::string> const & str, Tuple & to,
+                std::index_sequence<Is...>) {
+  return ((cast(str[Is], std::get<Is>(to))) && ...);
+}
+
+template <typename Tuple>
+bool cast_tuple(std::vector<std::string> const & str, Tuple & to) {
+  constexpr size_t N = std::tuple_size_v<Tuple>;
+  return str.size() == N && cast_tuple(str, to, std::make_index_sequence<N>{});
 }
 }
 
 namespace string_utils {
+
 template <typename... Ts>
 bool cast(std::string const & str, std::variant<Ts...> & to) {
-  return detail::_cast_chain<0>(str, to);
-}
+  return (detail::cast_alternative<Ts>(str, to) || ...);
 }
-#endif
 
-namespace string_utils {
 template <typename T>
 bool cast(std::string const & str, std::optional<T> & to) {
   auto [value, success] = cast<T>(str);
   if (success) { to = std::move(value); }
   return true;
 }
+
+template <typename... Ts>
+bool cast(std::vector<std::string> const & str, std::tuple<Ts...> & to) {
+  return detail::cast_tuple(str, to);
+}
+
+template <typename K, typename V>
+bool cast(std::vector<std::string> const & str, std::pair<K, V> & to) {
+  return detail::cast_tuple(str, to);
+}
+
 }
 
 // This should be placed last in the file

+ 29 - 0
include/string_utils/split.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <tuple>
+#include <utility>
+
+#include "string_utils/cast.h"
+#include "string_utils/tokenizer.h"
+
+namespace string_utils {
+
+template <typename... Ts>
+auto to_tuple(std::string const &input, char const f = ',') {
+  std::tuple<Ts...> rval;
+  auto result = tokenizer({f}, {'"', ""})(input);
+  return std::make_pair(rval, cast(result, rval));
+}
+
+template <typename K, typename V = std::string>
+auto to_keyval(std::string const &input, char const f = '=') {
+  std::pair<K, V> rval;
+  auto result = tokenizer({f}).max_outputs(2)(input);
+  return std::make_pair(rval, cast(result, rval));
+}
+
+inline auto to_keyval(std::string const &input, char const f = '=') {
+  return to_keyval<std::string, std::string>(input, f);
+}
+
+}

+ 32 - 0
string-utils.xcodeproj/project.pbxproj

@@ -13,6 +13,10 @@
 		CD26688B252FFAAE00B3E667 /* libstring-utils.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CD266862252FF4B600B3E667 /* libstring-utils.a */; };
 		CD2668B4252FFACB00B3E667 /* GoogleMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD266875252FF51F00B3E667 /* GoogleMock.framework */; };
 		CD2668B5252FFAD200B3E667 /* tokenizer_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD266880252FFA7E00B3E667 /* tokenizer_test.cxx */; };
+		CDC883EA28560A7C0088C91E /* any_of.h in Headers */ = {isa = PBXBuildFile; fileRef = CDC883E228560A7C0088C91E /* any_of.h */; };
+		CDC883EB28560A7C0088C91E /* tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = CDC883E328560A7C0088C91E /* tokenizer.h */; };
+		CDC883EC28560A7C0088C91E /* cast.h in Headers */ = {isa = PBXBuildFile; fileRef = CDC883E428560A7C0088C91E /* cast.h */; };
+		CDC883ED28560A7C0088C91E /* split.h in Headers */ = {isa = PBXBuildFile; fileRef = CDC883E528560A7C0088C91E /* split.h */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -63,6 +67,10 @@
 		CD266880252FFA7E00B3E667 /* tokenizer_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = tokenizer_test.cxx; sourceTree = "<group>"; };
 		CD266886252FFAAE00B3E667 /* string_utils-test.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "string_utils-test.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD26688A252FFAAE00B3E667 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		CDC883E228560A7C0088C91E /* any_of.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = any_of.h; sourceTree = "<group>"; };
+		CDC883E328560A7C0088C91E /* tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tokenizer.h; sourceTree = "<group>"; };
+		CDC883E428560A7C0088C91E /* cast.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cast.h; sourceTree = "<group>"; };
+		CDC883E528560A7C0088C91E /* split.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = split.h; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -90,6 +98,7 @@
 			children = (
 				CD26686D252FF51F00B3E667 /* GoogleMock.xcodeproj */,
 				CD26686A252FF4E100B3E667 /* string_utils */,
+				CDC883E028560A7C0088C91E /* include */,
 				CD26686C252FF4F300B3E667 /* src */,
 				CD266869252FF4C100B3E667 /* test */,
 				CD266887252FFAAE00B3E667 /* string_utils-test */,
@@ -151,6 +160,25 @@
 			name = Frameworks;
 			sourceTree = "<group>";
 		};
+		CDC883E028560A7C0088C91E /* include */ = {
+			isa = PBXGroup;
+			children = (
+				CDC883E128560A7C0088C91E /* string_utils */,
+			);
+			path = include;
+			sourceTree = "<group>";
+		};
+		CDC883E128560A7C0088C91E /* string_utils */ = {
+			isa = PBXGroup;
+			children = (
+				CDC883E228560A7C0088C91E /* any_of.h */,
+				CDC883E328560A7C0088C91E /* tokenizer.h */,
+				CDC883E428560A7C0088C91E /* cast.h */,
+				CDC883E528560A7C0088C91E /* split.h */,
+			);
+			path = string_utils;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXHeadersBuildPhase section */
@@ -159,6 +187,10 @@
 			buildActionMask = 2147483647;
 			files = (
 				CD26686B252FF4E800B3E667 /* string_utils in Headers */,
+				CDC883EB28560A7C0088C91E /* tokenizer.h in Headers */,
+				CDC883EA28560A7C0088C91E /* any_of.h in Headers */,
+				CDC883ED28560A7C0088C91E /* split.h in Headers */,
+				CDC883EC28560A7C0088C91E /* cast.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 10 - 1
string-utils.xcodeproj/xcshareddata/xcschemes/string-utils.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1240"
+   LastUpgradeVersion = "1340"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -28,6 +28,15 @@
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       shouldUseLaunchSchemeArgsEnv = "YES"
       codeCoverageEnabled = "YES">
+      <CodeCoverageTargets>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "CD266861252FF4B600B3E667"
+            BuildableName = "libstring-utils.a"
+            BlueprintName = "string-utils"
+            ReferencedContainer = "container:string-utils.xcodeproj">
+         </BuildableReference>
+      </CodeCoverageTargets>
       <Testables>
          <TestableReference
             skipped = "NO">

+ 54 - 1
test/cast_test.cxx

@@ -7,9 +7,11 @@
 //
 
 #include "string_utils/cast.h"
+#include "string_utils/split.h"
 
 #include "xcode_gtest_helper.h"
 
+using testing::FieldsAre;
 using testing::Pair;
 using namespace string_utils;
 
@@ -69,7 +71,8 @@ TEST(CastBooleanTest, DoesNotCastAllIntegers) {
 
 MATCHER_P(HoldsAlternative, typearg,
           std::string("holds a value of type ") + typeid(typearg).name()) {
-  return std::holds_alternative<decltype(typearg)>(arg);
+  using T = std::remove_const_t<std::decay_t<decltype(typearg)>>;
+  return std::holds_alternative<T>(arg);
 }
 
 template <typename T> auto HoldsAlternative() { return HoldsAlternative(T()); }
@@ -83,3 +86,53 @@ TEST(CastVariantTest, EvaluatesTypesLeftToRight) {
   auto [value, success] = cast<std::variant<int, std::string>>("2");
   EXPECT_THAT(value, HoldsAlternative<int>());
 }
+
+TEST(CastKeyValTest, FailsOnTooFewTokens) {
+  auto [value, success] = to_keyval("key");
+  EXPECT_FALSE(success);
+}
+
+TEST(CastKeyValTest, SplitsOnFirstToken) {
+  auto [value, success] = to_keyval("key=value");
+  EXPECT_TRUE(success);
+  EXPECT_THAT(value, Pair("key", "value"));
+}
+
+TEST(CastKeyValTest, CanProvideAlternateSplitToken) {
+  auto [value, success] = to_keyval("key=value", 'v');
+  EXPECT_TRUE(success);
+  EXPECT_THAT(value, Pair("key=", "alue"));
+}
+
+TEST(CastKeyValTest, ExtraTokensAreStoredInValue) {
+  auto [value, success] = to_keyval("key=value=mapping");
+  EXPECT_TRUE(success);
+  EXPECT_THAT(value, Pair("key", "value=mapping"));
+}
+
+TEST(CastTupleTest, FailsOnTooFewTokens) {
+  auto [value, success] = to_tuple<int, int, std::string>("0,A");
+  EXPECT_FALSE(success);
+}
+
+TEST(CastTupleTest, FailsOnTooManyTokens) {
+  auto [value, success] = to_tuple<int, int, std::string>("0,1,A,B");
+  EXPECT_FALSE(success);
+}
+
+TEST(CastTupleTest, ParsesIfAllGood) {
+  auto [value, success] = to_tuple<int, int, std::string>("0,1,A");
+  EXPECT_TRUE(success);
+  EXPECT_THAT(value, FieldsAre(0, 1, "A"));
+}
+
+TEST(CastTupleTest, FailsOnAnyParseError) {
+  auto [value, success] = to_tuple<int, int, std::string>("0,Q,A");
+  EXPECT_FALSE(success);
+}
+
+TEST(CastTupleTest, CanQuoteArguments) {
+  auto [value, success] = to_tuple<int, int, std::string>("0,1,\"A,B\"");
+  EXPECT_TRUE(success);
+  EXPECT_THAT(value, FieldsAre(0, 1, "A,B"));
+}

+ 1 - 0
test/xcode_gtest_helper.h

@@ -12,6 +12,7 @@
 
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wquoted-include-in-framework-header"
+#pragma clang diagnostic ignored "-Wcomma"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>