-
Notifications
You must be signed in to change notification settings - Fork 225
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes #15
- Loading branch information
Showing
7 changed files
with
199 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import Foundation | ||
|
||
class BlockContext { | ||
class var contextKey:String { return "block_context" } | ||
|
||
var blocks:[String:BlockNode] | ||
|
||
init(blocks:[String:BlockNode]) { | ||
self.blocks = blocks | ||
} | ||
|
||
func pop(blockName:String) -> BlockNode? { | ||
return blocks.removeValueForKey(blockName) | ||
} | ||
} | ||
|
||
func any<Element>(elements:[Element], closure:(Element -> Bool)) -> Element? { | ||
for element in elements { | ||
if closure(element) { | ||
return element | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
class ExtendsNode : Node { | ||
let templateName:String | ||
let blocks:[String:BlockNode] | ||
|
||
class func parse(parser:TokenParser, token:Token) -> TokenParser.Result { | ||
let bits = token.contents.componentsSeparatedByString("\"") | ||
|
||
if bits.count != 3 { | ||
return .Error(error:NodeError(token: token, message: "Tag takes one argument, the template file to be extended")) | ||
} | ||
|
||
switch parser.parse() { | ||
case .Success(let nodes): | ||
if (any(nodes) { ($0 as? ExtendsNode) != nil }) != nil { | ||
return .Error(error:"'extends' cannot appear more than once in the same template") | ||
} | ||
|
||
let blockNodes = filter(nodes) { node in node is BlockNode } | ||
|
||
let nodes = reduce(blockNodes, [String:BlockNode](), { (accumulator, node:Node) -> [String:BlockNode] in | ||
let node = (node as! BlockNode) | ||
var dict = accumulator | ||
dict[node.name] = node | ||
return dict | ||
}) | ||
|
||
return .Success(node:ExtendsNode(templateName: bits[1], blocks: nodes)) | ||
case .Error(let error): | ||
return .Error(error:error) | ||
} | ||
} | ||
|
||
init(templateName:String, blocks:[String:BlockNode]) { | ||
self.templateName = templateName | ||
self.blocks = blocks | ||
} | ||
|
||
func render(context: Context) -> Result { | ||
if let loader = context["loader"] as? TemplateLoader { | ||
if let template = loader.loadTemplate(templateName) { | ||
let blockContext = BlockContext(blocks: blocks) | ||
context.push([BlockContext.contextKey: blockContext]) | ||
let result = template.render(context) | ||
context.pop() | ||
return result | ||
} | ||
|
||
let paths:String = join(", ", loader.paths.map { path in | ||
return path.description | ||
}) | ||
let error = "Template '\(templateName)' not found in \(paths)" | ||
return .Error(error) | ||
} | ||
|
||
let error = "Template loader not in context" | ||
return .Error(error) | ||
} | ||
} | ||
|
||
class BlockNode : Node { | ||
let name:String | ||
let nodes:[Node] | ||
|
||
class func parse(parser:TokenParser, token:Token) -> TokenParser.Result { | ||
let bits = token.components() | ||
|
||
if bits.count != 2 { | ||
return .Error(error:NodeError(token: token, message: "Tag takes one argument, the template file to be included")) | ||
} | ||
|
||
let blockName = bits[1] | ||
var nodes = [Node]() | ||
|
||
switch parser.parse(until(["endblock"])) { | ||
case .Success(let blockNodes): | ||
nodes = blockNodes | ||
case .Error(let error): | ||
return .Error(error: error) | ||
} | ||
|
||
return .Success(node:BlockNode(name:blockName, nodes:nodes)) | ||
} | ||
|
||
init(name:String, nodes:[Node]) { | ||
self.name = name | ||
self.nodes = nodes | ||
} | ||
|
||
func render(context: Context) -> Result { | ||
if let blockContext = context[BlockContext.contextKey] as? BlockContext { | ||
if let node = blockContext.pop(name) { | ||
return node.render(context) | ||
} | ||
} | ||
|
||
return renderNodes(nodes, context) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import Foundation | ||
import XCTest | ||
import Stencil | ||
import PathKit | ||
|
||
class InheritenceTests: NodeTests { | ||
var loader:TemplateLoader! | ||
|
||
override func setUp() { | ||
super.setUp() | ||
|
||
let path = (Path(__FILE__) + Path("../..")).absolute() | ||
loader = TemplateLoader(paths: [path]) | ||
} | ||
|
||
func testInheritence() { | ||
context = Context(dictionary: ["loader": loader]) | ||
let template = loader.loadTemplate("child.html")! | ||
let result = template.render(context) | ||
|
||
switch result { | ||
case .Success(let rendered): | ||
XCTAssertEqual(rendered, "Header\nChild") | ||
case .Error(let error): | ||
XCTAssert(false, "Unexpected error") | ||
} | ||
} | ||
} | ||
|
||
//class BlockNodeTests: NodeTests { | ||
// func testBlockNodeWithoutChildren() { | ||
// let context = Context() | ||
// let block = BlockNode(name:"header", nodes:[TextNode(text: "contents")]) | ||
// let result = block.render(context) | ||
// | ||
// assertSuccess(result) { rendered in | ||
// XCTAssertEqual(rendered, "contents") | ||
// } | ||
// } | ||
// | ||
// func testBlockNodeWithChild() { | ||
// let context = Context() | ||
// let node = BlockNode(name:"header", nodes:[TextNode(text: "contents")]) | ||
// let childBlock = BlockNode(name: "header", nodes: [TextNode(text: "child contents")]) | ||
// let result = node.render(context) | ||
// | ||
// assertSuccess(result) { rendered in | ||
// XCTAssertEqual(rendered, "child contents") | ||
// } | ||
// } | ||
//} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
{% block header %}Header{% endblock %} | ||
{% block body %}Body{% endblock %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
{% extends "base.html" %} | ||
{% block body %}Child{% endblock %} |