Add 'Namespace' a container for tags and filters
This commit is contained in:
@@ -96,7 +96,7 @@ When the `ForNode` is rendered in a context, it will look up the variable `artic
|
||||
|
||||
There are two ways to register custom template tags. A simple way which allows you to map 1:1 a block token to a Node. You can also register a more advanced template tag which has it’s own block of code for handling parsing if you want to parse up until another token such as if you are trying to provide flow-control.
|
||||
|
||||
The tags are registered onto the `TokenParser` which you can access from your `Template`.
|
||||
The tags are registered with a `Namespace` passed when rendering your `Template`.
|
||||
|
||||
#### Simple Tags
|
||||
|
||||
@@ -105,7 +105,7 @@ A simple tag is registered with a string for the tag name and a block of code wh
|
||||
Here’s an example. Registering a template tag called `custom` which just renders `Hello World` in the rendered template:
|
||||
|
||||
```swift
|
||||
parser.registerSimpleTag("custom") { context in
|
||||
namespace.registerSimpleTag("custom") { context in
|
||||
return "Hello World"
|
||||
}
|
||||
```
|
||||
@@ -120,7 +120,7 @@ You would use it as such in a template:
|
||||
|
||||
If you need more control or functionality than the simple tag’s above, you can use the node based API where you can provide a block of code to deal with parsing. There are a few examples of this in use over at `Node.swift` inside Stencil. There is an implementation of `if` and `for` template tags.
|
||||
|
||||
You would register a template tag using the `registerTag` API inside a `TokenParser` which accepts a name for the tag and a block of code to handle parsing. The block of code is invoked with the parser and the current token as an argument. This allows you to use the API on `TokenParser` to parse node’s further in the token array.
|
||||
You would register a template tag using the `registerTag` API inside a `Namespace` which accepts a name for the tag and a block of code to handle parsing. The block of code is invoked with the parser and the current token as an argument. This allows you to use the API on `TokenParser` to parse node’s further in the token array.
|
||||
|
||||
As an example, we’re going to create a template tag called `debug` which will optionally render nodes from `debug` up until `enddebug`. When rendering the `DebugNode`, it will only render the nodes inside if a variable called `debug` is set to `true` inside the template Context.
|
||||
|
||||
@@ -163,7 +163,7 @@ class DebugNode : Node {
|
||||
We will need to write a parser to parse up until the `enddebug` template block and create a `DebugNode` with the nodes in-between. If there was another error form another Node inside, then we will return that error.
|
||||
|
||||
```swift
|
||||
parser.registerTag("debug") { parser, token in
|
||||
namespace.registerTag("debug") { parser, token in
|
||||
// Use the parser to parse every token up until the `enddebug` block.
|
||||
let nodes = try until(["enddebug"]))
|
||||
return DebugNode(nodes)
|
||||
|
||||
44
README.md
44
README.md
@@ -112,18 +112,6 @@ For example, `Stencil` to `stencil`.
|
||||
{{ "Stencil"|lowercase }}
|
||||
```
|
||||
|
||||
#### Registering custom filters
|
||||
|
||||
```swift
|
||||
template.parser.registerFilter("double") { value in
|
||||
if let value = value as? Int {
|
||||
return value * 2
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
```
|
||||
|
||||
### Tags
|
||||
|
||||
Tags are a mechanism to execute a piece of code, allowing you to have
|
||||
@@ -194,13 +182,37 @@ let context = Context(dictionary: [
|
||||
])
|
||||
```
|
||||
|
||||
### Customisation
|
||||
|
||||
You can build your own custom filters and tags and pass them down while
|
||||
rendering your template. Any custom filters or tags must be registered
|
||||
with a namespace which contains all filters and tags available to the template.
|
||||
|
||||
```swift
|
||||
let namespace = Namespace()
|
||||
// Register your filters and tags with the namespace
|
||||
let rendered = try template.render(context, namespace: namespace)
|
||||
```
|
||||
|
||||
#### Registering custom filters
|
||||
|
||||
```swift
|
||||
namespace.registerFilter("double") { value in
|
||||
if let value = value as? Int {
|
||||
return value * 2
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
```
|
||||
|
||||
#### Building custom tags
|
||||
|
||||
You can build a custom template tag. There are a couple of APIs to allow
|
||||
you to write your own custom tags. The following is the simplest form:
|
||||
|
||||
```swift
|
||||
template.parser.registerSimpleTag("custom") { context in
|
||||
namespace.registerSimpleTag("custom") { context in
|
||||
return "Hello World"
|
||||
}
|
||||
```
|
||||
@@ -214,9 +226,8 @@ of template tags. You will need to call the `registerTag` API which accepts a
|
||||
closure to handle the parsing. You can find examples of the `now`, `if` and
|
||||
`for` tags found inside `Node.swift`.
|
||||
|
||||
Custom template tags must be registered prior to calling `Template.render` the first time.
|
||||
|
||||
The architecture of Stencil along with how to build advanced plugins can be found in the [architecture](ARCHITECTURE.md) document.
|
||||
The architecture of Stencil along with how to build advanced plugins can be
|
||||
found in the [architecture](ARCHITECTURE.md) document.
|
||||
|
||||
### Comments
|
||||
|
||||
@@ -230,4 +241,3 @@ To comment out part of your template, you can use the following syntax:
|
||||
|
||||
Stencil is licensed under the BSD license. See [LICENSE](LICENSE) for more
|
||||
info.
|
||||
|
||||
|
||||
44
Stencil/Namespace.swift
Normal file
44
Stencil/Namespace.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
public class Namespace {
|
||||
public typealias TagParser = (TokenParser, Token) throws -> NodeType
|
||||
|
||||
var tags = [String: TagParser]()
|
||||
var filters = [String: Filter]()
|
||||
|
||||
public init() {
|
||||
registerDefaultTags()
|
||||
registerDefaultFilters()
|
||||
}
|
||||
|
||||
private func registerDefaultTags() {
|
||||
registerTag("for", parser: ForNode.parse)
|
||||
registerTag("if", parser: IfNode.parse)
|
||||
registerTag("ifnot", parser: IfNode.parse_ifnot)
|
||||
registerTag("now", parser: NowNode.parse)
|
||||
registerTag("include", parser: IncludeNode.parse)
|
||||
registerTag("extends", parser: ExtendsNode.parse)
|
||||
registerTag("block", parser: BlockNode.parse)
|
||||
}
|
||||
|
||||
private func registerDefaultFilters() {
|
||||
registerFilter("capitalize", filter: capitalise)
|
||||
registerFilter("uppercase", filter: uppercase)
|
||||
registerFilter("lowercase", filter: lowercase)
|
||||
}
|
||||
|
||||
/// Registers a new template tag
|
||||
public func registerTag(name: String, parser: TagParser) {
|
||||
tags[name] = parser
|
||||
}
|
||||
|
||||
/// Registers a simple template tag with a name and a handler
|
||||
public func registerSimpleTag(name: String, handler: Context throws -> String) {
|
||||
registerTag(name, parser: { parser, token in
|
||||
return SimpleNode(handler: handler)
|
||||
})
|
||||
}
|
||||
|
||||
/// Registers a template filter with the given name
|
||||
public func registerFilter(name: String, filter: Filter) {
|
||||
filters[name] = filter
|
||||
}
|
||||
}
|
||||
@@ -16,38 +16,12 @@ public typealias Filter = Any? throws -> Any?
|
||||
public class TokenParser {
|
||||
public typealias TagParser = (TokenParser, Token) throws -> NodeType
|
||||
|
||||
private var tokens:[Token]
|
||||
private var tags = [String:TagParser]()
|
||||
private var filters = [String: Filter]()
|
||||
private var tokens: [Token]
|
||||
private let namespace: Namespace
|
||||
|
||||
public init(tokens:[Token]) {
|
||||
public init(tokens: [Token], namespace: Namespace) {
|
||||
self.tokens = tokens
|
||||
registerTag("for", parser: ForNode.parse)
|
||||
registerTag("if", parser: IfNode.parse)
|
||||
registerTag("ifnot", parser: IfNode.parse_ifnot)
|
||||
registerTag("now", parser: NowNode.parse)
|
||||
registerTag("include", parser: IncludeNode.parse)
|
||||
registerTag("extends", parser: ExtendsNode.parse)
|
||||
registerTag("block", parser: BlockNode.parse)
|
||||
registerFilter("capitalize", filter: capitalise)
|
||||
registerFilter("uppercase", filter: uppercase)
|
||||
registerFilter("lowercase", filter: lowercase)
|
||||
}
|
||||
|
||||
/// Registers a new template tag
|
||||
public func registerTag(name:String, parser:TagParser) {
|
||||
tags[name] = parser
|
||||
}
|
||||
|
||||
/// Registers a simple template tag with a name and a handler
|
||||
public func registerSimpleTag(name:String, handler:(Context throws -> String)) {
|
||||
registerTag(name, parser: { parser, token in
|
||||
return SimpleNode(handler: handler)
|
||||
})
|
||||
}
|
||||
|
||||
public func registerFilter(name: String, filter: Filter) {
|
||||
filters[name] = filter
|
||||
self.namespace = namespace
|
||||
}
|
||||
|
||||
/// Parse the given tokens into nodes
|
||||
@@ -75,7 +49,7 @@ public class TokenParser {
|
||||
}
|
||||
|
||||
if let tag = tag {
|
||||
if let parser = self.tags[tag] {
|
||||
if let parser = namespace.tags[tag] {
|
||||
nodes.append(try parser(self, token))
|
||||
} else {
|
||||
throw TemplateSyntaxError("Unknown template tag '\(tag)'")
|
||||
@@ -102,7 +76,7 @@ public class TokenParser {
|
||||
}
|
||||
|
||||
public func findFilter(name: String) throws -> Filter {
|
||||
if let filter = filters[name] {
|
||||
if let filter = namespace.filters[name] {
|
||||
return filter
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@ import PathKit
|
||||
|
||||
/// A class representing a template
|
||||
public class Template {
|
||||
public let parser:TokenParser
|
||||
private var nodes:[NodeType]? = nil
|
||||
let tokens: [Token]
|
||||
|
||||
/// Create a template with the given name inside the given bundle
|
||||
public convenience init(named:String, inBundle bundle:NSBundle? = nil) throws {
|
||||
@@ -29,16 +28,13 @@ public class Template {
|
||||
/// Create a template with a template string
|
||||
public init(templateString:String) {
|
||||
let lexer = Lexer(templateString: templateString)
|
||||
let tokens = lexer.tokenize()
|
||||
parser = TokenParser(tokens: tokens)
|
||||
tokens = lexer.tokenize()
|
||||
}
|
||||
|
||||
/// Render the given template
|
||||
public func render(context:Context? = nil) throws -> String {
|
||||
if nodes == nil {
|
||||
nodes = try parser.parse()
|
||||
}
|
||||
|
||||
return try renderNodes(nodes!, context ?? Context())
|
||||
public func render(context: Context? = nil, namespace: Namespace? = nil) throws -> String {
|
||||
let parser = TokenParser(tokens: tokens, namespace: namespace ?? Namespace())
|
||||
let nodes = try parser.parse()
|
||||
return try renderNodes(nodes, context ?? Context())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ describe("template filters") {
|
||||
|
||||
$0.it("allows you to register a custom filter") {
|
||||
let template = Template(templateString: "{{ name|repeat }}")
|
||||
template.parser.registerFilter("repeat") { value in
|
||||
|
||||
let namespace = Namespace()
|
||||
namespace.registerFilter("repeat") { value in
|
||||
if let value = value as? String {
|
||||
return "\(value) \(value)"
|
||||
}
|
||||
@@ -15,17 +17,18 @@ describe("template filters") {
|
||||
return nil
|
||||
}
|
||||
|
||||
let result = try template.render(context)
|
||||
let result = try template.render(context, namespace: namespace)
|
||||
try expect(result) == "Kyle Kyle"
|
||||
}
|
||||
|
||||
$0.it("allows you to register a custom filter") {
|
||||
let template = Template(templateString: "{{ name|repeat }}")
|
||||
template.parser.registerFilter("repeat") { value in
|
||||
let namespace = Namespace()
|
||||
namespace.registerFilter("repeat") { value in
|
||||
throw TemplateSyntaxError("No Repeat")
|
||||
}
|
||||
|
||||
try expect(try template.render(context)).toThrow(TemplateSyntaxError("No Repeat"))
|
||||
try expect(try template.render(context, namespace: namespace)).toThrow(TemplateSyntaxError("No Repeat"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ describe("IfNode") {
|
||||
Token.Block(value: "endif")
|
||||
]
|
||||
|
||||
let parser = TokenParser(tokens: tokens)
|
||||
let parser = TokenParser(tokens: tokens, namespace: Namespace())
|
||||
let nodes = try parser.parse()
|
||||
let node = nodes.first as? IfNode
|
||||
let trueNode = node?.trueNodes.first as? TextNode
|
||||
@@ -35,7 +35,7 @@ describe("IfNode") {
|
||||
Token.Block(value: "endif")
|
||||
]
|
||||
|
||||
let parser = TokenParser(tokens: tokens)
|
||||
let parser = TokenParser(tokens: tokens, namespace: Namespace())
|
||||
let nodes = try parser.parse()
|
||||
let node = nodes.first as? IfNode
|
||||
let trueNode = node?.trueNodes.first as? TextNode
|
||||
@@ -54,7 +54,7 @@ describe("IfNode") {
|
||||
Token.Block(value: "if value"),
|
||||
]
|
||||
|
||||
let parser = TokenParser(tokens: tokens)
|
||||
let parser = TokenParser(tokens: tokens, namespace: Namespace())
|
||||
let error = TemplateSyntaxError("`endif` was not found.")
|
||||
try expect(try parser.parse()).toThrow(error)
|
||||
}
|
||||
@@ -64,7 +64,7 @@ describe("IfNode") {
|
||||
Token.Block(value: "ifnot value"),
|
||||
]
|
||||
|
||||
let parser = TokenParser(tokens: tokens)
|
||||
let parser = TokenParser(tokens: tokens, namespace: Namespace())
|
||||
let error = TemplateSyntaxError("`endif` was not found.")
|
||||
try expect(try parser.parse()).toThrow(error)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ describe("NowNode") {
|
||||
$0.describe("parsing") {
|
||||
$0.it("parses default format without any now arguments") {
|
||||
let tokens = [ Token.Block(value: "now") ]
|
||||
let parser = TokenParser(tokens: tokens)
|
||||
let parser = TokenParser(tokens: tokens, namespace: Namespace())
|
||||
|
||||
let nodes = try parser.parse()
|
||||
let node = nodes.first as? NowNode
|
||||
@@ -17,7 +17,7 @@ describe("NowNode") {
|
||||
|
||||
$0.it("parses now with a format") {
|
||||
let tokens = [ Token.Block(value: "now \"HH:mm\"") ]
|
||||
let parser = TokenParser(tokens: tokens)
|
||||
let parser = TokenParser(tokens: tokens, namespace: Namespace())
|
||||
let nodes = try parser.parse()
|
||||
let node = nodes.first as? NowNode
|
||||
try expect(nodes.count) == 1
|
||||
|
||||
@@ -6,7 +6,7 @@ describe("TokenParser") {
|
||||
$0.it("can parse a text token") {
|
||||
let parser = TokenParser(tokens: [
|
||||
Token.Text(value: "Hello World")
|
||||
])
|
||||
], namespace: Namespace())
|
||||
|
||||
let nodes = try parser.parse()
|
||||
let node = nodes.first as? TextNode
|
||||
@@ -18,7 +18,7 @@ describe("TokenParser") {
|
||||
$0.it("can parse a variable token") {
|
||||
let parser = TokenParser(tokens: [
|
||||
Token.Variable(value: "'name'")
|
||||
])
|
||||
], namespace: Namespace())
|
||||
|
||||
let nodes = try parser.parse()
|
||||
let node = nodes.first as? VariableNode
|
||||
@@ -30,7 +30,7 @@ describe("TokenParser") {
|
||||
$0.it("can parse a comment token") {
|
||||
let parser = TokenParser(tokens: [
|
||||
Token.Comment(value: "Secret stuff!")
|
||||
])
|
||||
], namespace: Namespace())
|
||||
|
||||
let nodes = try parser.parse()
|
||||
try expect(nodes.count) == 0
|
||||
@@ -39,7 +39,7 @@ describe("TokenParser") {
|
||||
$0.it("can parse a tag token") {
|
||||
let parser = TokenParser(tokens: [
|
||||
Token.Block(value: "now"),
|
||||
])
|
||||
], namespace: Namespace())
|
||||
|
||||
let nodes = try parser.parse()
|
||||
try expect(nodes.count) == 1
|
||||
@@ -48,7 +48,7 @@ describe("TokenParser") {
|
||||
$0.it("errors when parsing an unknown tag") {
|
||||
let parser = TokenParser(tokens: [
|
||||
Token.Block(value: "unknown"),
|
||||
])
|
||||
], namespace: Namespace())
|
||||
|
||||
try expect(try parser.parse()).toThrow(TemplateSyntaxError("Unknown template tag 'unknown'"))
|
||||
}
|
||||
|
||||
@@ -38,24 +38,26 @@ describe("Stencil") {
|
||||
|
||||
$0.it("can render a custom template tag") {
|
||||
let templateString = "{% custom %}"
|
||||
let template = Template(templateString:templateString)
|
||||
let template = Template(templateString: templateString)
|
||||
|
||||
template.parser.registerTag("custom") { parser, token in
|
||||
let namespace = Namespace()
|
||||
namespace.registerTag("custom") { parser, token in
|
||||
return CustomNode()
|
||||
}
|
||||
|
||||
let result = try template.render()
|
||||
let result = try template.render(namespace: namespace)
|
||||
try expect(result) == "Hello World"
|
||||
}
|
||||
|
||||
$0.it("can render a simple custom tag") {
|
||||
let templateString = "{% custom %}"
|
||||
let template = Template(templateString:templateString)
|
||||
let template = Template(templateString: templateString)
|
||||
|
||||
template.parser.registerSimpleTag("custom") { context in
|
||||
let namespace = Namespace()
|
||||
namespace.registerSimpleTag("custom") { context in
|
||||
return "Hello World"
|
||||
}
|
||||
|
||||
try expect(try template.render()) == "Hello World"
|
||||
try expect(try template.render(namespace: namespace)) == "Hello World"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ describe("Include") {
|
||||
$0.describe("parsing") {
|
||||
$0.it("throws an error when no template is given") {
|
||||
let tokens = [ Token.Block(value: "include") ]
|
||||
let parser = TokenParser(tokens: tokens)
|
||||
let parser = TokenParser(tokens: tokens, namespace: Namespace())
|
||||
|
||||
let error = TemplateSyntaxError("'include' tag takes one argument, the template file to be included")
|
||||
try expect(try parser.parse()).toThrow(error)
|
||||
@@ -18,7 +18,7 @@ describe("Include") {
|
||||
|
||||
$0.it("can parse a valid include block") {
|
||||
let tokens = [ Token.Block(value: "include \"test.html\"") ]
|
||||
let parser = TokenParser(tokens: tokens)
|
||||
let parser = TokenParser(tokens: tokens, namespace: Namespace())
|
||||
|
||||
let nodes = try parser.parse()
|
||||
let node = nodes.first as? IncludeNode
|
||||
|
||||
Reference in New Issue
Block a user