From 7247d0a83dfb2c8ba82b7dbd1a75809105aa305f Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Sat, 22 Sep 2018 16:04:57 +0100 Subject: [PATCH] Dynamic member lookup (via marker protocol) --- Sources/DynamicMemberLookup.swift | 15 +++++++++++++ Sources/Variable.swift | 2 ++ Tests/StencilTests/VariableSpec.swift | 31 ++++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 Sources/DynamicMemberLookup.swift diff --git a/Sources/DynamicMemberLookup.swift b/Sources/DynamicMemberLookup.swift new file mode 100644 index 0000000..47aae01 --- /dev/null +++ b/Sources/DynamicMemberLookup.swift @@ -0,0 +1,15 @@ +/// Marker protocol so we can know which types support `@dynamicMemberLookup`. Add this to your own types that support +/// lookup by String. +public protocol DynamicMemberLookup { + /// Get a value for a given `String` key + subscript(dynamicMember member: String) -> Any? { get } +} + +public extension DynamicMemberLookup where Self: RawRepresentable { + subscript(dynamicMember member: String) -> Any? { + switch member { + case "rawValue": return rawValue + default: return nil + } + } +} diff --git a/Sources/Variable.swift b/Sources/Variable.swift index 995706c..ca2d245 100644 --- a/Sources/Variable.swift +++ b/Sources/Variable.swift @@ -110,6 +110,8 @@ public struct Variable: Equatable, Resolvable { return object.value(forKey: bit) } #endif + } else if let value = context as? DynamicMemberLookup { + return value[dynamicMember: bit] } else if let value = context { return Mirror(reflecting: value).getValue(for: bit) } diff --git a/Tests/StencilTests/VariableSpec.swift b/Tests/StencilTests/VariableSpec.swift index e834064..52b276d 100644 --- a/Tests/StencilTests/VariableSpec.swift +++ b/Tests/StencilTests/VariableSpec.swift @@ -30,6 +30,17 @@ private class Blog: WebSite { let featuring: Article? = Article(author: Person(name: "Jhon")) } +@dynamicMemberLookup +private struct DynamicStruct: DynamicMemberLookup { + subscript(dynamicMember member: String) -> Any? { + member == "test" ? "this is a dynamic response" : nil + } +} + +private enum DynamicEnum: String, DynamicMemberLookup { + case someValue = "this is raw value" +} + final class VariableTests: XCTestCase { let context: Context = { let ext = Extension() @@ -49,7 +60,11 @@ final class VariableTests: XCTestCase { ], "article": Article(author: Person(name: "Kyle")), "blog": Blog(), - "tuple": (one: 1, two: 2) + "tuple": (one: 1, two: 2), + "dynamic": [ + "enum": DynamicEnum.someValue, + "struct": DynamicStruct() + ] ], environment: environment) #if os(OSX) context["object"] = Object() @@ -158,6 +173,20 @@ final class VariableTests: XCTestCase { } } + func testDynamicMemberLookup() { + it("can resolve dynamic member lookup") { + let variable = Variable("dynamic.struct.test") + let result = try variable.resolve(self.context) as? String + try expect(result) == "this is a dynamic response" + } + + it("can resolve dynamic enum rawValue") { + let variable = Variable("dynamic.enum.rawValue") + let result = try variable.resolve(self.context) as? String + try expect(result) == "this is raw value" + } + } + func testReflection() { it("can resolve a property with reflection") { let variable = Variable("article.author.name")