From 0f3a302108804d7576a999afcaaff9fa2f39cafc Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Wed, 27 Dec 2017 05:27:26 +0100 Subject: [PATCH 1/6] fix(variable): Resolving variable as string literal (#168) In Swift 4, a Substring was returned. --- CHANGELOG.md | 1 + Sources/Variable.swift | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd8e81f8..7fb29236 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Fixed rendering `{{ block.super }}` with several levels of inheritance - Fixed checking dictionary values for nil in `default` filter +- Fixed comparing string variables with string literals ## 0.10.1 diff --git a/Sources/Variable.swift b/Sources/Variable.swift index afc5dbd5..659cf51d 100644 --- a/Sources/Variable.swift +++ b/Sources/Variable.swift @@ -60,7 +60,7 @@ public struct Variable : Equatable, Resolvable { if (variable.hasPrefix("'") && variable.hasSuffix("'")) || (variable.hasPrefix("\"") && variable.hasSuffix("\"")) { // String literal - return variable[variable.characters.index(after: variable.startIndex) ..< variable.characters.index(before: variable.endIndex)] + return String(variable[variable.characters.index(after: variable.startIndex) ..< variable.characters.index(before: variable.endIndex)]) } if let number = Number(variable) { From a4b75f3c89d21c5bbd87034080a7a08c688ed46a Mon Sep 17 00:00:00 2001 From: Kyle Fuller Date: Tue, 26 Dec 2017 20:32:25 -0800 Subject: [PATCH 2/6] docs(changelog): Add further information regarding substring comparison --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fb29236..865bc3ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - Fixed rendering `{{ block.super }}` with several levels of inheritance - Fixed checking dictionary values for nil in `default` filter -- Fixed comparing string variables with string literals +- Fixed comparing string variables with string literals, in Swift 4 string literals became `Substring` and thus couldn't be directly compared to strings. ## 0.10.1 From 79a16854e71bd81c46b41f3d8d22cdab97ec3e67 Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Fri, 29 Dec 2017 17:26:38 +0100 Subject: [PATCH 3/6] fixed implicit conversion of integer literals to float --- CHANGELOG.md | 1 + Sources/Variable.swift | 5 ++++- Tests/StencilTests/FilterSpec.swift | 14 +++++++++++++- Tests/StencilTests/VariableSpec.swift | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 865bc3ff..2e5a0e09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Fixed rendering `{{ block.super }}` with several levels of inheritance - Fixed checking dictionary values for nil in `default` filter - Fixed comparing string variables with string literals, in Swift 4 string literals became `Substring` and thus couldn't be directly compared to strings. +- Integer literals now resolve into Int values, not Float ## 0.10.1 diff --git a/Sources/Variable.swift b/Sources/Variable.swift index 659cf51d..c17b9660 100644 --- a/Sources/Variable.swift +++ b/Sources/Variable.swift @@ -63,8 +63,11 @@ public struct Variable : Equatable, Resolvable { return String(variable[variable.characters.index(after: variable.startIndex) ..< variable.characters.index(before: variable.endIndex)]) } + // Number literal + if let int = Int(variable) { + return int + } if let number = Number(variable) { - // Number literal return number } diff --git a/Tests/StencilTests/FilterSpec.swift b/Tests/StencilTests/FilterSpec.swift index ded572c0..25016107 100644 --- a/Tests/StencilTests/FilterSpec.swift +++ b/Tests/StencilTests/FilterSpec.swift @@ -136,7 +136,19 @@ func testFilter() { let result = try template.render(Context(dictionary: [:])) try expect(result) == "Hello World" } - + + $0.it("can use int as default") { + let template = Template(templateString: "{{ value|default:1 }}") + let result = try template.render(Context(dictionary: [:])) + try expect(result) == "1" + } + + $0.it("can use float as default") { + let template = Template(templateString: "{{ value|default:1.5 }}") + let result = try template.render(Context(dictionary: [:])) + try expect(result) == "1.5" + } + $0.it("checks for underlying nil value correctly") { let template = Template(templateString: "Hello {{ user.name|default:\"anonymous\" }}") let nilName: String? = nil diff --git a/Tests/StencilTests/VariableSpec.swift b/Tests/StencilTests/VariableSpec.swift index 44af2cc2..1a567d8a 100644 --- a/Tests/StencilTests/VariableSpec.swift +++ b/Tests/StencilTests/VariableSpec.swift @@ -61,7 +61,7 @@ func testVariable() { $0.it("can resolve an integer literal") { let variable = Variable("5") - let result = try variable.resolve(context) as? Number + let result = try variable.resolve(context) as? Int try expect(result) == 5 } From 0156f6f37b1f763e59f186853da3eded2bcb47eb Mon Sep 17 00:00:00 2001 From: Kyle Fuller Date: Sun, 31 Dec 2017 12:26:25 -0800 Subject: [PATCH 4/6] test: Count for unordered dictionary in ForLoop tests Closes #166 --- Tests/StencilTests/ForNodeSpec.swift | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/Tests/StencilTests/ForNodeSpec.swift b/Tests/StencilTests/ForNodeSpec.swift index 8804f0bf..ded1ced7 100644 --- a/Tests/StencilTests/ForNodeSpec.swift +++ b/Tests/StencilTests/ForNodeSpec.swift @@ -135,22 +135,37 @@ func testForNode() { let template = Template(templateString: templateString) let result = try template.render(context) - let fixture = "one: I\ntwo: II\n\n" - try expect(result) == fixture + let sortedResult = result.split(separator: "\n").sorted(by: <) + try expect(sortedResult) == ["one: I", "two: II"] } $0.it("renders supports iterating over dictionary") { - let nodes: [NodeType] = [VariableNode(variable: "key")] + let nodes: [NodeType] = [ + VariableNode(variable: "key"), + TextNode(text: ","), + ] 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" + let result = try node.render(context) + + let sortedResult = result.split(separator: ",").sorted(by: <) + try expect(sortedResult) == ["one", "two"] } $0.it("renders supports iterating over dictionary") { - let nodes: [NodeType] = [VariableNode(variable: "key"), VariableNode(variable: "value")] + let nodes: [NodeType] = [ + VariableNode(variable: "key"), + TextNode(text: "="), + VariableNode(variable: "value"), + TextNode(text: ","), + ] 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" + + let result = try node.render(context) + + let sortedResult = result.split(separator: ",").sorted(by: <) + try expect(sortedResult) == ["one=I", "two=II"] } $0.it("handles invalid input") { From c0e66eb96f33efa1f632f8a5d67f0b8f5713bf4f Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Wed, 27 Dec 2017 22:13:33 +0100 Subject: [PATCH 5/6] feat: Allow iterating over tuple, struct and class properties Closes #173 --- CHANGELOG.md | 2 + Sources/ForTag.swift | 16 ++++++ Tests/StencilTests/ForNodeSpec.swift | 77 ++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) 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" + } + } + } From a6dba678284f0e4889a7ed9975069987da3b5c79 Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Mon, 1 Jan 2018 14:57:23 +0100 Subject: [PATCH 6/6] fixed tests on swift 3.1 --- Tests/StencilTests/ForNodeSpec.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/StencilTests/ForNodeSpec.swift b/Tests/StencilTests/ForNodeSpec.swift index 4c4877f6..9cc98cb7 100644 --- a/Tests/StencilTests/ForNodeSpec.swift +++ b/Tests/StencilTests/ForNodeSpec.swift @@ -129,13 +129,13 @@ func testForNode() { $0.it("can iterate over dictionary") { let templateString = "{% for key,value in dict %}" + - "{{ key }}: {{ value }}\n" + - "{% endfor %}\n" + "{{ key }}: {{ value }}," + + "{% endfor %}" let template = Template(templateString: templateString) let result = try template.render(context) - let sortedResult = result.split(separator: "\n").sorted(by: <) + let sortedResult = result.characters.split(separator: ",").map(String.init).sorted(by: <) try expect(sortedResult) == ["one: I", "two: II"] } @@ -148,7 +148,7 @@ func testForNode() { let node = ForNode(resolvable: Variable("dict"), loopVariables: ["key"], nodes: nodes, emptyNodes: emptyNodes, where: nil) let result = try node.render(context) - let sortedResult = result.split(separator: ",").sorted(by: <) + let sortedResult = result.characters.split(separator: ",").map(String.init).sorted(by: <) try expect(sortedResult) == ["one", "two"] } @@ -164,7 +164,7 @@ func testForNode() { let result = try node.render(context) - let sortedResult = result.split(separator: ",").sorted(by: <) + let sortedResult = result.characters.split(separator: ",").map(String.init).sorted(by: <) try expect(sortedResult) == ["one=I", "two=II"] }