@@ -8,6 +8,10 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
1474245D3CE34A8BC76F8D20 /* Pods_StencilTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40E4E61A4F4EA12FE3FA6E39 /* Pods_StencilTests.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
1474245D3CE34A8BC76F8D20 /* Pods_StencilTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40E4E61A4F4EA12FE3FA6E39 /* Pods_StencilTests.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||||
|
27A848E41B42240E004ACA13 /* Inheritence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A848E31B42240E004ACA13 /* Inheritence.swift */; };
|
||||||
|
27A848E91B42242C004ACA13 /* base.html in Resources */ = {isa = PBXBuildFile; fileRef = 27A848E71B42242C004ACA13 /* base.html */; };
|
||||||
|
27A848EA1B42242C004ACA13 /* child.html in Resources */ = {isa = PBXBuildFile; fileRef = 27A848E81B42242C004ACA13 /* child.html */; };
|
||||||
|
27A848EC1B42247D004ACA13 /* InheritenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A848EB1B42247D004ACA13 /* InheritenceTests.swift */; };
|
||||||
27CE0ADE1A50BEC3004A105B /* TemplateLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27CE0ADD1A50BEC3004A105B /* TemplateLoader.swift */; };
|
27CE0ADE1A50BEC3004A105B /* TemplateLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27CE0ADD1A50BEC3004A105B /* TemplateLoader.swift */; };
|
||||||
27CE0AE01A50BF05004A105B /* TemplateLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27CE0ADF1A50BF05004A105B /* TemplateLoaderTests.swift */; };
|
27CE0AE01A50BF05004A105B /* TemplateLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27CE0ADF1A50BF05004A105B /* TemplateLoaderTests.swift */; };
|
||||||
27CE0AFA1A50C963004A105B /* test.html in Resources */ = {isa = PBXBuildFile; fileRef = 27CE0AF91A50C963004A105B /* test.html */; };
|
27CE0AFA1A50C963004A105B /* test.html in Resources */ = {isa = PBXBuildFile; fileRef = 27CE0AF91A50C963004A105B /* test.html */; };
|
||||||
@@ -45,6 +49,10 @@
|
|||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
216AE96E764D5BD92D11049B /* Pods-Stencil.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stencil.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Stencil/Pods-Stencil.debug.xcconfig"; sourceTree = "<group>"; };
|
216AE96E764D5BD92D11049B /* Pods-Stencil.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stencil.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Stencil/Pods-Stencil.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
27A848E31B42240E004ACA13 /* Inheritence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Inheritence.swift; sourceTree = "<group>"; };
|
||||||
|
27A848E71B42242C004ACA13 /* base.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = base.html; sourceTree = "<group>"; };
|
||||||
|
27A848E81B42242C004ACA13 /* child.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = child.html; sourceTree = "<group>"; };
|
||||||
|
27A848EB1B42247D004ACA13 /* InheritenceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InheritenceTests.swift; sourceTree = "<group>"; };
|
||||||
27CE0ADD1A50BEC3004A105B /* TemplateLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemplateLoader.swift; sourceTree = "<group>"; };
|
27CE0ADD1A50BEC3004A105B /* TemplateLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemplateLoader.swift; sourceTree = "<group>"; };
|
||||||
27CE0ADF1A50BF05004A105B /* TemplateLoaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemplateLoaderTests.swift; sourceTree = "<group>"; };
|
27CE0ADF1A50BF05004A105B /* TemplateLoaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemplateLoaderTests.swift; sourceTree = "<group>"; };
|
||||||
27CE0AF91A50C963004A105B /* test.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = test.html; sourceTree = "<group>"; };
|
27CE0AF91A50C963004A105B /* test.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = test.html; sourceTree = "<group>"; };
|
||||||
@@ -102,6 +110,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
27CE0B001A50CBD1004A105B /* Include.swift */,
|
27CE0B001A50CBD1004A105B /* Include.swift */,
|
||||||
|
27A848E31B42240E004ACA13 /* Inheritence.swift */,
|
||||||
);
|
);
|
||||||
path = TemplateLoader;
|
path = TemplateLoader;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -110,6 +119,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
27CE0B031A50CBEA004A105B /* IncludeTests.swift */,
|
27CE0B031A50CBEA004A105B /* IncludeTests.swift */,
|
||||||
|
27A848EB1B42247D004ACA13 /* InheritenceTests.swift */,
|
||||||
);
|
);
|
||||||
path = TemplateLoader;
|
path = TemplateLoader;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -176,6 +186,8 @@
|
|||||||
27CE0ADF1A50BF05004A105B /* TemplateLoaderTests.swift */,
|
27CE0ADF1A50BF05004A105B /* TemplateLoaderTests.swift */,
|
||||||
27CE0B021A50CBEA004A105B /* TemplateLoader */,
|
27CE0B021A50CBEA004A105B /* TemplateLoader */,
|
||||||
27CE0AF91A50C963004A105B /* test.html */,
|
27CE0AF91A50C963004A105B /* test.html */,
|
||||||
|
27A848E71B42242C004ACA13 /* base.html */,
|
||||||
|
27A848E81B42242C004ACA13 /* child.html */,
|
||||||
77FAAE6219F91E480029DC5E /* Supporting Files */,
|
77FAAE6219F91E480029DC5E /* Supporting Files */,
|
||||||
);
|
);
|
||||||
path = StencilTests;
|
path = StencilTests;
|
||||||
@@ -312,6 +324,8 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
27CE0AFA1A50C963004A105B /* test.html in Resources */,
|
27CE0AFA1A50C963004A105B /* test.html in Resources */,
|
||||||
|
27A848EA1B42242C004ACA13 /* child.html in Resources */,
|
||||||
|
27A848E91B42242C004ACA13 /* base.html in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -410,6 +424,7 @@
|
|||||||
71CE4C0A19FD29D000B9E0C5 /* Result.swift in Sources */,
|
71CE4C0A19FD29D000B9E0C5 /* Result.swift in Sources */,
|
||||||
7725B3D519F9438F002CF74B /* Node.swift in Sources */,
|
7725B3D519F9438F002CF74B /* Node.swift in Sources */,
|
||||||
27CE0ADE1A50BEC3004A105B /* TemplateLoader.swift in Sources */,
|
27CE0ADE1A50BEC3004A105B /* TemplateLoader.swift in Sources */,
|
||||||
|
27A848E41B42240E004ACA13 /* Inheritence.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -419,6 +434,7 @@
|
|||||||
files = (
|
files = (
|
||||||
77FAAE6519F91E480029DC5E /* StencilTests.swift in Sources */,
|
77FAAE6519F91E480029DC5E /* StencilTests.swift in Sources */,
|
||||||
7725B3D319F9437F002CF74B /* NodeTests.swift in Sources */,
|
7725B3D319F9437F002CF74B /* NodeTests.swift in Sources */,
|
||||||
|
27A848EC1B42247D004ACA13 /* InheritenceTests.swift in Sources */,
|
||||||
27CE0AE01A50BF05004A105B /* TemplateLoaderTests.swift in Sources */,
|
27CE0AE01A50BF05004A105B /* TemplateLoaderTests.swift in Sources */,
|
||||||
7725B3D919F94A61002CF74B /* ParserTests.swift in Sources */,
|
7725B3D919F94A61002CF74B /* ParserTests.swift in Sources */,
|
||||||
77EB082719F96E9C001870F1 /* TemplateTests.swift in Sources */,
|
77EB082719F96E9C001870F1 /* TemplateTests.swift in Sources */,
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ public class Context : Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func push() {
|
public func push() {
|
||||||
push(Dictionary<String, String>())
|
push(Dictionary<String, AnyObject>())
|
||||||
}
|
}
|
||||||
|
|
||||||
public func push(dictionary:Dictionary<String, String>) {
|
public func push(dictionary:Dictionary<String, AnyObject>) {
|
||||||
dictionaries.append(dictionary)
|
dictionaries.append(dictionary)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ public class TokenParser {
|
|||||||
registerTag("ifnot", parser: IfNode.parse_ifnot)
|
registerTag("ifnot", parser: IfNode.parse_ifnot)
|
||||||
registerTag("now", parser: NowNode.parse)
|
registerTag("now", parser: NowNode.parse)
|
||||||
registerTag("include", parser: IncludeNode.parse)
|
registerTag("include", parser: IncludeNode.parse)
|
||||||
|
registerTag("extends", parser: ExtendsNode.parse)
|
||||||
|
registerTag("block", parser: BlockNode.parse)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers a new template tag
|
/// Registers a new template tag
|
||||||
|
|||||||
124
Stencil/TemplateLoader/Inheritence.swift
Normal file
124
Stencil/TemplateLoader/Inheritence.swift
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
51
StencilTests/TemplateLoader/InheritenceTests.swift
Normal file
51
StencilTests/TemplateLoader/InheritenceTests.swift
Normal file
@@ -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")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
2
StencilTests/base.html
Normal file
2
StencilTests/base.html
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
{% block header %}Header{% endblock %}
|
||||||
|
{% block body %}Body{% endblock %}
|
||||||
2
StencilTests/child.html
Normal file
2
StencilTests/child.html
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block body %}Child{% endblock %}
|
||||||
Reference in New Issue
Block a user