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:
- osx
env:
- SWIFT_VERSION=2.2
language: generic
sudo: required
dist: trusty
osx_image: xcode7.3
osx_image: xcode8
install:
- 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:
- 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(
name: "Stencil",
dependencies: [
.Package(url: "https://github.com/kylef/PathKit.git", majorVersion: 0, minor: 6),
],
testDependencies: [
.Package(url: "https://github.com/kylef/Spectre.git", 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

@@ -14,10 +14,10 @@ public class Context {
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
get {
for dictionary in Array(dictionaries.reverse()) {
for dictionary in Array(dictionaries.reversed()) {
if let value = dictionary[key] {
return value
}
@@ -37,19 +37,19 @@ public class 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 ?? [:])
}
/// Pop the last level off of the Context
private 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)
defer { pop() }
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

View File

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

View File

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

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

@@ -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,12 +69,12 @@ 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)
return try context.push([BlockContext.contextKey: blockContext]) {
return try context.push(dictionary: [BlockContext.contextKey: blockContext]) {
return try template.render(context)
}
}
@@ -85,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 {
@@ -94,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)
}
@@ -103,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.substringFromIndex(index)
let substring = content.substring(from: index)
if substring.hasPrefix(until) {
let result = content.substringToIndex(index)
let result = content.substring(to: index)
content = substring
if returnUntil {
content = content.substringFromIndex(until.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.substringFromIndex(index)
let substring = content.substring(from: index)
for string in until {
if substring.hasPrefix(string) {
let result = content.substringToIndex(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,7 +1,7 @@
import Foundation
public struct TemplateSyntaxError : ErrorType, Equatable, CustomStringConvertible {
public struct TemplateSyntaxError : Error, Equatable, CustomStringConvertible {
public let description:String
public init(_ description:String) {
@@ -17,48 +17,48 @@ public func ==(lhs:TemplateSyntaxError, rhs:TemplateSyntaxError) -> Bool {
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
@@ -68,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 {

View File

@@ -2,10 +2,10 @@
import Foundation
public class NowNode : NodeType {
public let format:Variable
open class NowNode : NodeType {
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?
let components = token.components()
@@ -23,21 +23,21 @@ public class NowNode : NodeType {
self.format = format ?? Variable("\"yyyy-MM-dd 'at' HH:mm\"")
}
public func render(context: Context) throws -> String {
let date = NSDate()
open func render(_ context: Context) throws -> String {
let date = Date()
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
} else if let format = format as? String {
formatter = NSDateFormatter()
formatter = DateFormatter()
formatter!.dateFormat = format
} else {
return ""
}
return formatter!.stringFromDate(date)
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,7 +36,7 @@ public class 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 parser = TokenParser(tokens: tokens, namespace: context.namespace)
let nodes = try parser.parse()

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

@@ -24,6 +24,6 @@
},
"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

@@ -50,7 +50,7 @@ func testContext() {
$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

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

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"
}
}

View File

@@ -5,7 +5,7 @@ import PathKit
func testTemplateLoader() {
describe("TemplateLoader") {
let path = Path(__FILE__) + ".." + "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

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