Files
swiftpm-stencil/Sources/Node.swift
2019-01-12 22:28:09 +00:00

150 lines
4.1 KiB
Swift

import Foundation
public protocol NodeType {
/// Render the node in the given context
func render(_ context: Context) throws -> String
/// Reference to this node's token
var token: Token? { get }
}
/// Render the collection of nodes in the given context
public func renderNodes(_ nodes: [NodeType], _ context: Context) throws -> String {
return try nodes
.map {
do {
return try $0.render(context)
} catch {
throw error.withToken($0.token)
}
}
.joined()
}
public class SimpleNode: NodeType {
public let handler: (Context) throws -> String
public let token: Token?
public init(token: Token, handler: @escaping (Context) throws -> String) {
self.token = token
self.handler = handler
}
public func render(_ context: Context) throws -> String {
return try handler(context)
}
}
public class TextNode: NodeType {
public let text: String
public let token: Token?
public init(text: String) {
self.text = text
self.token = nil
}
public func render(_ context: Context) throws -> String {
return self.text
}
}
public protocol Resolvable {
func resolve(_ context: Context) throws -> Any?
}
public class VariableNode: NodeType {
public let variable: Resolvable
public var token: Token?
let condition: Expression?
let elseExpression: Resolvable?
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
var components = token.components
func hasToken(_ token: String, at index: Int) -> Bool {
return components.count > (index + 1) && components[index] == token
}
let condition: Expression?
let elseExpression: Resolvable?
if hasToken("if", at: 1) {
let components = components.suffix(from: 2)
if let elseIndex = components.index(of: "else") {
condition = try parser.compileExpression(components: Array(components.prefix(upTo: elseIndex)), token: token)
let elseToken = components.suffix(from: elseIndex.advanced(by: 1)).joined(separator: " ")
elseExpression = try parser.compileResolvable(elseToken, containedIn: token)
} else {
condition = try parser.compileExpression(components: Array(components), token: token)
elseExpression = nil
}
} else {
condition = nil
elseExpression = nil
}
guard let resolvable = components.first else {
throw TemplateSyntaxError(reason: "Missing variable name", token: token)
}
let filter = try parser.compileResolvable(resolvable, containedIn: token)
return VariableNode(variable: filter, token: token, condition: condition, elseExpression: elseExpression)
}
public init(variable: Resolvable, token: Token? = nil) {
self.variable = variable
self.token = token
self.condition = nil
self.elseExpression = nil
}
init(variable: Resolvable, token: Token? = nil, condition: Expression?, elseExpression: Resolvable?) {
self.variable = variable
self.token = token
self.condition = condition
self.elseExpression = elseExpression
}
public init(variable: String, token: Token? = nil) {
self.variable = Variable(variable)
self.token = token
self.condition = nil
self.elseExpression = nil
}
public func render(_ context: Context) throws -> String {
if let condition = self.condition, try condition.evaluate(context: context) == false {
return try elseExpression?.resolve(context).map(stringify) ?? ""
}
let result = try variable.resolve(context)
return stringify(result)
}
}
func stringify(_ result: Any?) -> String {
if let result = result as? String {
return result
} else if let array = result as? [Any?] {
return unwrap(array).description
} else if let result = result as? CustomStringConvertible {
return result.description
} else if let result = result as? NSObject {
return result.description
}
return ""
}
func unwrap(_ array: [Any?]) -> [Any] {
return array.map { (item: Any?) -> Any in
if let item = item {
if let items = item as? [Any?] {
return unwrap(items)
} else {
return item
}
} else { return item as Any }
}
}