feat(filters): Allow filters with arguments

This commit is contained in:
Kyle Fuller
2016-11-27 01:59:57 +00:00
parent 1e3afc0dd5
commit 60b378d482
6 changed files with 95 additions and 10 deletions

View File

@@ -2,6 +2,10 @@
## Master ## Master
### Enhancements
- You may now register custom template filters which make use of arguments.
### Bug Fixes ### Bug Fixes
- Variables (`{{ variable.5 }}`) that reference an array index at an unknown - Variables (`{{ variable.5 }}`) that reference an array index at an unknown

View File

@@ -197,7 +197,27 @@ let rendered = try template.render(context, namespace: namespace)
#### Registering custom filters #### Registering custom filters
```swift ```swift
namespace.registerFilter("double") { value in namespace.registerFilter("double") { (value: Any?) in
if let value = value as? Int {
return value * 2
}
return value
}
```
#### Registering custom filters with arguments
```swift
namespace.registerFilter("multiply") { (value: Any?, arguments: [Any?]) in
let amount: Int
if let value = arguments.first as? Int {
amount = value
} else {
throw TemplateSyntaxError("multiple tag must be called with an integer argument")
}
if let value = value as? Int { if let value = value as? Int {
return value * 2 return value * 2
} }

View File

@@ -1,3 +1,26 @@
public protocol FilterType {
func invoke(value: Any?, arguments: [Any?]) throws -> Any?
}
enum Filter: FilterType {
case simple(((Any?) throws -> Any?))
case arguments(((Any?, [Any?]) throws -> Any?))
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)
}
}
}
open class Namespace { open class Namespace {
public typealias TagParser = (TokenParser, Token) throws -> NodeType public typealias TagParser = (TokenParser, Token) throws -> NodeType
@@ -40,7 +63,12 @@ open class Namespace {
} }
/// Registers a template filter with the given name /// Registers a template filter with the given name
open func registerFilter(_ name: String, filter: @escaping Filter) { open func registerFilter(_ name: String, filter: @escaping (Any?) throws -> Any?) {
filters[name] = filter filters[name] = .simple(filter)
}
/// Registers a template filter with the given name
open func registerFilter(_ name: String, filter: @escaping (Any?, [Any?]) throws -> Any?) {
filters[name] = .arguments(filter)
} }
} }

View File

@@ -12,7 +12,6 @@ public func until(_ tags: [String]) -> ((TokenParser, Token) -> Bool) {
} }
} }
public typealias Filter = (Any?) throws -> Any?
/// A class for parsing an array of tokens and converts them into a collection of Node's /// A class for parsing an array of tokens and converts them into a collection of Node's
open class TokenParser { open class TokenParser {
@@ -77,7 +76,7 @@ open class TokenParser {
tokens.insert(token, at: 0) tokens.insert(token, at: 0)
} }
open func findFilter(_ name: String) throws -> Filter { open func findFilter(_ name: String) throws -> FilterType {
if let filter = namespace.filters[name] { if let filter = namespace.filters[name] {
return filter return filter
} }

View File

@@ -2,7 +2,7 @@ import Foundation
class FilterExpression : Resolvable { class FilterExpression : Resolvable {
let filters: [Filter] let filters: [(FilterType, [Variable])]
let variable: Variable let variable: Variable
init(token: String, parser: TokenParser) throws { init(token: String, parser: TokenParser) throws {
@@ -17,7 +17,11 @@ class FilterExpression : Resolvable {
let filterBits = bits[bits.indices.suffix(from: 1)] let filterBits = bits[bits.indices.suffix(from: 1)]
do { do {
filters = try filterBits.map { try parser.findFilter($0) } filters = try filterBits.map {
let (name, arguments) = parseFilterComponents(token: $0)
let filter = try parser.findFilter(name)
return (filter, arguments)
}
} catch { } catch {
filters = [] filters = []
throw error throw error
@@ -28,7 +32,8 @@ class FilterExpression : Resolvable {
let result = try variable.resolve(context) let result = try variable.resolve(context)
return try filters.reduce(result) { x, y in return try filters.reduce(result) { x, y in
return try y(x) let arguments = try y.1.map { try $0.resolve(context) }
return try y.0.invoke(value: x, arguments: arguments)
} }
} }
} }
@@ -135,3 +140,10 @@ extension Dictionary : Normalizable {
return dictionary return dictionary
} }
} }
func parseFilterComponents(token: String) -> (String, [Variable]) {
var components = token.characters.split(separator: ":").map(String.init)
let name = components.removeFirst()
let variables = components.joined(separator: ":").characters.split(separator: ",").map { Variable(String($0)) }
return (name, variables)
}

View File

@@ -10,7 +10,7 @@ func testFilter() {
let template = Template(templateString: "{{ name|repeat }}") let template = Template(templateString: "{{ name|repeat }}")
let namespace = Namespace() let namespace = Namespace()
namespace.registerFilter("repeat") { value in namespace.registerFilter("repeat") { (value: Any?) in
if let value = value as? String { if let value = value as? String {
return "\(value) \(value)" return "\(value) \(value)"
} }
@@ -22,10 +22,27 @@ func testFilter() {
try expect(result) == "Kyle Kyle" try expect(result) == "Kyle Kyle"
} }
$0.it("allows you to register a custom filter which accepts arguments") {
let template = Template(templateString: "{{ name|repeat:'value' }}")
let namespace = Namespace()
namespace.registerFilter("repeat") { value, arguments in
print(arguments)
if !arguments.isEmpty {
return "\(value!) \(value!) with args \(arguments.first!!)"
}
return nil
}
let result = try template.render(Context(dictionary: context, namespace: namespace))
try expect(result) == "Kyle Kyle with args value"
}
$0.it("allows you to register a custom which throws") { $0.it("allows you to register a custom which throws") {
let template = Template(templateString: "{{ name|repeat }}") let template = Template(templateString: "{{ name|repeat }}")
let namespace = Namespace() let namespace = Namespace()
namespace.registerFilter("repeat") { value in namespace.registerFilter("repeat") { (value: Any?) in
throw TemplateSyntaxError("No Repeat") throw TemplateSyntaxError("No Repeat")
} }
@@ -37,6 +54,11 @@ func testFilter() {
let result = try template.render(Context(dictionary: ["name": "kyle"])) let result = try template.render(Context(dictionary: ["name": "kyle"]))
try expect(result) == "KYLE" try expect(result) == "KYLE"
} }
$0.it("throws when you pass arguments to simple filter") {
let template = Template(templateString: "{{ name|uppercase:5 }}")
try expect(try template.render(Context(dictionary: ["name": "kyle"]))).toThrow()
}
} }