fix(for): Support looping dictionaries

This commit is contained in:
Kyle Fuller
2017-04-18 23:26:56 +01:00
parent 6f48fe2d91
commit a8d680b30e
2 changed files with 62 additions and 35 deletions

View File

@@ -1,6 +1,8 @@
import Foundation
class ForNode : NodeType { class ForNode : NodeType {
let resolvable: Resolvable let resolvable: Resolvable
let loopVariable:String let loopVariables: [String]
let nodes:[NodeType] let nodes:[NodeType]
let emptyNodes: [NodeType] let emptyNodes: [NodeType]
let `where`: Expression? 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)`.") 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] let variable = components[3]
var emptyNodes = [NodeType]() var emptyNodes = [NodeType]()
@@ -36,42 +42,52 @@ class ForNode : NodeType {
} else { } else {
`where` = nil `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.resolvable = resolvable
self.loopVariable = loopVariable self.loopVariables = loopVariables
self.nodes = nodes self.nodes = nodes
self.emptyNodes = emptyNodes self.emptyNodes = emptyNodes
self.where = `where` self.where = `where`
} }
func render(_ context: Context) throws -> String { 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 { var values: [Any]
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,
]
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) try renderNodes(nodes, context)
} }
}.joined(separator: "") }.joined(separator: "")
}
} }
return try context.push { return try context.push {

View File

@@ -8,18 +8,22 @@ func testForNode() {
let context = Context(dictionary: [ let context = Context(dictionary: [
"items": [1, 2, 3], "items": [1, 2, 3],
"emptyItems": [Int](), "emptyItems": [Int](),
"dict": [
"one": "I",
"two": "II",
]
]) ])
$0.it("renders the given nodes for each item") { $0.it("renders the given nodes for each item") {
let nodes: [NodeType] = [VariableNode(variable: "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" try expect(try node.render(context)) == "123"
} }
$0.it("renders the given empty nodes when no items found item") { $0.it("renders the given empty nodes when no items found item") {
let nodes: [NodeType] = [VariableNode(variable: "item")] let nodes: [NodeType] = [VariableNode(variable: "item")]
let emptyNodes: [NodeType] = [TextNode(text: "empty")] 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" try expect(try node.render(context)) == "empty"
} }
@@ -29,7 +33,7 @@ func testForNode() {
]) ])
let nodes: [NodeType] = [VariableNode(variable: "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(any_context)) == "123" try expect(try node.render(any_context)) == "123"
} }
@@ -40,33 +44,33 @@ func testForNode() {
]) ])
let nodes: [NodeType] = [VariableNode(variable: "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(nsarray_context)) == "123" try expect(try node.render(nsarray_context)) == "123"
} }
#endif #endif
$0.it("renders the given nodes while providing if the item is first in the context") { $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 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" try expect(try node.render(context)) == "1true2false3false"
} }
$0.it("renders the given nodes while providing if the item is last in the context") { $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 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" try expect(try node.render(context)) == "1false2false3true"
} }
$0.it("renders the given nodes while providing item counter") { $0.it("renders the given nodes while providing item counter") {
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.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" try expect(try node.render(context)) == "112233"
} }
$0.it("renders the given nodes while filtering items using where expression") { $0.it("renders the given nodes while filtering items using where expression") {
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")] let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")]
let `where` = try parseExpression(components: ["item", ">", "1"], tokenParser: TokenParser(tokens: [], environment: Environment())) 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" try expect(try node.render(context)) == "2132"
} }
@@ -74,7 +78,7 @@ func testForNode() {
let nodes: [NodeType] = [VariableNode(variable: "item")] let nodes: [NodeType] = [VariableNode(variable: "item")]
let emptyNodes: [NodeType] = [TextNode(text: "empty")] let emptyNodes: [NodeType] = [TextNode(text: "empty")]
let `where` = try parseExpression(components: ["item", "==", "0"], tokenParser: TokenParser(tokens: [], environment: Environment())) 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" try expect(try node.render(context)) == "empty"
} }
@@ -100,6 +104,13 @@ func testForNode() {
try expect(result) == fixture 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"
}
} }
} }