diff --git a/CHANGELOG.md b/CHANGELOG.md index 86d4ec9e..1e7a9657 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,9 @@ ### New Features -_None_ +- 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) ### Internal Changes diff --git a/Sources/Node.swift b/Sources/Node.swift index c4bb77ac..dc7a72c9 100644 --- a/Sources/Node.swift +++ b/Sources/Node.swift @@ -58,18 +58,64 @@ public protocol Resolvable { 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() + + func hasToken(_ token: String, at index: Int) -> Bool { + return components.count > (index + 1) && components[index] == token + } + + let condition: Expression? + let elseExpression: Resolvable? + + if hasToken("if", at: 1) { + 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: Array(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, 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?, 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, try condition.evaluate(context: context) == false { + return try elseExpression?.resolve(context).map(stringify) ?? "" + } + 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..8d3c4ac1 100644 --- a/Tests/StencilTests/VariableSpec.swift +++ b/Tests/StencilTests/VariableSpec.swift @@ -337,4 +337,23 @@ 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("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() + } + } }