From b7e200a8a04558fbaa8cd459d080221f50de71fe Mon Sep 17 00:00:00 2001 From: Kyle Fuller Date: Mon, 28 Nov 2016 19:12:48 +0000 Subject: [PATCH] feat(for): Support filters Closes #70 --- CHANGELOG.md | 8 +++++ Sources/ForTag.swift | 11 +++---- Sources/Parser.swift | 2 +- Tests/StencilTests/ForNodeSpec.swift | 45 +++++++++++++++++++++++----- 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7c6270..6c005a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,14 @@ {{ value|join:", " }} ``` +- `{% for %}` tag now supports filters. + + ```html+django + {% for user in non_admins|default:admins %} + {{ user }} + {% endfor %} + ``` + ### Bug Fixes - Variables (`{{ variable.5 }}`) that reference an array index at an unknown diff --git a/Sources/ForTag.swift b/Sources/ForTag.swift index 5db7565..5628bbe 100644 --- a/Sources/ForTag.swift +++ b/Sources/ForTag.swift @@ -1,5 +1,5 @@ class ForNode : NodeType { - let variable:Variable + let resolvable: Resolvable let loopVariable:String let nodes:[NodeType] let emptyNodes: [NodeType] @@ -27,18 +27,19 @@ class ForNode : NodeType { _ = parser.nextToken() } - return ForNode(variable: variable, loopVariable: loopVariable, nodes: forNodes, emptyNodes:emptyNodes) + let filter = try parser.compileFilter(variable) + return ForNode(resolvable: filter, loopVariable: loopVariable, nodes: forNodes, emptyNodes:emptyNodes) } - init(variable:String, loopVariable:String, nodes:[NodeType], emptyNodes:[NodeType]) { - self.variable = Variable(variable) + init(resolvable: Resolvable, loopVariable:String, nodes:[NodeType], emptyNodes:[NodeType]) { + self.resolvable = resolvable self.loopVariable = loopVariable self.nodes = nodes self.emptyNodes = emptyNodes } func render(_ context: Context) throws -> String { - let values = try variable.resolve(context) + let values = try resolvable.resolve(context) if let values = values as? [Any] , values.count > 0 { let count = values.count diff --git a/Sources/Parser.swift b/Sources/Parser.swift index 9bf2b53..c5d6b1b 100644 --- a/Sources/Parser.swift +++ b/Sources/Parser.swift @@ -84,7 +84,7 @@ public class TokenParser { throw TemplateSyntaxError("Invalid filter '\(name)'") } - func compileFilter(_ token: String) throws -> Resolvable { + public func compileFilter(_ token: String) throws -> Resolvable { return try FilterExpression(token: token, parser: self) } } diff --git a/Tests/StencilTests/ForNodeSpec.swift b/Tests/StencilTests/ForNodeSpec.swift index e8564d2..666971e 100644 --- a/Tests/StencilTests/ForNodeSpec.swift +++ b/Tests/StencilTests/ForNodeSpec.swift @@ -12,14 +12,14 @@ func testForNode() { $0.it("renders the given nodes for each item") { let nodes: [NodeType] = [VariableNode(variable: "item")] - let node = ForNode(variable: "items", loopVariable: "item", nodes: nodes, emptyNodes: []) + let node = ForNode(resolvable: Variable("items"), loopVariable: "item", nodes: nodes, emptyNodes: []) try expect(try node.render(context)) == "123" } $0.it("renders the given empty nodes when no items found item") { let nodes: [NodeType] = [VariableNode(variable: "item")] let emptyNodes: [NodeType] = [TextNode(text: "empty")] - let node = ForNode(variable: "emptyItems", loopVariable: "item", nodes: nodes, emptyNodes: emptyNodes) + let node = ForNode(resolvable: Variable("emptyItems"), loopVariable: "item", nodes: nodes, emptyNodes: emptyNodes) try expect(try node.render(context)) == "empty" } @@ -29,8 +29,8 @@ func testForNode() { ]) let nodes: [NodeType] = [VariableNode(variable: "item")] - let node = ForNode(variable: "items", loopVariable: "item", nodes: nodes, emptyNodes: []) - try expect(try node.render(any_context)) == "123" + let node = ForNode(resolvable: Variable("items"), loopVariable: "item", nodes: nodes, emptyNodes: []) + try expect(try node.render(any_context)) == "123" } #if os(OSX) @@ -40,27 +40,56 @@ func testForNode() { ]) let nodes: [NodeType] = [VariableNode(variable: "item")] - let node = ForNode(variable: "items", loopVariable: "item", nodes: nodes, emptyNodes: []) + let node = ForNode(resolvable: Variable("items"), loopVariable: "item", nodes: nodes, emptyNodes: []) try expect(try node.render(nsarray_context)) == "123" } #endif $0.it("renders the given nodes while providing if the item is first in the context") { let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.first")] - let node = ForNode(variable: "items", loopVariable: "item", nodes: nodes, emptyNodes: []) + let node = ForNode(resolvable: Variable("items"), loopVariable: "item", nodes: nodes, emptyNodes: []) try expect(try node.render(context)) == "1true2false3false" } $0.it("renders the given nodes while providing if the item is last in the context") { let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.last")] - let node = ForNode(variable: "items", loopVariable: "item", nodes: nodes, emptyNodes: []) + let node = ForNode(resolvable: Variable("items"), loopVariable: "item", nodes: nodes, emptyNodes: []) try expect(try node.render(context)) == "1false2false3true" } $0.it("renders the given nodes while providing item counter") { let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")] - let node = ForNode(variable: "items", loopVariable: "item", nodes: nodes, emptyNodes: []) + let node = ForNode(resolvable: Variable("items"), loopVariable: "item", nodes: nodes, emptyNodes: []) try expect(try node.render(context)) == "112233" } + + $0.it("can render a filter") { + let templateString = "{% for article in ars|default:articles %}" + + "- {{ article.title }} by {{ article.author }}.\n" + + "{% endfor %}\n" + + let context = Context(dictionary: [ + "articles": [ + Article(title: "Migrating from OCUnit to XCTest", author: "Kyle Fuller"), + Article(title: "Memory Management with ARC", author: "Kyle Fuller"), + ] + ]) + + let template = Template(templateString: templateString) + let result = try template.render(context) + + let fixture = "" + + "- Migrating from OCUnit to XCTest by Kyle Fuller.\n" + + "- Memory Management with ARC by Kyle Fuller.\n" + + "\n" + + try expect(result) == fixture + } } } + + +fileprivate struct Article { + let title: String + let author: String +}