Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow context to be initialised with Any #281

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion Sources/Context.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
adam-fowler marked this conversation as resolved.
Show resolved Hide resolved
}
}

/// Push a new level onto the context for the duration of the execution of the given closure
public func push<Result>(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<Result>(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] = [:]

Expand Down
8 changes: 4 additions & 4 deletions Sources/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

}
4 changes: 2 additions & 2 deletions Sources/Include.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 5 additions & 1 deletion Sources/Template.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
14 changes: 14 additions & 0 deletions Sources/Variable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
28 changes: 28 additions & 0 deletions Tests/StencilTests/ContextSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
}
2 changes: 1 addition & 1 deletion Tests/StencilTests/EnvironmentSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
2 changes: 1 addition & 1 deletion Tests/StencilTests/StencilSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions Tests/StencilTests/TemplateSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
Expand Down