diff --git a/Sources/Context.swift b/Sources/Context.swift index 734518a3..b9eb56c4 100644 --- a/Sources/Context.swift +++ b/Sources/Context.swift @@ -14,6 +14,10 @@ public class Context { self.environment = environment ?? Environment() } + convenience public 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,28 @@ 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 { + return Mirror(reflecting: object).asDictionary() + } + } + /// 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..3e35aa60 100644 --- a/Sources/Variable.swift +++ b/Sources/Variable.swift @@ -262,6 +262,20 @@ extension Mirror { } return result } + + func asDictionary() -> [String: Any] { + var dictionary : [String: Any] = superclassMirror?.asDictionary() ?? [:] + for child in children { + if let name = child.label { + if let result = (child.value as? AnyOptional)?.wrapped { + dictionary[name] = result + } else { + 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..614e3cc5 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" } }