Add an include tag
This commit is contained in:
16
README.md
16
README.md
@@ -128,6 +128,22 @@ A for loop allows you to iterate over an array found by variable lookup.
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
#### include
|
||||
|
||||
You can include another template using the `include` tag.
|
||||
|
||||
```html+django
|
||||
{% include "comment.html" %}
|
||||
```
|
||||
|
||||
The `include` tag requires a TemplateLoader to be found inside your context with the paths, or bundles used to lookup the template.
|
||||
|
||||
```swift
|
||||
let context = Context(dictionary: [
|
||||
"loader": TemplateLoader(bundle:[NSBundle.mainBundle()])
|
||||
])
|
||||
```
|
||||
|
||||
#### Building custom tags
|
||||
|
||||
You can build a custom template tag. There are a couple of APIs to allow
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
27CE0ADE1A50BEC3004A105B /* TemplateLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27CE0ADD1A50BEC3004A105B /* TemplateLoader.swift */; };
|
||||
27CE0AE01A50BF05004A105B /* TemplateLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27CE0ADF1A50BF05004A105B /* TemplateLoaderTests.swift */; };
|
||||
27CE0AFA1A50C963004A105B /* test.html in Resources */ = {isa = PBXBuildFile; fileRef = 27CE0AF91A50C963004A105B /* test.html */; };
|
||||
27CE0B011A50CBD1004A105B /* Include.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27CE0B001A50CBD1004A105B /* Include.swift */; };
|
||||
27CE0B041A50CBEA004A105B /* IncludeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27CE0B031A50CBEA004A105B /* IncludeTests.swift */; };
|
||||
27E2138D1A4CD5F50073E063 /* UniversalFramework_Base.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 27E2138A1A4CD5F50073E063 /* UniversalFramework_Base.xcconfig */; };
|
||||
27E2138E1A4CD5F50073E063 /* UniversalFramework_Framework.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 27E2138B1A4CD5F50073E063 /* UniversalFramework_Framework.xcconfig */; };
|
||||
27E2138F1A4CD5F50073E063 /* UniversalFramework_Test.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 27E2138C1A4CD5F50073E063 /* UniversalFramework_Test.xcconfig */; };
|
||||
@@ -75,6 +77,8 @@
|
||||
27CE0ADF1A50BF05004A105B /* TemplateLoaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemplateLoaderTests.swift; sourceTree = "<group>"; };
|
||||
27CE0AE11A50BFAF004A105B /* PathKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PathKit.xcodeproj; path = PathKit/PathKit.xcodeproj; sourceTree = "<group>"; };
|
||||
27CE0AF91A50C963004A105B /* test.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = test.html; sourceTree = "<group>"; };
|
||||
27CE0B001A50CBD1004A105B /* Include.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Include.swift; sourceTree = "<group>"; };
|
||||
27CE0B031A50CBEA004A105B /* IncludeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IncludeTests.swift; sourceTree = "<group>"; };
|
||||
27E2138A1A4CD5F50073E063 /* UniversalFramework_Base.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = UniversalFramework_Base.xcconfig; sourceTree = "<group>"; };
|
||||
27E2138B1A4CD5F50073E063 /* UniversalFramework_Framework.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = UniversalFramework_Framework.xcconfig; sourceTree = "<group>"; };
|
||||
27E2138C1A4CD5F50073E063 /* UniversalFramework_Test.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = UniversalFramework_Test.xcconfig; sourceTree = "<group>"; };
|
||||
@@ -128,6 +132,22 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
27CE0AFF1A50CBBF004A105B /* TemplateLoader */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
27CE0B001A50CBD1004A105B /* Include.swift */,
|
||||
);
|
||||
path = TemplateLoader;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
27CE0B021A50CBEA004A105B /* TemplateLoader */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
27CE0B031A50CBEA004A105B /* IncludeTests.swift */,
|
||||
);
|
||||
path = TemplateLoader;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
27E213891A4CD5F50073E063 /* Configurations */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -171,6 +191,7 @@
|
||||
27CE0ADD1A50BEC3004A105B /* TemplateLoader.swift */,
|
||||
7725B3CE19F94214002CF74B /* Tokenizer.swift */,
|
||||
7725B3CC19F92B61002CF74B /* Variable.swift */,
|
||||
27CE0AFF1A50CBBF004A105B /* TemplateLoader */,
|
||||
77FAAE5519F91E480029DC5E /* Supporting Files */,
|
||||
);
|
||||
path = Stencil;
|
||||
@@ -195,6 +216,7 @@
|
||||
77EB082819FA85F2001870F1 /* LexerTests.swift */,
|
||||
77EB082619F96E9C001870F1 /* TemplateTests.swift */,
|
||||
27CE0ADF1A50BF05004A105B /* TemplateLoaderTests.swift */,
|
||||
27CE0B021A50CBEA004A105B /* TemplateLoader */,
|
||||
27CE0AF91A50C963004A105B /* test.html */,
|
||||
77FAAE6219F91E480029DC5E /* Supporting Files */,
|
||||
);
|
||||
@@ -345,6 +367,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
27CE0B011A50CBD1004A105B /* Include.swift in Sources */,
|
||||
77FAAE6F19F920750029DC5E /* Context.swift in Sources */,
|
||||
77EB082B19FA8600001870F1 /* Lexer.swift in Sources */,
|
||||
7725B3CF19F94214002CF74B /* Tokenizer.swift in Sources */,
|
||||
@@ -367,6 +390,7 @@
|
||||
7725B3D919F94A61002CF74B /* ParserTests.swift in Sources */,
|
||||
77EB082719F96E9C001870F1 /* TemplateTests.swift in Sources */,
|
||||
7725B3CB19F92B4F002CF74B /* VariableTests.swift in Sources */,
|
||||
27CE0B041A50CBEA004A105B /* IncludeTests.swift in Sources */,
|
||||
77EB082919FA85F2001870F1 /* LexerTests.swift in Sources */,
|
||||
77FAAE7119F9208C0029DC5E /* ContextTests.swift in Sources */,
|
||||
);
|
||||
|
||||
@@ -36,6 +36,7 @@ public class TokenParser {
|
||||
registerTag("if", IfNode.parse)
|
||||
registerTag("ifnot", IfNode.parse_ifnot)
|
||||
registerTag("now", NowNode.parse)
|
||||
registerTag("include", IncludeNode.parse)
|
||||
}
|
||||
|
||||
/// Registers a new template tag
|
||||
|
||||
@@ -11,7 +11,7 @@ import PathKit
|
||||
|
||||
// A class for loading a template from disk
|
||||
public class TemplateLoader {
|
||||
let paths:[Path]
|
||||
public let paths:[Path]
|
||||
|
||||
public init(paths:[Path]) {
|
||||
self.paths = paths
|
||||
|
||||
44
Stencil/TemplateLoader/Include.swift
Normal file
44
Stencil/TemplateLoader/Include.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
import Foundation
|
||||
import PathKit
|
||||
|
||||
extension String : Error {
|
||||
public var description:String {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
public class IncludeNode : Node {
|
||||
public let templateName:String
|
||||
|
||||
public 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 included"))
|
||||
}
|
||||
|
||||
return .Success(node:IncludeNode(templateName: bits[1]))
|
||||
}
|
||||
|
||||
public init(templateName:String) {
|
||||
self.templateName = templateName
|
||||
}
|
||||
|
||||
public func render(context: Context) -> Result {
|
||||
if let loader = context["loader"] as? TemplateLoader {
|
||||
if let template = loader.loadTemplate(templateName) {
|
||||
return template.render(context)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
75
StencilTests/TemplateLoader/IncludeTests.swift
Normal file
75
StencilTests/TemplateLoader/IncludeTests.swift
Normal file
@@ -0,0 +1,75 @@
|
||||
import Foundation
|
||||
import XCTest
|
||||
import Stencil
|
||||
import PathKit
|
||||
|
||||
class IncludeTests: NodeTests {
|
||||
|
||||
var loader:TemplateLoader!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
let path = (Path(__FILE__) + Path("../..")).absolute()
|
||||
loader = TemplateLoader(paths: [path])
|
||||
}
|
||||
|
||||
// MARK: Parsing
|
||||
|
||||
func testParseMissingTemplate() {
|
||||
let tokens = [ Token.Block(value: "include") ]
|
||||
let parser = TokenParser(tokens: tokens)
|
||||
|
||||
assertFailure(parser.parse(), "include: Tag takes one argument, the template file to be included")
|
||||
}
|
||||
|
||||
func testParse() {
|
||||
let tokens = [ Token.Block(value: "include \"test.html\"") ]
|
||||
let parser = TokenParser(tokens: tokens)
|
||||
|
||||
assertSuccess(parser.parse()) { nodes in
|
||||
let node = nodes.first! as IncludeNode
|
||||
XCTAssertEqual(nodes.count, 1)
|
||||
XCTAssertEqual(node.templateName, "test.html")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Render
|
||||
|
||||
func testRenderWithoutLoader() {
|
||||
let node = IncludeNode(templateName: "test.html")
|
||||
let result = node.render(Context())
|
||||
|
||||
switch result {
|
||||
case .Success(let string):
|
||||
XCTAssert(false, "Unexpected error")
|
||||
case .Error(let error):
|
||||
XCTAssertEqual("\(error)", "Template loader not in context")
|
||||
}
|
||||
}
|
||||
|
||||
func testRenderWithoutTemplateNamed() {
|
||||
let node = IncludeNode(templateName: "unknown.html")
|
||||
let result = node.render(Context(dictionary:["loader":loader]))
|
||||
|
||||
switch result {
|
||||
case .Success(let string):
|
||||
XCTAssert(false, "Unexpected error")
|
||||
case .Error(let error):
|
||||
XCTAssertTrue("\(error)".hasPrefix("Template 'unknown.html' not found"))
|
||||
}
|
||||
}
|
||||
|
||||
func testRender() {
|
||||
let node = IncludeNode(templateName: "test.html")
|
||||
let result = node.render(Context(dictionary:["loader":loader, "target": "World"]))
|
||||
|
||||
switch result {
|
||||
case .Success(let string):
|
||||
XCTAssertEqual(string, "Hello World!")
|
||||
case .Error(let error):
|
||||
XCTAssert(false, "Unexpected error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
Hello {{ target }}.
|
||||
Hello {{ target }}!
|
||||
Reference in New Issue
Block a user