refactor: Introducing Environments
This commit is contained in:
28
CHANGELOG.md
28
CHANGELOG.md
@@ -4,11 +4,39 @@
|
||||
|
||||
### Breaking
|
||||
|
||||
- It is no longer possible to create `Context` objects. Instead, you can pass a
|
||||
dictionary directly to a `Template`s `render` method.
|
||||
|
||||
```diff
|
||||
- try template.render(Context(dictionary: ["name": "Kyle"]))
|
||||
+ try template.render(["name": "Kyle"])
|
||||
```
|
||||
|
||||
- Template loader are no longer passed into a `Context`, instead you will need
|
||||
to pass the `Loader` to an `Environment` and create a template from the
|
||||
`Environment`.
|
||||
|
||||
```diff
|
||||
let loader = FileSystemLoader(paths: ["templates/"])
|
||||
|
||||
- let template = loader.loadTemplate(name: "index.html")
|
||||
- try template.render(Context(dictionary: ["loader": loader]))
|
||||
|
||||
+ let environment = Environment(loader: loader)
|
||||
+ try environment.renderTemplate(name: "index.html")
|
||||
```
|
||||
|
||||
- `Loader`s will now throw a `TemplateDoesNotExist` error when a template
|
||||
is not found.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- `Environment` is a new way to load templates. You can configure an
|
||||
environment with custom template filters, tags and loaders and then create a
|
||||
template from an environment.
|
||||
|
||||
Environment also provides a convenience method to render a template directly.
|
||||
|
||||
- `FileSystemLoader` will now ensure that template paths are within the base
|
||||
path. Any template names that try to escape the base path will raise a
|
||||
`SuspiciousFileOperation` error.
|
||||
|
||||
17
README.md
17
README.md
@@ -19,25 +19,24 @@ There are {{ articles.count }} articles.
|
||||
```
|
||||
|
||||
```swift
|
||||
import Stencil
|
||||
|
||||
struct Article {
|
||||
let title: String
|
||||
let author: String
|
||||
}
|
||||
|
||||
let context = Context(dictionary: [
|
||||
let context = [
|
||||
"articles": [
|
||||
Article(title: "Migrating from OCUnit to XCTest", author: "Kyle Fuller"),
|
||||
Article(title: "Memory Management with ARC", author: "Kyle Fuller"),
|
||||
]
|
||||
])
|
||||
]
|
||||
|
||||
do {
|
||||
let template = try Template(named: "template.html")
|
||||
let rendered = try template.render(context)
|
||||
print(rendered)
|
||||
} catch {
|
||||
print("Failed to render template \(error)")
|
||||
}
|
||||
let environment = Environment(loader: FileSystemLoader(paths: ["templates/"])
|
||||
let rendered = try environment.renderTemplate(name: context)
|
||||
|
||||
print(rendered)
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
/// A container for template variables.
|
||||
public class Context {
|
||||
var dictionaries: [[String: Any?]]
|
||||
|
||||
public let environment: Environment
|
||||
let namespace: Namespace
|
||||
|
||||
/// Initialise a Context with an optional dictionary and optional namespace
|
||||
public init(dictionary: [String: Any]? = nil, namespace: Namespace = Namespace()) {
|
||||
init(dictionary: [String: Any]? = nil, namespace: Namespace? = nil, environment: Environment? = nil) {
|
||||
if let dictionary = dictionary {
|
||||
dictionaries = [dictionary]
|
||||
} else {
|
||||
dictionaries = []
|
||||
}
|
||||
|
||||
self.namespace = namespace
|
||||
self.namespace = namespace ?? environment?.namespace ?? Namespace()
|
||||
self.environment = environment ?? Environment()
|
||||
}
|
||||
|
||||
public subscript(key: String) -> Any? {
|
||||
|
||||
36
Sources/Environment.swift
Normal file
36
Sources/Environment.swift
Normal file
@@ -0,0 +1,36 @@
|
||||
public struct Environment {
|
||||
var namespace: Namespace
|
||||
|
||||
public var loader: Loader?
|
||||
|
||||
public init(loader: Loader? = nil, namespace: Namespace? = nil) {
|
||||
self.loader = loader
|
||||
self.namespace = namespace ?? Namespace()
|
||||
}
|
||||
|
||||
public func loadTemplate(name: String) throws -> Template {
|
||||
if let loader = loader {
|
||||
return try loader.loadTemplate(name: name, environment: self)
|
||||
} else {
|
||||
throw TemplateDoesNotExist(templateNames: [name], loader: nil)
|
||||
}
|
||||
}
|
||||
|
||||
public func loadTemplate(names: [String]) throws -> Template {
|
||||
if let loader = loader {
|
||||
return try loader.loadTemplate(names: names, environment: self)
|
||||
} else {
|
||||
throw TemplateDoesNotExist(templateNames: names, loader: nil)
|
||||
}
|
||||
}
|
||||
|
||||
public func renderTemplate(name: String, context: [String: Any]? = nil) throws -> String {
|
||||
let template = try loadTemplate(name: name)
|
||||
return try template.render(context)
|
||||
}
|
||||
|
||||
public func renderTemplate(string: String, context: [String: Any]? = nil) throws -> String {
|
||||
let template = Template(templateString: string, environment: self)
|
||||
return try template.render(context)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,19 @@
|
||||
public class TemplateDoesNotExist: Error, CustomStringConvertible {
|
||||
let templateNames: [String]
|
||||
let loader: Loader
|
||||
let loader: Loader?
|
||||
|
||||
public init(templateNames: [String], loader: Loader) {
|
||||
public init(templateNames: [String], loader: Loader? = nil) {
|
||||
self.templateNames = templateNames
|
||||
self.loader = loader
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
let templates = templateNames.joined(separator: ", ")
|
||||
return "Template named `\(templates)` does not exist in loader \(loader)"
|
||||
|
||||
if let loader = loader {
|
||||
return "Template named `\(templates)` does not exist in loader \(loader)"
|
||||
}
|
||||
|
||||
return "Template named `\(templates)` does not exist. No loaders found"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,16 +19,15 @@ class IncludeNode : NodeType {
|
||||
}
|
||||
|
||||
func render(_ context: Context) throws -> String {
|
||||
guard let loader = context["loader"] as? Loader else {
|
||||
throw TemplateSyntaxError("Template loader not in context")
|
||||
}
|
||||
|
||||
guard let templateName = try self.templateName.resolve(context) as? String else {
|
||||
throw TemplateSyntaxError("'\(self.templateName)' could not be resolved as a string")
|
||||
}
|
||||
|
||||
let template = try loader.loadTemplate(name: templateName)
|
||||
return try template.render(context)
|
||||
let template = try context.environment.loadTemplate(name: templateName)
|
||||
|
||||
return try context.push {
|
||||
return try template.render(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,15 +59,11 @@ class ExtendsNode : NodeType {
|
||||
}
|
||||
|
||||
func render(_ context: Context) throws -> String {
|
||||
guard let loader = context["loader"] as? Loader else {
|
||||
throw TemplateSyntaxError("Template loader not in context")
|
||||
}
|
||||
|
||||
guard let templateName = try self.templateName.resolve(context) as? String else {
|
||||
throw TemplateSyntaxError("'\(self.templateName)' could not be resolved as a string")
|
||||
}
|
||||
|
||||
let template = try loader.loadTemplate(name: templateName)
|
||||
let template = try context.environment.loadTemplate(name: templateName)
|
||||
|
||||
let blockContext: BlockContext
|
||||
if let context = context[BlockContext.contextKey] as? BlockContext {
|
||||
|
||||
@@ -3,16 +3,16 @@ import PathKit
|
||||
|
||||
|
||||
public protocol Loader {
|
||||
func loadTemplate(name: String) throws -> Template
|
||||
func loadTemplate(names: [String]) throws -> Template
|
||||
func loadTemplate(name: String, environment: Environment) throws -> Template
|
||||
func loadTemplate(names: [String], environment: Environment) throws -> Template
|
||||
}
|
||||
|
||||
|
||||
extension Loader {
|
||||
func loadTemplate(names: [String]) throws -> Template {
|
||||
public func loadTemplate(names: [String], environment: Environment) throws -> Template {
|
||||
for name in names {
|
||||
do {
|
||||
return try loadTemplate(name: name)
|
||||
return try loadTemplate(name: name, environment: environment)
|
||||
} catch is TemplateDoesNotExist {
|
||||
continue
|
||||
} catch {
|
||||
@@ -43,7 +43,7 @@ public class FileSystemLoader: Loader, CustomStringConvertible {
|
||||
return "FileSystemLoader(\(paths))"
|
||||
}
|
||||
|
||||
public func loadTemplate(name: String) throws -> Template {
|
||||
public func loadTemplate(name: String, environment: Environment) throws -> Template {
|
||||
for path in paths {
|
||||
let templatePath = try path.safeJoin(path: Path(name))
|
||||
|
||||
@@ -51,19 +51,19 @@ public class FileSystemLoader: Loader, CustomStringConvertible {
|
||||
continue
|
||||
}
|
||||
|
||||
return try Template(path: templatePath)
|
||||
return try Template(path: templatePath, environment: environment, name: name)
|
||||
}
|
||||
|
||||
throw TemplateDoesNotExist(templateNames: [name], loader: self)
|
||||
}
|
||||
|
||||
public func loadTemplate(names: [String]) throws -> Template {
|
||||
public func loadTemplate(names: [String], environment: Environment) throws -> Template {
|
||||
for path in paths {
|
||||
for templateName in names {
|
||||
let templatePath = try path.safeJoin(path: Path(templateName))
|
||||
|
||||
if templatePath.exists {
|
||||
return try Template(path: templatePath)
|
||||
return try Template(path: templatePath, environment: environment, name: templateName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,17 @@ let NSFileNoSuchFileError = 4
|
||||
|
||||
/// A class representing a template
|
||||
public class Template: ExpressibleByStringLiteral {
|
||||
let environment: Environment
|
||||
let tokens: [Token]
|
||||
|
||||
/// The name of the loaded Template if the Template was loaded from a Loader
|
||||
public let name: String?
|
||||
|
||||
/// Create a template with a template string
|
||||
public init(templateString: String) {
|
||||
public init(templateString: String, environment: Environment? = nil, name: String? = nil) {
|
||||
self.environment = environment ?? Environment()
|
||||
self.name = name
|
||||
|
||||
let lexer = Lexer(templateString: templateString)
|
||||
tokens = lexer.tokenize()
|
||||
}
|
||||
@@ -31,11 +38,13 @@ public class Template: ExpressibleByStringLiteral {
|
||||
}
|
||||
|
||||
/// Create a template with a file found at the given path
|
||||
public convenience init(path: Path) throws {
|
||||
self.init(templateString: try path.read())
|
||||
public convenience init(path: Path, environment: Environment? = nil, name: String? = nil) throws {
|
||||
self.init(templateString: try path.read(), environment: environment, name: name)
|
||||
}
|
||||
|
||||
// Create a template with a template string literal
|
||||
// MARK: ExpressibleByStringLiteral
|
||||
|
||||
// Create a templaVte with a template string literal
|
||||
public convenience required init(stringLiteral value: String) {
|
||||
self.init(templateString: value)
|
||||
}
|
||||
@@ -50,11 +59,16 @@ public class Template: ExpressibleByStringLiteral {
|
||||
self.init(stringLiteral: value)
|
||||
}
|
||||
|
||||
/// Render the given template
|
||||
public func render(_ context: Context? = nil) throws -> String {
|
||||
let context = context ?? Context()
|
||||
/// Render the given template with a context
|
||||
func render(_ context: Context) throws -> String {
|
||||
let context = context ?? Context(environment: environment)
|
||||
let parser = TokenParser(tokens: tokens, namespace: context.namespace)
|
||||
let nodes = try parser.parse()
|
||||
return try renderNodes(nodes, context)
|
||||
}
|
||||
|
||||
/// Render the given template
|
||||
public func render(_ dictionary: [String: Any]? = nil) throws -> String {
|
||||
return try render(Context(dictionary: dictionary, environment: environment))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Spectre
|
||||
import Stencil
|
||||
@testable import Stencil
|
||||
|
||||
|
||||
func testContext() {
|
||||
|
||||
41
Tests/StencilTests/EnvironmentSpec.swift
Normal file
41
Tests/StencilTests/EnvironmentSpec.swift
Normal file
@@ -0,0 +1,41 @@
|
||||
import Spectre
|
||||
import Stencil
|
||||
|
||||
|
||||
func testEnvironment() {
|
||||
describe("Environment") {
|
||||
let environment = Environment(loader: ExampleLoader())
|
||||
|
||||
$0.it("can load a template from a name") {
|
||||
let template = try environment.loadTemplate(name: "example.html")
|
||||
try expect(template.name) == "example.html"
|
||||
}
|
||||
|
||||
$0.it("can load a template from a names") {
|
||||
let template = try environment.loadTemplate(names: ["first.html", "example.html"])
|
||||
try expect(template.name) == "example.html"
|
||||
}
|
||||
|
||||
$0.it("can render a template from a string") {
|
||||
let result = try environment.renderTemplate(string: "Hello World")
|
||||
try expect(result) == "Hello World"
|
||||
}
|
||||
|
||||
$0.it("can render a template from a file") {
|
||||
let result = try environment.renderTemplate(name: "example.html")
|
||||
try expect(result) == "Hello World!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fileprivate class ExampleLoader: Loader {
|
||||
func loadTemplate(name: String, environment: Environment) throws -> Template {
|
||||
if name == "example.html" {
|
||||
return Template(templateString: "Hello World!", environment: environment, name: name)
|
||||
}
|
||||
|
||||
throw TemplateDoesNotExist(templateNames: [name], loader: self)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import Spectre
|
||||
import Stencil
|
||||
@testable import Stencil
|
||||
|
||||
|
||||
func testFilter() {
|
||||
|
||||
@@ -7,6 +7,7 @@ func testInclude() {
|
||||
describe("Include") {
|
||||
let path = Path(#file) + ".." + "fixtures"
|
||||
let loader = FileSystemLoader(paths: [path])
|
||||
let environment = Environment(loader: loader)
|
||||
|
||||
$0.describe("parsing") {
|
||||
$0.it("throws an error when no template is given") {
|
||||
@@ -35,7 +36,7 @@ func testInclude() {
|
||||
do {
|
||||
_ = try node.render(Context())
|
||||
} catch {
|
||||
try expect("\(error)") == "Template loader not in context"
|
||||
try expect("\(error)") == "Template named `test.html` does not exist. No loaders found"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +44,7 @@ func testInclude() {
|
||||
let node = IncludeNode(templateName: Variable("\"unknown.html\""))
|
||||
|
||||
do {
|
||||
_ = try node.render(Context(dictionary: ["loader": loader]))
|
||||
_ = try node.render(Context(environment: environment))
|
||||
} catch {
|
||||
try expect("\(error)".hasPrefix("Template named `unknown.html` does not exist in loader")).to.beTrue()
|
||||
}
|
||||
@@ -51,7 +52,7 @@ func testInclude() {
|
||||
|
||||
$0.it("successfully renders a found included template") {
|
||||
let node = IncludeNode(templateName: Variable("\"test.html\""))
|
||||
let context = Context(dictionary: ["loader":loader, "target": "World"])
|
||||
let context = Context(dictionary: ["target": "World"], environment: environment)
|
||||
let value = try node.render(context)
|
||||
try expect(value) == "Hello World!"
|
||||
}
|
||||
|
||||
@@ -7,17 +7,16 @@ func testInheritence() {
|
||||
describe("Inheritence") {
|
||||
let path = Path(#file) + ".." + "fixtures"
|
||||
let loader = FileSystemLoader(paths: [path])
|
||||
let environment = Environment(loader: loader)
|
||||
|
||||
$0.it("can inherit from another template") {
|
||||
let context = Context(dictionary: ["loader": loader])
|
||||
let template = try loader.loadTemplate(name: "child.html")
|
||||
try expect(try template.render(context)) == "Header\nChild"
|
||||
let template = try environment.loadTemplate(name: "child.html")
|
||||
try expect(try template.render()) == "Header\nChild"
|
||||
}
|
||||
|
||||
$0.it("can inherit from another template inheriting from another template") {
|
||||
let context = Context(dictionary: ["loader": loader])
|
||||
let template = try loader.loadTemplate(name: "child-child.html")
|
||||
try expect(try template.render(context)) == "Child Child Header\nChild"
|
||||
let template = try environment.loadTemplate(name: "child-child.html")
|
||||
try expect(try template.render()) == "Child Child Header\nChild"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,25 +7,26 @@ func testTemplateLoader() {
|
||||
describe("FileSystemLoader") {
|
||||
let path = Path(#file) + ".." + "fixtures"
|
||||
let loader = FileSystemLoader(paths: [path])
|
||||
let environment = Environment(loader: loader)
|
||||
|
||||
$0.it("errors when a template cannot be found") {
|
||||
try expect(try loader.loadTemplate(name: "unknown.html")).toThrow()
|
||||
try expect(try environment.loadTemplate(name: "unknown.html")).toThrow()
|
||||
}
|
||||
|
||||
$0.it("errors when an array of templates cannot be found") {
|
||||
try expect(try loader.loadTemplate(names: ["unknown.html", "unknown2.html"])).toThrow()
|
||||
try expect(try environment.loadTemplate(names: ["unknown.html", "unknown2.html"])).toThrow()
|
||||
}
|
||||
|
||||
$0.it("can load a template from a file") {
|
||||
_ = try loader.loadTemplate(name: "test.html")
|
||||
_ = try environment.loadTemplate(name: "test.html")
|
||||
}
|
||||
|
||||
$0.it("errors when loading absolute file outside of the selected path") {
|
||||
try expect(try loader.loadTemplate(name: "/etc/hosts")).toThrow()
|
||||
try expect(try environment.loadTemplate(name: "/etc/hosts")).toThrow()
|
||||
}
|
||||
|
||||
$0.it("errors when loading relative file outside of the selected path") {
|
||||
try expect(try loader.loadTemplate(name: "../LoaderSpec.swift")).toThrow()
|
||||
try expect(try environment.loadTemplate(name: "../LoaderSpec.swift")).toThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Spectre
|
||||
import Stencil
|
||||
@testable import Stencil
|
||||
|
||||
|
||||
class ErrorNode : NodeType {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Spectre
|
||||
import Stencil
|
||||
@testable import Stencil
|
||||
|
||||
|
||||
func testTokenParser() {
|
||||
|
||||
@@ -25,12 +25,12 @@ func testStencil() {
|
||||
" - {{ article.title }} by {{ article.author }}.\n" +
|
||||
"{% endfor %}\n"
|
||||
|
||||
let context = Context(dictionary: [
|
||||
let context = [
|
||||
"articles": [
|
||||
Article(title: "Migrating from OCUnit to XCTest", author: "Kyle Fuller"),
|
||||
Article(title: "Memory Management with ARC", author: "Kyle Fuller"),
|
||||
]
|
||||
])
|
||||
]
|
||||
|
||||
let template = Template(templateString: templateString)
|
||||
let result = try template.render(context)
|
||||
@@ -45,28 +45,27 @@ func testStencil() {
|
||||
}
|
||||
|
||||
$0.it("can render a custom template tag") {
|
||||
let templateString = "{% custom %}"
|
||||
let template = Template(templateString: templateString)
|
||||
|
||||
let namespace = Namespace()
|
||||
namespace.registerTag("custom") { parser, token in
|
||||
return CustomNode()
|
||||
}
|
||||
|
||||
let result = try template.render(Context(namespace: namespace))
|
||||
let environment = Environment(namespace: namespace)
|
||||
let result = try environment.renderTemplate(string: "{% custom %}")
|
||||
|
||||
try expect(result) == "Hello World"
|
||||
}
|
||||
|
||||
$0.it("can render a simple custom tag") {
|
||||
let templateString = "{% custom %}"
|
||||
let template = Template(templateString: templateString)
|
||||
|
||||
let namespace = Namespace()
|
||||
namespace.registerSimpleTag("custom") { context in
|
||||
return "Hello World"
|
||||
}
|
||||
|
||||
try expect(try template.render(Context(namespace: namespace))) == "Hello World"
|
||||
let environment = Environment(namespace: namespace)
|
||||
let result = try environment.renderTemplate(string: "{% custom %}")
|
||||
|
||||
try expect(result) == "Hello World"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,16 +5,14 @@ import Stencil
|
||||
func testTemplate() {
|
||||
describe("Template") {
|
||||
$0.it("can render a template from a string") {
|
||||
let context = Context(dictionary: [ "name": "Kyle" ])
|
||||
let template = Template(templateString: "Hello World")
|
||||
let result = try template.render(context)
|
||||
let result = try template.render([ "name": "Kyle" ])
|
||||
try expect(result) == "Hello World"
|
||||
}
|
||||
|
||||
$0.it("can render a template from a string literal") {
|
||||
let context = Context(dictionary: [ "name": "Kyle" ])
|
||||
let template: Template = "Hello World"
|
||||
let result = try template.render(context)
|
||||
let result = try template.render([ "name": "Kyle" ])
|
||||
try expect(result) == "Hello World"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
import Spectre
|
||||
import Stencil
|
||||
@testable import Stencil
|
||||
|
||||
|
||||
#if os(OSX)
|
||||
|
||||
@@ -18,6 +18,7 @@ public func stencilTests() {
|
||||
testInclude()
|
||||
testInheritence()
|
||||
testFilterTag()
|
||||
testEnvironment()
|
||||
testStencil()
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,6 @@ template. It’s somewhat like a dictionary, however you can push and pop to
|
||||
scope variables. So that means that when iterating over a for loop, you can
|
||||
push a new scope into the context to store any variables local to the scope.
|
||||
|
||||
You can initialise a ``Context`` with a ``Dictionary``.
|
||||
|
||||
.. code-block:: swift
|
||||
|
||||
Context(dictionary: [String: Any]? = nil)
|
||||
|
||||
API
|
||||
----
|
||||
|
||||
|
||||
43
docs/api/environment.rst
Normal file
43
docs/api/environment.rst
Normal file
@@ -0,0 +1,43 @@
|
||||
Environment
|
||||
===========
|
||||
|
||||
An environment contains shared configuration such as custom filters and tags
|
||||
along with template loaders.
|
||||
|
||||
.. code-block:: swift
|
||||
|
||||
let environment = Environment()
|
||||
|
||||
You can optionally provide a loader or namespace when creating an environment:
|
||||
|
||||
.. code-block:: swift
|
||||
|
||||
let environment = Environment(loader: ..., namespace: ...)
|
||||
|
||||
Rendering a Template
|
||||
--------------------
|
||||
|
||||
Environment providences coninience methods to render a template either from a
|
||||
string or a template loader.
|
||||
|
||||
.. code-block:: swift
|
||||
|
||||
let template = "Hello {{ name }}"
|
||||
let context = ["name": "Kyle"]
|
||||
let rendered = environment.render(templateString: template, context: context)
|
||||
|
||||
Rendering a template from the configured loader:
|
||||
|
||||
.. code-block:: swift
|
||||
|
||||
let context = ["name": "Kyle"]
|
||||
let rendered = environment.render(templateName: "example.html", context: context)
|
||||
|
||||
Loading a Template
|
||||
------------------
|
||||
|
||||
Environment provides an API to load a template from the configured loader.
|
||||
|
||||
.. code-block:: swift
|
||||
|
||||
let template = try environment.loadTemplate(name: "example.html")
|
||||
38
docs/api/loader.rst
Normal file
38
docs/api/loader.rst
Normal file
@@ -0,0 +1,38 @@
|
||||
Loader
|
||||
======
|
||||
|
||||
Loaders are responsible for loading templates from a resource such as the file
|
||||
system.
|
||||
|
||||
Stencil provides a ``FileSytemLoader`` which allows you to load a template
|
||||
directly from the file system.
|
||||
|
||||
``Loader`` is a protocol, so you can implement your own compatible loaders. You
|
||||
will need to implement a ``loadTemplate`` method to load the template,
|
||||
throwing a ``TemplateDoesNotExist`` when the template is not found.
|
||||
|
||||
.. code-block:: swift
|
||||
|
||||
class ExampleMemoryLoader: Loader {
|
||||
func loadTemplate(name: String, environment: Environment) throws -> Template {
|
||||
if name == "index.html" {
|
||||
return Template(templateString: "Hello", environment: environment)
|
||||
}
|
||||
|
||||
throw TemplateDoesNotExist()
|
||||
}
|
||||
}
|
||||
|
||||
FileSystemLoader
|
||||
----------------
|
||||
|
||||
Loads templates from the file system. This loader can find templates in folders
|
||||
on the file system.
|
||||
|
||||
.. code-block:: swift
|
||||
|
||||
FileSystemLoader(paths: ["./templates"])
|
||||
|
||||
.. code-block:: swift
|
||||
|
||||
FileSystemLoader(bundle: [Bundle.main])
|
||||
@@ -206,13 +206,13 @@ You can include another template using the `include` tag.
|
||||
|
||||
{% include "comment.html" %}
|
||||
|
||||
The `include` tag requires a FileSystemLoader to be found inside your context with the paths, or bundles used to lookup the template.
|
||||
The `include` tag requires you to provide a loader which will be used to lookup
|
||||
the template.
|
||||
|
||||
.. code-block:: swift
|
||||
|
||||
let context = Context(dictionary: [
|
||||
"loader": FileSystemLoader(bundle: [NSBundle.mainBundle()])
|
||||
])
|
||||
let environment = Environment(bundle: [Bundle.main])
|
||||
let template = environment.loadTemplate(name: "index.html")
|
||||
|
||||
``extends``
|
||||
~~~~~~~~~~~
|
||||
|
||||
@@ -9,7 +9,9 @@ namespace which contains all filters and tags available to the template.
|
||||
|
||||
let namespace = Namespace()
|
||||
// Register your filters and tags with the namespace
|
||||
let rendered = try template.render(context, namespace: namespace)
|
||||
|
||||
let environment = Environment(namespace: namespace)
|
||||
try environment.renderTemplate(name: "example.html")
|
||||
|
||||
Custom Filters
|
||||
--------------
|
||||
|
||||
@@ -17,17 +17,19 @@ feel right at home with Stencil.
|
||||
|
||||
.. code-block:: swift
|
||||
|
||||
struct Article {
|
||||
let title: String
|
||||
let author: String
|
||||
}
|
||||
import Stencil
|
||||
|
||||
let context = Context(dictionary: [
|
||||
struct Article {
|
||||
let title: String
|
||||
let author: String
|
||||
}
|
||||
|
||||
let context = [
|
||||
"articles": [
|
||||
Article(title: "Migrating from OCUnit to XCTest", author: "Kyle Fuller"),
|
||||
Article(title: "Memory Management with ARC", author: "Kyle Fuller"),
|
||||
]
|
||||
])
|
||||
]
|
||||
|
||||
do {
|
||||
let template = try Template(named: "template.html")
|
||||
|
||||
Reference in New Issue
Block a user