diff --git a/Stencil/Node.swift b/Stencil/Node.swift index 45643d1..04caaa5 100644 --- a/Stencil/Node.swift +++ b/Stencil/Node.swift @@ -93,3 +93,64 @@ public class NowNode : Node { return ("\(date)", nil) } } + +public class ForNode : Node { + let variable:Variable + let loopVariable:String + let nodes:[Node] + + public class func parse(parser:TokenParser, token:Token) -> Node { + let components = token.components() + let count = countElements(components) + + if count == 4 && components[2] == "in" { + let loopVariable = components[1] + let variable = components[3] + let nodes = parser.parse(until(["endfor", "empty"])) + var emptyNodes = [Node]() + + if let token = parser.nextToken() { + if token.contents == "empty" { + emptyNodes = parser.parse(until(["endfor"])) + parser.nextToken() + } + } + + return ForNode(variable: variable, loopVariable: loopVariable, nodes: nodes, emptyNodes:emptyNodes) + } else { + // TODO error + } + + return TextNode(text: "TODO return some error") + } + + public init(variable:String, loopVariable:String, nodes:[Node], emptyNodes:[Node]) { + self.variable = Variable(variable) + self.loopVariable = loopVariable + self.nodes = nodes + } + + public func render(context: Context) -> (String?, Error?) { + let values = variable.resolve(context) as? [AnyObject] + var result = "" + + if let values = values { + for item in values { + context.push() + context[loopVariable] = item + let (string, error) = renderNodes(nodes, context) + context.pop() + + if let error = error { + return (nil, error) + } + + if let string = string { + result += string + } + } + } + + return (result, nil) + } +} diff --git a/Stencil/Parser.swift b/Stencil/Parser.swift index d3492ae..d293337 100644 --- a/Stencil/Parser.swift +++ b/Stencil/Parser.swift @@ -8,12 +8,25 @@ import Foundation +public func until(tags:[String])(parser:TokenParser, token:Token) -> Bool { + if let name = token.components().first { + for tag in tags { + if name == tag { + return true + } + } + } + + return false +} + public class TokenParser { private var tokens:[Token] private var tags = Dictionary (Node))>() public init(tokens:[Token]) { self.tokens = tokens + tags["for"] = ForNode.parse tags["now"] = NowNode.parse } diff --git a/Stencil/Tokenizer.swift b/Stencil/Tokenizer.swift index d765ac6..d78b764 100644 --- a/Stencil/Tokenizer.swift +++ b/Stencil/Tokenizer.swift @@ -30,6 +30,19 @@ public enum Token : Equatable { return value.value.stringByTrimmingCharactersInSet(characterSet).componentsSeparatedByCharactersInSet(characterSet) } } + + var contents:String { + switch self { + case .Block(let value): + return value + case .Variable(let value): + return value + case .Text(let value): + return value + case .Comment(let value): + return value + } + } } public func ==(lhs:Token, rhs:Token) -> Bool { diff --git a/StencilTests/NodeTests.swift b/StencilTests/NodeTests.swift index c0aaee8..3109c2c 100644 --- a/StencilTests/NodeTests.swift +++ b/StencilTests/NodeTests.swift @@ -30,6 +30,7 @@ class NodeTests: XCTestCase { context = Context(dictionary: [ "name": "Kyle", "age": 27, + "items": [1,2,3], ]) } } @@ -76,3 +77,12 @@ class RenderNodeTests: NodeTests { XCTAssertTrue(result == nil) } } + +class ForNodeTests: NodeTests { + func testForNodeRender() { + let node = ForNode(variable: "items", loopVariable: "item", nodes: [VariableNode(variable: "item")], emptyNodes:[]) + let result = node.render(context) + + XCTAssertEqual(result.0!, "123") + } +}