From cc614b78a6c1d2f2b318784cf382eebe5cedc624 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Wed, 17 Jul 2019 12:35:22 +0100 Subject: [PATCH 1/6] Allow context to be initialised with Any Initialise Context with Any by converting to dictionary Allow Any to be pushed onto a Context in a similar way Change Environment functions to use these new functions --- Sources/Context.swift | 22 ++++++++++++++++++- Sources/Environment.swift | 8 +++---- Sources/Include.swift | 4 ++-- Sources/Template.swift | 6 ++++- Sources/Variable.swift | 10 +++++++++ Tests/StencilTests/ContextSpec.swift | 28 ++++++++++++++++++++++++ Tests/StencilTests/EnvironmentSpec.swift | 2 +- Tests/StencilTests/StencilSpec.swift | 2 +- Tests/StencilTests/TemplateSpec.swift | 4 ++-- 9 files changed, 74 insertions(+), 12 deletions(-) diff --git a/Sources/Context.swift b/Sources/Context.swift index 007cf688..33412e15 100644 --- a/Sources/Context.swift +++ b/Sources/Context.swift @@ -14,6 +14,10 @@ public class Context { self.environment = environment ?? Environment() } + convenience init(object: Any? = nil, environment: Environment? = nil) { + self.init(dictionary: Context.dictionaryFromAny(object: object ?? [:]), environment: environment) + } + public subscript(key: String) -> Any? { /// Retrieves a variable's value, starting at the current context and going upwards get { @@ -45,13 +49,29 @@ public class Context { return dictionaries.popLast() } + /// return a dictionary describing an object + static fileprivate func dictionaryFromAny(object: Any) -> [String: Any] { + if let dictionary = object as? [String: Any] { + return dictionary + } else { + let dictionary = Mirror(reflecting: object).asDictionary() + return dictionary + } + } + /// Push a new level onto the context for the duration of the execution of the given closure public func push(dictionary: [String: Any] = [:], closure: (() throws -> Result)) rethrows -> Result { push(dictionary) defer { _ = pop() } return try closure() } - + + /// Push a new level onto the context for the duration of the execution of the given closure + public func push(object: Any, closure: (() throws -> Result)) rethrows -> Result { + let dictionary = Context.dictionaryFromAny(object: object) + return try push(dictionary: dictionary, closure: closure) + } + public func flatten() -> [String: Any] { var accumulator: [String: Any] = [:] diff --git a/Sources/Environment.swift b/Sources/Environment.swift index 0c2c72e7..635fa16f 100644 --- a/Sources/Environment.swift +++ b/Sources/Environment.swift @@ -29,20 +29,20 @@ public struct Environment { } } - public func renderTemplate(name: String, context: [String: Any] = [:]) throws -> String { + public func renderTemplate(name: String, context: Any? = nil) throws -> String { let template = try loadTemplate(name: name) return try render(template: template, context: context) } - public func renderTemplate(string: String, context: [String: Any] = [:]) throws -> String { + public func renderTemplate(string: String, context: Any? = nil) throws -> String { let template = templateClass.init(templateString: string, environment: self) return try render(template: template, context: context) } - func render(template: Template, context: [String: Any]) throws -> String { + func render(template: Template, context: Any?) throws -> String { // update template environment as it can be created from string literal with default environment template.environment = self - return try template.render(context) + return try template.render(object: context) } } diff --git a/Sources/Include.swift b/Sources/Include.swift index 9d49ed30..26798455 100644 --- a/Sources/Include.swift +++ b/Sources/Include.swift @@ -33,8 +33,8 @@ class IncludeNode: NodeType { let template = try context.environment.loadTemplate(name: templateName) do { - let subContext = includeContext.flatMap { context[$0] as? [String: Any] } ?? [:] - return try context.push(dictionary: subContext) { + let subContext = includeContext.flatMap { context[$0] } ?? [:] + return try context.push(object: subContext) { try template.render(context) } } catch { diff --git a/Sources/Template.swift b/Sources/Template.swift index d4d18687..8995f0d3 100644 --- a/Sources/Template.swift +++ b/Sources/Template.swift @@ -73,8 +73,12 @@ open class Template: ExpressibleByStringLiteral { } // swiftlint:disable discouraged_optional_collection - /// Render the given template open func render(_ dictionary: [String: Any]? = nil) throws -> String { return try render(Context(dictionary: dictionary ?? [:], environment: environment)) } + + /// Render the given template + open func render(object: Any?) throws -> String { + return try render(Context(object: object, environment: environment)) + } } diff --git a/Sources/Variable.swift b/Sources/Variable.swift index 44569dff..abb118b6 100644 --- a/Sources/Variable.swift +++ b/Sources/Variable.swift @@ -262,6 +262,16 @@ extension Mirror { } return result } + + func asDictionary() -> [String: Any] { + var dictionary : [String: Any] = superclassMirror?.asDictionary() ?? [:] + for child in children { + if let name = child.label { + dictionary[name] = child.value + } + } + return dictionary + } } protocol AnyOptional { diff --git a/Tests/StencilTests/ContextSpec.swift b/Tests/StencilTests/ContextSpec.swift index 191529cf..8ca6db85 100644 --- a/Tests/StencilTests/ContextSpec.swift +++ b/Tests/StencilTests/ContextSpec.swift @@ -88,4 +88,32 @@ final class ContextTests: XCTestCase { } } } + + func testContextAnyInitialization() { + class SuperTest { + init() {} + let int : Int = 23 + } + class Test : SuperTest { + override init() { + super.init() + } + let string : String = "test string" + let optional : String? = "test optional" + let nilOptional : String? = nil + } + describe("Any Initialization") { + var context = Context() + $0.before { + context = Context(object: Test()) + } + + $0.it("Test dictionary values") { + try expect(context["int"] as? Int) == 23 + try expect(context["string"] as? String) == "test string" + try expect(context["optional"] as? String) == "test optional" + try expect(context["nilOptional"] == nil) == true + } + } + } } diff --git a/Tests/StencilTests/EnvironmentSpec.swift b/Tests/StencilTests/EnvironmentSpec.swift index f5b829ff..5b09781b 100644 --- a/Tests/StencilTests/EnvironmentSpec.swift +++ b/Tests/StencilTests/EnvironmentSpec.swift @@ -438,7 +438,7 @@ private class ExampleLoader: Loader { private class CustomTemplate: Template { // swiftlint:disable discouraged_optional_collection - override func render(_ dictionary: [String: Any]? = nil) throws -> String { + override func render(object: Any? = nil) throws -> String { return "here" } } diff --git a/Tests/StencilTests/StencilSpec.swift b/Tests/StencilTests/StencilSpec.swift index dc8aa6e0..0ba80c96 100644 --- a/Tests/StencilTests/StencilSpec.swift +++ b/Tests/StencilTests/StencilSpec.swift @@ -44,7 +44,7 @@ final class StencilTests: XCTestCase { ] let template = Template(templateString: templateString) - let result = try template.render(context) + let result = try template.render(object: context) try expect(result) == """ There are 2 articles. diff --git a/Tests/StencilTests/TemplateSpec.swift b/Tests/StencilTests/TemplateSpec.swift index b1c66a1d..e78076c1 100644 --- a/Tests/StencilTests/TemplateSpec.swift +++ b/Tests/StencilTests/TemplateSpec.swift @@ -6,13 +6,13 @@ final class TemplateTests: XCTestCase { func testTemplate() { it("can render a template from a string") { let template = Template(templateString: "Hello World") - let result = try template.render([ "name": "Kyle" ]) + let result = try template.render(object:[ "name": "Kyle" ]) try expect(result) == "Hello World" } it("can render a template from a string literal") { let template: Template = "Hello World" - let result = try template.render([ "name": "Kyle" ]) + let result = try template.render(object:[ "name": "Kyle" ]) try expect(result) == "Hello World" } } From 03f9156cb9d7abeb4dd1b339c4f734e455fb3fc8 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Thu, 18 Jul 2019 07:40:45 +0100 Subject: [PATCH 2/6] Unwrap optional values --- Sources/Variable.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/Variable.swift b/Sources/Variable.swift index abb118b6..3e35aa60 100644 --- a/Sources/Variable.swift +++ b/Sources/Variable.swift @@ -267,7 +267,11 @@ extension Mirror { var dictionary : [String: Any] = superclassMirror?.asDictionary() ?? [:] for child in children { if let name = child.label { - dictionary[name] = child.value + if let result = (child.value as? AnyOptional)?.wrapped { + dictionary[name] = result + } else { + dictionary[name] = child.value + } } } return dictionary From b98f85ee81ec4376271609a83db35d5b261da7a6 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Sun, 12 Jan 2020 21:04:24 +0100 Subject: [PATCH 3/6] Update Tests/StencilTests/TemplateSpec.swift Co-Authored-By: Ilya Puchka --- Tests/StencilTests/TemplateSpec.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/StencilTests/TemplateSpec.swift b/Tests/StencilTests/TemplateSpec.swift index e78076c1..deb4167b 100644 --- a/Tests/StencilTests/TemplateSpec.swift +++ b/Tests/StencilTests/TemplateSpec.swift @@ -6,7 +6,7 @@ final class TemplateTests: XCTestCase { func testTemplate() { it("can render a template from a string") { let template = Template(templateString: "Hello World") - let result = try template.render(object:[ "name": "Kyle" ]) + let result = try template.render(object: ["name": "Kyle"]) try expect(result) == "Hello World" } From f746ba1b9fbcd1188a8be17156c1c1c009c4c5b6 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Sun, 12 Jan 2020 21:04:36 +0100 Subject: [PATCH 4/6] Update Tests/StencilTests/TemplateSpec.swift Co-Authored-By: Ilya Puchka --- Tests/StencilTests/TemplateSpec.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/StencilTests/TemplateSpec.swift b/Tests/StencilTests/TemplateSpec.swift index deb4167b..614e3cc5 100644 --- a/Tests/StencilTests/TemplateSpec.swift +++ b/Tests/StencilTests/TemplateSpec.swift @@ -12,7 +12,7 @@ final class TemplateTests: XCTestCase { it("can render a template from a string literal") { let template: Template = "Hello World" - let result = try template.render(object:[ "name": "Kyle" ]) + let result = try template.render(object: ["name": "Kyle"]) try expect(result) == "Hello World" } } From c0510d83044df017c2917eb22d0b8688aeffe9e5 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Sun, 12 Jan 2020 21:07:00 +0100 Subject: [PATCH 5/6] Update Sources/Context.swift Make init(object:environment) public Co-Authored-By: Ilya Puchka --- Sources/Context.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Context.swift b/Sources/Context.swift index 33412e15..c8f33158 100644 --- a/Sources/Context.swift +++ b/Sources/Context.swift @@ -14,7 +14,7 @@ public class Context { self.environment = environment ?? Environment() } - convenience init(object: Any? = nil, environment: Environment? = nil) { + convenience public init(object: Any? = nil, environment: Environment? = nil) { self.init(dictionary: Context.dictionaryFromAny(object: object ?? [:]), environment: environment) } From 73747852eaed5150690aaff53475f953d284b7b5 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Thu, 13 Aug 2020 18:03:52 +0100 Subject: [PATCH 6/6] Update Sources/Context.swift Co-authored-by: David Jennes --- Sources/Context.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/Context.swift b/Sources/Context.swift index 8d3621d2..b9eb56c4 100644 --- a/Sources/Context.swift +++ b/Sources/Context.swift @@ -54,8 +54,7 @@ public class Context { if let dictionary = object as? [String: Any] { return dictionary } else { - let dictionary = Mirror(reflecting: object).asDictionary() - return dictionary + return Mirror(reflecting: object).asDictionary() } }