feat: New extensions API (#78)

This commit is contained in:
Kyle Fuller
2016-12-07 21:27:31 +00:00
committed by GitHub
parent d3706f074d
commit d7b152089e
15 changed files with 128 additions and 287 deletions

View File

@@ -3,16 +3,14 @@ public class Context {
var dictionaries: [[String: Any?]]
public let environment: Environment
let namespace: Namespace
init(dictionary: [String: Any]? = nil, namespace: Namespace? = nil, environment: Environment? = nil) {
init(dictionary: [String: Any]? = nil, environment: Environment? = nil) {
if let dictionary = dictionary {
dictionaries = [dictionary]
} else {
dictionaries = []
}
self.namespace = namespace ?? environment?.namespace ?? Namespace()
self.environment = environment ?? Environment()
}

View File

@@ -1,11 +1,11 @@
public struct Environment {
var namespace: Namespace
public let extensions: [Extension]
public var loader: Loader?
public init(loader: Loader? = nil, namespace: Namespace? = nil) {
public init(loader: Loader? = nil, extensions: [Extension]? = nil) {
self.loader = loader
self.namespace = namespace ?? Namespace()
self.extensions = [DefaultExtension()] + (extensions ?? [])
}
public func loadTemplate(name: String) throws -> Template {

View File

@@ -1,33 +1,39 @@
protocol FilterType {
func invoke(value: Any?, arguments: [Any?]) throws -> Any?
}
open class Extension {
typealias TagParser = (TokenParser, Token) throws -> NodeType
var tags = [String: TagParser]()
enum Filter: FilterType {
case simple(((Any?) throws -> Any?))
case arguments(((Any?, [Any?]) throws -> Any?))
var filters = [String: Filter]()
func invoke(value: Any?, arguments: [Any?]) throws -> Any? {
switch self {
case let .simple(filter):
if !arguments.isEmpty {
throw TemplateSyntaxError("cannot invoke filter with an argument")
}
public init() {
}
return try filter(value)
case let .arguments(filter):
return try filter(value, arguments)
}
/// Registers a new template tag
public func registerTag(_ name: String, parser: @escaping (TokenParser, Token) throws -> NodeType) {
tags[name] = parser
}
/// Registers a simple template tag with a name and a handler
public func registerSimpleTag(_ name: String, handler: @escaping (Context) throws -> String) {
registerTag(name, parser: { parser, token in
return SimpleNode(handler: handler)
})
}
/// Registers a template filter with the given name
public func registerFilter(_ name: String, filter: @escaping (Any?) throws -> Any?) {
filters[name] = .simple(filter)
}
/// Registers a template filter with the given name
public func registerFilter(_ name: String, filter: @escaping (Any?, [Any?]) throws -> Any?) {
filters[name] = .arguments(filter)
}
}
public class Namespace {
public typealias TagParser = (TokenParser, Token) throws -> NodeType
var tags = [String: TagParser]()
var filters = [String: Filter]()
public init() {
class DefaultExtension: Extension {
override init() {
super.init()
registerDefaultTags()
registerDefaultFilters()
}
@@ -52,26 +58,27 @@ public class Namespace {
registerFilter("lowercase", filter: lowercase)
registerFilter("join", filter: joinFilter)
}
}
/// Registers a new template tag
public func registerTag(_ name: String, parser: @escaping TagParser) {
tags[name] = parser
}
/// Registers a simple template tag with a name and a handler
public func registerSimpleTag(_ name: String, handler: @escaping (Context) throws -> String) {
registerTag(name, parser: { parser, token in
return SimpleNode(handler: handler)
})
}
protocol FilterType {
func invoke(value: Any?, arguments: [Any?]) throws -> Any?
}
/// Registers a template filter with the given name
public func registerFilter(_ name: String, filter: @escaping (Any?) throws -> Any?) {
filters[name] = .simple(filter)
}
enum Filter: FilterType {
case simple(((Any?) throws -> Any?))
case arguments(((Any?, [Any?]) throws -> Any?))
/// Registers a template filter with the given name
public func registerFilter(_ name: String, filter: @escaping (Any?, [Any?]) throws -> Any?) {
filters[name] = .arguments(filter)
func invoke(value: Any?, arguments: [Any?]) throws -> Any? {
switch self {
case let .simple(filter):
if !arguments.isEmpty {
throw TemplateSyntaxError("cannot invoke filter with an argument")
}
return try filter(value)
case let .arguments(filter):
return try filter(value, arguments)
}
}
}

View File

@@ -18,11 +18,11 @@ public class TokenParser {
public typealias TagParser = (TokenParser, Token) throws -> NodeType
fileprivate var tokens: [Token]
fileprivate let namespace: Namespace
fileprivate let environment: Environment
public init(tokens: [Token], namespace: Namespace) {
public init(tokens: [Token], environment: Environment) {
self.tokens = tokens
self.namespace = namespace
self.environment = environment
}
/// Parse the given tokens into nodes
@@ -42,19 +42,14 @@ public class TokenParser {
case .variable:
nodes.append(VariableNode(variable: try compileFilter(token.contents)))
case .block:
let tag = token.components().first
if let parse_until = parse_until , parse_until(self, token) {
prependToken(token)
return nodes
}
if let tag = tag {
if let parser = namespace.tags[tag] {
nodes.append(try parser(self, token))
} else {
throw TemplateSyntaxError("Unknown template tag '\(tag)'")
}
if let tag = token.components().first {
let parser = try findTag(name: tag)
nodes.append(try parser(self, token))
}
case .comment:
continue
@@ -76,15 +71,28 @@ public class TokenParser {
tokens.insert(token, at: 0)
}
func findFilter(_ name: String) throws -> FilterType {
if let filter = namespace.filters[name] {
return filter
func findTag(name: String) throws -> Extension.TagParser {
for ext in environment.extensions {
if let filter = ext.tags[name] {
return filter
}
}
throw TemplateSyntaxError("Invalid filter '\(name)'")
throw TemplateSyntaxError("Unknown template tag '\(name)'")
}
func findFilter(_ name: String) throws -> FilterType {
for ext in environment.extensions {
if let filter = ext.filters[name] {
return filter
}
}
throw TemplateSyntaxError("Unknown filter '\(name)'")
}
public func compileFilter(_ token: String) throws -> Resolvable {
return try FilterExpression(token: token, parser: self)
}
}

View File

@@ -65,7 +65,7 @@ public class Template: ExpressibleByStringLiteral {
/// Render the given template with a context
func render(_ context: Context) throws -> String {
let context = context
let parser = TokenParser(tokens: tokens, namespace: context.namespace)
let parser = TokenParser(tokens: tokens, environment: context.environment)
let nodes = try parser.parse()
return try renderNodes(nodes, context)
}