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 %}
|
{% 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
|
#### Building custom tags
|
||||||
|
|
||||||
You can build a custom template tag. There are a couple of APIs to allow
|
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 */; };
|
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 */; };
|
||||||
|
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 */; };
|
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 */; };
|
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 */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
27E2138C1A4CD5F50073E063 /* UniversalFramework_Test.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = UniversalFramework_Test.xcconfig; sourceTree = "<group>"; };
|
||||||
@@ -128,6 +132,22 @@
|
|||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
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 */ = {
|
27E213891A4CD5F50073E063 /* Configurations */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -171,6 +191,7 @@
|
|||||||
27CE0ADD1A50BEC3004A105B /* TemplateLoader.swift */,
|
27CE0ADD1A50BEC3004A105B /* TemplateLoader.swift */,
|
||||||
7725B3CE19F94214002CF74B /* Tokenizer.swift */,
|
7725B3CE19F94214002CF74B /* Tokenizer.swift */,
|
||||||
7725B3CC19F92B61002CF74B /* Variable.swift */,
|
7725B3CC19F92B61002CF74B /* Variable.swift */,
|
||||||
|
27CE0AFF1A50CBBF004A105B /* TemplateLoader */,
|
||||||
77FAAE5519F91E480029DC5E /* Supporting Files */,
|
77FAAE5519F91E480029DC5E /* Supporting Files */,
|
||||||
);
|
);
|
||||||
path = Stencil;
|
path = Stencil;
|
||||||
@@ -195,6 +216,7 @@
|
|||||||
77EB082819FA85F2001870F1 /* LexerTests.swift */,
|
77EB082819FA85F2001870F1 /* LexerTests.swift */,
|
||||||
77EB082619F96E9C001870F1 /* TemplateTests.swift */,
|
77EB082619F96E9C001870F1 /* TemplateTests.swift */,
|
||||||
27CE0ADF1A50BF05004A105B /* TemplateLoaderTests.swift */,
|
27CE0ADF1A50BF05004A105B /* TemplateLoaderTests.swift */,
|
||||||
|
27CE0B021A50CBEA004A105B /* TemplateLoader */,
|
||||||
27CE0AF91A50C963004A105B /* test.html */,
|
27CE0AF91A50C963004A105B /* test.html */,
|
||||||
77FAAE6219F91E480029DC5E /* Supporting Files */,
|
77FAAE6219F91E480029DC5E /* Supporting Files */,
|
||||||
);
|
);
|
||||||
@@ -345,6 +367,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
27CE0B011A50CBD1004A105B /* Include.swift in Sources */,
|
||||||
77FAAE6F19F920750029DC5E /* Context.swift in Sources */,
|
77FAAE6F19F920750029DC5E /* Context.swift in Sources */,
|
||||||
77EB082B19FA8600001870F1 /* Lexer.swift in Sources */,
|
77EB082B19FA8600001870F1 /* Lexer.swift in Sources */,
|
||||||
7725B3CF19F94214002CF74B /* Tokenizer.swift in Sources */,
|
7725B3CF19F94214002CF74B /* Tokenizer.swift in Sources */,
|
||||||
@@ -367,6 +390,7 @@
|
|||||||
7725B3D919F94A61002CF74B /* ParserTests.swift in Sources */,
|
7725B3D919F94A61002CF74B /* ParserTests.swift in Sources */,
|
||||||
77EB082719F96E9C001870F1 /* TemplateTests.swift in Sources */,
|
77EB082719F96E9C001870F1 /* TemplateTests.swift in Sources */,
|
||||||
7725B3CB19F92B4F002CF74B /* VariableTests.swift in Sources */,
|
7725B3CB19F92B4F002CF74B /* VariableTests.swift in Sources */,
|
||||||
|
27CE0B041A50CBEA004A105B /* IncludeTests.swift in Sources */,
|
||||||
77EB082919FA85F2001870F1 /* LexerTests.swift in Sources */,
|
77EB082919FA85F2001870F1 /* LexerTests.swift in Sources */,
|
||||||
77FAAE7119F9208C0029DC5E /* ContextTests.swift in Sources */,
|
77FAAE7119F9208C0029DC5E /* ContextTests.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ public class TokenParser {
|
|||||||
registerTag("if", IfNode.parse)
|
registerTag("if", IfNode.parse)
|
||||||
registerTag("ifnot", IfNode.parse_ifnot)
|
registerTag("ifnot", IfNode.parse_ifnot)
|
||||||
registerTag("now", NowNode.parse)
|
registerTag("now", NowNode.parse)
|
||||||
|
registerTag("include", IncludeNode.parse)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers a new template tag
|
/// Registers a new template tag
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import PathKit
|
|||||||
|
|
||||||
// A class for loading a template from disk
|
// A class for loading a template from disk
|
||||||
public class TemplateLoader {
|
public class TemplateLoader {
|
||||||
let paths:[Path]
|
public let paths:[Path]
|
||||||
|
|
||||||
public init(paths:[Path]) {
|
public init(paths:[Path]) {
|
||||||
self.paths = paths
|
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