[Project] Use 2 spaces for indentation
This commit is contained in:
@@ -123,7 +123,9 @@
|
||||
F0837D60120FB15CC00B9EBD /* Pods */,
|
||||
BE2E2DF8F488C4669126E920 /* Frameworks */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
};
|
||||
77FAAE5319F91E480029DC5E /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user