From ea33fca91479d622a059249c1b6263279d20d4ae Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Fri, 21 Sep 2018 00:11:10 +0100 Subject: [PATCH 1/3] use condition in variable node --- CHANGELOG.md | 4 +++- Sources/Node.swift | 30 +++++++++++++++++++++++++++ Sources/Parser.swift | 3 +-- Tests/StencilTests/VariableSpec.swift | 14 +++++++++++++ 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac886951..783233f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,9 @@ ### New Features -_None_ +- Now you can conditionally render variables with `{{ variable if condition }}`, which is a shorthand for `{% if condition %}{{ variable }}{% endif %}` + [Ilya Puchka](https://github.com/ilyapuchka) + [#243](https://github.com/stencilproject/Stencil/pull/243) ### Internal Changes diff --git a/Sources/Node.swift b/Sources/Node.swift index c4bb77ac..eb1a63c3 100644 --- a/Sources/Node.swift +++ b/Sources/Node.swift @@ -58,18 +58,48 @@ public protocol Resolvable { public class VariableNode : NodeType { public let variable: Resolvable public var token: Token? + let condition: Expression? + + class func parse(_ parser:TokenParser, token:Token) throws -> NodeType { + var components = token.components() + + func hasToken(_ token: String, at index: Int) -> Bool { + return components.count > (index + 1) && components[index] == token + } + + let condition = hasToken("if", at: 1) + ? try parseExpression(components: Array(components.suffix(from: 2)), tokenParser: parser, token: token) + : nil + + let filter = try parser.compileResolvable(components[0], containedIn: token) + return VariableNode(variable: filter, token: token, condition: condition) + } public init(variable: Resolvable, token: Token? = nil) { self.variable = variable self.token = token + self.condition = nil + } + + init(variable: Resolvable, token: Token? = nil, condition: Expression?) { + self.variable = variable + self.token = token + self.condition = condition } public init(variable: String, token: Token? = nil) { self.variable = Variable(variable) self.token = token + self.condition = nil } public func render(_ context: Context) throws -> String { + if let condition = self.condition { + guard try condition.evaluate(context: context) == true else { + return "" + } + } + let result = try variable.resolve(context) return stringify(result) } diff --git a/Sources/Parser.swift b/Sources/Parser.swift index 5de93e80..1f5d50d7 100644 --- a/Sources/Parser.swift +++ b/Sources/Parser.swift @@ -40,8 +40,7 @@ public class TokenParser { case .text(let text, _): nodes.append(TextNode(text: text)) case .variable: - let filter = try compileResolvable(token.contents, containedIn: token) - nodes.append(VariableNode(variable: filter, token: token)) + try nodes.append(VariableNode.parse(self, token: token)) case .block: if let parse_until = parse_until , parse_until(self, token) { prependToken(token) diff --git a/Tests/StencilTests/VariableSpec.swift b/Tests/StencilTests/VariableSpec.swift index 83034811..9005d4d4 100644 --- a/Tests/StencilTests/VariableSpec.swift +++ b/Tests/StencilTests/VariableSpec.swift @@ -337,4 +337,18 @@ func testVariable() { } } + + describe("inline if expression") { + + $0.it("can conditionally render variable") { + let template: Template = "{{ variable if variable|uppercase == \"A\" }}" + try expect(template.render(Context(dictionary: ["variable": "a"]))) == "a" + try expect(template.render(Context(dictionary: ["variable": "b"]))) == "" + } + + $0.it("throws when used invalid condition") { + let template: Template = "{{ variable if variable \"A\" }}" + try expect(template.render(Context(dictionary: ["variable": "a"]))).toThrow() + } + } } From 32603e455ad0f3cfb6f10d824e2495e09227cc92 Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Fri, 21 Sep 2018 00:50:05 +0100 Subject: [PATCH 2/3] added support for else expression --- CHANGELOG.md | 2 +- Sources/Node.swift | 32 +++++++++++++++++++++------ Tests/StencilTests/VariableSpec.swift | 5 +++++ 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 783233f6..a8a94e2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ ### New Features -- Now you can conditionally render variables with `{{ variable if condition }}`, which is a shorthand for `{% if condition %}{{ variable }}{% endif %}` +- Now you can conditionally render variables with `{{ variable if condition }}`, which is a shorthand for `{% if condition %}{{ variable }}{% endif %}`. You can also use `else` like `{{ variable1 if condition else variable2 }}`, which is a shorthand for `{% if condition %}{{ variable1 }}{% else %}{{ variable2 }}{% endif %}` [Ilya Puchka](https://github.com/ilyapuchka) [#243](https://github.com/stencilproject/Stencil/pull/243) diff --git a/Sources/Node.swift b/Sources/Node.swift index eb1a63c3..b3b49a20 100644 --- a/Sources/Node.swift +++ b/Sources/Node.swift @@ -59,6 +59,7 @@ public class VariableNode : NodeType { public let variable: Resolvable public var token: Token? let condition: Expression? + let elseExpression: Resolvable? class func parse(_ parser:TokenParser, token:Token) throws -> NodeType { var components = token.components() @@ -67,36 +68,53 @@ public class VariableNode : NodeType { return components.count > (index + 1) && components[index] == token } - let condition = hasToken("if", at: 1) - ? try parseExpression(components: Array(components.suffix(from: 2)), tokenParser: parser, token: token) - : nil + let condition: Expression? + let elseExpression: Resolvable? + + if hasToken("if", at: 1) { + let components = Array(components.suffix(from: 2)) + if let elseIndex = components.index(of: "else") { + condition = try parseExpression(components: Array(components.prefix(upTo: elseIndex)), tokenParser: parser, token: token) + let elseToken = components.suffix(from: elseIndex.advanced(by: 1)).joined(separator: " ") + elseExpression = try parser.compileResolvable(elseToken, containedIn: token) + } else { + condition = try parseExpression(components: components, tokenParser: parser, token: token) + elseExpression = nil + } + } else { + condition = nil + elseExpression = nil + } let filter = try parser.compileResolvable(components[0], containedIn: token) - return VariableNode(variable: filter, token: token, condition: condition) + return VariableNode(variable: filter, token: token, condition: condition, elseExpression: elseExpression) } public init(variable: Resolvable, token: Token? = nil) { self.variable = variable self.token = token self.condition = nil + self.elseExpression = nil } - init(variable: Resolvable, token: Token? = nil, condition: Expression?) { + init(variable: Resolvable, token: Token? = nil, condition: Expression?, elseExpression: Resolvable?) { self.variable = variable self.token = token self.condition = condition + self.elseExpression = elseExpression } public init(variable: String, token: Token? = nil) { self.variable = Variable(variable) self.token = token self.condition = nil + self.elseExpression = nil } public func render(_ context: Context) throws -> String { if let condition = self.condition { - guard try condition.evaluate(context: context) == true else { - return "" + if try condition.evaluate(context: context) == false { + return try elseExpression?.resolve(context).map(stringify) ?? "" } } diff --git a/Tests/StencilTests/VariableSpec.swift b/Tests/StencilTests/VariableSpec.swift index 9005d4d4..8d3c4ac1 100644 --- a/Tests/StencilTests/VariableSpec.swift +++ b/Tests/StencilTests/VariableSpec.swift @@ -346,6 +346,11 @@ func testVariable() { try expect(template.render(Context(dictionary: ["variable": "b"]))) == "" } + $0.it("can render with else expression") { + let template: Template = "{{ variable if variable|uppercase == \"A\" else fallback|uppercase }}" + try expect(template.render(Context(dictionary: ["variable": "b", "fallback": "c"]))) == "C" + } + $0.it("throws when used invalid condition") { let template: Template = "{{ variable if variable \"A\" }}" try expect(template.render(Context(dictionary: ["variable": "a"]))).toThrow() From 4da69dcec8f894f8e8f437ba6388c797b4b1fc05 Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Fri, 21 Sep 2018 18:40:33 +0100 Subject: [PATCH 3/3] addressing code review comments --- Sources/Node.swift | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Sources/Node.swift b/Sources/Node.swift index b3b49a20..dc7a72c9 100644 --- a/Sources/Node.swift +++ b/Sources/Node.swift @@ -72,13 +72,13 @@ public class VariableNode : NodeType { let elseExpression: Resolvable? if hasToken("if", at: 1) { - let components = Array(components.suffix(from: 2)) + let components = components.suffix(from: 2) if let elseIndex = components.index(of: "else") { condition = try parseExpression(components: Array(components.prefix(upTo: elseIndex)), tokenParser: parser, token: token) let elseToken = components.suffix(from: elseIndex.advanced(by: 1)).joined(separator: " ") elseExpression = try parser.compileResolvable(elseToken, containedIn: token) } else { - condition = try parseExpression(components: components, tokenParser: parser, token: token) + condition = try parseExpression(components: Array(components), tokenParser: parser, token: token) elseExpression = nil } } else { @@ -112,10 +112,8 @@ public class VariableNode : NodeType { } public func render(_ context: Context) throws -> String { - if let condition = self.condition { - if try condition.evaluate(context: context) == false { - return try elseExpression?.resolve(context).map(stringify) ?? "" - } + if let condition = self.condition, try condition.evaluate(context: context) == false { + return try elseExpression?.resolve(context).map(stringify) ?? "" } let result = try variable.resolve(context)