21 Commits
0.5.3 ... 0.6.0

Author SHA1 Message Date
Kyle Fuller
65c3052aee Release 0.6.0 2016-09-13 19:15:52 +01:00
Kyle Fuller
7bbd4f2817 [Pod] Add support for tvos 2016-09-13 19:12:47 +01:00
Kyle Fuller
7416e6150d [Travis CI] Test on Linux 2016-09-13 19:12:47 +01:00
Kyle Fuller
feff3b18b1 Add support for Swift 3.0 2016-09-13 19:12:47 +01:00
Kyle Fuller
f393efbd0b Merge pull request #68 from zhangbozhb/master
bug fix:fix memory leak when parse template
2016-09-04 16:17:28 +02:00
Kyle Fuller
df650c6b20 [Travis CI] Test on Swift 2.2 2016-09-04 09:26:43 +02:00
travel
3285bac373 bug fix:fix memory leak when parse template 2016-08-05 08:26:16 +08:00
Kyle Fuller
80427a51e6 Merge pull request #67 from ikesyo/ifnode-evaluate-bool-value
[IfNode] Accept and evaluate a `Bool` value as a valid expression
2016-07-19 19:15:26 +01:00
Syo Ikeda
7bfb69cc82 [IfNode] Fix the ifnot error message 2016-07-20 02:42:09 +09:00
Syo Ikeda
5007ba2c9a [IfNode] Accept and evaluate a Bool value as a valid expression 2016-07-20 02:42:09 +09:00
Syo Ikeda
2d73c58df6 [IfNodeSpec] Add a failing test for bool expression 2016-07-20 02:42:09 +09:00
Kyle Fuller
4ffc888ba4 Release 0.6.0-beta.1 2016-04-04 22:39:19 +02:00
Kyle Fuller
3c21975b97 Merge pull request #61 from GregKaleka/patch-1
Minor grammatical fixes to README.md
2016-03-16 13:53:25 +00:00
Greg Kaleka
df9065f5a8 Minor grammatical fixes to README.md 2016-03-13 21:30:48 -07:00
Kyle Fuller
05b71736aa Merge pull request #59 from shnhrrsn/namespace-fix
Added namespace to Context
2016-03-09 23:41:43 +00:00
shnhrrsn
aa1399be55 Fixed tests for namespace changes 2016-03-06 00:39:10 -05:00
shnhrrsn
bdc14ab1e1 Added namespace to Context 2016-03-05 23:57:15 -05:00
Kyle Fuller
67d4c52535 [Context] Ensure pop happens when an error is thrown 2016-03-05 00:37:12 +00:00
Kyle Fuller
48026cde2c Split tags into separate files 2016-03-05 00:15:18 +00:00
Kyle Fuller
dc4b965aaa Merge pull request #55 from dtrenz/dtrenz-readme-usage
Added `try` to throwing expressions in example.
2016-02-27 15:46:05 -05:00
Dan Trenz
2190afee0d Added try to throwing expressions in example.
Both `Template(named: "template.stencil")` and `Template(named: "template.stencil")` throw but were not preceded by `try`. This usage example, in it's current form, triggers compiler errors.
2016-02-27 15:20:55 -05:00
44 changed files with 477 additions and 472 deletions

View File

@@ -1 +1 @@
DEVELOPMENT-SNAPSHOT-2016-01-25-a
3.0-GM-CANDIDATE

View File

@@ -1,11 +1,11 @@
os:
- osx
- linux
- osx
- linux
language: generic
sudo: required
dist: trusty
osx_image: xcode7.2
osx_image: xcode8
install:
- eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/02090c7ede5a637b76e6df1710e83cd0bbe7dcdf/swiftenv-install.sh)"
- eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/9f442512a46d7a2af7b850d65a7e9bd31edfb09b/swiftenv-install.sh)"
script:
- make test
- swift test

6
CHANGELOG.md Normal file
View File

@@ -0,0 +1,6 @@
# Stencil Changelog
## 0.6.0
### Enhancements
- Adds support for Swift 3.0.

View File

@@ -1,7 +0,0 @@
stencil:
@echo "Building Stencil"
@swift build
test: stencil
@echo "Running Tests"
@.build/debug/spectre-build

View File

@@ -3,9 +3,9 @@ import PackageDescription
let package = Package(
name: "Stencil",
dependencies: [
.Package(url: "https://github.com/kylef/PathKit.git", majorVersion: 0, minor: 6),
],
testDependencies: [
.Package(url: "https://github.com/kylef/spectre-build", majorVersion: 0),
.Package(url: "https://github.com/kylef/PathKit.git", majorVersion: 0, minor: 7),
// https://github.com/apple/swift-package-manager/pull/597
.Package(url: "https://github.com/kylef/Spectre", majorVersion: 0, minor: 7),
]
)

View File

