Introduce variable filters
This commit is contained in:
33
Stencil/Filters.swift
Normal file
33
Stencil/Filters.swift
Normal file
@@ -0,0 +1,33 @@
|
||||
func toString(value: Any?) -> String? {
|
||||
if let value = value as? String {
|
||||
return value
|
||||
} else if let value = value as? CustomStringConvertible {
|
||||
return value.description
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func capitalise(value: Any?) -> Any? {
|
||||
if let value = toString(value) {
|
||||
return value.capitalizedString
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func uppercase(value: Any?) -> Any? {
|
||||
if let value = toString(value) {
|
||||
return value.uppercaseString
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func lowercase(value: Any?) -> Any? {
|
||||
if let value = toString(value) {
|
||||
return value.lowercaseString
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
@@ -46,22 +46,28 @@ public class TextNode : NodeType {
|
||||
}
|
||||
}
|
||||
|
||||
public class VariableNode : NodeType {
|
||||
public let variable:Variable
|
||||
public protocol Resolvable {
|
||||
func resolve(context: Context) -> Any?
|
||||
}
|
||||
|
||||
public init(variable:Variable) {
|
||||
public class VariableNode : NodeType {
|
||||
public let variable: Resolvable
|
||||
|
||||
public init(variable: Resolvable) {
|
||||
self.variable = variable
|
||||
}
|
||||
|
||||
public init(variable:String) {
|
||||
public init(variable: String) {
|
||||
self.variable = Variable(variable)
|
||||
}
|
||||
|
||||
public func render(context:Context) throws -> String {
|
||||
let result:AnyObject? = variable.resolve(context)
|
||||
public func render(context: Context) throws -> String {
|
||||
let result = variable.resolve(context)
|
||||
|
||||
if let result = result as? String {
|
||||
return result
|
||||
} else if let result = result as? CustomStringConvertible {
|
||||
return result.description
|
||||
} else if let result = result as? NSObject {
|
||||
return result.description
|
||||
}
|
||||
@@ -94,7 +100,7 @@ public class NowNode : NodeType {
|
||||
|
||||
public func render(context: Context) throws -> String {
|
||||
let date = NSDate()
|
||||
let format: AnyObject? = self.format.resolve(context)
|
||||
let format = self.format.resolve(context)
|
||||
var formatter:NSDateFormatter?
|
||||
|
||||
if let format = format as? NSDateFormatter {
|
||||
@@ -212,7 +218,7 @@ public class IfNode : NodeType {
|
||||
}
|
||||
|
||||
public func render(context: Context) throws -> String {
|
||||
let result: AnyObject? = variable.resolve(context)
|
||||
let result = variable.resolve(context)
|
||||
var truthy = false
|
||||
|
||||
if let result = result as? [AnyObject] {
|
||||
|
||||
@@ -10,12 +10,15 @@ public func until(tags:[String])(parser:TokenParser, token:Token) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
public typealias Filter = Any? -> Any?
|
||||
|
||||
/// A class for parsing an array of tokens and converts them into a collection of Node's
|
||||
public class TokenParser {
|
||||
public typealias TagParser = (TokenParser, Token) throws -> NodeType
|
||||
|
||||
private var tokens:[Token]
|
||||
private var tags = [String:TagParser]()
|
||||
private var filters = [String: Filter]()
|
||||
|
||||
public init(tokens:[Token]) {
|
||||
self.tokens = tokens
|
||||
@@ -26,6 +29,9 @@ public class TokenParser {
|
||||
registerTag("include", parser: IncludeNode.parse)
|
||||
registerTag("extends", parser: ExtendsNode.parse)
|
||||
registerTag("block", parser: BlockNode.parse)
|
||||
registerFilter("capitalize", filter: capitalise)
|
||||
registerFilter("uppercase", filter: uppercase)
|
||||
registerFilter("lowercase", filter: lowercase)
|
||||
}
|
||||
|
||||
/// Registers a new template tag
|
||||
@@ -40,6 +46,10 @@ public class TokenParser {
|
||||
})
|
||||
}
|
||||
|
||||
public func registerFilter(name: String, filter: Filter) {
|
||||
filters[name] = filter
|
||||
}
|
||||
|
||||
/// Parse the given tokens into nodes
|
||||
public func parse() throws -> [NodeType] {
|
||||
return try parse(nil)
|
||||
@@ -54,8 +64,8 @@ public class TokenParser {
|
||||
switch token {
|
||||
case .Text(let text):
|
||||
nodes.append(TextNode(text: text))
|
||||
case .Variable(let variable):
|
||||
nodes.append(VariableNode(variable: variable))
|
||||
case .Variable:
|
||||
nodes.append(VariableNode(variable: try compileFilter(token.contents)))
|
||||
case .Block:
|
||||
let tag = token.components().first
|
||||
|
||||
@@ -88,4 +98,16 @@ public class TokenParser {
|
||||
public func prependToken(token:Token) {
|
||||
tokens.insert(token, atIndex: 0)
|
||||
}
|
||||
|
||||
public func findFilter(name: String) throws -> Filter {
|
||||
if let filter = filters[name] {
|
||||
return filter
|
||||
}
|
||||
|
||||
throw TemplateSyntaxError("Invalid filter '\(name)'")
|
||||
}
|
||||
|
||||
func compileFilter(token: String) throws -> Resolvable {
|
||||
return try FilterExpression(token: token, parser: self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,40 @@
|
||||
import Foundation
|
||||
|
||||
|
||||
class FilterExpression : Resolvable {
|
||||
let filters: [Filter]
|
||||
let variable: Variable
|
||||
|
||||
init(token: String, parser: TokenParser) throws {
|
||||
let bits = token.componentsSeparatedByString("|")
|
||||
if bits.isEmpty {
|
||||
filters = []
|
||||
variable = Variable("")
|
||||
throw TemplateSyntaxError("Variable tags must include at least 1 argument")
|
||||
}
|
||||
|
||||
variable = Variable(bits[0])
|
||||
let filterBits = bits[1 ..< bits.endIndex]
|
||||
|
||||
do {
|
||||
filters = try filterBits.map { try parser.findFilter($0) }
|
||||
} catch {
|
||||
filters = []
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
func resolve(context: Context) -> Any? {
|
||||
let result = variable.resolve(context)
|
||||
|
||||
return filters.reduce(result) { x, y in
|
||||
return y(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure used to represent a template variable, and to resolve it in a given context.
|
||||
public struct Variable : Equatable {
|
||||
public struct Variable : Equatable, Resolvable {
|
||||
public let variable:String
|
||||
|
||||
/// Create a variable with a string representing the variable
|
||||
@@ -15,11 +47,12 @@ public struct Variable : Equatable {
|
||||
}
|
||||
|
||||
/// Resolve the variable in the given context
|
||||
public func resolve(context:Context) -> AnyObject? {
|
||||
public func resolve(context:Context) -> Any? {
|
||||
var current:AnyObject? = context
|
||||
|
||||
if (variable.hasPrefix("'") && variable.hasSuffix("'")) || (variable.hasPrefix("\"") && variable.hasSuffix("\"")) {
|
||||
return variable.substringWithRange(variable.startIndex.successor() ..< variable.endIndex.predecessor())
|
||||
// String literal
|
||||
return variable[variable.startIndex.successor() ..< variable.endIndex.predecessor()]
|
||||
}
|
||||
|
||||
for bit in lookup() {
|
||||
|
||||
Reference in New Issue
Block a user