diff --git a/Sources/HummingbirdMustache/Lambda.swift b/Sources/HummingbirdMustache/Lambda.swift
index 5bb3e3f..58b32ae 100644
--- a/Sources/HummingbirdMustache/Lambda.swift
+++ b/Sources/HummingbirdMustache/Lambda.swift
@@ -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 "\(template.render(object))"
+/// }))
+/// let mustache = "{{#wrapped}}{{name}} is awesome.{{/wrapped}}"
+/// let template = try HBMustacheTemplate(string: mustache)
+/// let output = template.render(willy)
+/// print(output) // Willy is awesome
+/// ```
+///
+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)
+ }
+}
diff --git a/Sources/HummingbirdMustache/String.swift b/Sources/HummingbirdMustache/String.swift
new file mode 100644
index 0000000..eba3037
--- /dev/null
+++ b/Sources/HummingbirdMustache/String.swift
@@ -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
+ }
+}
diff --git a/Sources/HummingbirdMustache/Template+Render.swift b/Sources/HummingbirdMustache/Template+Render.swift
index 5499e1f..0d72411 100644
--- a/Sources/HummingbirdMustache/Template+Render.swift
+++ b/Sources/HummingbirdMustache/Template+Render.swift
@@ -1,5 +1,10 @@
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 {
var string = ""
for token in tokens {
@@ -11,7 +16,7 @@ extension HBMustacheTemplate {
if let template = child as? HBMustacheTemplate {
string += template.render(object)
} else {
- string += htmlEscape(String(describing: child))
+ string += String(describing: child).htmlEscape()
}
}
case .unescapedVariable(let variable, let method):
@@ -34,7 +39,13 @@ extension HBMustacheTemplate {
}
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 {
switch child {
case let array as HBMustacheSequence:
@@ -42,7 +53,7 @@ extension HBMustacheTemplate {
case let bool as Bool:
return bool ? template.render(parent) : ""
case let lambda as HBMustacheLambda:
- return lambda(parent, template)
+ return lambda.run(parent, template)
case .some(let value):
return template.render(value)
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 {
switch child {
case let array as HBMustacheSequence:
@@ -62,7 +79,8 @@ extension HBMustacheTemplate {
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 names: ArraySlice, from object: Any) -> Any? {
guard let name = names.first else { return object }
@@ -78,6 +96,9 @@ extension HBMustacheTemplate {
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?
if name == "." {
child = object
@@ -87,6 +108,8 @@ extension HBMustacheTemplate {
let nameSplit = name.split(separator: ".").map { String($0) }
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,
let runnable = child as? HBMustacheMethods {
if let result = runnable.runMethod(method) {
@@ -95,23 +118,5 @@ extension HBMustacheTemplate {
}
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
- }
}
diff --git a/Tests/HummingbirdMustacheTests/TemplateRendererTests.swift b/Tests/HummingbirdMustacheTests/TemplateRendererTests.swift
index 360f8a9..e18d651 100644
--- a/Tests/HummingbirdMustacheTests/TemplateRendererTests.swift
+++ b/Tests/HummingbirdMustacheTests/TemplateRendererTests.swift
@@ -166,7 +166,7 @@ final class TemplateRendererTests: XCTestCase {
func wrapped(object: Any, template: HBMustacheTemplate) -> String {
return "\(template.render(object))"
}
- let object: [String: Any] = ["name": "Willy", "wrapped": wrapped]
+ let object: [String: Any] = ["name": "Willy", "wrapped": HBMustacheLambda(wrapped)]
XCTAssertEqual(template.render(object), """
Willy is awesome.
""")