@@ -25,8 +25,8 @@ let context = Context(dictionary: [
])
do {
let template = Template(named: "template.stencil")
let rendered = template.render(context)
let template = try Template(named: "template.stencil")
let rendered = try template.render(context)
print(rendered)
} catch {
print("Failed to render template \(error)")
@@ -73,8 +73,8 @@ following lookup:
For example, if `people` was an array:
```html+django
There are {{ people.count }} people, {{ people.first }} is first person.
Followed by {{ people.1 }}.
There are {{ people.count }} people. {{ people.first }} is the first person,
followed by {{ people.1 }}.
```
#### Filters
@@ -142,7 +142,7 @@ A for loop allows you to iterate over an array found by variable lookup.
{% for item in items %}
{{ item }}
{% empty %}
There we're no items.
There were no items.
{% endfor %}
```

View File

@@ -1,21 +1,23 @@
/// A container for template variables.
public class Context {
var dictionaries:[[String: Any]]
var dictionaries: [[String: Any]]
let namespace: Namespace
/// Initialise a Context with a dictionary
public init(dictionary:[String: Any]) {
dictionaries = [dictionary]
/// Initialise a Context with an optional dictionary and optional namespace
public init(dictionary: [String: Any]? = nil, namespace: Namespace = Namespace()) {
if let dictionary = dictionary {
dictionaries = [dictionary]
} else {
dictionaries = []
}
self.namespace = namespace
}
/// Initialise an empty Context
public init() {
dictionaries = []
}
public subscript(key: String) -> Any? {
open subscript(key: String) -> Any? {
/// Retrieves a variable's value, starting at the current context and going upwards
get {
for dictionary in Array(dictionaries.reverse()) {
for dictionary in Array(dictionaries.reversed()) {
if let value = dictionary[key] {
return value
}
@@ -35,20 +37,19 @@ public class Context {
}
/// Push a new level into the Context
public func push(dictionary: [String: Any]? = nil) {
fileprivate func push(_ dictionary: [String: Any]? = nil) {
dictionaries.append(dictionary ?? [:])
}
/// Pop the last level off of the Context
public func pop() -> [String: Any]? {
fileprivate func pop() -> [String: Any]? {
return dictionaries.popLast()
}
/// Push a new level onto the context for the duration of the execution of the given closure
public func push<Result>(dictionary: [String: Any]? = nil, @noescape closure: (() throws -> Result)) rethrows -> Result {
open func push<Result>(dictionary: [String: Any]? = nil, closure: (() throws -> Result)) rethrows -> Result {
push(dictionary)
let result = try closure()
pop()
return result
defer { _ = pop() }
return try closure()
}
}

View File

@@ -1,4 +1,4 @@
func toString(value: Any?) -> String? {
func toString(_ value: Any?) -> String? {
if let value = value as? String {
return value
} else if let value = value as? CustomStringConvertible {
@@ -8,25 +8,25 @@ func toString(value: Any?) -> String? {
return nil
}
func capitalise(value: Any?) -> Any? {
func capitalise(_ value: Any?) -> Any? {
if let value = toString(value) {
return value.capitalizedString
return value.capitalized
}
return value
}
func uppercase(value: Any?) -> Any? {
func uppercase(_ value: Any?) -> Any? {
if let value = toString(value) {
return value.uppercaseString
return value.uppercased()
}
return value
}
func lowercase(value: Any?) -> Any? {
func lowercase(_ value: Any?) -> Any? {
if let value = toString(value) {
return value.lowercaseString
return value.lowercased()
}
return value

62
Sources/ForTag.swift Normal file
View File

@@ -0,0 +1,62 @@
open class ForNode : NodeType {
let variable:Variable
let loopVariable:String
let nodes:[NodeType]
let emptyNodes: [NodeType]
open class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
let components = token.components()
guard components.count == 4 && components[2] == "in" else {
throw TemplateSyntaxError("'for' statements should use the following 'for x in y' `\(token.contents)`.")
}
let loopVariable = components[1]
let variable = components[3]
var emptyNodes = [NodeType]()
let forNodes = try parser.parse(until(["endfor", "empty"]))
guard let token = parser.nextToken() else {
throw TemplateSyntaxError("`endfor` was not found.")
}
if token.contents == "empty" {
emptyNodes = try parser.parse(until(["endfor"]))
_ = parser.nextToken()
}
return ForNode(variable: variable, loopVariable: loopVariable, nodes: forNodes, emptyNodes:emptyNodes)
}
public init(variable:String, loopVariable:String, nodes:[NodeType], emptyNodes:[NodeType]) {
self.variable = Variable(variable)
self.loopVariable = loopVariable
self.nodes = nodes
self.emptyNodes = emptyNodes
}
open func render(_ context: Context) throws -> String {
let values = try variable.resolve(context)
if let values = values as? [Any] , values.count > 0 {
let count = values.count
return try values.enumerated().map { index, item in
let forContext: [String: Any] = [
"first": index == 0,
"last": index == (count - 1),
"counter": index + 1,
]
return try context.push(dictionary: [loopVariable: item, "forloop": forContext]) {
try renderNodes(nodes, context)
}
}.joined(separator: "")
}
return try context.push {
try renderNodes(emptyNodes, context)
}
}
}

80
Sources/IfTag.swift Normal file
View File

@@ -0,0 +1,80 @@
open class IfNode : NodeType {
open let variable:Variable
open let trueNodes:[NodeType]
open let falseNodes:[NodeType]
open class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
let components = token.components()
guard components.count == 2 else {
throw TemplateSyntaxError("'if' statements should use the following 'if condition' `\(token.contents)`.")
}
let variable = components[1]
var trueNodes = [NodeType]()
var falseNodes = [NodeType]()
trueNodes = try parser.parse(until(["endif", "else"]))
guard let token = parser.nextToken() else {
throw TemplateSyntaxError("`endif` was not found.")
}
if token.contents == "else" {
falseNodes = try parser.parse(until(["endif"]))
_ = parser.nextToken()
}
return IfNode(variable: variable, trueNodes: trueNodes, falseNodes: falseNodes)
}
open class func parse_ifnot(_ parser:TokenParser, token:Token) throws -> NodeType {
let components = token.components()
guard components.count == 2 else {
throw TemplateSyntaxError("'ifnot' statements should use the following 'ifnot condition' `\(token.contents)`.")
}
let variable = components[1]
var trueNodes = [NodeType]()
var falseNodes = [NodeType]()
falseNodes = try parser.parse(until(["endif", "else"]))
guard let token = parser.nextToken() else {
throw TemplateSyntaxError("`endif` was not found.")
}
if token.contents == "else" {
trueNodes = try parser.parse(until(["endif"]))
_ = parser.nextToken()
}
return IfNode(variable: variable, trueNodes: trueNodes, falseNodes: falseNodes)
}
public init(variable:String, trueNodes:[NodeType], falseNodes:[NodeType]) {
self.variable = Variable(variable)
self.trueNodes = trueNodes
self.falseNodes = falseNodes
}
open func render(_ context: Context) throws -> String {
let result = try variable.resolve(context)
var truthy = false
if let result = result as? [Any] {
truthy = !result.isEmpty
} else if let result = result as? [String:Any] {
truthy = !result.isEmpty
} else if let result = result as? Bool {
truthy = result
} else if result != nil {
truthy = true
}
return try context.push {
if truthy {
return try renderNodes(trueNodes, context)
} else {
return try renderNodes(falseNodes, context)
}
}
}
}

View File

@@ -1,10 +1,10 @@
import PathKit
public class IncludeNode : NodeType {
public let templateName: Variable
open class IncludeNode : NodeType {
open let templateName: Variable
public class func parse(parser: TokenParser, token: Token) throws -> NodeType {
open class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
let bits = token.components()
guard bits.count == 2 else {
@@ -18,7 +18,7 @@ public class IncludeNode : NodeType {
self.templateName = templateName
}
public func render(context: Context) throws -> String {
open func render(_ context: Context) throws -> String {
guard let loader = context["loader"] as? TemplateLoader else {
throw TemplateSyntaxError("Template loader not in context")
}
@@ -28,7 +28,7 @@ public class IncludeNode : NodeType {
}
guard let template = loader.loadTemplate(templateName) else {
let paths = loader.paths.map { $0.description }.joinWithSeparator(", ")
let paths = loader.paths.map { $0.description }.joined(separator: ", ")
throw TemplateSyntaxError("'\(templateName)' template not found in \(paths)")
}

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2014 Cocode. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@@ -7,14 +7,14 @@ class BlockContext {
self.blocks = blocks
}
func pop(blockName: String) -> BlockNode? {
return blocks.removeValueForKey(blockName)
func pop(_ blockName: String) -> BlockNode? {
return blocks.removeValue(forKey: blockName)
}
}
extension CollectionType {
func any(closure: Generator.Element -> Bool) -> Generator.Element? {
extension Collection {
func any(_ closure: (Iterator.Element) -> Bool) -> Iterator.Element? {
for element in self {
if closure(element) {
return element
@@ -30,7 +30,7 @@ class ExtendsNode : NodeType {
let templateName: Variable
let blocks: [String:BlockNode]
class func parse(parser: TokenParser, token: Token) throws -> NodeType {
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
let bits = token.components()
guard bits.count == 2 else {
@@ -59,7 +59,7 @@ class ExtendsNode : NodeType {
self.blocks = blocks
}
func render(context: Context) throws -> String {
func render(_ context: Context) throws -> String {
guard let loader = context["loader"] as? TemplateLoader else {
throw TemplateSyntaxError("Template loader not in context")
}
@@ -69,15 +69,14 @@ class ExtendsNode : NodeType {
}
guard let template = loader.loadTemplate(templateName) else {
let paths:String = loader.paths.map { $0.description }.joinWithSeparator(", ")
let paths:String = loader.paths.map { $0.description }.joined(separator: ", ")
throw TemplateSyntaxError("'\(templateName)' template not found in \(paths)")
}
let blockContext = BlockContext(blocks: blocks)
context.push([BlockContext.contextKey: blockContext])
let result = try template.render(context)
context.pop()
return result
return try context.push(dictionary: [BlockContext.contextKey: blockContext]) {
return try template.render(context)
}
}
}
@@ -86,7 +85,7 @@ class BlockNode : NodeType {
let name: String
let nodes: [NodeType]
class func parse(parser: TokenParser, token: Token) throws -> NodeType {
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
let bits = token.components()
guard bits.count == 2 else {
@@ -95,7 +94,7 @@ class BlockNode : NodeType {
let blockName = bits[1]
let nodes = try parser.parse(until(["endblock"]))
parser.nextToken()
_ = parser.nextToken()
return BlockNode(name:blockName, nodes:nodes)
}
@@ -104,8 +103,8 @@ class BlockNode : NodeType {
self.nodes = nodes
}
func render(context: Context) throws -> String {
if let blockContext = context[BlockContext.contextKey] as? BlockContext, node = blockContext.pop(name) {
func render(_ context: Context) throws -> String {
if let blockContext = context[BlockContext.contextKey] as? BlockContext, let node = blockContext.pop(name) {
return try node.render(context)
}

View File

@@ -7,18 +7,20 @@ public struct Lexer {
func createToken(string:String) -> Token {
func strip() -> String {
return string[string.startIndex.successor().successor()..<string.endIndex.predecessor().predecessor()].trim(" ")
let start = string.index(string.startIndex, offsetBy: 2)
let end = string.index(string.endIndex, offsetBy: -2)
return string[start..<end].trim(character: " ")
}
if string.hasPrefix("{{") {
return Token.Variable(value: strip())
return .variable(value: strip())
} else if string.hasPrefix("{%") {
return Token.Block(value: strip())
return .block(value: strip())
} else if string.hasPrefix("{#") {
return Token.Comment(value: strip())
return .comment(value: strip())
}
return Token.Text(value: string)
return .text(value: string)
}
/// Returns an array of tokens from a given template string.
@@ -36,14 +38,14 @@ public struct Lexer {
while !scanner.isEmpty {
if let text = scanner.scan(until: ["{{", "{%", "{#"]) {
if !text.1.isEmpty {
tokens.append(createToken(text.1))
tokens.append(createToken(string: text.1))
}
let end = map[text.0]!
let result = scanner.scan(until: end, returnUntil: true)
tokens.append(createToken(result))
tokens.append(createToken(string: result))
} else {
tokens.append(createToken(scanner.content))
tokens.append(createToken(string: scanner.content))
scanner.content = ""
}
}
@@ -64,49 +66,50 @@ class Scanner {
return content.isEmpty
}
func scan(until until: String, returnUntil: Bool = false) -> String {
func scan(until: String, returnUntil: Bool = false) -> String {
if until.isEmpty {
return ""
}
var index = content.startIndex
while index != content.endIndex {
let substring = content[index..<content.endIndex]
let substring = content.substring(from: index)
if substring.hasPrefix(until) {
let result = content[content.startIndex..<index]
let result = content.substring(to: index)
content = substring
if returnUntil {
content = content[until.endIndex..<content.endIndex]
content = content.substring(from: until.endIndex)
return result + until
}
return result
}
index = index.successor()
index = content.index(after: index)
}
return ""
}
func scan(until until: [String]) -> (String, String)? {
func scan(until: [String]) -> (String, String)? {
if until.isEmpty {
return nil
}
var index = content.startIndex
while index != content.endIndex {
let substring = content[index..<content.endIndex]
let substring = content.substring(from: index)
for string in until {
if substring.hasPrefix(string) {
let result = content[content.startIndex..<index]
let result = content.substring(to: index)
content = substring
return (string, result)
}
}
index = index.successor()
index = content.index(after: index)
}
return nil
@@ -117,31 +120,33 @@ class Scanner {
extension String {
func findFirstNot(character: Character) -> String.Index? {
var index = startIndex
while index != endIndex {
if character != self[index] {
return index
}
index = index.successor()
index = self.index(after: index)
}
return nil
}
func findLastNot(character: Character) -> String.Index? {
var index = endIndex.predecessor()
var index = self.index(before: endIndex)
while index != startIndex {
if character != self[index] {
return index.successor()
return self.index(after: index)
}
index = index.predecessor()
index = self.index(before: index)
}
return nil
}
func trim(character: Character) -> String {
let first = findFirstNot(character) ?? startIndex
let last = findLastNot(character) ?? endIndex
let first = findFirstNot(character: character) ?? startIndex
let last = findLastNot(character: character) ?? endIndex
return self[first..<last]
}
}

View File

@@ -1,4 +1,4 @@
public class Namespace {
open class Namespace {
public typealias TagParser = (TokenParser, Token) throws -> NodeType
var tags = [String: TagParser]()
@@ -9,7 +9,7 @@ public class Namespace {
registerDefaultFilters()
}
private func registerDefaultTags() {
fileprivate func registerDefaultTags() {
registerTag("for", parser: ForNode.parse)
registerTag("if", parser: IfNode.parse)
registerTag("ifnot", parser: IfNode.parse_ifnot)
@@ -21,26 +21,26 @@ public class Namespace {
registerTag("block", parser: BlockNode.parse)
}
private func registerDefaultFilters() {
fileprivate 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) {
open func registerTag(_ name: String, parser: @escaping TagParser) {
tags[name] = parser
}
/// Registers a simple template tag with a name and a handler
public func registerSimpleTag(name: String, handler: Context throws -> String) {
open func registerSimpleTag(_ name: String, handler: @escaping (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) {
open func registerFilter(_ name: String, filter: @escaping Filter) {
filters[name] = filter
}
}

View File

@@ -1,6 +1,7 @@
import Foundation
public struct TemplateSyntaxError : ErrorType, Equatable, CustomStringConvertible {
public struct TemplateSyntaxError : Error, Equatable, CustomStringConvertible {
public let description:String
public init(_ description:String) {
@@ -8,50 +9,56 @@ public struct TemplateSyntaxError : ErrorType, Equatable, CustomStringConvertibl
}
}
public func ==(lhs:TemplateSyntaxError, rhs:TemplateSyntaxError) -> Bool {
return lhs.description == rhs.description
}
public protocol NodeType {
/// Render the node in the given context
func render(context:Context) throws -> String
func render(_ context:Context) throws -> String
}
/// Render the collection of nodes in the given context
public func renderNodes(nodes:[NodeType], _ context:Context) throws -> String {
return try nodes.map { try $0.render(context) }.joinWithSeparator("")
public func renderNodes(_ nodes:[NodeType], _ context:Context) throws -> String {
return try nodes.map { try $0.render(context) }.joined(separator: "")
}
public class SimpleNode : NodeType {
let handler:Context throws -> String
open class SimpleNode : NodeType {
let handler:(Context) throws -> String
public init(handler:Context throws -> String) {
public init(handler:@escaping (Context) throws -> String) {
self.handler = handler
}
public func render(context: Context) throws -> String {
open func render(_ context: Context) throws -> String {
return try handler(context)
}
}
public class TextNode : NodeType {
public let text:String
open class TextNode : NodeType {
open let text:String
public init(text:String) {
self.text = text
}
public func render(context:Context) throws -> String {
open func render(_ context:Context) throws -> String {
return self.text
}
}
public protocol Resolvable {
func resolve(context: Context) throws -> Any?
func resolve(_ context: Context) throws -> Any?
}
public class VariableNode : NodeType {
public let variable: Resolvable
open class VariableNode : NodeType {
open let variable: Resolvable
public init(variable: Resolvable) {
self.variable = variable
@@ -61,7 +68,7 @@ public class VariableNode : NodeType {
self.variable = Variable(variable)
}
public func render(context: Context) throws -> String {
open func render(_ context: Context) throws -> String {
let result = try variable.resolve(context)
if let result = result as? String {
@@ -75,191 +82,3 @@ public class VariableNode : NodeType {
return ""
}
}
#if !os(Linux)
public class NowNode : NodeType {
public let format:Variable
public class func parse(parser:TokenParser, token:Token) throws -> NodeType {
var format:Variable?
let components = token.components()
guard components.count <= 2 else {
throw TemplateSyntaxError("'now' tags may only have one argument: the format string `\(token.contents)`.")
}
if components.count == 2 {
format = Variable(components[1])
}
return NowNode(format:format)
}
public init(format:Variable?) {
self.format = format ?? Variable("\"yyyy-MM-dd 'at' HH:mm\"")
}
public func render(context: Context) throws -> String {
let date = NSDate()
let format = try self.format.resolve(context)
var formatter:NSDateFormatter?
if let format = format as? NSDateFormatter {
formatter = format
} else if let format = format as? String {
formatter = NSDateFormatter()
formatter!.dateFormat = format
} else {
return ""
}
return formatter!.stringFromDate(date)
}
}
#endif
public class ForNode : NodeType {
let variable:Variable
let loopVariable:String
let nodes:[NodeType]
let emptyNodes: [NodeType]
public class func parse(parser:TokenParser, token:Token) throws -> NodeType {
let components = token.components()
guard components.count == 4 && components[2] == "in" else {
throw TemplateSyntaxError("'for' statements should use the following 'for x in y' `\(token.contents)`.")
}
let loopVariable = components[1]
let variable = components[3]
var emptyNodes = [NodeType]()
let forNodes = try parser.parse(until(["endfor", "empty"]))
guard let token = parser.nextToken() else {
throw TemplateSyntaxError("`endfor` was not found.")
}
if token.contents == "empty" {
emptyNodes = try parser.parse(until(["endfor"]))
parser.nextToken()
}
return ForNode(variable: variable, loopVariable: loopVariable, nodes: forNodes, emptyNodes:emptyNodes)
}
public init(variable:String, loopVariable:String, nodes:[NodeType], emptyNodes:[NodeType]) {
self.variable = Variable(variable)
self.loopVariable = loopVariable
self.nodes = nodes
self.emptyNodes = emptyNodes
}
public func render(context: Context) throws -> String {
let values = try variable.resolve(context)
if let values = values as? [Any] where values.count > 0 {
let count = values.count
return try values.enumerate().map { index, item in
let forContext: [String: Any] = [
"first": index == 0,
"last": index == (count - 1),
"counter": index + 1,
]
return try context.push([loopVariable: item, "forloop": forContext]) {
try renderNodes(nodes, context)
}
}.joinWithSeparator("")
}
return try context.push {
try renderNodes(emptyNodes, context)
}
}
}
public class IfNode : NodeType {
public let variable:Variable
public let trueNodes:[NodeType]
public let falseNodes:[NodeType]
public class func parse(parser:TokenParser, token:Token) throws -> NodeType {
let components = token.components()
guard components.count == 2 else {
throw TemplateSyntaxError("'if' statements should use the following 'if condition' `\(token.contents)`.")
}
let variable = components[1]
var trueNodes = [NodeType]()
var falseNodes = [NodeType]()
trueNodes = try parser.parse(until(["endif", "else"]))
guard let token = parser.nextToken() else {
throw TemplateSyntaxError("`endif` was not found.")
}
if token.contents == "else" {
falseNodes = try parser.parse(until(["endif"]))
parser.nextToken()
}
return IfNode(variable: variable, trueNodes: trueNodes, falseNodes: falseNodes)
}
public class func parse_ifnot(parser:TokenParser, token:Token) throws -> NodeType {
let components = token.components()
guard components.count == 2 else {
throw TemplateSyntaxError("'ifnot' statements should use the following 'if condition' `\(token.contents)`.")
}
let variable = components[1]
var trueNodes = [NodeType]()
var falseNodes = [NodeType]()
falseNodes = try parser.parse(until(["endif", "else"]))
guard let token = parser.nextToken() else {
throw TemplateSyntaxError("`endif` was not found.")
}
if token.contents == "else" {
trueNodes = try parser.parse(until(["endif"]))
parser.nextToken()
}
return IfNode(variable: variable, trueNodes: trueNodes, falseNodes: falseNodes)
}
public init(variable:String, trueNodes:[NodeType], falseNodes:[NodeType]) {
self.variable = Variable(variable)
self.trueNodes = trueNodes
self.falseNodes = falseNodes
}
public func render(context: Context) throws -> String {
let result = try variable.resolve(context)
var truthy = false
if let result = result as? [Any] {
truthy = !result.isEmpty
} else if let result = result as? [String:Any] {
truthy = !result.isEmpty
} else if result != nil {
truthy = true
}
context.push()
let output:String
if truthy {
output = try renderNodes(trueNodes, context)
} else {
output = try renderNodes(falseNodes, context)
}
context.pop()
return output
}
}

43
Sources/NowTag.swift Normal file
View File

@@ -0,0 +1,43 @@
#if !os(Linux)
import Foundation
open class NowNode : NodeType {
open let format:Variable
open class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
var format:Variable?
let components = token.components()
guard components.count <= 2 else {
throw TemplateSyntaxError("'now' tags may only have one argument: the format string `\(token.contents)`.")
}
if components.count == 2 {
format = Variable(components[1])
}
return NowNode(format:format)
}
public init(format:Variable?) {
self.format = format ?? Variable("\"yyyy-MM-dd 'at' HH:mm\"")
}
open func render(_ context: Context) throws -> String {
let date = Date()
let format = try self.format.resolve(context)
var formatter:DateFormatter?
if let format = format as? DateFormatter {
formatter = format
} else if let format = format as? String {
formatter = DateFormatter()
formatter!.dateFormat = format
} else {
return ""
}
return formatter!.string(from: date)
}
}
#endif

View File

@@ -1,4 +1,4 @@
public func until(tags: [String]) -> ((TokenParser, Token) -> Bool) {
public func until(_ tags: [String]) -> ((TokenParser, Token) -> Bool) {
return { parser, token in
if let name = token.components().first {
for tag in tags {
@@ -12,14 +12,14 @@ public func until(tags: [String]) -> ((TokenParser, Token) -> Bool) {
}
}
public typealias Filter = Any? throws -> Any?
public typealias Filter = (Any?) throws -> Any?
/// A class for parsing an array of tokens and converts them into a collection of Node's
public class TokenParser {
open class TokenParser {
public typealias TagParser = (TokenParser, Token) throws -> NodeType
private var tokens: [Token]
private let namespace: Namespace
fileprivate var tokens: [Token]
fileprivate let namespace: Namespace
public init(tokens: [Token], namespace: Namespace) {
self.tokens = tokens
@@ -27,25 +27,25 @@ public class TokenParser {
}
/// Parse the given tokens into nodes
public func parse() throws -> [NodeType] {
open func parse() throws -> [NodeType] {
return try parse(nil)
}
public func parse(parse_until:((parser:TokenParser, token:Token) -> (Bool))?) throws -> [NodeType] {
open func parse(_ parse_until:((_ parser:TokenParser, _ token:Token) -> (Bool))?) throws -> [NodeType] {
var nodes = [NodeType]()
while tokens.count > 0 {
let token = nextToken()!
switch token {
case .Text(let text):
case .text(let text):
nodes.append(TextNode(text: text))
case .Variable:
case .variable:
nodes.append(VariableNode(variable: try compileFilter(token.contents)))
case .Block:
case .block:
let tag = token.components().first
if let parse_until = parse_until where parse_until(parser: self, token: token) {
if let parse_until = parse_until , parse_until(self, token) {
prependToken(token)
return nodes
}
@@ -57,7 +57,7 @@ public class TokenParser {
throw TemplateSyntaxError("Unknown template tag '\(tag)'")
}
}
case .Comment:
case .comment:
continue
}
}
@@ -65,19 +65,19 @@ public class TokenParser {
return nodes
}
public func nextToken() -> Token? {
open func nextToken() -> Token? {
if tokens.count > 0 {
return tokens.removeAtIndex(0)
return tokens.remove(at: 0)
}
return nil
}
public func prependToken(token:Token) {
tokens.insert(token, atIndex: 0)
open func prependToken(_ token:Token) {
tokens.insert(token, at: 0)
}
public func findFilter(name: String) throws -> Filter {
open func findFilter(_ name: String) throws -> Filter {
if let filter = namespace.filters[name] {
return filter
}
@@ -85,7 +85,7 @@ public class TokenParser {
throw TemplateSyntaxError("Invalid filter '\(name)'")
}
func compileFilter(token: String) throws -> Resolvable {
func compileFilter(_ token: String) throws -> Resolvable {
return try FilterExpression(token: token, parser: self)
}
}

View File

@@ -6,13 +6,13 @@ let NSFileNoSuchFileError = 4
#endif
/// A class representing a template
public class Template {
open class Template {
let tokens: [Token]
/// Create a template with the given name inside the given bundle
public convenience init(named:String, inBundle bundle:NSBundle? = nil) throws {
let useBundle = bundle ?? NSBundle.mainBundle()
guard let url = useBundle.URLForResource(named, withExtension: nil) else {
public convenience init(named:String, inBundle bundle:Bundle? = nil) throws {
let useBundle = bundle ?? Bundle.main
guard let url = useBundle.url(forResource: named, withExtension: nil) else {
throw NSError(domain: NSCocoaErrorDomain, code: NSFileNoSuchFileError, userInfo: nil)
}
@@ -20,8 +20,8 @@ public class Template {
}
/// Create a template with a file found at the given URL
public convenience init(URL:NSURL) throws {
try self.init(path: Path(URL.path!))
public convenience init(URL:Foundation.URL) throws {
try self.init(path: Path(URL.path))
}
/// Create a template with a file found at the given path
@@ -36,9 +36,10 @@ public class Template {
}
/// Render the given template
public func render(context: Context? = nil, namespace: Namespace? = nil) throws -> String {
let parser = TokenParser(tokens: tokens, namespace: namespace ?? Namespace())
open func render(_ context: Context? = nil) throws -> String {
let context = context ?? Context()
let parser = TokenParser(tokens: tokens, namespace: context.namespace)
let nodes = try parser.parse()
return try renderNodes(nodes, context ?? Context())
return try renderNodes(nodes, context)
}
}

View File

@@ -3,24 +3,24 @@ import PathKit
// A class for loading a template from disk
public class TemplateLoader {
public let paths: [Path]
open class TemplateLoader {
open let paths: [Path]
public init(paths: [Path]) {
self.paths = paths
}
public init(bundle: [NSBundle]) {
public init(bundle: [Bundle]) {
self.paths = bundle.map {
return Path($0.bundlePath)
}
}
public func loadTemplate(templateName: String) -> Template? {
open func loadTemplate(_ templateName: String) -> Template? {
return loadTemplate([templateName])
}
public func loadTemplate(templateNames: [String]) -> Template? {
open func loadTemplate(_ templateNames: [String]) -> Template? {
for path in paths {
for templateName in templateNames {
let templatePath = path + Path(templateName)

View File

@@ -2,7 +2,7 @@ import Foundation
/// Split a string by spaces leaving quoted phrases together
func smartSplit(value: String) -> [String] {
func smartSplit(_ value: String) -> [String] {
var word = ""
var separator: Character = " "
var components: [String] = []
@@ -37,40 +37,40 @@ func smartSplit(value: String) -> [String] {
public enum Token : Equatable {
/// A token representing a piece of text.
case Text(value: String)
case text(value: String)
/// A token representing a variable.
case Variable(value: String)
case variable(value: String)
/// A token representing a comment.
case Comment(value: String)
case comment(value: String)
/// A token representing a template block.
case Block(value: String)
case block(value: String)
/// Returns the underlying value as an array seperated by spaces
public func components() -> [String] {
switch self {
case .Block(let value):
case .block(let value):
return smartSplit(value)
case .Variable(let value):
case .variable(let value):
return smartSplit(value)
case .Text(let value):
case .text(let value):
return smartSplit(value)
case .Comment(let value):
case .comment(let value):
return smartSplit(value)
}
}
public var contents: String {
switch self {
case .Block(let value):
case .block(let value):
return value
case .Variable(let value):
case .variable(let value):
return value
case .Text(let value):
case .text(let value):
return value
case .Comment(let value):
case .comment(let value):
return value
}
}
@@ -79,13 +79,13 @@ public enum Token : Equatable {
public func == (lhs: Token, rhs: Token) -> Bool {
switch (lhs, rhs) {
case (.Text(let lhsValue), .Text(let rhsValue)):
case (.text(let lhsValue), .text(let rhsValue)):
return lhsValue == rhsValue
case (.Variable(let lhsValue), .Variable(let rhsValue)):
case (.variable(let lhsValue), .variable(let rhsValue)):
return lhsValue == rhsValue
case (.Block(let lhsValue), .Block(let rhsValue)):
case (.block(let lhsValue), .block(let rhsValue)):
return lhsValue == rhsValue
case (.Comment(let lhsValue), .Comment(let rhsValue)):
case (.comment(let lhsValue), .comment(let rhsValue)):
return lhsValue == rhsValue
default:
return false

View File

@@ -6,7 +6,7 @@ class FilterExpression : Resolvable {
let variable: Variable
init(token: String, parser: TokenParser) throws {
let bits = token.characters.split("|").map({ String($0).trim(" ") })
let bits = token.characters.split(separator: "|").map({ String($0).trim(character: " ") })
if bits.isEmpty {
filters = []
variable = Variable("")
@@ -14,7 +14,7 @@ class FilterExpression : Resolvable {
}
variable = Variable(bits[0])
let filterBits = bits[1 ..< bits.endIndex]
let filterBits = bits[bits.indices.suffix(from: 1)]
do {
filters = try filterBits.map { try parser.findFilter($0) }
@@ -24,7 +24,7 @@ class FilterExpression : Resolvable {
}
}
func resolve(context: Context) throws -> Any? {
func resolve(_ context: Context) throws -> Any? {
let result = try variable.resolve(context)
return try filters.reduce(result) { x, y in
@@ -42,17 +42,17 @@ public struct Variable : Equatable, Resolvable {
self.variable = variable
}
private func lookup() -> [String] {
return variable.characters.split(".").map(String.init)
fileprivate func lookup() -> [String] {
return variable.characters.split(separator: ".").map(String.init)
}
/// Resolve the variable in the given context
public func resolve(context: Context) throws -> Any? {
public func resolve(_ context: Context) throws -> Any? {
var current: Any? = context
if (variable.hasPrefix("'") && variable.hasSuffix("'")) || (variable.hasPrefix("\"") && variable.hasSuffix("\"")) {
// String literal
return variable[variable.startIndex.successor() ..< variable.endIndex.predecessor()]
return variable[variable.characters.index(after: variable.startIndex) ..< variable.characters.index(before: variable.endIndex)]
}
for bit in lookup() {
@@ -76,7 +76,7 @@ public struct Variable : Equatable, Resolvable {
#if os(Linux)
return nil
#else
current = object.valueForKey(bit)
current = object.value(forKey: bit)
#endif
} else {
return nil
@@ -92,7 +92,7 @@ public func ==(lhs: Variable, rhs: Variable) -> Bool {
}
func normalize(current: Any?) -> Any? {
func normalize(_ current: Any?) -> Any? {
if let current = current as? Normalizable {
return current.normalize()
}

View File

@@ -1,6 +1,6 @@
{
"name": "Stencil",
"version": "0.5.3",
"version": "0.6.0",
"summary": "Stencil is a simple and powerful template language for Swift.",
"homepage": "https://github.com/kylef/Stencil",
"license": {
@@ -13,17 +13,18 @@
"social_media_url": "http://twitter.com/kylefuller",
"source": {
"git": "https://github.com/kylef/Stencil.git",
"tag": "0.5.3"
"tag": "0.6.0"
},
"source_files": [
"Sources/*.swift"
],
"platforms": {
"ios": "8.0",
"osx": "10.9"
"osx": "10.9",
"tvos": "9.0"
},
"requires_arc": true,
"dependencies": {
"PathKit": [ "~> 0.6.0" ]
"PathKit": [ "~> 0.7.0" ]
}
}

3
Tests/LinuxMain.swift Normal file
View File

@@ -0,0 +1,3 @@
import StencilTests
stencilTests()

View File

@@ -27,35 +27,30 @@ func testContext() {
}
$0.it("allows you to retrieve a value from a parent") {
context.push()
try expect(context["name"] as? String) == "Kyle"
try context.push {
try expect(context["name"] as? String) == "Kyle"
}
}
$0.it("allows you to override a parent's value") {
context.push()
context["name"] = "Katie"
try expect(context["name"] as? String) == "Katie"
try context.push {
context["name"] = "Katie"
try expect(context["name"] as? String) == "Katie"
}
}
$0.it("allows you to pop to restore previous state") {
context.push()
context["name"] = "Katie"
context.pop()
context.push {
context["name"] = "Katie"
}
try expect(context["name"] as? String) == "Kyle"
}
$0.it("allows you to push a dictionary onto the stack") {
context.push(["name": "Katie"])
try expect(context["name"] as? String) == "Katie"
}
$0.it("allows you to push a dictionary and run a closure then restoring previous state") {
var didRun = false
try context.push(["name": "Katie"]) {
try context.push(dictionary: ["name": "Katie"]) {
didRun = true
try expect(context["name"] as? String) == "Katie"
}

View File

@@ -4,7 +4,7 @@ import Stencil
func testFilter() {
describe("template filters") {
let context = Context(dictionary: ["name": "Kyle"])
let context: [String: Any] = ["name": "Kyle"]
$0.it("allows you to register a custom filter") {
let template = Template(templateString: "{{ name|repeat }}")
@@ -18,7 +18,7 @@ func testFilter() {
return nil
}
let result = try template.render(context, namespace: namespace)
let result = try template.render(Context(dictionary: context, namespace: namespace))
try expect(result) == "Kyle Kyle"
}
@@ -29,7 +29,7 @@ func testFilter() {
throw TemplateSyntaxError("No Repeat")
}
try expect(try template.render(context, namespace: namespace)).toThrow(TemplateSyntaxError("No Repeat"))
try expect(try template.render(Context(dictionary: context, namespace: namespace))).toThrow(TemplateSyntaxError("No Repeat"))
}
$0.it("allows whitespace in expression") {

View File

@@ -6,12 +6,12 @@ func testIfNode() {
describe("IfNode") {
$0.describe("parsing") {
$0.it("can parse an if block") {
let tokens = [
Token.Block(value: "if value"),
Token.Text(value: "true"),
Token.Block(value: "else"),
Token.Text(value: "false"),
Token.Block(value: "endif")
let tokens: [Token] = [
.block(value: "if value"),
.text(value: "true"),
.block(value: "else"),
.text(value: "false"),
.block(value: "endif")
]
let parser = TokenParser(tokens: tokens, namespace: Namespace())
@@ -29,12 +29,12 @@ func testIfNode() {
}
$0.it("can parse an ifnot block") {
let tokens = [
Token.Block(value: "ifnot value"),
Token.Text(value: "false"),
Token.Block(value: "else"),
Token.Text(value: "true"),
Token.Block(value: "endif")
let tokens: [Token] = [
.block(value: "ifnot value"),
.text(value: "false"),
.block(value: "else"),
.text(value: "true"),
.block(value: "endif")
]
let parser = TokenParser(tokens: tokens, namespace: Namespace())
@@ -52,8 +52,8 @@ func testIfNode() {
}
$0.it("throws an error when parsing an if block without an endif") {
let tokens = [
Token.Block(value: "if value"),
let tokens: [Token] = [
.block(value: "if value"),
]
let parser = TokenParser(tokens: tokens, namespace: Namespace())
@@ -62,8 +62,8 @@ func testIfNode() {
}
$0.it("throws an error when parsing an ifnot without an endif") {
let tokens = [
Token.Block(value: "ifnot value"),
let tokens: [Token] = [
.block(value: "ifnot value"),
]
let parser = TokenParser(tokens: tokens, namespace: Namespace())
@@ -73,14 +73,26 @@ func testIfNode() {
}
$0.describe("rendering") {
let context = Context(dictionary: ["items": true])
$0.it("renders the truth when expression evaluates to true") {
let context = Context(dictionary: ["items": true])
let node = IfNode(variable: "items", trueNodes: [TextNode(text: "true")], falseNodes: [TextNode(text: "false")])
try expect(try node.render(context)) == "true"
}
$0.it("renders the false when expression evaluates to false") {
let context = Context(dictionary: ["items": false])
let node = IfNode(variable: "items", trueNodes: [TextNode(text: "true")], falseNodes: [TextNode(text: "false")])
try expect(try node.render(context)) == "false"
}
$0.it("renders the truth when expression is not nil") {
let context = Context(dictionary: ["known": "known"])
let node = IfNode(variable: "known", trueNodes: [TextNode(text: "true")], falseNodes: [TextNode(text: "false")])
try expect(try node.render(context)) == "true"
}
$0.it("renders the false when expression is nil") {
let context = Context(dictionary: [:])
let node = IfNode(variable: "unknown", trueNodes: [TextNode(text: "true")], falseNodes: [TextNode(text: "false")])
try expect(try node.render(context)) == "false"
}

View File

@@ -5,12 +5,12 @@ import PathKit
func testInclude() {
describe("Include") {
let path = Path(__FILE__) + ".." + ".." + "fixtures"
let path = Path(#file) + ".." + "fixtures"
let loader = TemplateLoader(paths: [path])
$0.describe("parsing") {
$0.it("throws an error when no template is given") {
let tokens = [ Token.Block(value: "include") ]
let tokens: [Token] = [ .block(value: "include") ]
let parser = TokenParser(tokens: tokens, namespace: Namespace())
let error = TemplateSyntaxError("'include' tag takes one argument, the template file to be included")
@@ -18,7 +18,7 @@ func testInclude() {
}
$0.it("can parse a valid include block") {
let tokens = [ Token.Block(value: "include \"test.html\"") ]
let tokens: [Token] = [ .block(value: "include \"test.html\"") ]
let parser = TokenParser(tokens: tokens, namespace: Namespace())
let nodes = try parser.parse()
@@ -33,7 +33,7 @@ func testInclude() {
let node = IncludeNode(templateName: Variable("\"test.html\""))
do {
try node.render(Context())
_ = try node.render(Context())
} catch {
try expect("\(error)") == "Template loader not in context"
}
@@ -43,7 +43,7 @@ func testInclude() {
let node = IncludeNode(templateName: Variable("\"unknown.html\""))
do {
try node.render(Context(dictionary: ["loader": loader]))
_ = try node.render(Context(dictionary: ["loader": loader]))
} catch {
try expect("\(error)".hasPrefix("'unknown.html' template not found")).to.beTrue()
}

View File

@@ -5,7 +5,7 @@ import PathKit
func testInheritence() {
describe("Inheritence") {
let path = Path(__FILE__) + ".." + ".." + "fixtures"
let path = Path(#file) + ".." + "fixtures"
let loader = TemplateLoader(paths: [path])
$0.it("can inherit from another template") {

View File

@@ -9,7 +9,7 @@ func testLexer() {
let tokens = lexer.tokenize()
try expect(tokens.count) == 1
try expect(tokens.first) == Token.Text(value: "Hello World")
try expect(tokens.first) == .text(value: "Hello World")
}
$0.it("can tokenize a comment") {
@@ -17,7 +17,7 @@ func testLexer() {
let tokens = lexer.tokenize()
try expect(tokens.count) == (1)
try expect(tokens.first) == Token.Comment(value: "Comment")
try expect(tokens.first) == .comment(value: "Comment")
}
$0.it("can tokenize a variable") {
@@ -25,7 +25,7 @@ func testLexer() {
let tokens = lexer.tokenize()
try expect(tokens.count) == 1
try expect(tokens.first) == Token.Variable(value: "Variable")
try expect(tokens.first) == .variable(value: "Variable")
}
$0.it("can tokenize a mixture of content") {
@@ -33,9 +33,9 @@ func testLexer() {
let tokens = lexer.tokenize()
try expect(tokens.count) == 3
try expect(tokens[0]) == Token.Text(value: "My name is ")
try expect(tokens[1]) == Token.Variable(value: "name")
try expect(tokens[2]) == Token.Text(value: ".")
try expect(tokens[0]) == Token.text(value: "My name is ")
try expect(tokens[1]) == Token.variable(value: "name")
try expect(tokens[2]) == Token.text(value: ".")
}
$0.it("can tokenize two variables without being greedy") {
@@ -43,8 +43,8 @@ func testLexer() {
let tokens = lexer.tokenize()
try expect(tokens.count) == 2
try expect(tokens[0]) == Token.Variable(value: "thing")
try expect(tokens[1]) == Token.Variable(value: "name")
try expect(tokens[0]) == Token.variable(value: "thing")
try expect(tokens[1]) == Token.variable(value: "name")
}
}
}

View File

@@ -3,7 +3,7 @@ import Stencil
class ErrorNode : NodeType {
func render(context: Context) throws -> String {
func render(_ context: Context) throws -> String {
throw TemplateSyntaxError("Custom Error")
}
}

View File

@@ -8,7 +8,7 @@ func testNowNode() {
describe("NowNode") {
$0.describe("parsing") {
$0.it("parses default format without any now arguments") {
let tokens = [ Token.Block(value: "now") ]
let tokens: [Token] = [ .block(value: "now") ]
let parser = TokenParser(tokens: tokens, namespace: Namespace())
let nodes = try parser.parse()
@@ -18,7 +18,7 @@ func testNowNode() {
}
$0.it("parses now with a format") {
let tokens = [ Token.Block(value: "now \"HH:mm\"") ]
let tokens: [Token] = [ .block(value: "now \"HH:mm\"") ]
let parser = TokenParser(tokens: tokens, namespace: Namespace())
let nodes = try parser.parse()
let node = nodes.first as? NowNode
@@ -31,9 +31,9 @@ func testNowNode() {
$0.it("renders the date") {
let node = NowNode(format: Variable("\"yyyy-MM-dd\""))
let formatter = NSDateFormatter()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let date = formatter.stringFromDate(NSDate())
let date = formatter.string(from: NSDate() as Date)
try expect(try node.render(Context())) == date
}

View File

@@ -6,7 +6,7 @@ func testTokenParser() {
describe("TokenParser") {
$0.it("can parse a text token") {
let parser = TokenParser(tokens: [
Token.Text(value: "Hello World")
.text(value: "Hello World")
], namespace: Namespace())
let nodes = try parser.parse()
@@ -18,7 +18,7 @@ func testTokenParser() {
$0.it("can parse a variable token") {
let parser = TokenParser(tokens: [
Token.Variable(value: "'name'")
.variable(value: "'name'")
], namespace: Namespace())
let nodes = try parser.parse()
@@ -30,7 +30,7 @@ func testTokenParser() {
$0.it("can parse a comment token") {
let parser = TokenParser(tokens: [
Token.Comment(value: "Secret stuff!")
.comment(value: "Secret stuff!")
], namespace: Namespace())
let nodes = try parser.parse()
@@ -44,7 +44,7 @@ func testTokenParser() {
}
let parser = TokenParser(tokens: [
Token.Block(value: "known"),
.block(value: "known"),
], namespace: namespace)
let nodes = try parser.parse()
@@ -53,7 +53,7 @@ func testTokenParser() {
$0.it("errors when parsing an unknown tag") {
let parser = TokenParser(tokens: [
Token.Block(value: "unknown"),
.block(value: "unknown"),
], namespace: Namespace())
try expect(try parser.parse()).toThrow(TemplateSyntaxError("Unknown template tag 'unknown'"))

View File

@@ -3,7 +3,7 @@ import Stencil
class CustomNode : NodeType {
func render(context:Context) throws -> String {
func render(_ context:Context) throws -> String {
return "Hello World"
}
}
@@ -46,7 +46,7 @@ func testStencil() {
return CustomNode()
}
let result = try template.render(namespace: namespace)
let result = try template.render(Context(namespace: namespace))
try expect(result) == "Hello World"
}
@@ -59,7 +59,7 @@ func testStencil() {
return "Hello World"
}
try expect(try template.render(namespace: namespace)) == "Hello World"
try expect(try template.render(Context(namespace: namespace))) == "Hello World"
}
}
}

View File

@@ -5,7 +5,7 @@ import PathKit
func testTemplateLoader() {
describe("TemplateLoader") {
let path = Path(__FILE__) + ".." + ".." + "Tests" + "fixtures"
let path = Path(#file) + ".." + "fixtures"
let loader = TemplateLoader(paths: [path])
$0.it("returns nil when a template cannot be found") {

View File

@@ -5,7 +5,7 @@ import Stencil
func testToken() {
describe("Token") {
$0.it("can split the contents into components") {
let token = Token.Text(value: "hello world")
let token = Token.text(value: "hello world")
let components = token.components()
try expect(components.count) == 2
@@ -14,7 +14,7 @@ func testToken() {
}
$0.it("can split the contents into components with single quoted strings") {
let token = Token.Text(value: "hello 'kyle fuller'")
let token = Token.text(value: "hello 'kyle fuller'")
let components = token.components()
try expect(components.count) == 2
@@ -23,7 +23,7 @@ func testToken() {
}
$0.it("can split the contents into components with double quoted strings") {
let token = Token.Text(value: "hello \"kyle fuller\"")
let token = Token.text(value: "hello \"kyle fuller\"")
let components = token.components()
try expect(components.count) == 2

View File

@@ -21,7 +21,7 @@ func testVariable() {
])
#if os(OSX)
context.push(["object": Object()])
context["object"] = Object()
#endif
$0.it("can resolve a string literal with double quotes") {

View File

@@ -0,0 +1,28 @@
import XCTest
public func stencilTests() {
testContext()
testFilter()
testLexer()
testToken()
testTokenParser()
testTemplateLoader()
testTemplate()
testVariable()
testNode()
testForNode()
testIfNode()
testNowNode()
testInclude()
testInheritence()
testStencil()
}
class StencilTests: XCTestCase {
func testRunStencilTests() {
stencilTests()
}
}

View File

@@ -1,15 +0,0 @@
testContext()
testFilter()
testLexer()
testToken()
testTokenParser()
testTemplateLoader()
testTemplate()
testVariable()
testNode()
testForNode()
testIfNode()
testNowNode()
testInclude()
testInheritence()
testStencil()