From 89256b96f48635bc6185b218ddb481f9302906c9 Mon Sep 17 00:00:00 2001 From: Kyle Fuller Date: Wed, 19 Apr 2017 00:03:47 +0100 Subject: [PATCH] feat(for): Allow iterating over dictionary key and value Closes #94 --- .swift-version | 2 +- CHANGELOG.md | 8 ++++++ Sources/ForTag.swift | 38 +++++++++++++++++++++++++--- Tests/StencilTests/ForNodeSpec.swift | 7 +++++ docs/builtins.rst | 12 ++++++++- 5 files changed, 61 insertions(+), 6 deletions(-) diff --git a/.swift-version b/.swift-version index cb2b00e..8c50098 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -3.0.1 +3.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index b26bedc..75a55c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,14 @@ {% endif %} ``` +- `for` block now allows you to iterate over array of tuples or dictionaries. + + ```html+django + {% for key, value in thing %} +
  • {{ key }}: {{ value }}
  • + {% endfor %} + ``` + ### Bug Fixes - You can now use literal filter arguments which contain quotes. diff --git a/Sources/ForTag.swift b/Sources/ForTag.swift index 12d8aba..8fbd221 100644 --- a/Sources/ForTag.swift +++ b/Sources/ForTag.swift @@ -53,13 +53,41 @@ class ForNode : NodeType { self.where = `where` } + func push(value: Any, context: Context, closure: () throws -> (Result)) rethrows -> Result { + if loopVariables.isEmpty { + return try context.push() { + return try closure() + } + } + + if let value = value as? (Any, Any) { + let first = loopVariables[0] + + if loopVariables.count == 2 { + let second = loopVariables[1] + + return try context.push(dictionary: [first: value.0, second: value.1]) { + return try closure() + } + } + + return try context.push(dictionary: [first: value.0]) { + return try closure() + } + } + + return try context.push(dictionary: [loopVariables.first!: value]) { + return try closure() + } + } + func render(_ context: Context) throws -> String { let resolved = try resolvable.resolve(context) var values: [Any] if let dictionary = resolved as? [String: Any], !dictionary.isEmpty { - values = Array(dictionary.keys) + values = dictionary.map { ($0.key, $0.value) } } else if let array = resolved as? [Any] { values = array } else { @@ -68,7 +96,7 @@ class ForNode : NodeType { if let `where` = self.where { values = try values.filter({ item -> Bool in - return try context.push(dictionary: [loopVariables.first!: item]) { () -> Bool in + return try push(value: item, context: context) { try `where`.evaluate(context: context) } }) @@ -84,8 +112,10 @@ class ForNode : NodeType { "counter": index + 1, ] - return try context.push(dictionary: [loopVariables.first!: item, "forloop": forContext]) { - try renderNodes(nodes, context) + return try context.push(dictionary: ["forloop": forContext]) { + return try push(value: item, context: context) { + try renderNodes(nodes, context) + } } }.joined(separator: "") } diff --git a/Tests/StencilTests/ForNodeSpec.swift b/Tests/StencilTests/ForNodeSpec.swift index a26e2f6..4c98232 100644 --- a/Tests/StencilTests/ForNodeSpec.swift +++ b/Tests/StencilTests/ForNodeSpec.swift @@ -111,6 +111,13 @@ func testForNode() { let node = ForNode(resolvable: Variable("dict"), loopVariables: ["key"], nodes: nodes, emptyNodes: emptyNodes, where: nil) try expect(try node.render(context)) == "onetwo" } + + $0.it("renders supports iterating over dictionary") { + let nodes: [NodeType] = [VariableNode(variable: "key"), VariableNode(variable: "value")] + let emptyNodes: [NodeType] = [TextNode(text: "empty")] + let node = ForNode(resolvable: Variable("dict"), loopVariables: ["key", "value"], nodes: nodes, emptyNodes: emptyNodes, where: nil) + try expect(try node.render(context)) == "oneItwoII" + } } } diff --git a/docs/builtins.rst b/docs/builtins.rst index dc7d23f..e9e9b9d 100644 --- a/docs/builtins.rst +++ b/docs/builtins.rst @@ -19,7 +19,17 @@ A for loop allows you to iterate over an array found by variable lookup. {% endfor %} -The ``for`` tag can contain optional ``where`` expression to filter out +The ``for`` tag can iterate over dictionaries. + +.. code-block:: html+django + + + +The ``for`` tag can contain optional ``where`` expression to filter out elements on which this expression evaluates to false. .. code-block:: html+django