Skip to content
This repository has been archived by the owner on Aug 4, 2022. It is now read-only.

Commit

Permalink
Merge pull request #9 from g-Off/parse-named-params
Browse files Browse the repository at this point in the history
Parse named params
  • Loading branch information
katbutler authored Nov 6, 2019
2 parents 2262502 + 149795a commit d4e8bc3
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 80 deletions.
6 changes: 3 additions & 3 deletions Sources/Liquid/Expression.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
90 changes: 45 additions & 45 deletions Sources/Liquid/Filter+Standard.swift

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions Sources/Liquid/Filter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 4 additions & 2 deletions Sources/Liquid/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
25 changes: 18 additions & 7 deletions Sources/Liquid/Variable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
Expand Down
77 changes: 57 additions & 20 deletions Tests/LiquidTests/FilterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,25 +54,25 @@ final class FilterTests: XCTestCase {
}

func testEscape() {
XCTAssertEqual(try Filters.escapeFilter(value: Value("<strong>"), args: [], encoder: Encoder()).toString(), "&lt;strong&gt;")
XCTAssertEqual(try Filters.escapeFilter(value: Value("<strong>"), args: [], kwargs: [:], encoder: Encoder()).toString(), "&lt;strong&gt;")
}

func testEscapeOnce() throws {
XCTAssertEqual(try Filters.escapeOnceFilter(value: Value("&lt;strong&gt;Hulk</strong>"), args: [], encoder: Encoder()).toString(), "&lt;strong&gt;Hulk&lt;/strong&gt;")
XCTAssertEqual(try Filters.escapeOnceFilter(value: Value("&lt;strong&gt;Hulk</strong>"), args: [], kwargs: [:], encoder: Encoder()).toString(), "&lt;strong&gt;Hulk&lt;/strong&gt;")
}

func testUrlEncode() {
XCTAssertEqual(try Filters.urlEncodeFilter(value: Value("[email protected]"), args: [], encoder: Encoder()).toString(), "foo%2B1%40example.com")
XCTAssertEqual(try Filters.urlEncodeFilter(value: Value("[email protected]"), 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(), "[email protected]")
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(), "[email protected]")

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() {
Expand Down Expand Up @@ -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: <Fox Mulder>\"]", filters: filters)

let values: [String: Any] = ["name": "Dana Scully", "yob": 1964]
XCTAssertTemplate("{{ 'testing' | echo: name, yob }}", "testing - [\"int: <1964>\", \"string: <Dana Scully>\"]", 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: <Fox Mulder>\", \"yob:int: <1961>\"]", filters: filters)

let values: [String: Any] = ["name": "Dana Scully", "yob": 1964]
XCTAssertTemplate("{{ 'testing' | echo: name: name, yob: yob }}", "testing - [\"name:string: <Dana Scully>\", \"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: <Fox Mulder>\", \"yob:int: <1961>\"]", filters: filters)
}
}
4 changes: 3 additions & 1 deletion Tests/LiquidTests/XCTestCase+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit d4e8bc3

Please sign in to comment.