diff --git a/Sources/IfTag.swift b/Sources/IfTag.swift index bd89b5e..02097ee 100644 --- a/Sources/IfTag.swift +++ b/Sources/IfTag.swift @@ -160,30 +160,48 @@ func parseExpression(components: [String], tokenParser: TokenParser) throws -> E } +/// Represents an if condition and the associated nodes when the condition +/// evaluates +final class IfCondition { + let expression: Expression? + let nodes: [NodeType] + + init(expression: Expression?, nodes: [NodeType]) { + self.expression = expression + self.nodes = nodes + } + + func render(_ context: Context) throws -> String { + return try context.push { + return try renderNodes(nodes, context) + } + } +} + + class IfNode : NodeType { - let expression: Expression - let trueNodes: [NodeType] - let falseNodes: [NodeType] + let conditions: [IfCondition] class func parse(_ parser: TokenParser, token: Token) throws -> NodeType { var components = token.components() components.removeFirst() - var trueNodes = [NodeType]() - var falseNodes = [NodeType]() - trueNodes = try parser.parse(until(["endif", "else"])) + var conditions: [IfCondition] = [] + + let expression = try parseExpression(components: components, tokenParser: parser) + let nodes = try parser.parse(until(["endif", "else"])) + conditions.append(IfCondition(expression: expression, nodes: nodes)) guard let token = parser.nextToken() else { throw TemplateSyntaxError("`endif` was not found.") } if token.contents == "else" { - falseNodes = try parser.parse(until(["endif"])) + conditions.append(IfCondition(expression: nil, nodes: try parser.parse(until(["endif"])))) _ = parser.nextToken() } - let expression = try parseExpression(components: components, tokenParser: parser) - return IfNode(expression: expression, trueNodes: trueNodes, falseNodes: falseNodes) + return IfNode(conditions: conditions) } class func parse_ifnot(_ parser: TokenParser, token: Token) throws -> NodeType { @@ -207,24 +225,29 @@ class IfNode : NodeType { } let expression = try parseExpression(components: components, tokenParser: parser) - return IfNode(expression: expression, trueNodes: trueNodes, falseNodes: falseNodes) + return IfNode(conditions: [ + IfCondition(expression: expression, nodes: trueNodes), + IfCondition(expression: nil, nodes: falseNodes), + ]) } - init(expression: Expression, trueNodes: [NodeType], falseNodes: [NodeType]) { - self.expression = expression - self.trueNodes = trueNodes - self.falseNodes = falseNodes + init(conditions: [IfCondition]) { + self.conditions = conditions } func render(_ context: Context) throws -> String { - let truthy = try expression.evaluate(context: context) + for condition in conditions { + if let expression = condition.expression { + let truthy = try expression.evaluate(context: context) - return try context.push { - if truthy { - return try renderNodes(trueNodes, context) + if truthy { + return try condition.render(context) + } } else { - return try renderNodes(falseNodes, context) + return try condition.render(context) } } + + return "" } } diff --git a/Tests/StencilTests/IfNodeSpec.swift b/Tests/StencilTests/IfNodeSpec.swift index f0fae3c..b5a7112 100644 --- a/Tests/StencilTests/IfNodeSpec.swift +++ b/Tests/StencilTests/IfNodeSpec.swift @@ -9,27 +9,23 @@ func testIfNode() { let tokens: [Token] = [ .block(value: "if value"), .text(value: "true"), - .block(value: "else"), - .text(value: "false"), .block(value: "endif") ] let parser = TokenParser(tokens: tokens, environment: Environment()) let nodes = try parser.parse() let node = nodes.first as? IfNode - let trueNode = node?.trueNodes.first as? TextNode - let falseNode = node?.falseNodes.first as? TextNode - try expect(nodes.count) == 1 - try expect(node?.trueNodes.count) == 1 + let conditions = node?.conditions + try expect(conditions?.count) == 1 + try expect(conditions?[0].nodes.count) == 1 + let trueNode = conditions?[0].nodes.first as? TextNode try expect(trueNode?.text) == "true" - try expect(node?.falseNodes.count) == 1 - try expect(falseNode?.text) == "false" } - $0.it("can parse an if with complex expression") { + $0.it("can parse an if with else block") { let tokens: [Token] = [ - .block(value: "if value == \"test\" and not name"), + .block(value: "if value"), .text(value: "true"), .block(value: "else"), .text(value: "false"), @@ -39,16 +35,31 @@ func testIfNode() { let parser = TokenParser(tokens: tokens, environment: Environment()) let nodes = try parser.parse() let node = nodes.first as? IfNode - let trueNode = node?.trueNodes.first as? TextNode - let falseNode = node?.falseNodes.first as? TextNode - try expect(nodes.count) == 1 - try expect(node?.trueNodes.count) == 1 + let conditions = node?.conditions + try expect(conditions?.count) == 2 + + try expect(conditions?[0].nodes.count) == 1 + let trueNode = conditions?[0].nodes.first as? TextNode try expect(trueNode?.text) == "true" - try expect(node?.falseNodes.count) == 1 + + try expect(conditions?[1].nodes.count) == 1 + let falseNode = conditions?[1].nodes.first as? TextNode try expect(falseNode?.text) == "false" } + $0.it("can parse an if with complex expression") { + let tokens: [Token] = [ + .block(value: "if value == \"test\" and not name"), + .text(value: "true"), + .block(value: "endif") + ] + + let parser = TokenParser(tokens: tokens, environment: Environment()) + let nodes = try parser.parse() + try expect(nodes.first is IfNode).beTrue() + } + $0.it("can parse an ifnot block") { let tokens: [Token] = [ .block(value: "ifnot value"), @@ -61,13 +72,15 @@ func testIfNode() { let parser = TokenParser(tokens: tokens, environment: Environment()) let nodes = try parser.parse() let node = nodes.first as? IfNode - let trueNode = node?.trueNodes.first as? TextNode - let falseNode = node?.falseNodes.first as? TextNode + let conditions = node?.conditions + try expect(conditions?.count) == 2 - try expect(nodes.count) == 1 - try expect(node?.trueNodes.count) == 1 + try expect(conditions?[0].nodes.count) == 1 + let trueNode = conditions?[0].nodes.first as? TextNode try expect(trueNode?.text) == "true" - try expect(node?.falseNodes.count) == 1 + + try expect(conditions?[1].nodes.count) == 1 + let falseNode = conditions?[1].nodes.first as? TextNode try expect(falseNode?.text) == "false" } @@ -93,14 +106,43 @@ func testIfNode() { } $0.describe("rendering") { - $0.it("renders the truth when expression evaluates to true") { - let node = IfNode(expression: StaticExpression(value: true), trueNodes: [TextNode(text: "true")], falseNodes: [TextNode(text: "false")]) - try expect(try node.render(Context())) == "true" + $0.it("renders a true expression") { + let node = IfNode(conditions: [ + IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "1")]), + IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "2")]), + IfCondition(expression: nil, nodes: [TextNode(text: "3")]), + ]) + + try expect(try node.render(Context())) == "1" } - $0.it("renders the false when expression evaluates to false") { - let node = IfNode(expression: StaticExpression(value: false), trueNodes: [TextNode(text: "true")], falseNodes: [TextNode(text: "false")]) - try expect(try node.render(Context())) == "false" + $0.it("renders the first true expression") { + let node = IfNode(conditions: [ + IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]), + IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "2")]), + IfCondition(expression: nil, nodes: [TextNode(text: "3")]), + ]) + + try expect(try node.render(Context())) == "2" + } + + $0.it("renders the empty expression when other conditions are falsy") { + let node = IfNode(conditions: [ + IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]), + IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "2")]), + IfCondition(expression: nil, nodes: [TextNode(text: "3")]), + ]) + + try expect(try node.render(Context())) == "3" + } + + $0.it("renders empty when no truthy conditions") { + let node = IfNode(conditions: [ + IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]), + IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "2")]), + ]) + + try expect(try node.render(Context())) == "" } }