diff --git a/Sources/Liquid/Expression.swift b/Sources/Liquid/Expression.swift
index 3dcdd35..ab8edc1 100644
--- a/Sources/Liquid/Expression.swift
+++ b/Sources/Liquid/Expression.swift
@@ -99,11 +99,11 @@ struct Expression: CustomStringConvertible {
} else {
switch filter {
case .size:
- result = try? Filters.sizeFilter(value: data, args: [], encoder: context.encoder)
+ result = try? Filters.sizeFilter(value: data, args: [], kwargs: [:], encoder: context.encoder)
case .first:
- result = try? Filters.firstFilter(value: data, args: [], encoder: context.encoder)
+ result = try? Filters.firstFilter(value: data, args: [], kwargs: [:], encoder: context.encoder)
case .last:
- result = try? Filters.lastFilter(value: data, args: [], encoder: context.encoder)
+ result = try? Filters.lastFilter(value: data, args: [], kwargs: [:], encoder: context.encoder)
}
}
} else {
diff --git a/Sources/Liquid/Filter+Standard.swift b/Sources/Liquid/Filter+Standard.swift
index aaa9777..3de6c87 100644
--- a/Sources/Liquid/Filter+Standard.swift
+++ b/Sources/Liquid/Filter+Standard.swift
@@ -56,7 +56,7 @@ enum Filters {
template.registerFilter(name: "date", filter: dateFilter)
}
- static func appendFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func appendFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
var result = value.toString()
args.forEach {
result += $0.toString()
@@ -64,28 +64,28 @@ enum Filters {
return Value(result)
}
- static func prependFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func prependFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard let first = args.first, args.count == 1 else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
return Value(first.toString() + value.toString())
}
- static func downcaseFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func downcaseFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
return Value(value.toString().lowercased())
}
- static func upcaseFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func upcaseFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
return Value(value.toString().uppercased())
}
- static func capitalizeFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func capitalizeFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
@@ -93,56 +93,56 @@ enum Filters {
return Value(string.prefix(1).capitalized + string.dropFirst())
}
- static func stripFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func stripFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
return Value(value.toString().strip())
}
- static func rstripFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func rstripFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
return Value(value.toString().rstrip())
}
- static func lstripFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func lstripFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
return Value(value.toString().lstrip())
}
- static func stripNewlinesFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func stripNewlinesFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
return Value(value.toString().replacingOccurrences(of: "\\s", with: "", options: [.regularExpression]))
}
- static func newlineToBRFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func newlineToBRFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
return Value(value.toString().replacingOccurrences(of: "\n", with: "
\n"))
}
- static func escapeFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func escapeFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
return Value(value.toString().htmlEscape(decimal: true, useNamedReferences: true))
}
- static func escapeOnceFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func escapeOnceFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
return Value(value.toString().htmlUnescape().htmlEscape(decimal: true, useNamedReferences: true))
}
- static func urlEncodeFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func urlEncodeFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
@@ -152,7 +152,7 @@ enum Filters {
return Value(inputString.addingPercentEncoding(withAllowedCharacters: allowedCharset) ?? inputString)
}
- static func urlDecodeFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func urlDecodeFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
@@ -160,14 +160,14 @@ enum Filters {
return Value(string.removingPercentEncoding ?? string)
}
- static func stripHTMLFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func stripHTMLFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
return Value(value.toString().replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression))
}
- static func truncateFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func truncateFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard !args.isEmpty, let firstArg = args.first else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
@@ -179,7 +179,7 @@ enum Filters {
return Value(String((string[string.startIndex.. Value {
+ static func truncateWordsFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard !args.isEmpty, let firstArg = args.first else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
@@ -206,7 +206,7 @@ enum Filters {
return Value(words.joined(separator: " ").appending(suffix))
}
- static func plusFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func plusFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.count == 1, let arg = args.first else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
@@ -216,7 +216,7 @@ enum Filters {
return Value(value.toDecimal() + arg.toDecimal())
}
- static func minusFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func minusFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.count == 1, let arg = args.first else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
@@ -226,7 +226,7 @@ enum Filters {
return Value(value.toDecimal() - arg.toDecimal())
}
- static func multipliedByFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func multipliedByFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.count == 1, let arg = args.first else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
@@ -236,7 +236,7 @@ enum Filters {
return Value(value.toDecimal() * arg.toDecimal())
}
- static func dividedByFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func dividedByFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.count == 1, let arg = args.first else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
@@ -246,7 +246,7 @@ enum Filters {
return Value(value.toDecimal() / arg.toDecimal())
}
- static func absFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func absFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
@@ -256,7 +256,7 @@ enum Filters {
return Value(abs(value.toDecimal()))
}
- static func ceilFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func ceilFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
@@ -266,7 +266,7 @@ enum Filters {
return Value(ceil(value.toDecimal().doubleValue))
}
- static func floorFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func floorFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
@@ -276,7 +276,7 @@ enum Filters {
return Value(floor(value.toDecimal().doubleValue))
}
- static func roundFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func roundFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.count <= 1 else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
@@ -285,7 +285,7 @@ enum Filters {
return Value(result)
}
- static func moduloFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func moduloFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.count == 1, let arg = args.first else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
@@ -296,7 +296,7 @@ enum Filters {
return Value(value.toDecimal().doubleValue.truncatingRemainder(dividingBy: arg.toDecimal().doubleValue))
}
- static func splitFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func splitFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard let separator = args.first?.toString() else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
@@ -304,14 +304,14 @@ enum Filters {
return Value(components.map { Value($0) })
}
- static func joinFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func joinFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard let separator = args.first?.toString() else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
return Value(value.toArray().map { $0.toString() }.joined(separator: separator))
}
- static func uniqueFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func uniqueFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
@@ -325,26 +325,26 @@ enum Filters {
return Value(unique)
}
- static func sizeFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func sizeFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
return Value(value.size)
}
- static func firstFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func firstFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
return value.toArray().first ?? Value()
}
- static func lastFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func lastFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
return value.toArray().last ?? Value()
}
- static func defaultFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func defaultFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.count == 1, let arg = args.first else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
@@ -354,7 +354,7 @@ enum Filters {
return value
}
}
- static func replaceFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func replaceFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.count == 2 else {
throw RuntimeError.invalidArgCount(expected: 2, received: args.count, tag: tagName())
}
@@ -363,7 +363,7 @@ enum Filters {
return Value(value.toString().replacingOccurrences(of: target, with: replacement))
}
- static func replaceFirstFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func replaceFirstFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.count == 2 else {
throw RuntimeError.invalidArgCount(expected: 2, received: args.count, tag: tagName())
}
@@ -376,7 +376,7 @@ enum Filters {
return Value(string.replacingCharacters(in: range, with: replacement))
}
- static func removeFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func removeFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.count == 1 else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
@@ -384,7 +384,7 @@ enum Filters {
return Value(value.toString().replacingOccurrences(of: target, with: ""))
}
- static func removeFirstFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func removeFirstFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.count == 1 else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
@@ -396,7 +396,7 @@ enum Filters {
return Value(string.replacingCharacters(in: range, with: ""))
}
- static func sliceFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func sliceFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard !args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
@@ -424,21 +424,21 @@ enum Filters {
return Value(String(string[sliceStartIndex.. Value {
+ static func reverseFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
return Value(value.toArray().reversed())
}
- static func compactFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func compactFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.isEmpty else {
throw RuntimeError.invalidArgCount(expected: 0, received: args.count, tag: tagName())
}
return Value(value.toArray().filter { !$0.isNil })
}
- static func mapFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func mapFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.count == 1 else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
@@ -448,7 +448,7 @@ enum Filters {
return Value(results)
}
- static func concatFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func concatFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.count == 1 else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
@@ -461,7 +461,7 @@ enum Filters {
return Value(array)
}
- static func sortFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func sortFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.count <= 1 else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
@@ -475,7 +475,7 @@ enum Filters {
}
}
- static func sortNaturalFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func sortNaturalFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.count <= 1 else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
@@ -491,7 +491,7 @@ enum Filters {
}
}
- static func dateFilter(value: Value, args: [Value], encoder: Encoder) throws -> Value {
+ static func dateFilter(value: Value, args: [Value], kwargs: [String: Value], encoder: Encoder) throws -> Value {
guard args.count == 1 else {
throw RuntimeError.invalidArgCount(expected: 1, received: args.count, tag: tagName())
}
diff --git a/Sources/Liquid/Filter.swift b/Sources/Liquid/Filter.swift
index 22ce0b0..f54153b 100644
--- a/Sources/Liquid/Filter.swift
+++ b/Sources/Liquid/Filter.swift
@@ -10,11 +10,13 @@ import Foundation
struct Filter {
let name: String
let args: [Expression]
+ let kwargs: [String: Expression]
- init(name: String, args: [Expression]) {
+ init(name: String, args: [Expression], kwargs: [String: Expression]) {
self.name = name
self.args = args
+ self.kwargs = kwargs
}
}
-public typealias FilterFunc = (_ value: Value, _ args: [Value], _ encoder: Encoder) throws -> Value
+public typealias FilterFunc = (_ value: Value, _ args: [Value], _ kwargs: [String: Value], _ encoder: Encoder) throws -> Value
diff --git a/Sources/Liquid/Parser.swift b/Sources/Liquid/Parser.swift
index b92cc03..fd7e9ff 100644
--- a/Sources/Liquid/Parser.swift
+++ b/Sources/Liquid/Parser.swift
@@ -19,9 +19,11 @@ final class Parser {
self.init(tokens: try Lexer.tokenize(string))
}
- func look(_ tokenKind: Lexer.Token.Kind) -> Bool {
+ func look(_ tokenKind: Lexer.Token.Kind, _ skip: Int = 0) -> Bool {
guard index != tokens.endIndex else { return false }
- return tokens[index].kind == tokenKind
+ guard (index + skip) < tokens.endIndex else { return false }
+
+ return tokens[index + skip].kind == tokenKind
}
func consumeId(_ id: String) -> Bool {
diff --git a/Sources/Liquid/Variable.swift b/Sources/Liquid/Variable.swift
index 7456199..d58fa53 100644
--- a/Sources/Liquid/Variable.swift
+++ b/Sources/Liquid/Variable.swift
@@ -25,27 +25,38 @@ struct Variable {
guard let name = parser.consume(.id) else {
break // TODO: throw an error ?
}
- var args: [Expression]?
+
+ var args: ([Expression], [String: Expression])?
+
if parser.consume(.colon) != nil {
args = parseFilterArgs(parser)
}
- filters.append(Filter(name: name, args: args ?? []))
+ filters.append(Filter(name: name, args: args?.0 ?? [], kwargs: args?.1 ?? [:]))
}
parser.consume(.endOfString)
}
- private func parseFilterArgs(_ parser: Parser) -> [Expression] {
+ private func parseFilterArgs(_ parser: Parser) -> ([Expression], [String:Expression]) {
var args: [Expression] = []
-
+ var kwargs: [String: Expression] = [:]
+
while !parser.look(.endOfString) {
- args.append(Expression.parse(parser))
+ if parser.look(.id) && parser.look(.colon, 1) {
+ let key = parser.consume(.id)!
+
+ parser.consume(.colon)
+ kwargs[key] = Expression.parse(parser)
+ } else {
+ args.append(Expression.parse(parser))
+ }
+
if !parser.look(.comma) {
break
}
parser.consume(.comma)
}
- return args
+ return (args, kwargs)
}
func evaluate(context: Context) throws -> Value {
@@ -54,7 +65,7 @@ struct Variable {
guard let filterFunc = context.filter(named: filter.name) else {
throw RuntimeError.unknownFilter(filter.name)
}
- value = try filterFunc(value, filter.args.map { $0.evaluate(context: context) }, context.encoder)
+ value = try filterFunc(value, filter.args.map { $0.evaluate(context: context) }, filter.kwargs.mapValues { $0.evaluate(context: context) }, context.encoder)
}
return value
}
diff --git a/Tests/LiquidTests/FilterTests.swift b/Tests/LiquidTests/FilterTests.swift
index bbef9ff..feb5f2f 100644
--- a/Tests/LiquidTests/FilterTests.swift
+++ b/Tests/LiquidTests/FilterTests.swift
@@ -54,25 +54,25 @@ final class FilterTests: XCTestCase {
}
func testEscape() {
- XCTAssertEqual(try Filters.escapeFilter(value: Value(""), args: [], encoder: Encoder()).toString(), "<strong>")
+ XCTAssertEqual(try Filters.escapeFilter(value: Value(""), args: [], kwargs: [:], encoder: Encoder()).toString(), "<strong>")
}
func testEscapeOnce() throws {
- XCTAssertEqual(try Filters.escapeOnceFilter(value: Value("<strong>Hulk"), args: [], encoder: Encoder()).toString(), "<strong>Hulk</strong>")
+ XCTAssertEqual(try Filters.escapeOnceFilter(value: Value("<strong>Hulk"), args: [], kwargs: [:], encoder: Encoder()).toString(), "<strong>Hulk</strong>")
}
func testUrlEncode() {
- XCTAssertEqual(try Filters.urlEncodeFilter(value: Value("foo+1@example.com"), args: [], encoder: Encoder()).toString(), "foo%2B1%40example.com")
+ XCTAssertEqual(try Filters.urlEncodeFilter(value: Value("foo+1@example.com"), args: [], kwargs: [:], encoder: Encoder()).toString(), "foo%2B1%40example.com")
}
func testUrlDecode() {
- XCTAssertEqual(try Filters.urlDecodeFilter(value: Value("foo+bar"), args: [], encoder: Encoder()).toString(), "foo bar")
- XCTAssertEqual(try Filters.urlDecodeFilter(value: Value("foo%20bar"), args: [], encoder: Encoder()).toString(), "foo bar")
- XCTAssertEqual(try Filters.urlDecodeFilter(value: Value("foo%2B1%40example.com"), args: [], encoder: Encoder()).toString(), "foo+1@example.com")
+ XCTAssertEqual(try Filters.urlDecodeFilter(value: Value("foo+bar"), args: [], kwargs: [:], encoder: Encoder()).toString(), "foo bar")
+ XCTAssertEqual(try Filters.urlDecodeFilter(value: Value("foo%20bar"), args: [], kwargs: [:], encoder: Encoder()).toString(), "foo bar")
+ XCTAssertEqual(try Filters.urlDecodeFilter(value: Value("foo%2B1%40example.com"), args: [], kwargs: [:], encoder: Encoder()).toString(), "foo+1@example.com")
- XCTAssertEqual(try Filters.urlDecodeFilter(value: Value("%20"), args: [], encoder: Encoder()).toString(), " ")
- XCTAssertEqual(try Filters.urlDecodeFilter(value: Value("%2"), args: [], encoder: Encoder()).toString(), "%2")
- XCTAssertEqual(try Filters.urlDecodeFilter(value: Value("%"), args: [], encoder: Encoder()).toString(), "%")
+ XCTAssertEqual(try Filters.urlDecodeFilter(value: Value("%20"), args: [], kwargs: [:], encoder: Encoder()).toString(), " ")
+ XCTAssertEqual(try Filters.urlDecodeFilter(value: Value("%2"), args: [], kwargs: [:], encoder: Encoder()).toString(), "%2")
+ XCTAssertEqual(try Filters.urlDecodeFilter(value: Value("%"), args: [], kwargs: [:], encoder: Encoder()).toString(), "%")
}
func testStripHtml() {
@@ -254,20 +254,57 @@ final class FilterTests: XCTestCase {
var encoder = Encoder()
encoder.locale = Locale(identifier: "en_US")
- XCTAssertEqual(try Filters.dateFilter(value: Value("2006-05-05T10:00:00Z"), args: [Value("%B")], encoder: encoder), Value("May"))
- XCTAssertEqual(try Filters.dateFilter(value: Value("2006-06-05T10:00:00Z"), args: [Value("%B")], encoder: encoder), Value("June"))
- XCTAssertEqual(try Filters.dateFilter(value: Value("2006-07-05T10:00:00Z"), args: [Value("%B")], encoder: encoder), Value("July"))
+ XCTAssertEqual(try Filters.dateFilter(value: Value("2006-05-05T10:00:00Z"), args: [Value("%B")], kwargs: [:], encoder: encoder), Value("May"))
+ XCTAssertEqual(try Filters.dateFilter(value: Value("2006-06-05T10:00:00Z"), args: [Value("%B")], kwargs: [:], encoder: encoder), Value("June"))
+ XCTAssertEqual(try Filters.dateFilter(value: Value("2006-07-05T10:00:00Z"), args: [Value("%B")], kwargs: [:], encoder: encoder), Value("July"))
- XCTAssertEqual(try Filters.dateFilter(value: Value("2006-07-05T10:00:00Z"), args: [Value("")], encoder: encoder), Value("7/5/06, 10:00:00 AM"))
- XCTAssertEqual(try Filters.dateFilter(value: Value("2006-07-05T10:00:00Z"), args: [Value()], encoder: encoder), Value("7/5/06, 10:00:00 AM"))
+ XCTAssertEqual(try Filters.dateFilter(value: Value("2006-07-05T10:00:00Z"), args: [Value("")], kwargs: [:], encoder: encoder), Value("7/5/06, 10:00:00 AM"))
+ XCTAssertEqual(try Filters.dateFilter(value: Value("2006-07-05T10:00:00Z"), args: [Value()], kwargs: [:], encoder: encoder), Value("7/5/06, 10:00:00 AM"))
let yearString = "\(Calendar.autoupdatingCurrent.component(.year, from: Date()))"
- XCTAssertEqual(try Filters.dateFilter(value: Value("2004-07-16T01:00:00Z"), args: [Value("%m/%d/%Y")], encoder: encoder), Value("07/16/2004"))
- XCTAssertEqual(try Filters.dateFilter(value: Value("now"), args: [Value("%Y")], encoder: encoder), Value(yearString))
- XCTAssertEqual(try Filters.dateFilter(value: Value("today"), args: [Value("%Y")], encoder: encoder), Value(yearString))
- XCTAssertEqual(try Filters.dateFilter(value: Value("Today"), args: [Value("%Y")], encoder: encoder), Value(yearString))
+ XCTAssertEqual(try Filters.dateFilter(value: Value("2004-07-16T01:00:00Z"), args: [Value("%m/%d/%Y")], kwargs: [:], encoder: encoder), Value("07/16/2004"))
+ XCTAssertEqual(try Filters.dateFilter(value: Value("now"), args: [Value("%Y")], kwargs: [:], encoder: encoder), Value(yearString))
+ XCTAssertEqual(try Filters.dateFilter(value: Value("today"), args: [Value("%Y")], kwargs: [:], encoder: encoder), Value(yearString))
+ XCTAssertEqual(try Filters.dateFilter(value: Value("Today"), args: [Value("%Y")], kwargs: [:], encoder: encoder), Value(yearString))
- XCTAssertEqual(try Filters.dateFilter(value: Value(1152098955), args: [Value("%m/%d/%Y")], encoder: encoder), Value("07/05/2006"))
- XCTAssertEqual(try Filters.dateFilter(value: Value("1152098955"), args: [Value("%m/%d/%Y")], encoder: encoder), Value("07/05/2006"))
+ XCTAssertEqual(try Filters.dateFilter(value: Value(1152098955), args: [Value("%m/%d/%Y")], kwargs: [:], encoder: encoder), Value("07/05/2006"))
+ XCTAssertEqual(try Filters.dateFilter(value: Value("1152098955"), args: [Value("%m/%d/%Y")], kwargs: [:], encoder: encoder), Value("07/05/2006"))
+ }
+
+ func testFilterArgs() {
+ let echoFilter: FilterFunc = { (value, args, kwargs, encoder) -> Value in
+ let strArgs = args.map { "\($0)" }.sorted()
+ return Value(value.toString() + " - " + strArgs.description)
+ }
+
+ let filters = ["echo": echoFilter]
+ XCTAssertTemplate("{{ 'testing' | echo: 'Fox Mulder', 1961 }}", "testing - [\"int: <1961>\", \"string: \"]", filters: filters)
+
+ let values: [String: Any] = ["name": "Dana Scully", "yob": 1964]
+ XCTAssertTemplate("{{ 'testing' | echo: name, yob }}", "testing - [\"int: <1964>\", \"string: \"]", values, filters: filters)
+ }
+
+ func testFilterKWArgs() {
+ let echoFilter: FilterFunc = { (value, args, kwargs, encoder) -> Value in
+ let strKWArgs = kwargs.map { "\($0):\($1)" }.sorted()
+ return Value(value.toString() + " - " + strKWArgs.description)
+ }
+
+ let filters = ["echo": echoFilter]
+ XCTAssertTemplate("{{ 'testing' | echo: name: 'Fox Mulder', yob: 1961 }}", "testing - [\"name:string: \", \"yob:int: <1961>\"]", filters: filters)
+
+ let values: [String: Any] = ["name": "Dana Scully", "yob": 1964]
+ XCTAssertTemplate("{{ 'testing' | echo: name: name, yob: yob }}", "testing - [\"name:string: \", \"yob:int: <1964>\"]", values, filters: filters)
+ }
+
+ func testFilterOrderedAndNamedArgs() {
+ let echoFilter: FilterFunc = { (value, args, kwargs, encoder) -> Value in
+ let strArgs = args.map { "\($0)" }.sorted()
+ let strKWArgs = kwargs.map { "\($0):\($1)" }.sorted()
+ return Value(value.toString() + " - " + strArgs.description + strKWArgs.description)
+ }
+
+ let filters = ["echo": echoFilter]
+ XCTAssertTemplate("{{ 'testing' | echo: 1, 2, 3, name: 'Fox Mulder', yob: 1961 }}", "testing - [\"int: <1>\", \"int: <2>\", \"int: <3>\"][\"name:string: \", \"yob:int: <1961>\"]", filters: filters)
}
}
diff --git a/Tests/LiquidTests/XCTestCase+Extensions.swift b/Tests/LiquidTests/XCTestCase+Extensions.swift
index 8ac51a0..d48061e 100644
--- a/Tests/LiquidTests/XCTestCase+Extensions.swift
+++ b/Tests/LiquidTests/XCTestCase+Extensions.swift
@@ -8,8 +8,10 @@
import XCTest
@testable import Liquid
-func XCTAssertTemplate(_ templateString: String, _ expression2: String, _ values: [String: Any?] = [:], fileSystem: FileSystem = BlankFileSystem(), _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) {
+func XCTAssertTemplate(_ templateString: String, _ expression2: String, _ values: [String: Any?] = [:], fileSystem: FileSystem = BlankFileSystem(), filters: [String: FilterFunc]? = nil, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) {
let template = Template(source: templateString, fileSystem: fileSystem)
+ filters?.forEach({ template.registerFilter(name: $0, filter: $1)})
+
XCTAssertNoThrow(try template.parse())
do {
let result = try template.render(values: values)