From a8d680b30e55de72b386baafc56b81cdb1f1b0c0 Mon Sep 17 00:00:00 2001 From: Kyle Fuller Date: Tue, 18 Apr 2017 23:26:56 +0100 Subject: [PATCH] fix(for): Support looping dictionaries --- Sources/ForTag.swift | 68 +++++++++++++++++----------- Tests/StencilTests/ForNodeSpec.swift | 29 ++++++++---- 2 files changed, 62 insertions(+), 35 deletions(-) diff --git a/Sources/ForTag.swift b/Sources/ForTag.swift index 55663bd..12d8aba 100644 --- a/Sources/ForTag.swift +++ b/Sources/ForTag.swift @@ -1,6 +1,8 @@ +import Foundation + class ForNode : NodeType { let resolvable: Resolvable - let loopVariable:String + let loopVariables: [String] let nodes:[NodeType] let emptyNodes: [NodeType] let `where`: Expression? @@ -13,7 +15,11 @@ class ForNode : NodeType { throw TemplateSyntaxError("'for' statements should use the following 'for x in y where condition' `\(token.contents)`.") } - let loopVariable = components[1] + let loopVariables = components[1].characters + .split(separator: ",") + .map(String.init) + .map { $0.trimmingCharacters(in: CharacterSet.whitespaces) } + let variable = components[3] var emptyNodes = [NodeType]() @@ -36,42 +42,52 @@ class ForNode : NodeType { } else { `where` = nil } - return ForNode(resolvable: filter, loopVariable: loopVariable, nodes: forNodes, emptyNodes:emptyNodes, where: `where`) + return ForNode(resolvable: filter, loopVariables: loopVariables, nodes: forNodes, emptyNodes:emptyNodes, where: `where`) } - init(resolvable: Resolvable, loopVariable:String, nodes:[NodeType], emptyNodes:[NodeType], where: Expression? = nil) { + init(resolvable: Resolvable, loopVariables: [String], nodes:[NodeType], emptyNodes:[NodeType], where: Expression? = nil) { self.resolvable = resolvable - self.loopVariable = loopVariable + self.loopVariables = loopVariables self.nodes = nodes self.emptyNodes = emptyNodes self.where = `where` } func render(_ context: Context) throws -> String { - let values = try resolvable.resolve(context) + let resolved = try resolvable.resolve(context) - if var values = values as? [Any], values.count > 0 { - if let `where` = self.where { - values = try values.filter({ item -> Bool in - return try context.push(dictionary: [loopVariable: item]) { () -> Bool in - try `where`.evaluate(context: context) - } - }) - } - if values.count > 0 { - let count = values.count - return try values.enumerated().map { index, item in - let forContext: [String: Any] = [ - "first": index == 0, - "last": index == (count - 1), - "counter": index + 1, - ] + var values: [Any] - return try context.push(dictionary: [loopVariable: item, "forloop": forContext]) { + if let dictionary = resolved as? [String: Any], !dictionary.isEmpty { + values = Array(dictionary.keys) + } else if let array = resolved as? [Any] { + values = array + } else { + values = [] + } + + if let `where` = self.where { + values = try values.filter({ item -> Bool in + return try context.push(dictionary: [loopVariables.first!: item]) { () -> Bool in + try `where`.evaluate(context: context) + } + }) + } + + if !values.isEmpty { + let count = values.count + + return try values.enumerated().map { index, item in + let forContext: [String: Any] = [ + "first": index == 0, + "last": index == (count - 1), + "counter": index + 1, + ] + + return try context.push(dictionary: [loopVariables.first!: item, "forloop": forContext]) { try renderNodes(nodes, context) - } - }.joined(separator: "") - } + } + }.joined(separator: "") } return try context.push { diff --git a/Tests/StencilTests/ForNodeSpec.swift b/Tests/StencilTests/ForNodeSpec.swift index 1440b03..a26e2f6 100644 --- a/Tests/StencilTests/ForNodeSpec.swift +++ b/Tests/StencilTests/ForNodeSpec.swift @@ -8,18 +8,22 @@ func testForNode() { let context = Context(dictionary: [ "items": [1, 2, 3], "emptyItems": [Int](), + "dict": [ + "one": "I", + "two": "II", + ] ]) $0.it("renders the given nodes for each item") { let nodes: [NodeType] = [VariableNode(variable: "item")] - let node = ForNode(resolvable: Variable("items"), loopVariable: "item", nodes: nodes, emptyNodes: []) + let node = ForNode(resolvable: Variable("items"), loopVariables: ["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(resolvable: Variable("emptyItems"), loopVariable: "item", nodes: nodes, emptyNodes: emptyNodes) + let node = ForNode(resolvable: Variable("emptyItems"), loopVariables: ["item"], nodes: nodes, emptyNodes: emptyNodes) try expect(try node.render(context)) == "empty" } @@ -29,7 +33,7 @@ func testForNode() { ]) let nodes: [NodeType] = [VariableNode(variable: "item")] - let node = ForNode(resolvable: Variable("items"), loopVariable: "item", nodes: nodes, emptyNodes: []) + let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) try expect(try node.render(any_context)) == "123" } @@ -40,33 +44,33 @@ func testForNode() { ]) let nodes: [NodeType] = [VariableNode(variable: "item")] - let node = ForNode(resolvable: Variable("items"), loopVariable: "item", nodes: nodes, emptyNodes: []) + let node = ForNode(resolvable: Variable("items"), loopVariables: ["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(resolvable: Variable("items"), loopVariable: "item", nodes: nodes, emptyNodes: []) + let node = ForNode(resolvable: Variable("items"), loopVariables: ["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(resolvable: Variable("items"), loopVariable: "item", nodes: nodes, emptyNodes: []) + let node = ForNode(resolvable: Variable("items"), loopVariables: ["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(resolvable: Variable("items"), loopVariable: "item", nodes: nodes, emptyNodes: []) + let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: []) try expect(try node.render(context)) == "112233" } $0.it("renders the given nodes while filtering items using where expression") { let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")] let `where` = try parseExpression(components: ["item", ">", "1"], tokenParser: TokenParser(tokens: [], environment: Environment())) - let node = ForNode(resolvable: Variable("items"), loopVariable: "item", nodes: nodes, emptyNodes: [], where: `where`) + let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [], where: `where`) try expect(try node.render(context)) == "2132" } @@ -74,7 +78,7 @@ func testForNode() { let nodes: [NodeType] = [VariableNode(variable: "item")] let emptyNodes: [NodeType] = [TextNode(text: "empty")] let `where` = try parseExpression(components: ["item", "==", "0"], tokenParser: TokenParser(tokens: [], environment: Environment())) - let node = ForNode(resolvable: Variable("emptyItems"), loopVariable: "item", nodes: nodes, emptyNodes: emptyNodes, where: `where`) + let node = ForNode(resolvable: Variable("emptyItems"), loopVariables: ["item"], nodes: nodes, emptyNodes: emptyNodes, where: `where`) try expect(try node.render(context)) == "empty" } @@ -100,6 +104,13 @@ func testForNode() { try expect(result) == fixture } + + $0.it("renders supports iterating over dictionary") { + let nodes: [NodeType] = [VariableNode(variable: "key")] + let emptyNodes: [NodeType] = [TextNode(text: "empty")] + let node = ForNode(resolvable: Variable("dict"), loopVariables: ["key"], nodes: nodes, emptyNodes: emptyNodes, where: nil) + try expect(try node.render(context)) == "onetwo" + } } }