Add library and support for partials
This commit is contained in:
17
Sources/HummingbirdMustache/Library.swift
Normal file
17
Sources/HummingbirdMustache/Library.swift
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
public class HBMustacheLibrary {
|
||||||
|
init() {
|
||||||
|
self.templates = [:]
|
||||||
|
}
|
||||||
|
|
||||||
|
public func register(_ template: HBMustacheTemplate, named name: String) {
|
||||||
|
templates[name] = template
|
||||||
|
}
|
||||||
|
|
||||||
|
public func render(_ object: Any, withTemplateNamed name: String) -> String? {
|
||||||
|
guard let template = templates[name] else { return nil }
|
||||||
|
return template.render(object, library: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
var templates: [String: HBMustacheTemplate]
|
||||||
|
}
|
||||||
@@ -13,39 +13,22 @@ extension Dictionary: HBMustacheParent where Key == String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protocol HBSequence {
|
protocol HBSequence {
|
||||||
func renderSection(with template: HBMustacheTemplate) -> String
|
func renderSection(with template: HBMustacheTemplate, library: HBMustacheLibrary?) -> String
|
||||||
func renderInvertedSection(with template: HBMustacheTemplate) -> String
|
func renderInvertedSection(with template: HBMustacheTemplate, library: HBMustacheLibrary?) -> String
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Array: HBSequence {
|
extension Array: HBSequence {
|
||||||
func renderSection(with template: HBMustacheTemplate) -> String {
|
func renderSection(with template: HBMustacheTemplate, library: HBMustacheLibrary?) -> String {
|
||||||
var string = ""
|
var string = ""
|
||||||
for obj in self {
|
for obj in self {
|
||||||
string += template.render(obj)
|
string += template.render(obj, library: library)
|
||||||
}
|
}
|
||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderInvertedSection(with template: HBMustacheTemplate) -> String {
|
func renderInvertedSection(with template: HBMustacheTemplate, library: HBMustacheLibrary?) -> String {
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
return template.render(self)
|
return template.render(self, library: library)
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Dictionary: HBSequence {
|
|
||||||
func renderSection(with template: HBMustacheTemplate) -> String {
|
|
||||||
var string = ""
|
|
||||||
for obj in self {
|
|
||||||
string += template.render(obj)
|
|
||||||
}
|
|
||||||
return string
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderInvertedSection(with template: HBMustacheTemplate) -> String {
|
|
||||||
if count == 0 {
|
|
||||||
return template.render(self)
|
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ extension HBMustacheTemplate {
|
|||||||
parser.unsafeAdvance()
|
parser.unsafeAdvance()
|
||||||
_ = try parseSection(&parser)
|
_ = try parseSection(&parser)
|
||||||
|
|
||||||
|
case ">":
|
||||||
|
parser.unsafeAdvance()
|
||||||
|
let name = try parseSectionName(&parser)
|
||||||
|
tokens.append(.partial(name))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
let name = try parseSectionName(&parser)
|
let name = try parseSectionName(&parser)
|
||||||
tokens.append(.variable(name))
|
tokens.append(.variable(name))
|
||||||
@@ -66,7 +71,9 @@ extension HBMustacheTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func parseSectionName(_ parser: inout HBParser) throws -> String {
|
static func parseSectionName(_ parser: inout HBParser) throws -> String {
|
||||||
|
parser.read(while: \.isWhitespace)
|
||||||
let text = parser.read(while: sectionNameChars )
|
let text = parser.read(while: sectionNameChars )
|
||||||
|
parser.read(while: \.isWhitespace)
|
||||||
guard try parser.read("}"), try parser.read("}") else { throw HBMustacheError.unfinishedSectionName }
|
guard try parser.read("}"), try parser.read("}") else { throw HBMustacheError.unfinishedSectionName }
|
||||||
return text.string
|
return text.string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
extension HBMustacheTemplate {
|
extension HBMustacheTemplate {
|
||||||
public func render(_ object: Any) -> String {
|
public func render(_ object: Any, library: HBMustacheLibrary? = nil) -> String {
|
||||||
var string = ""
|
var string = ""
|
||||||
for token in tokens {
|
for token in tokens {
|
||||||
switch token {
|
switch token {
|
||||||
@@ -16,40 +16,44 @@ extension HBMustacheTemplate {
|
|||||||
}
|
}
|
||||||
case .section(let variable, let template):
|
case .section(let variable, let template):
|
||||||
let child = getChild(named: variable, from: object)
|
let child = getChild(named: variable, from: object)
|
||||||
string += renderSection(child, parent: object, with: template)
|
string += renderSection(child, parent: object, with: template, library: library)
|
||||||
|
|
||||||
case .invertedSection(let variable, let template):
|
case .invertedSection(let variable, let template):
|
||||||
let child = getChild(named: variable, from: object)
|
let child = getChild(named: variable, from: object)
|
||||||
string += renderInvertedSection(child, parent: object, with: template)
|
string += renderInvertedSection(child, parent: object, with: template, library: library)
|
||||||
|
|
||||||
|
case .partial(let name):
|
||||||
|
if let text = library?.render(object, withTemplateNamed: name) {
|
||||||
|
string += text
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderSection(_ child: Any?, parent: Any, with template: HBMustacheTemplate) -> String {
|
func renderSection(_ child: Any?, parent: Any, with template: HBMustacheTemplate, library: HBMustacheLibrary?) -> String {
|
||||||
switch child {
|
switch child {
|
||||||
case let array as HBSequence:
|
case let array as HBSequence:
|
||||||
return array.renderSection(with: template)
|
return array.renderSection(with: template, library: library)
|
||||||
case let bool as Bool:
|
case let bool as Bool:
|
||||||
return bool ? template.render(parent) : ""
|
return bool ? template.render(parent, library: library) : ""
|
||||||
case .some(let value):
|
case .some(let value):
|
||||||
return template.render(value)
|
return template.render(value, library: library)
|
||||||
case .none:
|
case .none:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderInvertedSection(_ child: Any?, parent: Any, with template: HBMustacheTemplate) -> String {
|
func renderInvertedSection(_ child: Any?, parent: Any, with template: HBMustacheTemplate, library: HBMustacheLibrary?) -> String {
|
||||||
switch child {
|
switch child {
|
||||||
case let array as HBSequence:
|
case let array as HBSequence:
|
||||||
return array.renderInvertedSection(with: template)
|
return array.renderInvertedSection(with: template, library: library)
|
||||||
case let bool as Bool:
|
case let bool as Bool:
|
||||||
return bool ? "" : template.render(parent)
|
return bool ? "" : template.render(parent, library: library)
|
||||||
case .some:
|
case .some:
|
||||||
return ""
|
return ""
|
||||||
case .none:
|
case .none:
|
||||||
return template.render(parent)
|
return template.render(parent, library: library)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ public class HBMustacheTemplate {
|
|||||||
case unescapedVariable(String)
|
case unescapedVariable(String)
|
||||||
case section(String, HBMustacheTemplate)
|
case section(String, HBMustacheTemplate)
|
||||||
case invertedSection(String, HBMustacheTemplate)
|
case invertedSection(String, HBMustacheTemplate)
|
||||||
|
case partial(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
let tokens: [Token]
|
let tokens: [Token]
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ final class TemplateRendererTests: XCTestCase {
|
|||||||
func testIntegerSection() throws {
|
func testIntegerSection() throws {
|
||||||
let template = try HBMustacheTemplate(string: "test {{#.}}{{.}}{{/.}}")
|
let template = try HBMustacheTemplate(string: "test {{#.}}{{.}}{{/.}}")
|
||||||
XCTAssertEqual(template.render(23), "test 23")
|
XCTAssertEqual(template.render(23), "test 23")
|
||||||
XCTAssertEqual(template.render(0), "test ")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testStringSection() throws {
|
func testStringSection() throws {
|
||||||
@@ -80,12 +79,6 @@ final class TemplateRendererTests: XCTestCase {
|
|||||||
XCTAssertEqual(template2.render(Test(string: nil)), "test *")
|
XCTAssertEqual(template2.render(Test(string: nil)), "test *")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDictionarySequence() throws {
|
|
||||||
let template = try HBMustacheTemplate(string: "test {{#.}}{{value}}{{/.}}")
|
|
||||||
XCTAssert(template.render(["one": 1, "two": 2]) == "test 12" ||
|
|
||||||
template.render(["one": 1, "two": 2]) == "test 21")
|
|
||||||
}
|
|
||||||
|
|
||||||
func testStructureInStructure() throws {
|
func testStructureInStructure() throws {
|
||||||
struct SubTest {
|
struct SubTest {
|
||||||
let string: String?
|
let string: String?
|
||||||
@@ -159,4 +152,67 @@ final class TemplateRendererTests: XCTestCase {
|
|||||||
|
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testMustacheManualExample6() throws {
|
||||||
|
let template = try HBMustacheTemplate(string: """
|
||||||
|
{{#person?}}
|
||||||
|
Hi {{name}}!
|
||||||
|
{{/person?}}
|
||||||
|
""")
|
||||||
|
let object: [String: Any] = ["person?": ["name": "Jon"]]
|
||||||
|
XCTAssertEqual(template.render(object), """
|
||||||
|
Hi Jon!
|
||||||
|
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMustacheManualExample7() throws {
|
||||||
|
let template = try HBMustacheTemplate(string: """
|
||||||
|
{{#repo}}
|
||||||
|
<b>{{name}}</b>
|
||||||
|
{{/repo}}
|
||||||
|
{{^repo}}
|
||||||
|
No repos :(
|
||||||
|
{{/repo}}
|
||||||
|
""")
|
||||||
|
let object: [String: Any] = ["repo": []]
|
||||||
|
XCTAssertEqual(template.render(object), """
|
||||||
|
No repos :(
|
||||||
|
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMustacheManualExample8() throws {
|
||||||
|
let template = try HBMustacheTemplate(string: """
|
||||||
|
<h1>Today{{! ignore me }}.</h1>
|
||||||
|
""")
|
||||||
|
let object: [String: Any] = ["repo": []]
|
||||||
|
XCTAssertEqual(template.render(object), """
|
||||||
|
<h1>Today.</h1>
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMustacheManualExample9() throws {
|
||||||
|
let library = HBMustacheLibrary()
|
||||||
|
let template = try HBMustacheTemplate(string: """
|
||||||
|
<h2>Names</h2>
|
||||||
|
{{#names}}
|
||||||
|
{{> user}}
|
||||||
|
{{/names}}
|
||||||
|
""")
|
||||||
|
let template2 = try HBMustacheTemplate(string: """
|
||||||
|
<strong>{{.}}</strong>
|
||||||
|
""")
|
||||||
|
library.register(template, named: "base")
|
||||||
|
library.register(template2, named: "user")
|
||||||
|
|
||||||
|
let object: [String: Any] = ["names": ["john", "adam", "claire"]]
|
||||||
|
XCTAssertEqual(library.render(object, withTemplateNamed: "base"), """
|
||||||
|
<h2>Names</h2>
|
||||||
|
<strong>john</strong>
|
||||||
|
<strong>adam</strong>
|
||||||
|
<strong>claire</strong>
|
||||||
|
|
||||||
|
""")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user