diff --git a/Sources/HummingbirdMustache/Library.swift b/Sources/HummingbirdMustache/Library.swift new file mode 100644 index 0000000..7eb7fe7 --- /dev/null +++ b/Sources/HummingbirdMustache/Library.swift @@ -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] +} diff --git a/Sources/HummingbirdMustache/Renderable.swift b/Sources/HummingbirdMustache/Renderable.swift index 58581fe..84c2cfa 100644 --- a/Sources/HummingbirdMustache/Renderable.swift +++ b/Sources/HummingbirdMustache/Renderable.swift @@ -13,39 +13,22 @@ extension Dictionary: HBMustacheParent where Key == String { } protocol HBSequence { - func renderSection(with template: HBMustacheTemplate) -> String - func renderInvertedSection(with template: HBMustacheTemplate) -> String + func renderSection(with template: HBMustacheTemplate, library: HBMustacheLibrary?) -> String + func renderInvertedSection(with template: HBMustacheTemplate, library: HBMustacheLibrary?) -> String } extension Array: HBSequence { - func renderSection(with template: HBMustacheTemplate) -> String { + func renderSection(with template: HBMustacheTemplate, library: HBMustacheLibrary?) -> String { var string = "" for obj in self { - string += template.render(obj) + string += template.render(obj, library: library) } return string } - func renderInvertedSection(with template: HBMustacheTemplate) -> String { + func renderInvertedSection(with template: HBMustacheTemplate, library: HBMustacheLibrary?) -> String { if count == 0 { - return template.render(self) - } - 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 template.render(self, library: library) } return "" } diff --git a/Sources/HummingbirdMustache/Template+Parser.swift b/Sources/HummingbirdMustache/Template+Parser.swift index c469300..9aa2938 100644 --- a/Sources/HummingbirdMustache/Template+Parser.swift +++ b/Sources/HummingbirdMustache/Template+Parser.swift @@ -53,6 +53,11 @@ extension HBMustacheTemplate { parser.unsafeAdvance() _ = try parseSection(&parser) + case ">": + parser.unsafeAdvance() + let name = try parseSectionName(&parser) + tokens.append(.partial(name)) + default: let name = try parseSectionName(&parser) tokens.append(.variable(name)) @@ -66,7 +71,9 @@ extension HBMustacheTemplate { } static func parseSectionName(_ parser: inout HBParser) throws -> String { + parser.read(while: \.isWhitespace) let text = parser.read(while: sectionNameChars ) + parser.read(while: \.isWhitespace) guard try parser.read("}"), try parser.read("}") else { throw HBMustacheError.unfinishedSectionName } return text.string } diff --git a/Sources/HummingbirdMustache/Template+Render.swift b/Sources/HummingbirdMustache/Template+Render.swift index 189da34..48a6869 100644 --- a/Sources/HummingbirdMustache/Template+Render.swift +++ b/Sources/HummingbirdMustache/Template+Render.swift @@ -1,6 +1,6 @@ extension HBMustacheTemplate { - public func render(_ object: Any) -> String { + public func render(_ object: Any, library: HBMustacheLibrary? = nil) -> String { var string = "" for token in tokens { switch token { @@ -16,40 +16,44 @@ extension HBMustacheTemplate { } case .section(let variable, let template): 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): 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 } - func renderSection(_ child: Any?, parent: Any, with template: HBMustacheTemplate) -> String { + func renderSection(_ child: Any?, parent: Any, with template: HBMustacheTemplate, library: HBMustacheLibrary?) -> String { switch child { case let array as HBSequence: - return array.renderSection(with: template) + return array.renderSection(with: template, library: library) case let bool as Bool: - return bool ? template.render(parent) : "" + return bool ? template.render(parent, library: library) : "" case .some(let value): - return template.render(value) + return template.render(value, library: library) case .none: 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 { case let array as HBSequence: - return array.renderInvertedSection(with: template) + return array.renderInvertedSection(with: template, library: library) case let bool as Bool: - return bool ? "" : template.render(parent) + return bool ? "" : template.render(parent, library: library) case .some: return "" case .none: - return template.render(parent) + return template.render(parent, library: library) } } diff --git a/Sources/HummingbirdMustache/Template.swift b/Sources/HummingbirdMustache/Template.swift index 9559916..d0609d6 100644 --- a/Sources/HummingbirdMustache/Template.swift +++ b/Sources/HummingbirdMustache/Template.swift @@ -20,6 +20,7 @@ public class HBMustacheTemplate { case unescapedVariable(String) case section(String, HBMustacheTemplate) case invertedSection(String, HBMustacheTemplate) + case partial(String) } let tokens: [Token] diff --git a/Tests/HummingbirdMustacheTests/TemplateRendererTests.swift b/Tests/HummingbirdMustacheTests/TemplateRendererTests.swift index d0c1543..062c1fa 100644 --- a/Tests/HummingbirdMustacheTests/TemplateRendererTests.swift +++ b/Tests/HummingbirdMustacheTests/TemplateRendererTests.swift @@ -37,7 +37,6 @@ final class TemplateRendererTests: XCTestCase { func testIntegerSection() throws { let template = try HBMustacheTemplate(string: "test {{#.}}{{.}}{{/.}}") XCTAssertEqual(template.render(23), "test 23") - XCTAssertEqual(template.render(0), "test ") } func testStringSection() throws { @@ -80,12 +79,6 @@ final class TemplateRendererTests: XCTestCase { 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 { struct SubTest { 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}} + {{name}} + {{/repo}} + {{^repo}} + No repos :( + {{/repo}} + """) + let object: [String: Any] = ["repo": []] + XCTAssertEqual(template.render(object), """ + No repos :( + + """) + } + + func testMustacheManualExample8() throws { + let template = try HBMustacheTemplate(string: """ +