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
+
+
+ {% for key, value in dict %}
+ - {{ key }}: {{ value }}
+ {% endfor %}
+
+
+The ``for`` tag can contain optional ``where`` expression to filter out
elements on which this expression evaluates to false.
.. code-block:: html+django