Add library and support for partials

This commit is contained in:
Adam Fowler
2021-03-12 08:41:13 +00:00
parent 7f61c8dd72
commit fc53f09dfb
6 changed files with 109 additions and 41 deletions

View 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]
}

View File

@@ -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 ""
} }

View File

@@ -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
} }

View File

@@ -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)
} }
} }

View File

@@ -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]

View File

@@ -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>
""")
}
} }