diff --git a/CHANGELOG.md b/CHANGELOG.md index 865bc3ff..d86e25e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### Enhancements - Added support for resolving superclass properties for not-NSObject subclasses +- The `{% for %}` tag can now iterate over tuples, structures and classes via + their stored properties. ### Bug Fixes diff --git a/Sources/ForTag.swift b/Sources/ForTag.swift index d921a827..25005e66 100644 --- a/Sources/ForTag.swift +++ b/Sources/ForTag.swift @@ -94,6 +94,22 @@ class ForNode : NodeType { values = Array(range) } else if let range = resolved as? CountableRange { values = Array(range) + } else if let resolved = resolved { + let mirror = Mirror(reflecting: resolved) + switch mirror.displayStyle { + case .struct?, .tuple?: + values = Array(mirror.children) + case .class?: + var children = Array(mirror.children) + var currentMirror: Mirror? = mirror + while let superclassMirror = currentMirror?.superclassMirror { + children.append(contentsOf: superclassMirror.children) + currentMirror = superclassMirror + } + values = Array(children) + default: + values = [] + } } else { values = [] } diff --git a/Tests/StencilTests/ForNodeSpec.swift b/Tests/StencilTests/ForNodeSpec.swift index ded1ced7..4c4877f6 100644 --- a/Tests/StencilTests/ForNodeSpec.swift +++ b/Tests/StencilTests/ForNodeSpec.swift @@ -176,7 +176,84 @@ func testForNode() { let error = TemplateSyntaxError("'for' statements should use the following 'for x in y where condition' `for i`.") try expect(try parser.parse()).toThrow(error) } + + $0.it("can iterate over struct properties") { + struct MyStruct { + let string: String + let number: Int + } + + let context = Context(dictionary: [ + "struct": MyStruct(string: "abc", number: 123) + ]) + + let nodes: [NodeType] = [ + VariableNode(variable: "property"), + TextNode(text: "="), + VariableNode(variable: "value"), + TextNode(text: "\n"), + ] + let node = ForNode(resolvable: Variable("struct"), loopVariables: ["property", "value"], nodes: nodes, emptyNodes: []) + let result = try node.render(context) + + try expect(result) == "string=abc\nnumber=123\n" + } + + $0.it("can iterate tuple items") { + let context = Context(dictionary: [ + "tuple": (one: 1, two: "dva"), + ]) + + let nodes: [NodeType] = [ + VariableNode(variable: "label"), + TextNode(text: "="), + VariableNode(variable: "value"), + TextNode(text: "\n"), + ] + + let node = ForNode(resolvable: Variable("tuple"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: []) + let result = try node.render(context) + + try expect(result) == "one=1\ntwo=dva\n" + } + + $0.it("can iterate over class properties") { + class MyClass { + var baseString: String + var baseInt: Int + init(_ string: String, _ int: Int) { + baseString = string + baseInt = int + } + } + + class MySubclass: MyClass { + var childString: String + init(_ childString: String, _ string: String, _ int: Int) { + self.childString = childString + super.init(string, int) + } + } + + let context = Context(dictionary: [ + "class": MySubclass("child", "base", 1) + ]) + + let nodes: [NodeType] = [ + VariableNode(variable: "label"), + TextNode(text: "="), + VariableNode(variable: "value"), + TextNode(text: "\n"), + ] + + let node = ForNode(resolvable: Variable("class"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: []) + let result = try node.render(context) + + try expect(result) == "childString=child\nbaseString=base\nbaseInt=1\n" + } + } + }