feat: New extensions API (#78)
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user