diff --git a/.swift-version b/.swift-version index 8bbe6cf..2a2a444 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -2.2 +3.0-GM-CANDIDATE diff --git a/.travis.yml b/.travis.yml index 781552c..035fcf1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..cc6fe9f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# Stencil Changelog +## Master + +### Enhancements + +- Adds support for Swift 3.0. diff --git a/Package.swift b/Package.swift index 00638df..380d123 100644 --- a/Package.swift +++ b/Package.swift @@ -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), ] ) diff --git a/Sources/Context.swift b/Sources/Context.swift index 65fad03..0f98751 100644 --- a/Sources/Context.swift +++ b/Sources/Context.swift @@ -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(dictionary: [String: Any]? = nil, @noescape closure: (() throws -> Result)) rethrows -> Result { + open func push(dictionary: [String: Any]? = nil, closure: (() throws -> Result)) rethrows -> Result { push(dictionary) - defer { pop() } + defer { _ = pop() } return try closure() } } diff --git a/Sources/Filters.swift b/Sources/Filters.swift index d97b03f..6a6f3bb 100644 --- a/Sources/Filters.swift +++ b/Sources/Filters.swift @@ -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 diff --git a/Sources/ForTag.swift b/Sources/ForTag.swift index affbe90..c84a0af 100644 --- a/Sources/ForTag.swift +++ b/Sources/ForTag.swift @@ -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 { diff --git a/Sources/IfTag.swift b/Sources/IfTag.swift index 8dae896..3bd644b 100644 --- a/Sources/IfTag.swift +++ b/Sources/IfTag.swift @@ -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 diff --git a/Sources/Include.swift b/Sources/Include.swift index a5added..846fe1c 100644 --- a/Sources/Include.swift +++ b/Sources/Include.swift @@ -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)") } diff --git a/Sources/Inheritence.swift b/Sources/Inheritence.swift index ab07a8e..fd3ef73 100644 --- a/Sources/Inheritence.swift +++ b/Sources/Inheritence.swift @@ -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) } diff --git a/Sources/Lexer.swift b/Sources/Lexer.swift index 704dd4a..6dd9be2 100644 --- a/Sources/Lexer.swift +++ b/Sources/Lexer.swift @@ -7,18 +7,20 @@ public struct Lexer { func createToken(string:String) -> Token { func strip() -> String { - return string[string.startIndex.successor().successor().. 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.. 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 } } diff --git a/Sources/Node.swift b/Sources/Node.swift index 02a8376..97443f7 100644 --- a/Sources/Node.swift +++ b/Sources/Node.swift @@ -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 { diff --git a/Sources/NowTag.swift b/Sources/NowTag.swift index 711e664..cce7a13 100644 --- a/Sources/NowTag.swift +++ b/Sources/NowTag.swift @@ -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 diff --git a/Sources/Parser.swift b/Sources/Parser.swift index 1f433f5..864b304 100644 --- a/Sources/Parser.swift +++ b/Sources/Parser.swift @@ -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) } } diff --git a/Sources/Template.swift b/Sources/Template.swift index 367770f..aeb7c3d 100644 --- a/Sources/Template.swift +++ b/Sources/Template.swift @@ -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() diff --git a/Sources/TemplateLoader.swift b/Sources/TemplateLoader.swift index 99201f2..dcb6543 100644 --- a/Sources/TemplateLoader.swift +++ b/Sources/TemplateLoader.swift @@ -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) diff --git a/Sources/Tokenizer.swift b/Sources/Tokenizer.swift index 3b3c32e..c748868 100644 --- a/Sources/Tokenizer.swift +++ b/Sources/Tokenizer.swift @@ -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 diff --git a/Sources/Variable.swift b/Sources/Variable.swift index 291b529..1a4070b 100644 --- a/Sources/Variable.swift +++ b/Sources/Variable.swift @@ -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() } diff --git a/Stencil.podspec.json b/Stencil.podspec.json index 2075d11..f99cd05 100644 --- a/Stencil.podspec.json +++ b/Stencil.podspec.json @@ -24,6 +24,6 @@ }, "requires_arc": true, "dependencies": { - "PathKit": [ "~> 0.6.0" ] + "PathKit": [ "~> 0.7.0" ] } } diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift new file mode 100644 index 0000000..294b797 --- /dev/null +++ b/Tests/LinuxMain.swift @@ -0,0 +1,3 @@ +import StencilTests + +stencilTests() diff --git a/Tests/ContextSpec.swift b/Tests/StencilTests/ContextSpec.swift similarity index 96% rename from Tests/ContextSpec.swift rename to Tests/StencilTests/ContextSpec.swift index f0973a8..f4933e9 100644 --- a/Tests/ContextSpec.swift +++ b/Tests/StencilTests/ContextSpec.swift @@ -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" } diff --git a/Tests/FilterSpec.swift b/Tests/StencilTests/FilterSpec.swift similarity index 100% rename from Tests/FilterSpec.swift rename to Tests/StencilTests/FilterSpec.swift diff --git a/Tests/ForNodeSpec.swift b/Tests/StencilTests/ForNodeSpec.swift similarity index 100% rename from Tests/ForNodeSpec.swift rename to Tests/StencilTests/ForNodeSpec.swift diff --git a/Tests/IfNodeSpec.swift b/Tests/StencilTests/IfNodeSpec.swift similarity index 89% rename from Tests/IfNodeSpec.swift rename to Tests/StencilTests/IfNodeSpec.swift index 278699d..4a16bfe 100644 --- a/Tests/IfNodeSpec.swift +++ b/Tests/StencilTests/IfNodeSpec.swift @@ -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()) diff --git a/Tests/IncludeSpec.swift b/Tests/StencilTests/IncludeSpec.swift similarity index 85% rename from Tests/IncludeSpec.swift rename to Tests/StencilTests/IncludeSpec.swift index a20c41d..76bf47c 100644 --- a/Tests/IncludeSpec.swift +++ b/Tests/StencilTests/IncludeSpec.swift @@ -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() } diff --git a/Tests/InheritenceSpec.swift b/Tests/StencilTests/InheritenceSpec.swift similarity index 88% rename from Tests/InheritenceSpec.swift rename to Tests/StencilTests/InheritenceSpec.swift index 0b692b8..accd15a 100644 --- a/Tests/InheritenceSpec.swift +++ b/Tests/StencilTests/InheritenceSpec.swift @@ -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") { diff --git a/Tests/LexerSpec.swift b/Tests/StencilTests/LexerSpec.swift similarity index 66% rename from Tests/LexerSpec.swift rename to Tests/StencilTests/LexerSpec.swift index a5f6f79..bccd6ba 100644 --- a/Tests/LexerSpec.swift +++ b/Tests/StencilTests/LexerSpec.swift @@ -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") } } } diff --git a/Tests/NodeSpec.swift b/Tests/StencilTests/NodeSpec.swift similarity index 96% rename from Tests/NodeSpec.swift rename to Tests/StencilTests/NodeSpec.swift index c951e57..5406c3d 100644 --- a/Tests/NodeSpec.swift +++ b/Tests/StencilTests/NodeSpec.swift @@ -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") } } diff --git a/Tests/NowNodeSpec.swift b/Tests/StencilTests/NowNodeSpec.swift similarity index 82% rename from Tests/NowNodeSpec.swift rename to Tests/StencilTests/NowNodeSpec.swift index 912ad75..59653c9 100644 --- a/Tests/NowNodeSpec.swift +++ b/Tests/StencilTests/NowNodeSpec.swift @@ -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 } diff --git a/Tests/ParserSpec.swift b/Tests/StencilTests/ParserSpec.swift similarity index 87% rename from Tests/ParserSpec.swift rename to Tests/StencilTests/ParserSpec.swift index fdac74d..09e3918 100644 --- a/Tests/ParserSpec.swift +++ b/Tests/StencilTests/ParserSpec.swift @@ -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'")) diff --git a/Tests/StencilSpec.swift b/Tests/StencilTests/StencilSpec.swift similarity index 97% rename from Tests/StencilSpec.swift rename to Tests/StencilTests/StencilSpec.swift index 9d2a166..c7521e5 100644 --- a/Tests/StencilSpec.swift +++ b/Tests/StencilTests/StencilSpec.swift @@ -3,7 +3,7 @@ import Stencil class CustomNode : NodeType { - func render(context:Context) throws -> String { + func render(_ context:Context) throws -> String { return "Hello World" } } diff --git a/Tests/TemplateLoaderSpec.swift b/Tests/StencilTests/TemplateLoaderSpec.swift similarity index 92% rename from Tests/TemplateLoaderSpec.swift rename to Tests/StencilTests/TemplateLoaderSpec.swift index d280d3a..ae4eeb6 100644 --- a/Tests/TemplateLoaderSpec.swift +++ b/Tests/StencilTests/TemplateLoaderSpec.swift @@ -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") { diff --git a/Tests/TemplateSpec.swift b/Tests/StencilTests/TemplateSpec.swift similarity index 100% rename from Tests/TemplateSpec.swift rename to Tests/StencilTests/TemplateSpec.swift diff --git a/Tests/TokenSpec.swift b/Tests/StencilTests/TokenSpec.swift similarity index 83% rename from Tests/TokenSpec.swift rename to Tests/StencilTests/TokenSpec.swift index c084c57..c7f9db1 100644 --- a/Tests/TokenSpec.swift +++ b/Tests/StencilTests/TokenSpec.swift @@ -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 diff --git a/Tests/VariableSpec.swift b/Tests/StencilTests/VariableSpec.swift similarity index 100% rename from Tests/VariableSpec.swift rename to Tests/StencilTests/VariableSpec.swift diff --git a/Tests/StencilTests/XCTest.swift b/Tests/StencilTests/XCTest.swift new file mode 100644 index 0000000..789a317 --- /dev/null +++ b/Tests/StencilTests/XCTest.swift @@ -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() + } +} diff --git a/Tests/fixtures/base.html b/Tests/StencilTests/fixtures/base.html similarity index 100% rename from Tests/fixtures/base.html rename to Tests/StencilTests/fixtures/base.html diff --git a/Tests/fixtures/child.html b/Tests/StencilTests/fixtures/child.html similarity index 100% rename from Tests/fixtures/child.html rename to Tests/StencilTests/fixtures/child.html diff --git a/Tests/fixtures/test.html b/Tests/StencilTests/fixtures/test.html similarity index 100% rename from Tests/fixtures/test.html rename to Tests/StencilTests/fixtures/test.html diff --git a/Tests/main.swift b/Tests/main.swift deleted file mode 100644 index 421d40c..0000000 --- a/Tests/main.swift +++ /dev/null @@ -1,15 +0,0 @@ -testContext() -testFilter() -testLexer() -testToken() -testTokenParser() -testTemplateLoader() -testTemplate() -testVariable() -testNode() -testForNode() -testIfNode() -testNowNode() -testInclude() -testInheritence() -testStencil()