Wrap Lambda function in a struct to avoid crash in Mirror
This commit is contained in:
@@ -1,2 +1,36 @@
|
|||||||
|
|
||||||
public typealias HBMustacheLambda = (Any, HBMustacheTemplate) -> String
|
/// Lambda function. Can add this to object being rendered to filter contents of objects.
|
||||||
|
///
|
||||||
|
/// See http://mustache.github.io/mustache.5.html for more details on
|
||||||
|
/// mustache lambdas
|
||||||
|
/// e.g
|
||||||
|
/// ```
|
||||||
|
/// struct Object {
|
||||||
|
/// let name: String
|
||||||
|
/// let wrapped: HBMustacheLambda
|
||||||
|
/// }
|
||||||
|
/// let willy = Object(name: "Willy", wrapped: .init({ object, template in
|
||||||
|
/// return "<b>\(template.render(object))</b>"
|
||||||
|
/// }))
|
||||||
|
/// let mustache = "{{#wrapped}}{{name}} is awesome.{{/wrapped}}"
|
||||||
|
/// let template = try HBMustacheTemplate(string: mustache)
|
||||||
|
/// let output = template.render(willy)
|
||||||
|
/// print(output) // <b>Willy is awesome</b>
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
public struct HBMustacheLambda {
|
||||||
|
/// lambda callback
|
||||||
|
public typealias Callback = (Any, HBMustacheTemplate) -> String
|
||||||
|
|
||||||
|
let callback: Callback
|
||||||
|
|
||||||
|
/// Initialize `HBMustacheLambda`
|
||||||
|
/// - Parameter cb: function to be called by lambda
|
||||||
|
public init(_ cb: @escaping Callback) {
|
||||||
|
self.callback = cb
|
||||||
|
}
|
||||||
|
|
||||||
|
internal func run(_ object: Any, _ template: HBMustacheTemplate) -> String {
|
||||||
|
return callback(object, template)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
23
Sources/HummingbirdMustache/String.swift
Normal file
23
Sources/HummingbirdMustache/String.swift
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
extension String {
|
||||||
|
private static let htmlEscapedCharacters: [Character: String] = [
|
||||||
|
"<": "<",
|
||||||
|
">": ">",
|
||||||
|
"&": "&",
|
||||||
|
]
|
||||||
|
/// HTML escape string. Replace '<', '>' and '&' with HTML escaped versions
|
||||||
|
func htmlEscape() -> String {
|
||||||
|
var newString = ""
|
||||||
|
newString.reserveCapacity(self.count)
|
||||||
|
// currently doing this by going through each character could speed
|
||||||
|
// this us by treating as an array of UInt8's
|
||||||
|
for c in self {
|
||||||
|
if let replacement = Self.htmlEscapedCharacters[c] {
|
||||||
|
newString += replacement
|
||||||
|
} else {
|
||||||
|
newString.append(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newString
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
|
|
||||||
extension HBMustacheTemplate {
|
extension HBMustacheTemplate {
|
||||||
|
/// Render template using object
|
||||||
|
/// - Parameters:
|
||||||
|
/// - object: Object
|
||||||
|
/// - context: Context that render is occurring in. Contains information about position in sequence
|
||||||
|
/// - Returns: Rendered text
|
||||||
func render(_ object: Any, context: HBMustacheContext? = nil) -> String {
|
func render(_ object: Any, context: HBMustacheContext? = nil) -> String {
|
||||||
var string = ""
|
var string = ""
|
||||||
for token in tokens {
|
for token in tokens {
|
||||||
@@ -11,7 +16,7 @@ extension HBMustacheTemplate {
|
|||||||
if let template = child as? HBMustacheTemplate {
|
if let template = child as? HBMustacheTemplate {
|
||||||
string += template.render(object)
|
string += template.render(object)
|
||||||
} else {
|
} else {
|
||||||
string += htmlEscape(String(describing: child))
|
string += String(describing: child).htmlEscape()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .unescapedVariable(let variable, let method):
|
case .unescapedVariable(let variable, let method):
|
||||||
@@ -34,7 +39,13 @@ extension HBMustacheTemplate {
|
|||||||
}
|
}
|
||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render a section
|
||||||
|
/// - Parameters:
|
||||||
|
/// - child: Object to render section for
|
||||||
|
/// - parent: Current object being rendered
|
||||||
|
/// - template: Template to render with
|
||||||
|
/// - Returns: Rendered text
|
||||||
func renderSection(_ child: Any?, parent: Any, with template: HBMustacheTemplate) -> String {
|
func renderSection(_ child: Any?, parent: Any, with template: HBMustacheTemplate) -> String {
|
||||||
switch child {
|
switch child {
|
||||||
case let array as HBMustacheSequence:
|
case let array as HBMustacheSequence:
|
||||||
@@ -42,7 +53,7 @@ extension HBMustacheTemplate {
|
|||||||
case let bool as Bool:
|
case let bool as Bool:
|
||||||
return bool ? template.render(parent) : ""
|
return bool ? template.render(parent) : ""
|
||||||
case let lambda as HBMustacheLambda:
|
case let lambda as HBMustacheLambda:
|
||||||
return lambda(parent, template)
|
return lambda.run(parent, template)
|
||||||
case .some(let value):
|
case .some(let value):
|
||||||
return template.render(value)
|
return template.render(value)
|
||||||
case .none:
|
case .none:
|
||||||
@@ -50,6 +61,12 @@ extension HBMustacheTemplate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render an inverted section
|
||||||
|
/// - Parameters:
|
||||||
|
/// - child: Object to render section for
|
||||||
|
/// - parent: Current object being rendered
|
||||||
|
/// - template: Template to render with
|
||||||
|
/// - Returns: Rendered text
|
||||||
func renderInvertedSection(_ child: Any?, parent: Any, with template: HBMustacheTemplate) -> String {
|
func renderInvertedSection(_ child: Any?, parent: Any, with template: HBMustacheTemplate) -> String {
|
||||||
switch child {
|
switch child {
|
||||||
case let array as HBMustacheSequence:
|
case let array as HBMustacheSequence:
|
||||||
@@ -62,7 +79,8 @@ extension HBMustacheTemplate {
|
|||||||
return template.render(parent)
|
return template.render(parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get child object from variable name
|
||||||
func getChild(named name: String, from object: Any, method: String?, context: HBMustacheContext?) -> Any? {
|
func getChild(named name: String, from object: Any, method: String?, context: HBMustacheContext?) -> Any? {
|
||||||
func _getChild(named names: ArraySlice<String>, from object: Any) -> Any? {
|
func _getChild(named names: ArraySlice<String>, from object: Any) -> Any? {
|
||||||
guard let name = names.first else { return object }
|
guard let name = names.first else { return object }
|
||||||
@@ -78,6 +96,9 @@ extension HBMustacheTemplate {
|
|||||||
return _getChild(named: names2, from: childObject!)
|
return _getChild(named: names2, from: childObject!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// work out which object to access. "." means the current object, if the variable name is ""
|
||||||
|
// and we have a method to run on the variable then we need the context object, otherwise
|
||||||
|
// the name is split by "." and we use mirror to get the correct child object
|
||||||
let child: Any?
|
let child: Any?
|
||||||
if name == "." {
|
if name == "." {
|
||||||
child = object
|
child = object
|
||||||
@@ -87,6 +108,8 @@ extension HBMustacheTemplate {
|
|||||||
let nameSplit = name.split(separator: ".").map { String($0) }
|
let nameSplit = name.split(separator: ".").map { String($0) }
|
||||||
child = _getChild(named: nameSplit[...], from: object)
|
child = _getChild(named: nameSplit[...], from: object)
|
||||||
}
|
}
|
||||||
|
// if we want to run a method and the current child can have methods applied to it then
|
||||||
|
// run method on the current child
|
||||||
if let method = method,
|
if let method = method,
|
||||||
let runnable = child as? HBMustacheMethods {
|
let runnable = child as? HBMustacheMethods {
|
||||||
if let result = runnable.runMethod(method) {
|
if let result = runnable.runMethod(method) {
|
||||||
@@ -95,23 +118,5 @@ extension HBMustacheTemplate {
|
|||||||
}
|
}
|
||||||
return child
|
return child
|
||||||
}
|
}
|
||||||
|
|
||||||
private static let htmlEscapedCharacters: [Character: String] = [
|
|
||||||
"<": "<",
|
|
||||||
">": ">",
|
|
||||||
"&": "&",
|
|
||||||
]
|
|
||||||
func htmlEscape(_ string: String) -> String {
|
|
||||||
var newString = ""
|
|
||||||
newString.reserveCapacity(string.count)
|
|
||||||
for c in string {
|
|
||||||
if let replacement = Self.htmlEscapedCharacters[c] {
|
|
||||||
newString += replacement
|
|
||||||
} else {
|
|
||||||
newString.append(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newString
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ final class TemplateRendererTests: XCTestCase {
|
|||||||
func wrapped(object: Any, template: HBMustacheTemplate) -> String {
|
func wrapped(object: Any, template: HBMustacheTemplate) -> String {
|
||||||
return "<b>\(template.render(object))</b>"
|
return "<b>\(template.render(object))</b>"
|
||||||
}
|
}
|
||||||
let object: [String: Any] = ["name": "Willy", "wrapped": wrapped]
|
let object: [String: Any] = ["name": "Willy", "wrapped": HBMustacheLambda(wrapped)]
|
||||||
XCTAssertEqual(template.render(object), """
|
XCTAssertEqual(template.render(object), """
|
||||||
<b>Willy is awesome.</b>
|
<b>Willy is awesome.</b>
|
||||||
""")
|
""")
|
||||||
|
|||||||
Reference in New Issue
Block a user