diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ff8121..eed433d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,9 @@ - `Environment` allows you to provide a custom `Template` subclass, allowing new template to use a specific subclass. +- If expressions may now contain filters on variables. For example + `{% if name|uppercase == "TEST" %}` is now supported. + ### Deprecations - `Template` initialisers have been deprecated in favour of using a template diff --git a/Sources/Expression.swift b/Sources/Expression.swift index 8d1c951..f2c8a48 100644 --- a/Sources/Expression.swift +++ b/Sources/Expression.swift @@ -31,18 +31,18 @@ final class StaticExpression: Expression, CustomStringConvertible { final class VariableExpression: Expression, CustomStringConvertible { - let variable: Variable + let variable: Resolvable - init(variable: Variable) { + init(variable: Resolvable) { self.variable = variable } var description: String { - return "(variable: \(variable.variable))" + return "(variable: \(variable))" } /// Resolves a variable in the given context as boolean - func resolve(context: Context, variable: Variable) throws -> Bool { + func resolve(context: Context, variable: Resolvable) throws -> Bool { let result = try variable.resolve(context) var truthy = false diff --git a/Sources/IfTag.swift b/Sources/IfTag.swift index c962a61..bd89b5e 100644 --- a/Sources/IfTag.swift +++ b/Sources/IfTag.swift @@ -40,7 +40,7 @@ func findOperator(name: String) -> Operator? { enum IfToken { case infix(name: String, bindingPower: Int, op: InfixOperator.Type) case prefix(name: String, bindingPower: Int, op: PrefixOperator.Type) - case variable(Variable) + case variable(Resolvable) case end var bindingPower: Int { @@ -99,8 +99,8 @@ final class IfExpressionParser { let tokens: [IfToken] var position: Int = 0 - init(components: [String]) { - self.tokens = components.map { component in + init(components: [String], tokenParser: TokenParser) throws { + self.tokens = try components.map { component in if let op = findOperator(name: component) { switch op { case .infix(let name, let bindingPower, let cls): @@ -110,7 +110,7 @@ final class IfExpressionParser { } } - return .variable(Variable(component)) + return .variable(try tokenParser.compileFilter(component)) } } @@ -154,8 +154,8 @@ final class IfExpressionParser { } -func parseExpression(components: [String]) throws -> Expression { - let parser = IfExpressionParser(components: components) +func parseExpression(components: [String], tokenParser: TokenParser) throws -> Expression { + let parser = try IfExpressionParser(components: components, tokenParser: tokenParser) return try parser.parse() } @@ -182,7 +182,7 @@ class IfNode : NodeType { _ = parser.nextToken() } - let expression = try parseExpression(components: components) + let expression = try parseExpression(components: components, tokenParser: parser) return IfNode(expression: expression, trueNodes: trueNodes, falseNodes: falseNodes) } @@ -206,7 +206,7 @@ class IfNode : NodeType { _ = parser.nextToken() } - let expression = try parseExpression(components: components) + let expression = try parseExpression(components: components, tokenParser: parser) return IfNode(expression: expression, trueNodes: trueNodes, falseNodes: falseNodes) } diff --git a/Tests/StencilTests/ExpressionSpec.swift b/Tests/StencilTests/ExpressionSpec.swift index e14e551..a7b7241 100644 --- a/Tests/StencilTests/ExpressionSpec.swift +++ b/Tests/StencilTests/ExpressionSpec.swift @@ -4,6 +4,8 @@ import Spectre func testExpressions() { describe("Expression") { + let parser = TokenParser(tokens: [], environment: Environment()) + $0.describe("VariableExpression") { let expression = VariableExpression(variable: Variable("value")) @@ -103,19 +105,19 @@ func testExpressions() { $0.describe("expression parsing") { $0.it("can parse a variable expression") { - let expression = try parseExpression(components: ["value"]) + let expression = try parseExpression(components: ["value"], tokenParser: parser) try expect(expression.evaluate(context: Context())).to.beFalse() try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beTrue() } $0.it("can parse a not expression") { - let expression = try parseExpression(components: ["not", "value"]) + let expression = try parseExpression(components: ["not", "value"], tokenParser: parser) try expect(expression.evaluate(context: Context())).to.beTrue() try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beFalse() } $0.describe("and expression") { - let expression = try! parseExpression(components: ["lhs", "and", "rhs"]) + let expression = try! parseExpression(components: ["lhs", "and", "rhs"], tokenParser: parser) $0.it("evaluates to false with lhs false") { try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beFalse() @@ -135,7 +137,7 @@ func testExpressions() { } $0.describe("or expression") { - let expression = try! parseExpression(components: ["lhs", "or", "rhs"]) + let expression = try! parseExpression(components: ["lhs", "or", "rhs"], tokenParser: parser) $0.it("evaluates to true with lhs true") { try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beTrue() @@ -155,7 +157,7 @@ func testExpressions() { } $0.describe("equality expression") { - let expression = try! parseExpression(components: ["lhs", "==", "rhs"]) + let expression = try! parseExpression(components: ["lhs", "==", "rhs"], tokenParser: parser) $0.it("evaluates to true with equal lhs/rhs") { try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "a"]))).to.beTrue() @@ -191,7 +193,7 @@ func testExpressions() { } $0.describe("inequality expression") { - let expression = try! parseExpression(components: ["lhs", "!=", "rhs"]) + let expression = try! parseExpression(components: ["lhs", "!=", "rhs"], tokenParser: parser) $0.it("evaluates to true with inequal lhs/rhs") { try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "b"]))).to.beTrue() @@ -203,7 +205,7 @@ func testExpressions() { } $0.describe("more than expression") { - let expression = try! parseExpression(components: ["lhs", ">", "rhs"]) + let expression = try! parseExpression(components: ["lhs", ">", "rhs"], tokenParser: parser) $0.it("evaluates to true with lhs > rhs") { try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 4]))).to.beTrue() @@ -215,7 +217,7 @@ func testExpressions() { } $0.describe("more than equal expression") { - let expression = try! parseExpression(components: ["lhs", ">=", "rhs"]) + let expression = try! parseExpression(components: ["lhs", ">=", "rhs"], tokenParser: parser) $0.it("evaluates to true with lhs == rhs") { try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue() @@ -227,7 +229,7 @@ func testExpressions() { } $0.describe("less than expression") { - let expression = try! parseExpression(components: ["lhs", "<", "rhs"]) + let expression = try! parseExpression(components: ["lhs", "<", "rhs"], tokenParser: parser) $0.it("evaluates to true with lhs < rhs") { try expect(expression.evaluate(context: Context(dictionary: ["lhs": 4, "rhs": 4.5]))).to.beTrue() @@ -239,7 +241,7 @@ func testExpressions() { } $0.describe("less than equal expression") { - let expression = try! parseExpression(components: ["lhs", "<=", "rhs"]) + let expression = try! parseExpression(components: ["lhs", "<=", "rhs"], tokenParser: parser) $0.it("evaluates to true with lhs == rhs") { try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue() @@ -251,7 +253,7 @@ func testExpressions() { } $0.describe("multiple expression") { - let expression = try! parseExpression(components: ["one", "or", "two", "and", "not", "three"]) + let expression = try! parseExpression(components: ["one", "or", "two", "and", "not", "three"], tokenParser: parser) $0.it("evaluates to true with one") { try expect(expression.evaluate(context: Context(dictionary: ["one": true]))).to.beTrue() diff --git a/Tests/StencilTests/IfNodeSpec.swift b/Tests/StencilTests/IfNodeSpec.swift index 4dcd4e4..f0fae3c 100644 --- a/Tests/StencilTests/IfNodeSpec.swift +++ b/Tests/StencilTests/IfNodeSpec.swift @@ -103,5 +103,19 @@ func testIfNode() { try expect(try node.render(Context())) == "false" } } + + $0.it("supports variable filters in the if expression") { + let tokens: [Token] = [ + .block(value: "if value|uppercase == \"TEST\""), + .text(value: "true"), + .block(value: "endif") + ] + + let parser = TokenParser(tokens: tokens, environment: Environment()) + let nodes = try parser.parse() + + let result = try renderNodes(nodes, Context(dictionary: ["value": "test"])) + try expect(result) == "true" + } } }