refactor: Introducing Environments

This commit is contained in:
Kyle Fuller
2016-12-01 00:17:04 +00:00
parent 2be672c6a5
commit 9e2a061795
27 changed files with 289 additions and 91 deletions

View File

@@ -1,17 +1,19 @@
/// A container for template variables.
public class Context {
var dictionaries: [[String: Any?]]
public let environment: Environment
let namespace: Namespace
/// Initialise a Context with an optional dictionary and optional namespace
public init(dictionary: [String: Any]? = nil, namespace: Namespace = Namespace()) {
init(dictionary: [String: Any]? = nil, namespace: Namespace? = nil, environment: Environment? = nil) {
if let dictionary = dictionary {
dictionaries = [dictionary]
} else {
dictionaries = []
}
self.namespace = namespace
self.namespace = namespace ?? environment?.namespace ?? Namespace()
self.environment = environment ?? Environment()
}
public subscript(key: String) -> Any? {

36
Sources/Environment.swift Normal file
View File

@@ -0,0 +1,36 @@
public struct Environment {
var namespace: Namespace
public var loader: Loader?
public init(loader: Loader? = nil, namespace: Namespace? = nil) {
self.loader = loader
self.namespace = namespace ?? Namespace()
}
public func loadTemplate(name: String) throws -> Template {
if let loader = loader {
return try loader.loadTemplate(name: name, environment: self)
} else {
throw TemplateDoesNotExist(templateNames: [name], loader: nil)
}
}
public func loadTemplate(names: [String]) throws -> Template {
if let loader = loader {
return try loader.loadTemplate(names: names, environment: self)
} else {
throw TemplateDoesNotExist(templateNames: names, loader: nil)
}
}
public func renderTemplate(name: String, context: [String: Any]? = nil) throws -> String {
let template = try loadTemplate(name: name)
return try template.render(context)
}
public func renderTemplate(string: String, context: [String: Any]? = nil) throws -> String {
let template = Template(templateString: string, environment: self)
return try template.render(context)
}
}

View File

@@ -1,14 +1,19 @@
public class TemplateDoesNotExist: Error, CustomStringConvertible {
let templateNames: [String]
let loader: Loader
let loader: Loader?
public init(templateNames: [String], loader: Loader) {
public init(templateNames: [String], loader: Loader? = nil) {
self.templateNames = templateNames
self.loader = loader
}
public var description: String {
let templates = templateNames.joined(separator: ", ")
return "Template named `\(templates)` does not exist in loader \(loader)"
if let loader = loader {
return "Template named `\(templates)` does not exist in loader \(loader)"
}
return "Template named `\(templates)` does not exist. No loaders found"
}
}

View File

@@ -19,16 +19,15 @@ class IncludeNode : NodeType {
}
func render(_ context: Context) throws -> String {
guard let loader = context["loader"] as? Loader else {
throw TemplateSyntaxError("Template loader not in context")
}
guard let templateName = try self.templateName.resolve(context) as? String else {
throw TemplateSyntaxError("'\(self.templateName)' could not be resolved as a string")
}
let template = try loader.loadTemplate(name: templateName)
return try template.render(context)
let template = try context.environment.loadTemplate(name: templateName)
return try context.push {
return try template.render(context)
}
}
}

View File

@@ -59,15 +59,11 @@ class ExtendsNode : NodeType {
}
func render(_ context: Context) throws -> String {
guard let loader = context["loader"] as? Loader else {
throw TemplateSyntaxError("Template loader not in context")
}
guard let templateName = try self.templateName.resolve(context) as? String else {
throw TemplateSyntaxError("'\(self.templateName)' could not be resolved as a string")
}
let template = try loader.loadTemplate(name: templateName)
let template = try context.environment.loadTemplate(name: templateName)
let blockContext: BlockContext
if let context = context[BlockContext.contextKey] as? BlockContext {

View File

@@ -3,16 +3,16 @@ import PathKit
public protocol Loader {
func loadTemplate(name: String) throws -> Template
func loadTemplate(names: [String]) throws -> Template
func loadTemplate(name: String, environment: Environment) throws -> Template
func loadTemplate(names: [String], environment: Environment) throws -> Template
}
extension Loader {
func loadTemplate(names: [String]) throws -> Template {
public func loadTemplate(names: [String], environment: Environment) throws -> Template {
for name in names {
do {
return try loadTemplate(name: name)
return try loadTemplate(name: name, environment: environment)
} catch is TemplateDoesNotExist {
continue
} catch {
@@ -43,7 +43,7 @@ public class FileSystemLoader: Loader, CustomStringConvertible {
return "FileSystemLoader(\(paths))"
}
public func loadTemplate(name: String) throws -> Template {
public func loadTemplate(name: String, environment: Environment) throws -> Template {
for path in paths {
let templatePath = try path.safeJoin(path: Path(name))
@@ -51,19 +51,19 @@ public class FileSystemLoader: Loader, CustomStringConvertible {
continue
}
return try Template(path: templatePath)
return try Template(path: templatePath, environment: environment, name: name)
}
throw TemplateDoesNotExist(templateNames: [name], loader: self)
}
public func loadTemplate(names: [String]) throws -> Template {
public func loadTemplate(names: [String], environment: Environment) throws -> Template {
for path in paths {
for templateName in names {
let templatePath = try path.safeJoin(path: Path(templateName))
if templatePath.exists {
return try Template(path: templatePath)
return try Template(path: templatePath, environment: environment, name: templateName)
}
}
}

View File

@@ -7,10 +7,17 @@ let NSFileNoSuchFileError = 4
/// A class representing a template
public class Template: ExpressibleByStringLiteral {
let environment: Environment
let tokens: [Token]
/// The name of the loaded Template if the Template was loaded from a Loader
public let name: String?
/// Create a template with a template string
public init(templateString: String) {
public init(templateString: String, environment: Environment? = nil, name: String? = nil) {
self.environment = environment ?? Environment()
self.name = name
let lexer = Lexer(templateString: templateString)
tokens = lexer.tokenize()
}
@@ -31,11 +38,13 @@ public class Template: ExpressibleByStringLiteral {
}
/// Create a template with a file found at the given path
public convenience init(path: Path) throws {
self.init(templateString: try path.read())
public convenience init(path: Path, environment: Environment? = nil, name: String? = nil) throws {
self.init(templateString: try path.read(), environment: environment, name: name)
}
// Create a template with a template string literal
// MARK: ExpressibleByStringLiteral
// Create a templaVte with a template string literal
public convenience required init(stringLiteral value: String) {
self.init(templateString: value)
}
@@ -50,11 +59,16 @@ public class Template: ExpressibleByStringLiteral {
self.init(stringLiteral: value)
}
/// Render the given template
public func render(_ context: Context? = nil) throws -> String {
let context = context ?? Context()
/// Render the given template with a context
func render(_ context: Context) throws -> String {
let context = context ?? Context(environment: environment)
let parser = TokenParser(tokens: tokens, namespace: context.namespace)
let nodes = try parser.parse()
return try renderNodes(nodes, context)
}
/// Render the given template
public func render(_ dictionary: [String: Any]? = nil) throws -> String {
return try render(Context(dictionary: dictionary, environment: environment))
}
}