[Project] Use 2 spaces for indentation

This commit is contained in:
Kyle Fuller
2015-06-29 16:40:15 -07:00
parent 59bab00c97
commit 53d5a4f8c3
20 changed files with 1047 additions and 1045 deletions

View File

@@ -123,7 +123,9 @@
F0837D60120FB15CC00B9EBD /* Pods */,
BE2E2DF8F488C4669126E920 /* Frameworks */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
};
77FAAE5319F91E480029DC5E /* Products */ = {
isa = PBXGroup;

View File

@@ -2,51 +2,51 @@ import Foundation
/// A container for template variables.
public class Context : Equatable {
var dictionaries:[Dictionary<String, AnyObject>]
var dictionaries:[Dictionary<String, AnyObject>]
public init(dictionary:Dictionary<String, AnyObject>) {
dictionaries = [dictionary]
}
public init(dictionary:Dictionary<String, AnyObject>) {
dictionaries = [dictionary]
}
public init() {
dictionaries = []
}
public init() {
dictionaries = []
}
public subscript(key: String) -> AnyObject? {
/// Retrieves a variable's value, starting at the current context and going upwards
get {
for dictionary in reverse(dictionaries) {
if let value:AnyObject = dictionary[key] {
return value
}
}
return nil
public subscript(key: String) -> AnyObject? {
/// Retrieves a variable's value, starting at the current context and going upwards
get {
for dictionary in reverse(dictionaries) {
if let value:AnyObject = dictionary[key] {
return value
}
}
/// Set a variable in the current context, deleting the variable if it's nil
set(value) {
if dictionaries.count > 0 {
var dictionary = dictionaries.removeLast()
dictionary[key] = value
dictionaries.append(dictionary)
}
}
return nil
}
public func push() {
push(Dictionary<String, String>())
}
public func push(dictionary:Dictionary<String, String>) {
/// Set a variable in the current context, deleting the variable if it's nil
set(value) {
if dictionaries.count > 0 {
var dictionary = dictionaries.removeLast()
dictionary[key] = value
dictionaries.append(dictionary)
}
}
}
public func pop() {
dictionaries.removeLast()
}
public func push() {
push(Dictionary<String, String>())
}
public func push(dictionary:Dictionary<String, String>) {
dictionaries.append(dictionary)
}
public func pop() {
dictionaries.removeLast()
}
}
public func ==(lhs:Context, rhs:Context) -> Bool {
return lhs.dictionaries == rhs.dictionaries
return lhs.dictionaries == rhs.dictionaries
}

View File

@@ -1,57 +1,57 @@
import Foundation
public struct Lexer {
public let templateString:String
let regex = NSRegularExpression(pattern: "(\\{\\{.*?\\}\\}|\\{%.*?%\\}|\\{#.*?#\\})", options: nil, error: nil)!
public let templateString:String
let regex = NSRegularExpression(pattern: "(\\{\\{.*?\\}\\}|\\{%.*?%\\}|\\{#.*?#\\})", options: nil, error: nil)!
public init(templateString:String) {
self.templateString = templateString
public init(templateString:String) {
self.templateString = templateString
}
func createToken(string:String) -> Token {
func strip() -> String {
return string[string.startIndex.successor().successor()..<string.endIndex.predecessor().predecessor()].stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
}
func createToken(string:String) -> Token {
func strip() -> String {
return string[string.startIndex.successor().successor()..<string.endIndex.predecessor().predecessor()].stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
}
if string.hasPrefix("{{") {
return Token.Variable(value: strip())
} else if string.hasPrefix("{%") {
return Token.Block(value: strip())
} else if string.hasPrefix("{#") {
return Token.Comment(value: strip())
}
return Token.Text(value: string)
if string.hasPrefix("{{") {
return Token.Variable(value: strip())
} else if string.hasPrefix("{%") {
return Token.Block(value: strip())
} else if string.hasPrefix("{#") {
return Token.Comment(value: strip())
}
/// Returns an array of tokens from a given template string.
public func tokenize() -> [Token] {
// Unfortunately NSRegularExpression doesn't have a split.
// So here's a really terrible implementation
return Token.Text(value: string)
}
var tokens = [Token]()
/// Returns an array of tokens from a given template string.
public func tokenize() -> [Token] {
// Unfortunately NSRegularExpression doesn't have a split.
// So here's a really terrible implementation
let range = NSMakeRange(0, count(templateString))
var lastIndex = 0
let nsTemplateString = templateString as NSString
let options = NSMatchingOptions(0)
regex.enumerateMatchesInString(templateString, options: options, range: range) { (result, flags, b) in
if result.range.location != lastIndex {
let previousMatch = nsTemplateString.substringWithRange(NSMakeRange(lastIndex, result.range.location - lastIndex))
tokens.append(self.createToken(previousMatch))
}
var tokens = [Token]()
let match = nsTemplateString.substringWithRange(result.range)
tokens.append(self.createToken(match))
let range = NSMakeRange(0, count(templateString))
var lastIndex = 0
let nsTemplateString = templateString as NSString
let options = NSMatchingOptions(0)
regex.enumerateMatchesInString(templateString, options: options, range: range) { (result, flags, b) in
if result.range.location != lastIndex {
let previousMatch = nsTemplateString.substringWithRange(NSMakeRange(lastIndex, result.range.location - lastIndex))
tokens.append(self.createToken(previousMatch))
}
lastIndex = result.range.location + result.range.length
}
let match = nsTemplateString.substringWithRange(result.range)
tokens.append(self.createToken(match))
if lastIndex < count(templateString) {
let substring = (templateString as NSString).substringFromIndex(lastIndex)
tokens.append(Token.Text(value: substring))
}
return tokens
lastIndex = result.range.location + result.range.length
}
if lastIndex < count(templateString) {
let substring = (templateString as NSString).substringFromIndex(lastIndex)
tokens.append(Token.Text(value: substring))
}
return tokens
}
}

View File

@@ -1,304 +1,304 @@
import Foundation
struct NodeError : Error {
let token:Token
let message:String
let token:Token
let message:String
init(token:Token, message:String) {
self.token = token
self.message = message
}
init(token:Token, message:String) {
self.token = token
self.message = message
}
var description:String {
return "\(token.components().first!): \(message)"
}
var description:String {
return "\(token.components().first!): \(message)"
}
}
public protocol Node {
/// Return the node rendered as a string, or returns a failure
func render(context:Context) -> Result
/// Return the node rendered as a string, or returns a failure
func render(context:Context) -> Result
}
extension Array {
func map<U>(block:((Element) -> (U?, Error?))) -> ([U]?, Error?) {
var results = [U]()
func map<U>(block:((Element) -> (U?, Error?))) -> ([U]?, Error?) {
var results = [U]()
for item in self {
let (result, error) = block(item)
for item in self {
let (result, error) = block(item)
if let error = error {
return (nil, error)
} else if (result != nil) {
// let result = result exposing a bug in the Swift compier :(
results.append(result!)
}
}
return (results, nil)
if let error = error {
return (nil, error)
} else if (result != nil) {
// let result = result exposing a bug in the Swift compier :(
results.append(result!)
}
}
return (results, nil)
}
}
public func renderNodes(nodes:[Node], context:Context) -> Result {
var result = ""
var result = ""
for item in nodes {
switch item.render(context) {
case .Success(let string):
result += string
case .Error(let error):
return .Error(error)
}
for item in nodes {
switch item.render(context) {
case .Success(let string):
result += string
case .Error(let error):
return .Error(error)
}
}
return .Success(result)
return .Success(result)
}
public class SimpleNode : Node {
let handler:(Context) -> (Result)
let handler:(Context) -> (Result)
public init(handler:((Context) -> (Result))) {
self.handler = handler
}
public init(handler:((Context) -> (Result))) {
self.handler = handler
}
public func render(context:Context) -> Result {
return handler(context)
}
public func render(context:Context) -> Result {
return handler(context)
}
}
public class TextNode : Node {
public let text:String
public let text:String
public init(text:String) {
self.text = text
}
public init(text:String) {
self.text = text
}
public func render(context:Context) -> Result {
return .Success(self.text)
}
public func render(context:Context) -> Result {
return .Success(self.text)
}
}
public class VariableNode : Node {
public let variable:Variable
public let variable:Variable
public init(variable:Variable) {
self.variable = variable
public init(variable:Variable) {
self.variable = variable
}
public init(variable:String) {
self.variable = Variable(variable)
}
public func render(context:Context) -> Result {
let result:AnyObject? = variable.resolve(context)
if let result = result as? String {
return .Success(result)
} else if let result = result as? NSObject {
return .Success(result.description)
}
public init(variable:String) {
self.variable = Variable(variable)
}
public func render(context:Context) -> Result {
let result:AnyObject? = variable.resolve(context)
if let result = result as? String {
return .Success(result)
} else if let result = result as? NSObject {
return .Success(result.description)
}
return .Success("")
}
return .Success("")
}
}
public class NowNode : Node {
public let format:Variable
public let format:Variable
public class func parse(parser:TokenParser, token:Token) -> TokenParser.Result {
var format:Variable?
public class func parse(parser:TokenParser, token:Token) -> TokenParser.Result {
var format:Variable?
let components = token.components()
if components.count == 2 {
format = Variable(components[1])
}
return .Success(node:NowNode(format:format))
let components = token.components()
if components.count == 2 {
format = Variable(components[1])
}
public init(format:Variable?) {
if let format = format {
self.format = format
} else {
self.format = Variable("\"yyyy-MM-dd 'at' HH:mm\"")
}
return .Success(node:NowNode(format:format))
}
public init(format:Variable?) {
if let format = format {
self.format = format
} else {
self.format = Variable("\"yyyy-MM-dd 'at' HH:mm\"")
}
}
public func render(context: Context) -> Result {
let date = NSDate()
let format: AnyObject? = self.format.resolve(context)
var formatter:NSDateFormatter?
if let format = format as? NSDateFormatter {
formatter = format
} else if let format = format as? String {
formatter = NSDateFormatter()
formatter!.dateFormat = format
} else {
return .Success("")
}
public func render(context: Context) -> Result {
let date = NSDate()
let format: AnyObject? = self.format.resolve(context)
var formatter:NSDateFormatter?
if let format = format as? NSDateFormatter {
formatter = format
} else if let format = format as? String {
formatter = NSDateFormatter()
formatter!.dateFormat = format
} else {
return .Success("")
}
return .Success(formatter!.stringFromDate(date))
}
return .Success(formatter!.stringFromDate(date))
}
}
public class ForNode : Node {
let variable:Variable
let loopVariable:String
let nodes:[Node]
let variable:Variable
let loopVariable:String
let nodes:[Node]
public class func parse(parser:TokenParser, token:Token) -> TokenParser.Result {
let components = token.components()
public class func parse(parser:TokenParser, token:Token) -> TokenParser.Result {
let components = token.components()
if count(components) == 4 && components[2] == "in" {
let loopVariable = components[1]
let variable = components[3]
if count(components) == 4 && components[2] == "in" {
let loopVariable = components[1]
let variable = components[3]
var forNodes:[Node]!
var emptyNodes = [Node]()
var forNodes:[Node]!
var emptyNodes = [Node]()
switch parser.parse(until(["endfor", "empty"])) {
case .Success(let nodes):
forNodes = nodes
case .Error(let error):
return .Error(error: error)
}
switch parser.parse(until(["endfor", "empty"])) {
case .Success(let nodes):
forNodes = nodes
case .Error(let error):
return .Error(error: error)
}
if let token = parser.nextToken() {
if token.contents == "empty" {
switch parser.parse(until(["endfor"])) {
case .Success(let nodes):
emptyNodes = nodes
case .Error(let error):
return .Error(error: error)
}
if let token = parser.nextToken() {
if token.contents == "empty" {
switch parser.parse(until(["endfor"])) {
case .Success(let nodes):
emptyNodes = nodes
case .Error(let error):
return .Error(error: error)
}
parser.nextToken()
}
} else {
return .Error(error: NodeError(token: token, message: "`endfor` was not found."))
}
return .Success(node:ForNode(variable: variable, loopVariable: loopVariable, nodes: forNodes, emptyNodes:emptyNodes))
parser.nextToken()
}
} else {
return .Error(error: NodeError(token: token, message: "`endfor` was not found."))
}
return .Error(error: NodeError(token: token, message: "Invalid syntax. Expected `for x in y`."))
return .Success(node:ForNode(variable: variable, loopVariable: loopVariable, nodes: forNodes, emptyNodes:emptyNodes))
}
public init(variable:String, loopVariable:String, nodes:[Node], emptyNodes:[Node]) {
self.variable = Variable(variable)
self.loopVariable = loopVariable
self.nodes = nodes
}
return .Error(error: NodeError(token: token, message: "Invalid syntax. Expected `for x in y`."))
}
public func render(context: Context) -> Result {
let values = variable.resolve(context) as? [AnyObject]
var output = ""
public init(variable:String, loopVariable:String, nodes:[Node], emptyNodes:[Node]) {
self.variable = Variable(variable)
self.loopVariable = loopVariable
self.nodes = nodes
}
if let values = values {
for item in values {
context.push()
context[loopVariable] = item
let result = renderNodes(nodes, context)
context.pop()
public func render(context: Context) -> Result {
let values = variable.resolve(context) as? [AnyObject]
var output = ""
switch result {
case .Success(let string):
output += string
case .Error(let error):
return .Error(error)
}
}
if let values = values {
for item in values {
context.push()
context[loopVariable] = item
let result = renderNodes(nodes, context)
context.pop()
switch result {
case .Success(let string):
output += string
case .Error(let error):
return .Error(error)
}
return .Success(output)
}
}
return .Success(output)
}
}
public class IfNode : Node {
public let variable:Variable
public let trueNodes:[Node]
public let falseNodes:[Node]
public let variable:Variable
public let trueNodes:[Node]
public let falseNodes:[Node]
public class func parse(parser:TokenParser, token:Token) -> TokenParser.Result {
let variable = token.components()[1]
var trueNodes = [Node]()
var falseNodes = [Node]()
public class func parse(parser:TokenParser, token:Token) -> TokenParser.Result {
let variable = token.components()[1]
var trueNodes = [Node]()
var falseNodes = [Node]()
switch parser.parse(until(["endif", "else"])) {
case .Success(let nodes):
trueNodes = nodes
case .Error(let error):
return .Error(error: error)
}
if let token = parser.nextToken() {
if token.contents == "else" {
switch parser.parse(until(["endif"])) {
case .Success(let nodes):
falseNodes = nodes
case .Error(let error):
return .Error(error: error)
}
parser.nextToken()
}
} else {
return .Error(error:NodeError(token: token, message: "`endif` was not found."))
}
return .Success(node:IfNode(variable: variable, trueNodes: trueNodes, falseNodes: falseNodes))
switch parser.parse(until(["endif", "else"])) {
case .Success(let nodes):
trueNodes = nodes
case .Error(let error):
return .Error(error: error)
}
public class func parse_ifnot(parser:TokenParser, token:Token) -> TokenParser.Result {
let variable = token.components()[1]
var trueNodes = [Node]()
var falseNodes = [Node]()
switch parser.parse(until(["endif", "else"])) {
if let token = parser.nextToken() {
if token.contents == "else" {
switch parser.parse(until(["endif"])) {
case .Success(let nodes):
falseNodes = nodes
falseNodes = nodes
case .Error(let error):
return .Error(error: error)
return .Error(error: error)
}
if let token = parser.nextToken() {
if token.contents == "else" {
switch parser.parse(until(["endif"])) {
case .Success(let nodes):
trueNodes = nodes
case .Error(let error):
return .Error(error: error)
}
parser.nextToken()
}
} else {
return .Error(error:NodeError(token: token, message: "`endif` was not found."))
}
return .Success(node:IfNode(variable: variable, trueNodes: trueNodes, falseNodes: falseNodes))
parser.nextToken()
}
} else {
return .Error(error:NodeError(token: token, message: "`endif` was not found."))
}
public init(variable:String, trueNodes:[Node], falseNodes:[Node]) {
self.variable = Variable(variable)
self.trueNodes = trueNodes
self.falseNodes = falseNodes
return .Success(node:IfNode(variable: variable, trueNodes: trueNodes, falseNodes: falseNodes))
}
public class func parse_ifnot(parser:TokenParser, token:Token) -> TokenParser.Result {
let variable = token.components()[1]
var trueNodes = [Node]()
var falseNodes = [Node]()
switch parser.parse(until(["endif", "else"])) {
case .Success(let nodes):
falseNodes = nodes
case .Error(let error):
return .Error(error: error)
}
public func render(context: Context) -> Result {
let result: AnyObject? = variable.resolve(context)
var truthy = false
if let result = result as? [AnyObject] {
if result.count > 0 {
truthy = true
}
} else if let result: AnyObject = result {
truthy = true
if let token = parser.nextToken() {
if token.contents == "else" {
switch parser.parse(until(["endif"])) {
case .Success(let nodes):
trueNodes = nodes
case .Error(let error):
return .Error(error: error)
}
context.push()
let output = renderNodes(truthy ? trueNodes : falseNodes, context)
context.pop()
return output
parser.nextToken()
}
} else {
return .Error(error:NodeError(token: token, message: "`endif` was not found."))
}
return .Success(node:IfNode(variable: variable, trueNodes: trueNodes, falseNodes: falseNodes))
}
public init(variable:String, trueNodes:[Node], falseNodes:[Node]) {
self.variable = Variable(variable)
self.trueNodes = trueNodes
self.falseNodes = falseNodes
}
public func render(context: Context) -> Result {
let result: AnyObject? = variable.resolve(context)
var truthy = false
if let result = result as? [AnyObject] {
if result.count > 0 {
truthy = true
}
} else if let result: AnyObject = result {
truthy = true
}
context.push()
let output = renderNodes(truthy ? trueNodes : falseNodes, context)
context.pop()
return output
}
}

View File

@@ -1,109 +1,109 @@
import Foundation
public func until(tags:[String])(parser:TokenParser, token:Token) -> Bool {
if let name = token.components().first {
for tag in tags {
if name == tag {
return true
}
}
if let name = token.components().first {
for tag in tags {
if name == tag {
return true
}
}
}
return false
return false
}
/// A class for parsing an array of tokens and converts them into a collection of Node's
public class TokenParser {
public typealias TagParser = (TokenParser, Token) -> Result
public typealias NodeList = [Node]
public typealias TagParser = (TokenParser, Token) -> Result
public typealias NodeList = [Node]
public enum Result {
case Success(node: Node)
case Error(error: Stencil.Error)
}
public enum Result {
case Success(node: Node)
case Error(error: Stencil.Error)
}
public enum Results {
case Success(nodes: NodeList)
case Error(error: Stencil.Error)
}
public enum Results {
case Success(nodes: NodeList)
case Error(error: Stencil.Error)
}
private var tokens:[Token]
private var tags = Dictionary<String, TagParser>()
private var tokens:[Token]
private var tags = Dictionary<String, TagParser>()
public init(tokens:[Token]) {
self.tokens = tokens
registerTag("for", parser: ForNode.parse)
registerTag("if", parser: IfNode.parse)
registerTag("ifnot", parser: IfNode.parse_ifnot)
registerTag("now", parser: NowNode.parse)
registerTag("include", parser: IncludeNode.parse)
}
public init(tokens:[Token]) {
self.tokens = tokens
registerTag("for", parser: ForNode.parse)
registerTag("if", parser: IfNode.parse)
registerTag("ifnot", parser: IfNode.parse_ifnot)
registerTag("now", parser: NowNode.parse)
registerTag("include", parser: IncludeNode.parse)
}
/// Registers a new template tag
public func registerTag(name:String, parser:TagParser) {
tags[name] = parser
}
/// Registers a new template tag
public func registerTag(name:String, parser:TagParser) {
tags[name] = parser
}
/// Registers a simple template tag with a name and a handler
public func registerSimpleTag(name:String, handler:((Context) -> (Stencil.Result))) {
registerTag(name, parser: { (parser, token) -> TokenParser.Result in
return .Success(node:SimpleNode(handler: handler))
})
}
/// Registers a simple template tag with a name and a handler
public func registerSimpleTag(name:String, handler:((Context) -> (Stencil.Result))) {
registerTag(name, parser: { (parser, token) -> TokenParser.Result in
return .Success(node:SimpleNode(handler: handler))
})
}
/// Parse the given tokens into nodes
public func parse() -> Results {
return parse(nil)
}
/// Parse the given tokens into nodes
public func parse() -> Results {
return parse(nil)
}
public func parse(parse_until:((parser:TokenParser, token:Token) -> (Bool))?) -> TokenParser.Results {
var nodes = NodeList()
public func parse(parse_until:((parser:TokenParser, token:Token) -> (Bool))?) -> TokenParser.Results {
var nodes = NodeList()
while tokens.count > 0 {
let token = nextToken()!
while tokens.count > 0 {
let token = nextToken()!
switch token {
case .Text(let text):
nodes.append(TextNode(text: text))
case .Variable(let variable):
nodes.append(VariableNode(variable: variable))
case .Block(let value):
let tag = token.components().first
switch token {
case .Text(let text):
nodes.append(TextNode(text: text))
case .Variable(let variable):
nodes.append(VariableNode(variable: variable))
case .Block(let value):
let tag = token.components().first
if let parse_until = parse_until {
if parse_until(parser: self, token: token) {
prependToken(token)
return .Success(nodes:nodes)
}
}
if let parse_until = parse_until {
if parse_until(parser: self, token: token) {
prependToken(token)
return .Success(nodes:nodes)
}
}
if let tag = tag {
if let parser = self.tags[tag] {
switch parser(self, token) {
case .Success(let node):
nodes.append(node)
case .Error(let error):
return .Error(error:error)
}
}
}
case .Comment(let value):
continue
if let tag = tag {
if let parser = self.tags[tag] {
switch parser(self, token) {
case .Success(let node):
nodes.append(node)
case .Error(let error):
return .Error(error:error)
}
}
}
return .Success(nodes:nodes)
case .Comment(let value):
continue
}
}
public func nextToken() -> Token? {
if tokens.count > 0 {
return tokens.removeAtIndex(0)
}
return .Success(nodes:nodes)
}
return nil
public func nextToken() -> Token? {
if tokens.count > 0 {
return tokens.removeAtIndex(0)
}
public func prependToken(token:Token) {
tokens.insert(token, atIndex: 0)
}
return nil
}
public func prependToken(token:Token) {
tokens.insert(token, atIndex: 0)
}
}

View File

@@ -1,25 +1,25 @@
import Foundation
public protocol Error : Printable {
}
public func ==(lhs:Error, rhs:Error) -> Bool {
return lhs.description == rhs.description
return lhs.description == rhs.description
}
public enum Result : Equatable {
case Success(String)
case Error(Stencil.Error)
case Success(String)
case Error(Stencil.Error)
}
public func ==(lhs:Result, rhs:Result) -> Bool {
switch (lhs, rhs) {
case (.Success(let lhsValue), .Success(let rhsValue)):
return lhsValue == rhsValue
case (.Error(let lhsValue), .Error(let rhsValue)):
return lhsValue == rhsValue
default:
return false
}
switch (lhs, rhs) {
case (.Success(let lhsValue), .Success(let rhsValue)):
return lhsValue == rhsValue
case (.Error(let lhsValue), .Error(let rhsValue)):
return lhsValue == rhsValue
default:
return false
}
}

View File

@@ -3,71 +3,71 @@ import PathKit
/// A class representing a template
public class Template {
public let parser:TokenParser
public let parser:TokenParser
/// Create a template with the given name inside the main bundle
public convenience init?(named:String) {
self.init(named:named, inBundle:nil)
/// Create a template with the given name inside the main bundle
public convenience init?(named:String) {
self.init(named:named, inBundle:nil)
}
/// Create a template with the given name inside the given bundle
public convenience init?(named:String, inBundle bundle:NSBundle?) {
var url:NSURL?
if let bundle = bundle {
url = bundle.URLForResource(named, withExtension: nil)
} else {
url = NSBundle.mainBundle().URLForResource(named, withExtension: nil)
}
/// Create a template with the given name inside the given bundle
public convenience init?(named:String, inBundle bundle:NSBundle?) {
var url:NSURL?
self.init(URL:url!)
}
if let bundle = bundle {
url = bundle.URLForResource(named, withExtension: nil)
} else {
url = NSBundle.mainBundle().URLForResource(named, withExtension: nil)
}
self.init(URL:url!)
/// Create a template with a file found at the given URL
public convenience init?(URL:NSURL) {
var error:NSError?
let maybeTemplateString = NSString(contentsOfURL: URL, encoding: NSUTF8StringEncoding, error: &error)
if let templateString = maybeTemplateString {
self.init(templateString:templateString as String)
} else {
self.init(templateString:"")
return nil
}
}
/// Create a template with a file found at the given URL
public convenience init?(URL:NSURL) {
var error:NSError?
let maybeTemplateString = NSString(contentsOfURL: URL, encoding: NSUTF8StringEncoding, error: &error)
if let templateString = maybeTemplateString {
self.init(templateString:templateString as String)
} else {
self.init(templateString:"")
return nil
}
/// Create a template with a file found at the given path
public convenience init?(path:Path) {
var error:NSError?
if let string:String = path.read() {
self.init(templateString:string)
} else {
self.init(templateString:"")
return nil
}
}
/// Create a template with a file found at the given path
public convenience init?(path:Path) {
var error:NSError?
/// Create a template with a template string
public init(templateString:String) {
let lexer = Lexer(templateString: templateString)
let tokens = lexer.tokenize()
parser = TokenParser(tokens: tokens)
}
if let string:String = path.read() {
self.init(templateString:string)
} else {
self.init(templateString:"")
return nil
}
/// Render the given template in a context
public func render(context:Context) -> Result {
switch parser.parse() {
case .Success(let nodes):
return renderNodes(nodes, context)
case .Error(let error):
return .Error(error)
}
}
/// Create a template with a template string
public init(templateString:String) {
let lexer = Lexer(templateString: templateString)
let tokens = lexer.tokenize()
parser = TokenParser(tokens: tokens)
}
/// Render the given template in a context
public func render(context:Context) -> Result {
switch parser.parse() {
case .Success(let nodes):
return renderNodes(nodes, context)
case .Error(let error):
return .Error(error)
}
}
/// Render the given template without a context
public func render() -> Result {
let context = Context()
return render(context)
}
/// Render the given template without a context
public func render() -> Result {
let context = Context()
return render(context)
}
}

View File

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

View File

@@ -2,43 +2,43 @@ import Foundation
import PathKit
extension String : Error {
public var description:String {
return self
}
public var description:String {
return self
}
}
public class IncludeNode : Node {
public let templateName:String
public let templateName:String
public class func parse(parser:TokenParser, token:Token) -> TokenParser.Result {
let bits = token.contents.componentsSeparatedByString("\"")
public class func parse(parser:TokenParser, token:Token) -> TokenParser.Result {
let bits = token.contents.componentsSeparatedByString("\"")
if bits.count != 3 {
return .Error(error:NodeError(token: token, message: "Tag takes one argument, the template file to be included"))
}
return .Success(node:IncludeNode(templateName: bits[1]))
if bits.count != 3 {
return .Error(error:NodeError(token: token, message: "Tag takes one argument, the template file to be included"))
}
public init(templateName:String) {
self.templateName = templateName
return .Success(node:IncludeNode(templateName: bits[1]))
}
public init(templateName:String) {
self.templateName = templateName
}
public func render(context: Context) -> Result {
if let loader = context["loader"] as? TemplateLoader {
if let template = loader.loadTemplate(templateName) {
return template.render(context)
}
let paths:String = join(", ", loader.paths.map { path in
return path.description
})
let error = "Template '\(templateName)' not found in \(paths)"
return .Error(error)
}
public func render(context: Context) -> Result {
if let loader = context["loader"] as? TemplateLoader {
if let template = loader.loadTemplate(templateName) {
return template.render(context)
}
let paths:String = join(", ", loader.paths.map { path in
return path.description
})
let error = "Template '\(templateName)' not found in \(paths)"
return .Error(error)
}
let error = "Template loader not in context"
return .Error(error)
}
let error = "Template loader not in context"
return .Error(error)
}
}

View File

@@ -2,64 +2,64 @@ import Foundation
public enum Token : Equatable {
/// A token representing a piece of text.
case Text(value:String)
/// A token representing a piece of text.
case Text(value:String)
/// A token representing a variable.
case Variable(value:String)
/// A token representing a variable.
case Variable(value:String)
/// A token representing a comment.
case Comment(value:String)
/// A token representing a comment.
case Comment(value:String)
/// A token representing a template block.
case Block(value:String)
/// A token representing a template block.
case Block(value:String)
/// Returns the underlying value as an array seperated by spaces
func components() -> [String] {
// TODO: Make this smarter and treat quoted strings as a single component
let characterSet = NSCharacterSet.whitespaceAndNewlineCharacterSet()
func strip(value: String) -> [String] {
return value.stringByTrimmingCharactersInSet(characterSet).componentsSeparatedByCharactersInSet(characterSet)
}
/// Returns the underlying value as an array seperated by spaces
func components() -> [String] {
// TODO: Make this smarter and treat quoted strings as a single component
let characterSet = NSCharacterSet.whitespaceAndNewlineCharacterSet()
switch self {
case .Block(let value):
return strip(value)
case .Variable(let value):
return strip(value)
case .Text(let value):
return strip(value)
case .Comment(let value):
return strip(value)
}
func strip(value: String) -> [String] {
return value.stringByTrimmingCharactersInSet(characterSet).componentsSeparatedByCharactersInSet(characterSet)
}
var contents:String {
switch self {
case .Block(let value):
return value
case .Variable(let value):
return value
case .Text(let value):
return value
case .Comment(let value):
return value
}
switch self {
case .Block(let value):
return strip(value)
case .Variable(let value):
return strip(value)
case .Text(let value):
return strip(value)
case .Comment(let value):
return strip(value)
}
}
var contents:String {
switch self {
case .Block(let value):
return value
case .Variable(let value):
return value
case .Text(let value):
return value
case .Comment(let value):
return value
}
}
}
public func ==(lhs:Token, rhs:Token) -> Bool {
switch (lhs, rhs) {
case (.Text(let lhsValue), .Text(let rhsValue)):
return lhsValue == rhsValue
case (.Variable(let lhsValue), .Variable(let rhsValue)):
return lhsValue == rhsValue
case (.Block(let lhsValue), .Block(let rhsValue)):
return lhsValue == rhsValue
case (.Comment(let lhsValue), .Comment(let rhsValue)):
return lhsValue == rhsValue
default:
return false
}
switch (lhs, rhs) {
case (.Text(let lhsValue), .Text(let rhsValue)):
return lhsValue == rhsValue
case (.Variable(let lhsValue), .Variable(let rhsValue)):
return lhsValue == rhsValue
case (.Block(let lhsValue), .Block(let rhsValue)):
return lhsValue == rhsValue
case (.Comment(let lhsValue), .Comment(let rhsValue)):
return lhsValue == rhsValue
default:
return false
}
}

View File

@@ -3,51 +3,51 @@ import Foundation
/// A structure used to represent a template variable, and to resolve it in a given context.
public struct Variable : Equatable {
public let variable:String
public let variable:String
/// Create a variable with a string representing the variable
public init(_ variable:String) {
self.variable = variable
/// Create a variable with a string representing the variable
public init(_ variable:String) {
self.variable = variable
}
private func lookup() -> [String] {
return variable.componentsSeparatedByString(".")
}
/// Resolve the variable in the given context
public func resolve(context:Context) -> AnyObject? {
var current:AnyObject? = context
if (variable.hasPrefix("'") && variable.hasSuffix("'")) || (variable.hasPrefix("\"") && variable.hasSuffix("\"")) {
return variable.substringWithRange(variable.startIndex.successor() ..< variable.endIndex.predecessor())
}
private func lookup() -> [String] {
return variable.componentsSeparatedByString(".")
}
/// Resolve the variable in the given context
public func resolve(context:Context) -> AnyObject? {
var current:AnyObject? = context
if (variable.hasPrefix("'") && variable.hasSuffix("'")) || (variable.hasPrefix("\"") && variable.hasSuffix("\"")) {
return variable.substringWithRange(variable.startIndex.successor() ..< variable.endIndex.predecessor())
for bit in lookup() {
if let context = current as? Context {
current = context[bit]
} else if let dictionary = current as? Dictionary<String, AnyObject> {
current = dictionary[bit]
} else if let array = current as? [AnyObject] {
if let index = bit.toInt() {
current = array[index]
} else if bit == "first" {
current = array.first
} else if bit == "last" {
current = array.last
} else if bit == "count" {
current = count(array)
}
for bit in lookup() {
if let context = current as? Context {
current = context[bit]
} else if let dictionary = current as? Dictionary<String, AnyObject> {
current = dictionary[bit]
} else if let array = current as? [AnyObject] {
if let index = bit.toInt() {
current = array[index]
} else if bit == "first" {
current = array.first
} else if bit == "last" {
current = array.last
} else if bit == "count" {
current = count(array)
}
} else if let object = current as? NSObject {
current = object.valueForKey(bit)
} else {
return nil
}
}
return current
} else if let object = current as? NSObject {
current = object.valueForKey(bit)
} else {
return nil
}
}
return current
}
}
public func ==(lhs:Variable, rhs:Variable) -> Bool {
return lhs.variable == rhs.variable
return lhs.variable == rhs.variable
}

View File

@@ -3,63 +3,63 @@ import XCTest
import Stencil
class ContextTests: XCTestCase {
var context:Context!
var context:Context!
override func setUp() {
context = Context(dictionary: ["name": "Kyle"])
}
override func setUp() {
context = Context(dictionary: ["name": "Kyle"])
}
func testItAllowsYouToRetrieveAValue() {
let name = context["name"] as! String
XCTAssertEqual(name, "Kyle")
}
func testItAllowsYouToRetrieveAValue() {
let name = context["name"] as! String
XCTAssertEqual(name, "Kyle")
}
func testItAllowsYouToSetValue() {
context["name"] = "Katie"
func testItAllowsYouToSetValue() {
context["name"] = "Katie"
let name = context["name"] as! String
XCTAssertEqual(name, "Katie")
}
let name = context["name"] as! String
XCTAssertEqual(name, "Katie")
}
func testItAllowsYouToRemoveAValue() {
context["name"] = nil
XCTAssertNil(context["name"])
}
func testItAllowsYouToRemoveAValue() {
context["name"] = nil
XCTAssertNil(context["name"])
}
func testItAllowsYouToRetrieveAValueFromParent() {
context.push()
func testItAllowsYouToRetrieveAValueFromParent() {
context.push()
let name = context["name"] as! String
XCTAssertEqual(name, "Kyle")
}
let name = context["name"] as! String
XCTAssertEqual(name, "Kyle")
}
func testItAllowsYouToOverideAParentVariable() {
context.push()
context["name"] = "Katie"
func testItAllowsYouToOverideAParentVariable() {
context.push()
context["name"] = "Katie"
let name = context["name"] as! String
XCTAssertEqual(name, "Katie")
}
let name = context["name"] as! String
XCTAssertEqual(name, "Katie")
}
func testShowAllowYouToPopVariablesRestoringPreviousState() {
context.push()
context["name"] = "Katie"
context.pop()
func testShowAllowYouToPopVariablesRestoringPreviousState() {
context.push()
context["name"] = "Katie"
context.pop()
let name = context["name"] as! String
XCTAssertEqual(name, "Kyle")
}
let name = context["name"] as! String
XCTAssertEqual(name, "Kyle")
}
func testItAllowsYouToPushADictionaryToTheStack() {
context.push(["name": "Katie"])
func testItAllowsYouToPushADictionaryToTheStack() {
context.push(["name": "Katie"])
let name = context["name"] as! String
XCTAssertEqual(name, "Katie")
}
let name = context["name"] as! String
XCTAssertEqual(name, "Katie")
}
func testItAllowsYouToCompareTwoContextsForEquality() {
let otherContext = Context(dictionary: ["name": "Kyle"])
func testItAllowsYouToCompareTwoContextsForEquality() {
let otherContext = Context(dictionary: ["name": "Kyle"])
XCTAssertEqual(otherContext, context)
}
XCTAssertEqual(otherContext, context)
}
}

View File

@@ -4,47 +4,47 @@ import Stencil
class LexerTests: XCTestCase {
func testTokenizeText() {
let lexer = Lexer(templateString:"Hello World")
let tokens = lexer.tokenize()
func testTokenizeText() {
let lexer = Lexer(templateString:"Hello World")
let tokens = lexer.tokenize()
XCTAssertEqual(tokens.count, 1)
XCTAssertEqual(tokens.first!, Token.Text(value: "Hello World"))
}
XCTAssertEqual(tokens.count, 1)
XCTAssertEqual(tokens.first!, Token.Text(value: "Hello World"))
}
func testTokenizeComment() {
let lexer = Lexer(templateString:"{# Comment #}")
let tokens = lexer.tokenize()
func testTokenizeComment() {
let lexer = Lexer(templateString:"{# Comment #}")
let tokens = lexer.tokenize()
XCTAssertEqual(tokens.count, 1)
XCTAssertEqual(tokens.first!, Token.Comment(value: "Comment"))
}
XCTAssertEqual(tokens.count, 1)
XCTAssertEqual(tokens.first!, Token.Comment(value: "Comment"))
}
func testTokenizeVariable() {
let lexer = Lexer(templateString:"{{ Variable }}")
let tokens = lexer.tokenize()
func testTokenizeVariable() {
let lexer = Lexer(templateString:"{{ Variable }}")
let tokens = lexer.tokenize()
XCTAssertEqual(tokens.count, 1)
XCTAssertEqual(tokens.first!, Token.Variable(value: "Variable"))
}
XCTAssertEqual(tokens.count, 1)
XCTAssertEqual(tokens.first!, Token.Variable(value: "Variable"))
}
func testTokenizeMixture() {
let lexer = Lexer(templateString:"My name is {{ name }}.")
let tokens = lexer.tokenize()
func testTokenizeMixture() {
let lexer = Lexer(templateString:"My name is {{ name }}.")
let tokens = lexer.tokenize()
XCTAssertEqual(tokens.count, 3)
XCTAssertEqual(tokens[0], Token.Text(value: "My name is "))
XCTAssertEqual(tokens[1], Token.Variable(value: "name"))
XCTAssertEqual(tokens[2], Token.Text(value: "."))
}
XCTAssertEqual(tokens.count, 3)
XCTAssertEqual(tokens[0], Token.Text(value: "My name is "))
XCTAssertEqual(tokens[1], Token.Variable(value: "name"))
XCTAssertEqual(tokens[2], Token.Text(value: "."))
}
func testTokenizeTwoVariables() { // Don't be greedy
let lexer = Lexer(templateString:"{{ thing }}{{ name }}")
let tokens = lexer.tokenize()
func testTokenizeTwoVariables() { // Don't be greedy
let lexer = Lexer(templateString:"{{ thing }}{{ name }}")
let tokens = lexer.tokenize()
XCTAssertEqual(tokens.count, 2)
XCTAssertEqual(tokens[0], Token.Variable(value: "thing"))
XCTAssertEqual(tokens[1], Token.Variable(value: "name"))
}
XCTAssertEqual(tokens.count, 2)
XCTAssertEqual(tokens[0], Token.Variable(value: "thing"))
XCTAssertEqual(tokens[1], Token.Variable(value: "name"))
}
}

View File

@@ -3,248 +3,248 @@ import XCTest
import Stencil
class ErrorNodeError : Error {
var description: String {
return "Node Error"
}
var description: String {
return "Node Error"
}
}
class ErrorNode : Node {
func render(context: Context) -> Result {
func render(context: Context) -> Result {
return .Error(ErrorNodeError())
}
return .Error(ErrorNodeError())
}
}
class NodeTests: XCTestCase {
var context:Context!
var context:Context!
override func setUp() {
context = Context(dictionary: [
"name": "Kyle",
"age": 27,
"items": [1,2,3],
])
}
override func setUp() {
context = Context(dictionary: [
"name": "Kyle",
"age": 27,
"items": [1,2,3],
])
}
}
class TextNodeTests: NodeTests {
func testTextNodeResolvesText() {
let node = TextNode(text:"Hello World")
let result = node.render(context)
func testTextNodeResolvesText() {
let node = TextNode(text:"Hello World")
let result = node.render(context)
switch node.render(context) {
case .Success(let string):
XCTAssertEqual(string, "Hello World")
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
switch node.render(context) {
case .Success(let string):
XCTAssertEqual(string, "Hello World")
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
}
}
class VariableNodeTests: NodeTests {
func testVariableNodeResolvesVariable() {
let node = VariableNode(variable:Variable("name"))
let result = node.render(context)
func testVariableNodeResolvesVariable() {
let node = VariableNode(variable:Variable("name"))
let result = node.render(context)
switch node.render(context) {
case .Success(let string):
XCTAssertEqual(string, "Kyle")
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
switch node.render(context) {
case .Success(let string):
XCTAssertEqual(string, "Kyle")
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
}
func testVariableNodeResolvesNonStringVariable() {
let node = VariableNode(variable:Variable("age"))
let result = node.render(context)
func testVariableNodeResolvesNonStringVariable() {
let node = VariableNode(variable:Variable("age"))
let result = node.render(context)
switch node.render(context) {
case .Success(let string):
XCTAssertEqual(string, "27")
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
switch node.render(context) {
case .Success(let string):
XCTAssertEqual(string, "27")
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
}
}
class RenderNodeTests: NodeTests {
func testRenderingNodes() {
let nodes = [TextNode(text:"Hello "), VariableNode(variable: "name")] as [Node]
switch renderNodes(nodes, context) {
case .Success(let result):
XCTAssertEqual(result, "Hello Kyle")
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
func testRenderingNodes() {
let nodes = [TextNode(text:"Hello "), VariableNode(variable: "name")] as [Node]
switch renderNodes(nodes, context) {
case .Success(let result):
XCTAssertEqual(result, "Hello Kyle")
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
}
func testRenderingNodesWithFailure() {
let nodes = [TextNode(text:"Hello "), VariableNode(variable: "name"), ErrorNode()] as [Node]
func testRenderingNodesWithFailure() {
let nodes = [TextNode(text:"Hello "), VariableNode(variable: "name"), ErrorNode()] as [Node]
switch renderNodes(nodes, context) {
case .Success(let result):
XCTAssert(false, "Unexpected success")
case .Error(let error):
XCTAssertEqual("\(error)", "Node Error")
}
switch renderNodes(nodes, context) {
case .Success(let result):
XCTAssert(false, "Unexpected success")
case .Error(let error):
XCTAssertEqual("\(error)", "Node Error")
}
}
}
class ForNodeTests: NodeTests {
func testForNodeRender() {
let node = ForNode(variable: "items", loopVariable: "item", nodes: [VariableNode(variable: "item")], emptyNodes:[])
let result = node.render(context)
func testForNodeRender() {
let node = ForNode(variable: "items", loopVariable: "item", nodes: [VariableNode(variable: "item")], emptyNodes:[])
let result = node.render(context)
switch node.render(context) {
case .Success(let string):
XCTAssertEqual(string, "123")
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
switch node.render(context) {
case .Success(let string):
XCTAssertEqual(string, "123")
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
}
}
class IfNodeTests: NodeTests {
// MARK: Parsing
// MARK: Parsing
func testParseIf() {
let tokens = [
Token.Block(value: "if value"),
Token.Text(value: "true"),
Token.Block(value: "else"),
Token.Text(value: "false"),
Token.Block(value: "endif")
]
func testParseIf() {
let tokens = [
Token.Block(value: "if value"),
Token.Text(value: "true"),
Token.Block(value: "else"),
Token.Text(value: "false"),
Token.Block(value: "endif")
]
let parser = TokenParser(tokens: tokens)
assertSuccess(parser.parse()) { nodes in
let node = nodes.first as! IfNode
let trueNode = node.trueNodes.first as! TextNode
let falseNode = node.falseNodes.first as! TextNode
let parser = TokenParser(tokens: tokens)
assertSuccess(parser.parse()) { nodes in
let node = nodes.first as! IfNode
let trueNode = node.trueNodes.first as! TextNode
let falseNode = node.falseNodes.first as! TextNode
XCTAssertEqual(nodes.count, 1)
XCTAssertEqual(node.variable.variable, "value")
XCTAssertEqual(node.trueNodes.count, 1)
XCTAssertEqual(trueNode.text, "true")
XCTAssertEqual(node.falseNodes.count, 1)
XCTAssertEqual(falseNode.text, "false")
}
XCTAssertEqual(nodes.count, 1)
XCTAssertEqual(node.variable.variable, "value")
XCTAssertEqual(node.trueNodes.count, 1)
XCTAssertEqual(trueNode.text, "true")
XCTAssertEqual(node.falseNodes.count, 1)
XCTAssertEqual(falseNode.text, "false")
}
}
func testParseIfNot() {
let tokens = [
Token.Block(value: "ifnot value"),
Token.Text(value: "false"),
Token.Block(value: "else"),
Token.Text(value: "true"),
Token.Block(value: "endif")
]
func testParseIfNot() {
let tokens = [
Token.Block(value: "ifnot value"),
Token.Text(value: "false"),
Token.Block(value: "else"),
Token.Text(value: "true"),
Token.Block(value: "endif")
]
let parser = TokenParser(tokens: tokens)
assertSuccess(parser.parse()) { nodes in
let node = nodes.first as! IfNode
let trueNode = node.trueNodes.first as! TextNode
let falseNode = node.falseNodes.first as! TextNode
let parser = TokenParser(tokens: tokens)
assertSuccess(parser.parse()) { nodes in
let node = nodes.first as! IfNode
let trueNode = node.trueNodes.first as! TextNode
let falseNode = node.falseNodes.first as! TextNode
XCTAssertEqual(nodes.count, 1)
XCTAssertEqual(node.variable.variable, "value")
XCTAssertEqual(node.trueNodes.count, 1)
XCTAssertEqual(trueNode.text, "true")
XCTAssertEqual(node.falseNodes.count, 1)
XCTAssertEqual(falseNode.text, "false")
}
XCTAssertEqual(nodes.count, 1)
XCTAssertEqual(node.variable.variable, "value")
XCTAssertEqual(node.trueNodes.count, 1)
XCTAssertEqual(trueNode.text, "true")
XCTAssertEqual(node.falseNodes.count, 1)
XCTAssertEqual(falseNode.text, "false")
}
}
func testParseIfWithoutEndIfError() {
let tokens = [
Token.Block(value: "if value"),
]
func testParseIfWithoutEndIfError() {
let tokens = [
Token.Block(value: "if value"),
]
let parser = TokenParser(tokens: tokens)
assertFailure(parser.parse(), "if: `endif` was not found.")
let parser = TokenParser(tokens: tokens)
assertFailure(parser.parse(), "if: `endif` was not found.")
}
func testParseIfNotWithoutEndIfError() {
let tokens = [
Token.Block(value: "ifnot value"),
]
let parser = TokenParser(tokens: tokens)
assertFailure(parser.parse(), "ifnot: `endif` was not found.")
}
// MARK: Rendering
func testIfNodeRenderTruth() {
let node = IfNode(variable: "items", trueNodes: [TextNode(text: "true")], falseNodes: [TextNode(text: "false")])
let result = node.render(context)
switch node.render(context) {
case .Success(let string):
XCTAssertEqual(string, "true")
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
}
func testParseIfNotWithoutEndIfError() {
let tokens = [
Token.Block(value: "ifnot value"),
]
func testIfNodeRenderFalse() {
let node = IfNode(variable: "unknown", trueNodes: [TextNode(text: "true")], falseNodes: [TextNode(text: "false")])
let result = node.render(context)
let parser = TokenParser(tokens: tokens)
assertFailure(parser.parse(), "ifnot: `endif` was not found.")
}
// MARK: Rendering
func testIfNodeRenderTruth() {
let node = IfNode(variable: "items", trueNodes: [TextNode(text: "true")], falseNodes: [TextNode(text: "false")])
let result = node.render(context)
switch node.render(context) {
case .Success(let string):
XCTAssertEqual(string, "true")
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
}
func testIfNodeRenderFalse() {
let node = IfNode(variable: "unknown", trueNodes: [TextNode(text: "true")], falseNodes: [TextNode(text: "false")])
let result = node.render(context)
switch node.render(context) {
case .Success(let string):
XCTAssertEqual(string, "false")
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
switch node.render(context) {
case .Success(let string):
XCTAssertEqual(string, "false")
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
}
}
class NowNodeTests: NodeTests {
// MARK: Parsing
// MARK: Parsing
func testParseDefaultNow() {
let tokens = [ Token.Block(value: "now") ]
let parser = TokenParser(tokens: tokens)
func testParseDefaultNow() {
let tokens = [ Token.Block(value: "now") ]
let parser = TokenParser(tokens: tokens)
assertSuccess(parser.parse()) { nodes in
let node = nodes.first as! NowNode
XCTAssertEqual(nodes.count, 1)
XCTAssertEqual(node.format.variable, "\"yyyy-MM-dd 'at' HH:mm\"")
}
assertSuccess(parser.parse()) { nodes in
let node = nodes.first as! NowNode
XCTAssertEqual(nodes.count, 1)
XCTAssertEqual(node.format.variable, "\"yyyy-MM-dd 'at' HH:mm\"")
}
}
func testParseNowWithFormat() {
let tokens = [ Token.Block(value: "now \"HH:mm\"") ]
let parser = TokenParser(tokens: tokens)
func testParseNowWithFormat() {
let tokens = [ Token.Block(value: "now \"HH:mm\"") ]
let parser = TokenParser(tokens: tokens)
assertSuccess(parser.parse()) { nodes in
let node = nodes.first as! NowNode
XCTAssertEqual(nodes.count, 1)
XCTAssertEqual(node.format.variable, "\"HH:mm\"")
}
assertSuccess(parser.parse()) { nodes in
let node = nodes.first as! NowNode
XCTAssertEqual(nodes.count, 1)
XCTAssertEqual(node.format.variable, "\"HH:mm\"")
}
}
// MARK: Rendering
// MARK: Rendering
func testRenderNowNode() {
let node = NowNode(format: Variable("\"yyyy-MM-dd\""))
let result = node.render(context)
func testRenderNowNode() {
let node = NowNode(format: Variable("\"yyyy-MM-dd\""))
let result = node.render(context)
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let date = formatter.stringFromDate(NSDate())
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let date = formatter.stringFromDate(NSDate())
switch node.render(context) {
case .Success(let string):
XCTAssertEqual(string, date)
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
switch node.render(context) {
case .Success(let string):
XCTAssertEqual(string, date)
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
}
}

View File

@@ -3,47 +3,47 @@ import XCTest
import Stencil
class TokenParserTests: XCTestCase {
func testParsingTextToken() {
let parser = TokenParser(tokens: [
Token.Text(value: "Hello World")
])
func testParsingTextToken() {
let parser = TokenParser(tokens: [
Token.Text(value: "Hello World")
])
assertSuccess(parser.parse()) { nodes in
let node = nodes.first as! TextNode
XCTAssertEqual(nodes.count, 1)
XCTAssertEqual(node.text, "Hello World")
}
assertSuccess(parser.parse()) { nodes in
let node = nodes.first as! TextNode
XCTAssertEqual(nodes.count, 1)
XCTAssertEqual(node.text, "Hello World")
}
}
func testParsingVariableToken() {
let parser = TokenParser(tokens: [
Token.Variable(value: "name")
])
func testParsingVariableToken() {
let parser = TokenParser(tokens: [
Token.Variable(value: "name")
])
assertSuccess(parser.parse()) { nodes in
let node = nodes.first as! VariableNode
XCTAssertEqual(nodes.count, 1)
XCTAssertEqual(node.variable, Variable("name"))
}
assertSuccess(parser.parse()) { nodes in
let node = nodes.first as! VariableNode
XCTAssertEqual(nodes.count, 1)
XCTAssertEqual(node.variable, Variable("name"))
}
}
func testParsingCommentToken() {
let parser = TokenParser(tokens: [
Token.Comment(value: "Secret stuff!")
])
func testParsingCommentToken() {
let parser = TokenParser(tokens: [
Token.Comment(value: "Secret stuff!")
])
assertSuccess(parser.parse()) { nodes in
XCTAssertEqual(nodes.count, 0)
}
assertSuccess(parser.parse()) { nodes in
XCTAssertEqual(nodes.count, 0)
}
}
func testParsingTagToken() {
let parser = TokenParser(tokens: [
Token.Block(value: "now"),
])
func testParsingTagToken() {
let parser = TokenParser(tokens: [
Token.Block(value: "now"),
])
assertSuccess(parser.parse()) { nodes in
XCTAssertEqual(nodes.count, 1)
}
assertSuccess(parser.parse()) { nodes in
XCTAssertEqual(nodes.count, 1)
}
}
}

View File

@@ -3,77 +3,77 @@ import XCTest
import Stencil
func assertSuccess(result:TokenParser.Results, block:(([Node]) -> ())) {
switch result {
case .Success(let nodes):
block(nodes)
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
switch result {
case .Success(let nodes):
block(nodes)
case .Error(let error):
XCTAssert(false, "Unexpected error")
}
}
func assertFailure(result:TokenParser.Results, description:String) {
switch result {
case .Success(let nodes):
XCTAssert(false, "Unexpected error")
case .Error(let error):
XCTAssertEqual("\(error)", description)
}
switch result {
case .Success(let nodes):
XCTAssert(false, "Unexpected error")
case .Error(let error):
XCTAssertEqual("\(error)", description)
}
}
class CustomNode : Node {
func render(context:Context) -> Result {
return .Success("Hello World")
}
func render(context:Context) -> Result {
return .Success("Hello World")
}
}
class StencilTests: XCTestCase {
func testReadmeExample() {
let templateString = "There are {{ articles.count }} articles.\n" +
"\n" +
"{% for article in articles %}" +
" - {{ article.title }} by {{ article.author }}.\n" +
"{% endfor %}\n"
func testReadmeExample() {
let templateString = "There are {{ articles.count }} articles.\n" +
"\n" +
"{% for article in articles %}" +
" - {{ article.title }} by {{ article.author }}.\n" +
"{% endfor %}\n"
let context = Context(dictionary: [
"articles": [
[ "title": "Migrating from OCUnit to XCTest", "author": "Kyle Fuller" ],
[ "title": "Memory Management with ARC", "author": "Kyle Fuller" ],
]
])
let context = Context(dictionary: [
"articles": [
[ "title": "Migrating from OCUnit to XCTest", "author": "Kyle Fuller" ],
[ "title": "Memory Management with ARC", "author": "Kyle Fuller" ],
]
])
let template = Template(templateString:templateString)
let result = template.render(context)
let template = Template(templateString:templateString)
let result = template.render(context)
let fixture = "There are 2 articles.\n" +
"\n" +
" - Migrating from OCUnit to XCTest by Kyle Fuller.\n" +
" - Memory Management with ARC by Kyle Fuller.\n" +
"\n"
let fixture = "There are 2 articles.\n" +
"\n" +
" - Migrating from OCUnit to XCTest by Kyle Fuller.\n" +
" - Memory Management with ARC by Kyle Fuller.\n" +
"\n"
XCTAssertEqual(result, Result.Success(fixture))
XCTAssertEqual(result, Result.Success(fixture))
}
func testCustomTag() {
let templateString = "{% custom %}"
let template = Template(templateString:templateString)
template.parser.registerTag("custom") { parser, token in
return .Success(node:CustomNode())
}
func testCustomTag() {
let templateString = "{% custom %}"
let template = Template(templateString:templateString)
let result = template.render()
XCTAssertEqual(result, Result.Success("Hello World"))
}
template.parser.registerTag("custom") { parser, token in
return .Success(node:CustomNode())
}
func testSimpleCustomTag() {
let templateString = "{% custom %}"
let template = Template(templateString:templateString)
let result = template.render()
XCTAssertEqual(result, Result.Success("Hello World"))
template.parser.registerSimpleTag("custom") { context in
return .Success("Hello World")
}
func testSimpleCustomTag() {
let templateString = "{% custom %}"
let template = Template(templateString:templateString)
template.parser.registerSimpleTag("custom") { context in
return .Success("Hello World")
}
let result = template.render()
XCTAssertEqual(result, Result.Success("Hello World"))
}
let result = template.render()
XCTAssertEqual(result, Result.Success("Hello World"))
}
}

View File

@@ -5,71 +5,71 @@ import PathKit
class IncludeTests: NodeTests {
var loader:TemplateLoader!
var loader:TemplateLoader!
override func setUp() {
super.setUp()
override func setUp() {
super.setUp()
let path = (Path(__FILE__) + Path("../..")).absolute()
loader = TemplateLoader(paths: [path])
let path = (Path(__FILE__) + Path("../..")).absolute()
loader = TemplateLoader(paths: [path])
}
// MARK: Parsing
func testParseMissingTemplate() {
let tokens = [ Token.Block(value: "include") ]
let parser = TokenParser(tokens: tokens)
assertFailure(parser.parse(), "include: Tag takes one argument, the template file to be included")
}
func testParse() {
let tokens = [ Token.Block(value: "include \"test.html\"") ]
let parser = TokenParser(tokens: tokens)
assertSuccess(parser.parse()) { nodes in
let node = nodes.first as! IncludeNode
XCTAssertEqual(nodes.count, 1)
XCTAssertEqual(node.templateName, "test.html")
}
}
// MARK: Parsing
// MARK: Render
func testParseMissingTemplate() {
let tokens = [ Token.Block(value: "include") ]
let parser = TokenParser(tokens: tokens)
func testRenderWithoutLoader() {
let node = IncludeNode(templateName: "test.html")
let result = node.render(Context())
assertFailure(parser.parse(), "include: Tag takes one argument, the template file to be included")
switch result {
case .Success(let string):
XCTAssert(false, "Unexpected error")
case .Error(let error):
XCTAssertEqual("\(error)", "Template loader not in context")
}
}
func testParse() {
let tokens = [ Token.Block(value: "include \"test.html\"") ]
let parser = TokenParser(tokens: tokens)
func testRenderWithoutTemplateNamed() {
let node = IncludeNode(templateName: "unknown.html")
let result = node.render(Context(dictionary:["loader":loader]))
assertSuccess(parser.parse()) { nodes in
let node = nodes.first as! IncludeNode
XCTAssertEqual(nodes.count, 1)
XCTAssertEqual(node.templateName, "test.html")
}
switch result {
case .Success(let string):
XCTAssert(false, "Unexpected error")
case .Error(let error):
XCTAssertTrue("\(error)".hasPrefix("Template 'unknown.html' not found"))
}
}
// MARK: Render
func testRender() {
let node = IncludeNode(templateName: "test.html")
let result = node.render(Context(dictionary:["loader":loader, "target": "World"]))
func testRenderWithoutLoader() {
let node = IncludeNode(templateName: "test.html")
let result = node.render(Context())
switch result {
case .Success(let string):
XCTAssert(false, "Unexpected error")
case .Error(let error):
XCTAssertEqual("\(error)", "Template loader not in context")
}
}
func testRenderWithoutTemplateNamed() {
let node = IncludeNode(templateName: "unknown.html")
let result = node.render(Context(dictionary:["loader":loader]))
switch result {
case .Success(let string):
XCTAssert(false, "Unexpected error")
case .Error(let error):
XCTAssertTrue("\(error)".hasPrefix("Template 'unknown.html' not found"))
}
}
func testRender() {
let node = IncludeNode(templateName: "test.html")
let result = node.render(Context(dictionary:["loader":loader, "target": "World"]))
switch result {
case .Success(let string):
XCTAssertEqual(string, "Hello World!")
case .Error(let error):
XCTAssert(false, "Unexpected error: \(error)")
}
switch result {
case .Success(let string):
XCTAssertEqual(string, "Hello World!")
case .Error(let error):
XCTAssert(false, "Unexpected error: \(error)")
}
}
}

View File

@@ -5,20 +5,20 @@ import PathKit
class TemplateLoaderTests: XCTestCase {
func testLoadingUnknownTemplate() {
let loader = TemplateLoader(paths:[])
XCTAssertNil(loader.loadTemplate("unknown.html"))
}
func testLoadingUnknownTemplate() {
let loader = TemplateLoader(paths:[])
XCTAssertNil(loader.loadTemplate("unknown.html"))
}
func testLoadingUnknownTemplates() {
let loader = TemplateLoader(paths:[])
XCTAssertNil(loader.loadTemplate(["unknown.html", "unknown2.html"]))
}
func testLoadingUnknownTemplates() {
let loader = TemplateLoader(paths:[])
XCTAssertNil(loader.loadTemplate(["unknown.html", "unknown2.html"]))
}
func testLoadingTemplate() {
let path = (Path(__FILE__) + Path("..")).absolute()
let loader = TemplateLoader(paths: [path])
XCTAssertTrue(loader.loadTemplate("test.html") != nil)
}
func testLoadingTemplate() {
let path = (Path(__FILE__) + Path("..")).absolute()
let loader = TemplateLoader(paths: [path])
XCTAssertTrue(loader.loadTemplate("test.html") != nil)
}
}

View File

@@ -4,11 +4,11 @@ import Stencil
class TemplateTests: XCTestCase {
func testTemplate() {
let context = Context(dictionary: [ "name": "Kyle" ])
let template = Template(templateString: "Hello World")
let result = template.render(context)
XCTAssertEqual(result, Result.Success("Hello World"))
}
func testTemplate() {
let context = Context(dictionary: [ "name": "Kyle" ])
let template = Template(templateString: "Hello World")
let result = template.render(context)
XCTAssertEqual(result, Result.Success("Hello World"))
}
}

View File

@@ -3,60 +3,60 @@ import XCTest
import Stencil
@objc class Object : NSObject {
let title = "Hello World"
let title = "Hello World"
}
class VariableTests: XCTestCase {
var context:Context!
var context:Context!
override func setUp() {
context = Context(dictionary: [
"name": "Kyle",
"contacts": [ "Katie", "Orta", ],
"profiles": [ "github": "kylef", ],
"object": Object(),
])
}
override func setUp() {
context = Context(dictionary: [
"name": "Kyle",
"contacts": [ "Katie", "Orta", ],
"profiles": [ "github": "kylef", ],
"object": Object(),
])
}
func testResolvingStringLiteral() {
let variable = Variable("\"name\"")
let result = variable.resolve(context) as! String
XCTAssertEqual(result, "name")
}
func testResolvingStringLiteral() {
let variable = Variable("\"name\"")
let result = variable.resolve(context) as! String
XCTAssertEqual(result, "name")
}
func testResolvingVariable() {
let variable = Variable("name")
let result = variable.resolve(context) as! String
XCTAssertEqual(result, "Kyle")
}
func testResolvingVariable() {
let variable = Variable("name")
let result = variable.resolve(context) as! String
XCTAssertEqual(result, "Kyle")
}
func testResolvingItemFromDictionary() {
let variable = Variable("profiles.github")
let result = variable.resolve(context) as! String
XCTAssertEqual(result, "kylef")
}
func testResolvingItemFromDictionary() {
let variable = Variable("profiles.github")
let result = variable.resolve(context) as! String
XCTAssertEqual(result, "kylef")
}
func testResolvingItemFromArrayWithIndex() {
let variable = Variable("contacts.0")
let result = variable.resolve(context) as! String
XCTAssertEqual(result, "Katie")
}
func testResolvingItemFromArrayWithIndex() {
let variable = Variable("contacts.0")
let result = variable.resolve(context) as! String
XCTAssertEqual(result, "Katie")
}
func testResolvingFirstItemFromArray() {
let variable = Variable("contacts.first")
let result = variable.resolve(context) as! String
XCTAssertEqual(result, "Katie")
}
func testResolvingFirstItemFromArray() {
let variable = Variable("contacts.first")
let result = variable.resolve(context) as! String
XCTAssertEqual(result, "Katie")
}
func testResolvingLastItemFromArray() {
let variable = Variable("contacts.last")
let result = variable.resolve(context) as! String
XCTAssertEqual(result, "Orta")
}
func testResolvingLastItemFromArray() {
let variable = Variable("contacts.last")
let result = variable.resolve(context) as! String
XCTAssertEqual(result, "Orta")
}
func testResolvingValueViaKVO() {
let variable = Variable("object.title")
let result = variable.resolve(context) as! String
XCTAssertEqual(result, "Hello World")
}
func testResolvingValueViaKVO() {
let variable = Variable("object.title")
let result = variable.resolve(context) as! String
XCTAssertEqual(result, "Hello World")
}
}