diff --git a/CHANGELOG.md b/CHANGELOG.md index bd80299a..f12e238b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - The `{% for %}` tag can now iterate over tuples, structures and classes via their stored properties. - Added `split` filter +- Added `valueForKey` filter ### Bug Fixes diff --git a/Sources/Extension.swift b/Sources/Extension.swift index a3892766..21021a8d 100644 --- a/Sources/Extension.swift +++ b/Sources/Extension.swift @@ -26,6 +26,10 @@ open class Extension { /// Registers a template filter with the given name public func registerFilter(_ name: String, filter: @escaping (Any?, [Any?]) throws -> Any?) { + filters[name] = .arguments({ value, args, _ in try filter(value, args) }) + } + + public func registerFilter(_ name: String, filter: @escaping (Any?, [Any?], Context) throws -> Any?) { filters[name] = .arguments(filter) } } @@ -58,28 +62,28 @@ class DefaultExtension: Extension { registerFilter("lowercase", filter: lowercase) registerFilter("join", filter: joinFilter) registerFilter("split", filter: splitFilter) + registerFilter("valueForKey", filter: valueForKeyFilter) } } protocol FilterType { - func invoke(value: Any?, arguments: [Any?]) throws -> Any? + func invoke(value: Any?, arguments: [Any?], context: Context) throws -> Any? } enum Filter: FilterType { case simple(((Any?) throws -> Any?)) - case arguments(((Any?, [Any?]) throws -> Any?)) + case arguments(((Any?, [Any?], Context) throws -> Any?)) - func invoke(value: Any?, arguments: [Any?]) throws -> Any? { + func invoke(value: Any?, arguments: [Any?], context: Context) 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) + return try filter(value, arguments, context) } } } diff --git a/Sources/Filters.swift b/Sources/Filters.swift index cf8f0fcb..3bc32c92 100644 --- a/Sources/Filters.swift +++ b/Sources/Filters.swift @@ -53,3 +53,14 @@ func splitFilter(value: Any?, arguments: [Any?]) throws -> Any? { return value } + +func valueForKeyFilter(value: Any?, arguments: [Any?], context: Context) throws -> Any? { + guard arguments.count == 1 else { + throw TemplateSyntaxError("'split' filter takes a single argument") + } + + let key = stringify(arguments[0]) + return try context.push(dictionary: ["filter_value": value as Any]) { + return try Variable("filter_value.\(key)").resolve(context) + } +} diff --git a/Sources/Variable.swift b/Sources/Variable.swift index c17b9660..06f74bad 100644 --- a/Sources/Variable.swift +++ b/Sources/Variable.swift @@ -36,7 +36,7 @@ class FilterExpression : Resolvable { return try filters.reduce(result) { x, y in let arguments = try y.1.map { try $0.resolve(context) } - return try y.0.invoke(value: x, arguments: arguments) + return try y.0.invoke(value: x, arguments: arguments, context: context) } } } diff --git a/Tests/StencilTests/FilterSpec.swift b/Tests/StencilTests/FilterSpec.swift index bb24e602..98a42d1b 100644 --- a/Tests/StencilTests/FilterSpec.swift +++ b/Tests/StencilTests/FilterSpec.swift @@ -199,4 +199,18 @@ func testFilter() { } } + describe("valueForKey filter") { + $0.it("can get value for predefined key") { + let template = Template(templateString: "{{ value|valueForKey:\"name\" }}") + let result = try template.render(Context(dictionary: ["value": ["name": "One"]])) + try expect(result) == "One" + } + + $0.it("can get value for runtime key") { + let template = Template(templateString: "{{ value|valueForKey:key }}") + let result = try template.render(Context(dictionary: ["key": "name", "value": ["name": "One"]])) + try expect(result) == "One" + } + } + }