feat(filters): Allow filters with arguments
This commit is contained in:
@@ -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
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user