Introduce variable filters
This commit is contained in:
52
README.md
52
README.md
@@ -1,5 +1,4 @@
|
|||||||
Stencil
|
# Stencil
|
||||||
=======
|
|
||||||
|
|
||||||
[](https://circleci.com/gh/kylef/Stencil)
|
[](https://circleci.com/gh/kylef/Stencil)
|
||||||
|
|
||||||
@@ -7,7 +6,7 @@ Stencil is a simple and powerful template language for Swift. It provides a
|
|||||||
syntax similar to Django and Mustache. If you're familiar with these, you will
|
syntax similar to Django and Mustache. If you're familiar with these, you will
|
||||||
feel right at home with Stencil.
|
feel right at home with Stencil.
|
||||||
|
|
||||||
### Example
|
## Example
|
||||||
|
|
||||||
```html+django
|
```html+django
|
||||||
There are {{ articles.count }} articles.
|
There are {{ articles.count }} articles.
|
||||||
@@ -78,6 +77,53 @@ There are {{ people.count }} people, {{ people.first }} is first person.
|
|||||||
Followed by {{ people.1 }}.
|
Followed by {{ people.1 }}.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Filters
|
||||||
|
|
||||||
|
Filters allow you to transform the values of variables. For example, they look like:
|
||||||
|
|
||||||
|
```html+django
|
||||||
|
{{ variable|uppercase }}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Capitalize
|
||||||
|
|
||||||
|
The capitalize filter allows you to capitalize a string.
|
||||||
|
For example, `stencil` to `Stencil`.
|
||||||
|
|
||||||
|
```html+django
|
||||||
|
{{ "stencil"|capitalize }}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Uppercase
|
||||||
|
|
||||||
|
The uppercase filter allows you to transform a string to uppercase.
|
||||||
|
For example, `Stencil` to `STENCIL`.
|
||||||
|
|
||||||
|
```html+django
|
||||||
|
{{ "Stencil"|uppercase }}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Lowercase
|
||||||
|
|
||||||
|
The uppercase filter allows you to transform a string to lowercase.
|
||||||
|
For example, `Stencil` to `stencil`.
|
||||||
|
|
||||||
|
```html+django
|
||||||
|
{{ "Stencil"|lowercase }}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Registering custom filters
|
||||||
|
|
||||||
|
```
|
||||||
|
template.parser.registerFilter("double") { value in
|
||||||
|
if let value = value as? Int {
|
||||||
|
return value * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Tags
|
### Tags
|
||||||
|
|
||||||
Tags are a mechanism to execute a piece of code, allowing you to have
|
Tags are a mechanism to execute a piece of code, allowing you to have
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
27A848E91B42242C004ACA13 /* base.html in Resources */ = {isa = PBXBuildFile; fileRef = 27A848E71B42242C004ACA13 /* base.html */; };
|
27A848E91B42242C004ACA13 /* base.html in Resources */ = {isa = PBXBuildFile; fileRef = 27A848E71B42242C004ACA13 /* base.html */; };
|
||||||
27A848EA1B42242C004ACA13 /* child.html in Resources */ = {isa = PBXBuildFile; fileRef = 27A848E81B42242C004ACA13 /* child.html */; };
|
27A848EA1B42242C004ACA13 /* child.html in Resources */ = {isa = PBXBuildFile; fileRef = 27A848E81B42242C004ACA13 /* child.html */; };
|
||||||
27A848EC1B42247D004ACA13 /* InheritenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A848EB1B42247D004ACA13 /* InheritenceTests.swift */; };
|
27A848EC1B42247D004ACA13 /* InheritenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A848EB1B42247D004ACA13 /* InheritenceTests.swift */; };
|
||||||
|
27BA0A9E1BD9465700B7209B /* Filters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27BA0A9D1BD9465700B7209B /* Filters.swift */; settings = {ASSET_TAGS = (); }; };
|
||||||
|
27BA0AA01BD946C300B7209B /* FilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27BA0A9F1BD946C300B7209B /* FilterTests.swift */; settings = {ASSET_TAGS = (); }; };
|
||||||
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 */; };
|
||||||
@@ -53,6 +55,8 @@
|
|||||||
27A848E71B42242C004ACA13 /* base.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = base.html; 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>"; };
|
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>"; };
|
27A848EB1B42247D004ACA13 /* InheritenceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InheritenceTests.swift; sourceTree = "<group>"; };
|
||||||
|
27BA0A9D1BD9465700B7209B /* Filters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Filters.swift; sourceTree = "<group>"; };
|
||||||
|
27BA0A9F1BD946C300B7209B /* FilterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterTests.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>"; };
|
||||||
@@ -149,6 +153,7 @@
|
|||||||
children = (
|
children = (
|
||||||
77FAAE5719F91E480029DC5E /* Stencil.h */,
|
77FAAE5719F91E480029DC5E /* Stencil.h */,
|
||||||
77FAAE6E19F920750029DC5E /* Context.swift */,
|
77FAAE6E19F920750029DC5E /* Context.swift */,
|
||||||
|
27BA0A9D1BD9465700B7209B /* Filters.swift */,
|
||||||
77EB082A19FA8600001870F1 /* Lexer.swift */,
|
77EB082A19FA8600001870F1 /* Lexer.swift */,
|
||||||
7725B3D419F9438F002CF74B /* Node.swift */,
|
7725B3D419F9438F002CF74B /* Node.swift */,
|
||||||
7725B3D619F94A43002CF74B /* Parser.swift */,
|
7725B3D619F94A43002CF74B /* Parser.swift */,
|
||||||
@@ -177,6 +182,7 @@
|
|||||||
77FAAE7019F9208C0029DC5E /* ContextTests.swift */,
|
77FAAE7019F9208C0029DC5E /* ContextTests.swift */,
|
||||||
7725B3CA19F92B4F002CF74B /* VariableTests.swift */,
|
7725B3CA19F92B4F002CF74B /* VariableTests.swift */,
|
||||||
7725B3D219F9437F002CF74B /* NodeTests.swift */,
|
7725B3D219F9437F002CF74B /* NodeTests.swift */,
|
||||||
|
27BA0A9F1BD946C300B7209B /* FilterTests.swift */,
|
||||||
7725B3D819F94A61002CF74B /* ParserTests.swift */,
|
7725B3D819F94A61002CF74B /* ParserTests.swift */,
|
||||||
77EB082819FA85F2001870F1 /* LexerTests.swift */,
|
77EB082819FA85F2001870F1 /* LexerTests.swift */,
|
||||||
77EB082619F96E9C001870F1 /* TemplateTests.swift */,
|
77EB082619F96E9C001870F1 /* TemplateTests.swift */,
|
||||||
@@ -416,6 +422,7 @@
|
|||||||
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 */,
|
||||||
|
27BA0A9E1BD9465700B7209B /* Filters.swift in Sources */,
|
||||||
7725B3D719F94A43002CF74B /* Parser.swift in Sources */,
|
7725B3D719F94A43002CF74B /* Parser.swift in Sources */,
|
||||||
77EB082519F96E88001870F1 /* Template.swift in Sources */,
|
77EB082519F96E88001870F1 /* Template.swift in Sources */,
|
||||||
7725B3CD19F92B61002CF74B /* Variable.swift in Sources */,
|
7725B3CD19F92B61002CF74B /* Variable.swift in Sources */,
|
||||||
@@ -437,6 +444,7 @@
|
|||||||
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 */,
|
27CE0B041A50CBEA004A105B /* IncludeTests.swift in Sources */,
|
||||||
|
27BA0AA01BD946C300B7209B /* FilterTests.swift in Sources */,
|
||||||
77EB082919FA85F2001870F1 /* LexerTests.swift in Sources */,
|
77EB082919FA85F2001870F1 /* LexerTests.swift in Sources */,
|
||||||
77FAAE7119F9208C0029DC5E /* ContextTests.swift in Sources */,
|
77FAAE7119F9208C0029DC5E /* ContextTests.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
|||||||
33
Stencil/Filters.swift
Normal file
33
Stencil/Filters.swift
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
func toString(value: Any?) -> String? {
|
||||||
|
if let value = value as? String {
|
||||||
|
return value
|
||||||
|
} else if let value = value as? CustomStringConvertible {
|
||||||
|
return value.description
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func capitalise(value: Any?) -> Any? {
|
||||||
|
if let value = toString(value) {
|
||||||
|
return value.capitalizedString
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func uppercase(value: Any?) -> Any? {
|
||||||
|
if let value = toString(value) {
|
||||||
|
return value.uppercaseString
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func lowercase(value: Any?) -> Any? {
|
||||||
|
if let value = toString(value) {
|
||||||
|
return value.lowercaseString
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
@@ -46,22 +46,28 @@ public class TextNode : NodeType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class VariableNode : NodeType {
|
public protocol Resolvable {
|
||||||
public let variable:Variable
|
func resolve(context: Context) -> Any?
|
||||||
|
}
|
||||||
|
|
||||||
public init(variable:Variable) {
|
public class VariableNode : NodeType {
|
||||||
|
public let variable: Resolvable
|
||||||
|
|
||||||
|
public init(variable: Resolvable) {
|
||||||
self.variable = variable
|
self.variable = variable
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(variable:String) {
|
public init(variable: String) {
|
||||||
self.variable = Variable(variable)
|
self.variable = Variable(variable)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func render(context:Context) throws -> String {
|
public func render(context: Context) throws -> String {
|
||||||
let result:AnyObject? = variable.resolve(context)
|
let result = variable.resolve(context)
|
||||||
|
|
||||||
if let result = result as? String {
|
if let result = result as? String {
|
||||||
return result
|
return result
|
||||||
|
} else if let result = result as? CustomStringConvertible {
|
||||||
|
return result.description
|
||||||
} else if let result = result as? NSObject {
|
} else if let result = result as? NSObject {
|
||||||
return result.description
|
return result.description
|
||||||
}
|
}
|
||||||
@@ -94,7 +100,7 @@ public class NowNode : NodeType {
|
|||||||
|
|
||||||
public func render(context: Context) throws -> String {
|
public func render(context: Context) throws -> String {
|
||||||
let date = NSDate()
|
let date = NSDate()
|
||||||
let format: AnyObject? = self.format.resolve(context)
|
let format = self.format.resolve(context)
|
||||||
var formatter:NSDateFormatter?
|
var formatter:NSDateFormatter?
|
||||||
|
|
||||||
if let format = format as? NSDateFormatter {
|
if let format = format as? NSDateFormatter {
|
||||||
@@ -212,7 +218,7 @@ public class IfNode : NodeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func render(context: Context) throws -> String {
|
public func render(context: Context) throws -> String {
|
||||||
let result: AnyObject? = variable.resolve(context)
|
let result = variable.resolve(context)
|
||||||
var truthy = false
|
var truthy = false
|
||||||
|
|
||||||
if let result = result as? [AnyObject] {
|
if let result = result as? [AnyObject] {
|
||||||
|
|||||||
@@ -10,12 +10,15 @@ public func until(tags:[String])(parser:TokenParser, token:Token) -> Bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public typealias Filter = Any? -> Any?
|
||||||
|
|
||||||
/// A class for parsing an array of tokens and converts them into a collection of Node's
|
/// A class for parsing an array of tokens and converts them into a collection of Node's
|
||||||
public class TokenParser {
|
public class TokenParser {
|
||||||
public typealias TagParser = (TokenParser, Token) throws -> NodeType
|
public typealias TagParser = (TokenParser, Token) throws -> NodeType
|
||||||
|
|
||||||
private var tokens:[Token]
|
private var tokens:[Token]
|
||||||
private var tags = [String:TagParser]()
|
private var tags = [String:TagParser]()
|
||||||
|
private var filters = [String: Filter]()
|
||||||
|
|
||||||
public init(tokens:[Token]) {
|
public init(tokens:[Token]) {
|
||||||
self.tokens = tokens
|
self.tokens = tokens
|
||||||
@@ -26,6 +29,9 @@ public class TokenParser {
|
|||||||
registerTag("include", parser: IncludeNode.parse)
|
registerTag("include", parser: IncludeNode.parse)
|
||||||
registerTag("extends", parser: ExtendsNode.parse)
|
registerTag("extends", parser: ExtendsNode.parse)
|
||||||
registerTag("block", parser: BlockNode.parse)
|
registerTag("block", parser: BlockNode.parse)
|
||||||
|
registerFilter("capitalize", filter: capitalise)
|
||||||
|
registerFilter("uppercase", filter: uppercase)
|
||||||
|
registerFilter("lowercase", filter: lowercase)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers a new template tag
|
/// Registers a new template tag
|
||||||
@@ -40,6 +46,10 @@ public class TokenParser {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func registerFilter(name: String, filter: Filter) {
|
||||||
|
filters[name] = filter
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse the given tokens into nodes
|
/// Parse the given tokens into nodes
|
||||||
public func parse() throws -> [NodeType] {
|
public func parse() throws -> [NodeType] {
|
||||||
return try parse(nil)
|
return try parse(nil)
|
||||||
@@ -54,8 +64,8 @@ public class TokenParser {
|
|||||||
switch token {
|
switch token {
|
||||||
case .Text(let text):
|
case .Text(let text):
|
||||||
nodes.append(TextNode(text: text))
|
nodes.append(TextNode(text: text))
|
||||||
case .Variable(let variable):
|
case .Variable:
|
||||||
nodes.append(VariableNode(variable: variable))
|
nodes.append(VariableNode(variable: try compileFilter(token.contents)))
|
||||||
case .Block:
|
case .Block:
|
||||||
let tag = token.components().first
|
let tag = token.components().first
|
||||||
|
|
||||||
@@ -88,4 +98,16 @@ public class TokenParser {
|
|||||||
public func prependToken(token:Token) {
|
public func prependToken(token:Token) {
|
||||||
tokens.insert(token, atIndex: 0)
|
tokens.insert(token, atIndex: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func findFilter(name: String) throws -> Filter {
|
||||||
|
if let filter = filters[name] {
|
||||||
|
return filter
|
||||||
|
}
|
||||||
|
|
||||||
|
throw TemplateSyntaxError("Invalid filter '\(name)'")
|
||||||
|
}
|
||||||
|
|
||||||
|
func compileFilter(token: String) throws -> Resolvable {
|
||||||
|
return try FilterExpression(token: token, parser: self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,40 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
class FilterExpression : Resolvable {
|
||||||
|
let filters: [Filter]
|
||||||
|
let variable: Variable
|
||||||
|
|
||||||
|
init(token: String, parser: TokenParser) throws {
|
||||||
|
let bits = token.componentsSeparatedByString("|")
|
||||||
|
if bits.isEmpty {
|
||||||
|
filters = []
|
||||||
|
variable = Variable("")
|
||||||
|
throw TemplateSyntaxError("Variable tags must include at least 1 argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
variable = Variable(bits[0])
|
||||||
|
let filterBits = bits[1 ..< bits.endIndex]
|
||||||
|
|
||||||
|
do {
|
||||||
|
filters = try filterBits.map { try parser.findFilter($0) }
|
||||||
|
} catch {
|
||||||
|
filters = []
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolve(context: Context) -> Any? {
|
||||||
|
let result = variable.resolve(context)
|
||||||
|
|
||||||
|
return filters.reduce(result) { x, y in
|
||||||
|
return y(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A structure used to represent a template variable, and to resolve it in a given context.
|
/// A structure used to represent a template variable, and to resolve it in a given context.
|
||||||
public struct Variable : Equatable {
|
public struct Variable : Equatable, Resolvable {
|
||||||
public let variable:String
|
public let variable:String
|
||||||
|
|
||||||
/// Create a variable with a string representing the variable
|
/// Create a variable with a string representing the variable
|
||||||
@@ -15,11 +47,12 @@ public struct Variable : Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the variable in the given context
|
/// Resolve the variable in the given context
|
||||||
public func resolve(context:Context) -> AnyObject? {
|
public func resolve(context:Context) -> Any? {
|
||||||
var current:AnyObject? = context
|
var current:AnyObject? = context
|
||||||
|
|
||||||
if (variable.hasPrefix("'") && variable.hasSuffix("'")) || (variable.hasPrefix("\"") && variable.hasSuffix("\"")) {
|
if (variable.hasPrefix("'") && variable.hasSuffix("'")) || (variable.hasPrefix("\"") && variable.hasSuffix("\"")) {
|
||||||
return variable.substringWithRange(variable.startIndex.successor() ..< variable.endIndex.predecessor())
|
// String literal
|
||||||
|
return variable[variable.startIndex.successor() ..< variable.endIndex.predecessor()]
|
||||||
}
|
}
|
||||||
|
|
||||||
for bit in lookup() {
|
for bit in lookup() {
|
||||||
|
|||||||
23
StencilTests/FilterTests.swift
Normal file
23
StencilTests/FilterTests.swift
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import XCTest
|
||||||
|
import Stencil
|
||||||
|
|
||||||
|
|
||||||
|
class FilterTests: XCTestCase {
|
||||||
|
func testCapitalizeFilter() {
|
||||||
|
let template = Template(templateString: "{{ name|capitalize }}")
|
||||||
|
let result = try? template.render(Context(dictionary: ["name": "kyle"]))
|
||||||
|
XCTAssertEqual(result, "Kyle")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUppercaseFilter() {
|
||||||
|
let template = Template(templateString: "{{ name|uppercase }}")
|
||||||
|
let result = try? template.render(Context(dictionary: ["name": "kyle"]))
|
||||||
|
XCTAssertEqual(result, "KYLE")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLowercaseFilter() {
|
||||||
|
let template = Template(templateString: "{{ name|lowercase }}")
|
||||||
|
let result = try? template.render(Context(dictionary: ["name": "Kyle"]))
|
||||||
|
XCTAssertEqual(result, "kyle")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,13 +17,14 @@ class TokenParserTests: XCTestCase {
|
|||||||
|
|
||||||
func testParsingVariableToken() {
|
func testParsingVariableToken() {
|
||||||
let parser = TokenParser(tokens: [
|
let parser = TokenParser(tokens: [
|
||||||
Token.Variable(value: "name")
|
Token.Variable(value: "'name'")
|
||||||
])
|
])
|
||||||
|
|
||||||
assertSuccess(try parser.parse()) { nodes in
|
assertSuccess(try parser.parse()) { nodes in
|
||||||
let node = nodes.first as! VariableNode
|
let node = nodes.first as! VariableNode
|
||||||
XCTAssertEqual(nodes.count, 1)
|
XCTAssertEqual(nodes.count, 1)
|
||||||
XCTAssertEqual(node.variable, Variable("name"))
|
let result = try? node.render(Context())
|
||||||
|
XCTAssertEqual(result, "name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user