diff --git a/CHANGELOG.md b/CHANGELOG.md index 90e60879..981fb6b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Similar filters are suggested when unknown filter is used - Added `indent` filter - Allow using new lines inside tags +- Added support for iterating arrays of tuples ### Bug Fixes diff --git a/Sources/ForTag.swift b/Sources/ForTag.swift index 25005e66..b3e5f84f 100644 --- a/Sources/ForTag.swift +++ b/Sources/ForTag.swift @@ -53,25 +53,26 @@ class ForNode : NodeType { self.where = `where` } - func push(value: Any, context: Context, closure: () throws -> (Result)) rethrows -> Result { + func push(value: Any, context: Context, closure: () throws -> (Result)) throws -> 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() - } + let valueMirror = Mirror(reflecting: value) + if case .tuple? = valueMirror.displayStyle { + if loopVariables.count > Int(valueMirror.children.count) { + throw TemplateSyntaxError("Tuple '\(value)' has less values than loop variables") } + var variablesContext = [String: Any]() + valueMirror.children.prefix(loopVariables.count).enumerated().forEach({ (offset, element) in + if loopVariables[offset] != "_" { + variablesContext[loopVariables[offset]] = element.value + } + }) - return try context.push(dictionary: [first: value.0]) { + return try context.push(dictionary: variablesContext) { return try closure() } } diff --git a/Tests/StencilTests/ForNodeSpec.swift b/Tests/StencilTests/ForNodeSpec.swift index 9cc98cb7..925cb147 100644 --- a/Tests/StencilTests/ForNodeSpec.swift +++ b/Tests/StencilTests/ForNodeSpec.swift @@ -11,7 +11,8 @@ func testForNode() { "dict": [ "one": "I", "two": "II", - ] + ], + "tuples": [(1, 2, 3), (4, 5, 6)] ]) $0.it("renders the given nodes for each item") { @@ -127,6 +128,53 @@ func testForNode() { try expect(result) == fixture } + $0.context("given array of tuples") { + $0.it("can iterate over all tuple values") { + let templateString = "{% for first,second,third in tuples %}" + + "{{ first }}, {{ second }}, {{ third }}\n" + + "{% endfor %}\n" + + let template = Template(templateString: templateString) + let result = try template.render(context) + + let fixture = "1, 2, 3\n4, 5, 6\n\n" + try expect(result) == fixture + } + + $0.it("can iterate with less number of variables") { + let templateString = "{% for first,second in tuples %}" + + "{{ first }}, {{ second }}\n" + + "{% endfor %}\n" + + let template = Template(templateString: templateString) + let result = try template.render(context) + + let fixture = "1, 2\n4, 5\n\n" + try expect(result) == fixture + } + + $0.it("can use _ to skip variables") { + let templateString = "{% for first,_,third in tuples %}" + + "{{ first }}, {{ third }}\n" + + "{% endfor %}\n" + + let template = Template(templateString: templateString) + let result = try template.render(context) + + let fixture = "1, 3\n4, 6\n\n" + try expect(result) == fixture + } + + $0.it("throws when number of variables is more than number of tuple values") { + let templateString = "{% for key,value,smth in dict %}" + + "{% endfor %}\n" + + let template = Template(templateString: templateString) + try expect(template.render(context)).toThrow() + } + + } + $0.it("can iterate over dictionary") { let templateString = "{% for key,value in dict %}" + "{{ key }}: {{ value }}," +