Add support for Swift 3.0

This commit is contained in:
Kyle Fuller
2016-09-13 19:09:58 +01:00
parent f393efbd0b
commit feff3b18b1
41 changed files with 252 additions and 229 deletions

View File

@@ -1 +1 @@
2.2 3.0-GM-CANDIDATE

View File

@@ -1,14 +1,10 @@
os: os:
- osx - osx
env:
- SWIFT_VERSION=2.2
language: generic language: generic
sudo: required sudo: required
dist: trusty dist: trusty
osx_image: xcode7.3 osx_image: xcode8
install: install:
- eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/9f442512a46d7a2af7b850d65a7e9bd31edfb09b/swiftenv-install.sh)" - eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/9f442512a46d7a2af7b850d65a7e9bd31edfb09b/swiftenv-install.sh)"
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sudo python -m ensurepip; fi
- sudo pip install swim
script: script:
- swim test - swift test

6
CHANGELOG.md Normal file
View File

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

View File

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

View File

@@ -14,10 +14,10 @@ public class Context {
self.namespace = namespace self.namespace = namespace
} }
public subscript(key: String) -> Any? { open subscript(key: String) -> Any? {
/// Retrieves a variable's value, starting at the current context and going upwards /// Retrieves a variable's value, starting at the current context and going upwards
get { get {
for dictionary in Array(dictionaries.reverse()) { for dictionary in Array(dictionaries.reversed()) {
if let value = dictionary[key] { if let value = dictionary[key] {
return value return value
} }
@@ -37,19 +37,19 @@ public class Context {
} }
/// Push a new level into the Context /// Push a new level into the Context
private func push(dictionary: [String: Any]? = nil) { fileprivate func push(_ dictionary: [String: Any]? = nil) {
dictionaries.append(dictionary ?? [:]) dictionaries.append(dictionary ?? [:])
} }
/// Pop the last level off of the Context /// Pop the last level off of the Context
private func pop() -> [String: Any]? { fileprivate func pop() -> [String: Any]? {
return dictionaries.popLast() return dictionaries.popLast()
} }
/// Push a new level onto the context for the duration of the execution of the given closure /// 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) push(dictionary)
defer { pop() } defer { _ = pop() }
return try closure() 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 { if let value = value as? String {
return value return value
} else if let value = value as? CustomStringConvertible { } else if let value = value as? CustomStringConvertible {
@@ -8,25 +8,25 @@ func toString(value: Any?) -> String? {
return nil return nil
} }
func capitalise(value: Any?) -> Any? { func capitalise(_ value: Any?) -> Any? {
if let value = toString(value) { if let value = toString(value) {
return value.capitalizedString return value.capitalized
} }
return value return value
} }
func uppercase(value: Any?) -> Any? { func uppercase(_ value: Any?) -> Any? {
if let value = toString(value) { if let value = toString(value) {
return value.uppercaseString return value.uppercased()
} }
return value return value
} }
func lowercase(value: Any?) -> Any? { func lowercase(_ value: Any?) -> Any? {
if let value = toString(value) { if let value = toString(value) {
return value.lowercaseString return value.lowercased()
} }
return value return value

View File

@@ -1,10 +1,10 @@
public class ForNode : NodeType { open class ForNode : NodeType {
let variable:Variable let variable:Variable
let loopVariable:String let loopVariable:String
let nodes:[NodeType] let nodes:[NodeType]
let emptyNodes: [NodeType] let emptyNodes: [NodeType]
public class func parse(parser:TokenParser, token:Token) throws -> NodeType { open class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
let components = token.components() let components = token.components()
guard components.count == 4 && components[2] == "in" else { guard components.count == 4 && components[2] == "in" else {
@@ -24,7 +24,7 @@ public class ForNode : NodeType {
if token.contents == "empty" { if token.contents == "empty" {
emptyNodes = try parser.parse(until(["endfor"])) emptyNodes = try parser.parse(until(["endfor"]))
parser.nextToken() _ = parser.nextToken()
} }
return ForNode(variable: variable, loopVariable: loopVariable, nodes: forNodes, emptyNodes:emptyNodes) return ForNode(variable: variable, loopVariable: loopVariable, nodes: forNodes, emptyNodes:emptyNodes)
@@ -37,22 +37,22 @@ public class ForNode : NodeType {
self.emptyNodes = emptyNodes self.emptyNodes = emptyNodes
} }
public func render(context: Context) throws -> String { open func render(_ context: Context) throws -> String {
let values = try variable.resolve(context) let values = try variable.resolve(context)
if let values = values as? [Any] where values.count > 0 { if let values = values as? [Any] , values.count > 0 {
let count = values.count let count = values.count
return try values.enumerate().map { index, item in return try values.enumerated().map { index, item in
let forContext: [String: Any] = [ let forContext: [String: Any] = [
"first": index == 0, "first": index == 0,
"last": index == (count - 1), "last": index == (count - 1),
"counter": index + 1, "counter": index + 1,
] ]
return try context.push([loopVariable: item, "forloop": forContext]) { return try context.push(dictionary: [loopVariable: item, "forloop": forContext]) {
try renderNodes(nodes, context) try renderNodes(nodes, context)
} }
}.joinWithSeparator("") }.joined(separator: "")
} }
return try context.push { return try context.push {

View File

@@ -1,9 +1,9 @@
public class IfNode : NodeType { open class IfNode : NodeType {
public let variable:Variable open let variable:Variable
public let trueNodes:[NodeType] open let trueNodes:[NodeType]
public let falseNodes:[NodeType] open let falseNodes:[NodeType]
public class func parse(parser:TokenParser, token:Token) throws -> NodeType { open class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
let components = token.components() let components = token.components()
guard components.count == 2 else { guard components.count == 2 else {
throw TemplateSyntaxError("'if' statements should use the following 'if condition' `\(token.contents)`.") throw TemplateSyntaxError("'if' statements should use the following 'if condition' `\(token.contents)`.")
@@ -20,13 +20,13 @@ public class IfNode : NodeType {
if token.contents == "else" { if token.contents == "else" {
falseNodes = try parser.parse(until(["endif"])) falseNodes = try parser.parse(until(["endif"]))
parser.nextToken() _ = parser.nextToken()
} }
return IfNode(variable: variable, trueNodes: trueNodes, falseNodes: falseNodes) return IfNode(variable: variable, trueNodes: trueNodes, falseNodes: falseNodes)
} }
public class func parse_ifnot(parser:TokenParser, token:Token) throws -> NodeType { open class func parse_ifnot(_ parser:TokenParser, token:Token) throws -> NodeType {
let components = token.components() let components = token.components()
guard components.count == 2 else { guard components.count == 2 else {
throw TemplateSyntaxError("'ifnot' statements should use the following 'ifnot condition' `\(token.contents)`.") throw TemplateSyntaxError("'ifnot' statements should use the following 'ifnot condition' `\(token.contents)`.")
@@ -43,7 +43,7 @@ public class IfNode : NodeType {
if token.contents == "else" { if token.contents == "else" {
trueNodes = try parser.parse(until(["endif"])) trueNodes = try parser.parse(until(["endif"]))
parser.nextToken() _ = parser.nextToken()
} }
return IfNode(variable: variable, trueNodes: trueNodes, falseNodes: falseNodes) return IfNode(variable: variable, trueNodes: trueNodes, falseNodes: falseNodes)
@@ -55,7 +55,7 @@ public class IfNode : NodeType {
self.falseNodes = falseNodes self.falseNodes = falseNodes
} }
public func render(context: Context) throws -> String { open func render(_ context: Context) throws -> String {
let result = try variable.resolve(context) let result = try variable.resolve(context)
var truthy = false var truthy = false

View File

@@ -1,10 +1,10 @@
import PathKit import PathKit
public class IncludeNode : NodeType { open class IncludeNode : NodeType {
public let templateName: Variable 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() let bits = token.components()
guard bits.count == 2 else { guard bits.count == 2 else {
@@ -18,7 +18,7 @@ public class IncludeNode : NodeType {
self.templateName = templateName 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 { guard let loader = context["loader"] as? TemplateLoader else {
throw TemplateSyntaxError("Template loader not in context") throw TemplateSyntaxError("Template loader not in context")
} }
@@ -28,7 +28,7 @@ public class IncludeNode : NodeType {
} }
guard let template = loader.loadTemplate(templateName) else { 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)") throw TemplateSyntaxError("'\(templateName)' template not found in \(paths)")
} }

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import Foundation import Foundation
public struct TemplateSyntaxError : ErrorType, Equatable, CustomStringConvertible { public struct TemplateSyntaxError : Error, Equatable, CustomStringConvertible {
public let description:String public let description:String
public init(_ description:String) { public init(_ description:String) {
@@ -17,48 +17,48 @@ public func ==(lhs:TemplateSyntaxError, rhs:TemplateSyntaxError) -> Bool {
public protocol NodeType { public protocol NodeType {
/// Render the node in the given context /// 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 /// Render the collection of nodes in the given context
public func renderNodes(nodes:[NodeType], _ context:Context) throws -> String { public func renderNodes(_ nodes:[NodeType], _ context:Context) throws -> String {
return try nodes.map { try $0.render(context) }.joinWithSeparator("") return try nodes.map { try $0.render(context) }.joined(separator: "")
} }
public class SimpleNode : NodeType { open class SimpleNode : NodeType {
let handler:Context throws -> String let handler:(Context) throws -> String
public init(handler:Context throws -> String) { public init(handler:@escaping (Context) throws -> String) {
self.handler = handler self.handler = handler
} }
public func render(context: Context) throws -> String { open func render(_ context: Context) throws -> String {
return try handler(context) return try handler(context)
} }
} }
public class TextNode : NodeType { open class TextNode : NodeType {
public let text:String open let text:String
public init(text:String) { public init(text:String) {
self.text = text self.text = text
} }
public func render(context:Context) throws -> String { open func render(_ context:Context) throws -> String {
return self.text return self.text
} }
} }
public protocol Resolvable { public protocol Resolvable {
func resolve(context: Context) throws -> Any? func resolve(_ context: Context) throws -> Any?
} }
public class VariableNode : NodeType { open class VariableNode : NodeType {
public let variable: Resolvable open let variable: Resolvable
public init(variable: Resolvable) { public init(variable: Resolvable) {
self.variable = variable self.variable = variable
@@ -68,7 +68,7 @@ public class VariableNode : NodeType {
self.variable = Variable(variable) self.variable = Variable(variable)
} }
public func render(context: Context) throws -> String { open func render(_ context: Context) throws -> String {
let result = try variable.resolve(context) let result = try variable.resolve(context)
if let result = result as? String { if let result = result as? String {

View File

@@ -2,10 +2,10 @@
import Foundation import Foundation
public class NowNode : NodeType { open class NowNode : NodeType {
public let format:Variable open let format:Variable
public class func parse(parser:TokenParser, token:Token) throws -> NodeType { open class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
var format:Variable? var format:Variable?
let components = token.components() let components = token.components()
@@ -23,21 +23,21 @@ public class NowNode : NodeType {
self.format = format ?? Variable("\"yyyy-MM-dd 'at' HH:mm\"") self.format = format ?? Variable("\"yyyy-MM-dd 'at' HH:mm\"")
} }
public func render(context: Context) throws -> String { open func render(_ context: Context) throws -> String {
let date = NSDate() let date = Date()
let format = try self.format.resolve(context) let format = try self.format.resolve(context)
var formatter:NSDateFormatter? var formatter:DateFormatter?
if let format = format as? NSDateFormatter { if let format = format as? DateFormatter {
formatter = format formatter = format
} else if let format = format as? String { } else if let format = format as? String {
formatter = NSDateFormatter() formatter = DateFormatter()
formatter!.dateFormat = format formatter!.dateFormat = format
} else { } else {
return "" return ""
} }
return formatter!.stringFromDate(date) return formatter!.string(from: date)
} }
} }
#endif #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 return { parser, token in
if let name = token.components().first { if let name = token.components().first {
for tag in tags { 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 /// 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 public typealias TagParser = (TokenParser, Token) throws -> NodeType
private var tokens: [Token] fileprivate var tokens: [Token]
private let namespace: Namespace fileprivate let namespace: Namespace
public init(tokens: [Token], namespace: Namespace) { public init(tokens: [Token], namespace: Namespace) {
self.tokens = tokens self.tokens = tokens
@@ -27,25 +27,25 @@ public class TokenParser {
} }
/// Parse the given tokens into nodes /// Parse the given tokens into nodes
public func parse() throws -> [NodeType] { open func parse() throws -> [NodeType] {
return try parse(nil) 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]() var nodes = [NodeType]()
while tokens.count > 0 { while tokens.count > 0 {
let token = nextToken()! let token = nextToken()!
switch token { switch token {
case .Text(let text): case .text(let text):
nodes.append(TextNode(text: text)) nodes.append(TextNode(text: text))
case .Variable: case .variable:
nodes.append(VariableNode(variable: try compileFilter(token.contents))) nodes.append(VariableNode(variable: try compileFilter(token.contents)))
case .Block: case .block:
let tag = token.components().first 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) prependToken(token)
return nodes return nodes
} }
@@ -57,7 +57,7 @@ public class TokenParser {
throw TemplateSyntaxError("Unknown template tag '\(tag)'") throw TemplateSyntaxError("Unknown template tag '\(tag)'")
} }
} }
case .Comment: case .comment:
continue continue
} }
} }
@@ -65,19 +65,19 @@ public class TokenParser {
return nodes return nodes
} }
public func nextToken() -> Token? { open func nextToken() -> Token? {
if tokens.count > 0 { if tokens.count > 0 {
return tokens.removeAtIndex(0) return tokens.remove(at: 0)
} }
return nil return nil
} }
public func prependToken(token:Token) { open func prependToken(_ token:Token) {
tokens.insert(token, atIndex: 0) 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] { if let filter = namespace.filters[name] {
return filter return filter
} }
@@ -85,7 +85,7 @@ public class TokenParser {
throw TemplateSyntaxError("Invalid filter '\(name)'") throw TemplateSyntaxError("Invalid filter '\(name)'")
} }
func compileFilter(token: String) throws -> Resolvable { func compileFilter(_ token: String) throws -> Resolvable {
return try FilterExpression(token: token, parser: self) return try FilterExpression(token: token, parser: self)
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,6 +24,6 @@
}, },
"requires_arc": true, "requires_arc": true,
"dependencies": { "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

@@ -50,7 +50,7 @@ func testContext() {
$0.it("allows you to push a dictionary and run a closure then restoring previous state") { $0.it("allows you to push a dictionary and run a closure then restoring previous state") {
var didRun = false var didRun = false
try context.push(["name": "Katie"]) { try context.push(dictionary: ["name": "Katie"]) {
didRun = true didRun = true
try expect(context["name"] as? String) == "Katie" try expect(context["name"] as? String) == "Katie"
} }

View File

@@ -6,12 +6,12 @@ func testIfNode() {
describe("IfNode") { describe("IfNode") {
$0.describe("parsing") { $0.describe("parsing") {
$0.it("can parse an if block") { $0.it("can parse an if block") {
let tokens = [ let tokens: [Token] = [
Token.Block(value: "if value"), .block(value: "if value"),
Token.Text(value: "true"), .text(value: "true"),
Token.Block(value: "else"), .block(value: "else"),
Token.Text(value: "false"), .text(value: "false"),
Token.Block(value: "endif") .block(value: "endif")
] ]
let parser = TokenParser(tokens: tokens, namespace: Namespace()) let parser = TokenParser(tokens: tokens, namespace: Namespace())
@@ -29,12 +29,12 @@ func testIfNode() {
} }
$0.it("can parse an ifnot block") { $0.it("can parse an ifnot block") {
let tokens = [ let tokens: [Token] = [
Token.Block(value: "ifnot value"), .block(value: "ifnot value"),
Token.Text(value: "false"), .text(value: "false"),
Token.Block(value: "else"), .block(value: "else"),
Token.Text(value: "true"), .text(value: "true"),
Token.Block(value: "endif") .block(value: "endif")
] ]
let parser = TokenParser(tokens: tokens, namespace: Namespace()) 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") { $0.it("throws an error when parsing an if block without an endif") {
let tokens = [ let tokens: [Token] = [
Token.Block(value: "if value"), .block(value: "if value"),
] ]
let parser = TokenParser(tokens: tokens, namespace: Namespace()) 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") { $0.it("throws an error when parsing an ifnot without an endif") {
let tokens = [ let tokens: [Token] = [
Token.Block(value: "ifnot value"), .block(value: "ifnot value"),
] ]
let parser = TokenParser(tokens: tokens, namespace: Namespace()) let parser = TokenParser(tokens: tokens, namespace: Namespace())

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ func testNowNode() {
describe("NowNode") { describe("NowNode") {
$0.describe("parsing") { $0.describe("parsing") {
$0.it("parses default format without any now arguments") { $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 parser = TokenParser(tokens: tokens, namespace: Namespace())
let nodes = try parser.parse() let nodes = try parser.parse()
@@ -18,7 +18,7 @@ func testNowNode() {
} }
$0.it("parses now with a format") { $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 parser = TokenParser(tokens: tokens, namespace: Namespace())
let nodes = try parser.parse() let nodes = try parser.parse()
let node = nodes.first as? NowNode let node = nodes.first as? NowNode
@@ -31,9 +31,9 @@ func testNowNode() {
$0.it("renders the date") { $0.it("renders the date") {
let node = NowNode(format: Variable("\"yyyy-MM-dd\"")) let node = NowNode(format: Variable("\"yyyy-MM-dd\""))
let formatter = NSDateFormatter() let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd" 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 try expect(try node.render(Context())) == date
} }

View File

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

View File

@@ -3,7 +3,7 @@ import Stencil
class CustomNode : NodeType { class CustomNode : NodeType {
func render(context:Context) throws -> String { func render(_ context:Context) throws -> String {
return "Hello World" return "Hello World"
} }
} }

View File

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

View File

@@ -5,7 +5,7 @@ import Stencil
func testToken() { func testToken() {
describe("Token") { describe("Token") {
$0.it("can split the contents into components") { $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() let components = token.components()
try expect(components.count) == 2 try expect(components.count) == 2
@@ -14,7 +14,7 @@ func testToken() {
} }
$0.it("can split the contents into components with single quoted strings") { $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() let components = token.components()
try expect(components.count) == 2 try expect(components.count) == 2
@@ -23,7 +23,7 @@ func testToken() {
} }
$0.it("can split the contents into components with double quoted strings") { $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() let components = token.components()
try expect(components.count) == 2 try expect(components.count) == 2

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()