Skip to content

Commit

Permalink
Merge branch 'master' into fix-filter-splaces
Browse files Browse the repository at this point in the history
  • Loading branch information
ilyapuchka authored Jan 1, 2018
2 parents 2fcb427 + 2e80f70 commit 0218a91
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 11 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
### 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

- 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
- Fixed using spaces in filter expressions and variables lists


Expand Down
16 changes: 16 additions & 0 deletions Sources/ForTag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,22 @@ class ForNode : NodeType {
values = Array(range)
} else if let range = resolved as? CountableRange<Int> {
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 = []
}
Expand Down
5 changes: 4 additions & 1 deletion Sources/Variable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
14 changes: 13 additions & 1 deletion Tests/StencilTests/FilterSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
108 changes: 100 additions & 8 deletions Tests/StencilTests/ForNodeSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,28 +129,43 @@ 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 fixture = "one: I\ntwo: II\n\n"
try expect(result) == fixture
let sortedResult = result.characters.split(separator: ",").map(String.init).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.characters.split(separator: ",").map(String.init).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.characters.split(separator: ",").map(String.init).sorted(by: <)
try expect(sortedResult) == ["one=I", "two=II"]
}

$0.it("handles invalid input") {
Expand All @@ -161,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"
}

}

}


Expand Down
2 changes: 1 addition & 1 deletion Tests/StencilTests/VariableSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down

0 comments on commit 0218a91

Please sign in to comment.