This commit is contained in:
Adam Fowler
2021-03-15 15:09:16 +00:00
parent 978b14a96a
commit e391f5ae05
8 changed files with 70 additions and 27 deletions

View File

@@ -1,6 +1,7 @@
import Foundation import Foundation
extension HBMustacheLibrary { extension HBMustacheLibrary {
/// Load templates from a folder
func loadTemplates(from directory: String, withExtension extension: String = "mustache") { func loadTemplates(from directory: String, withExtension extension: String = "mustache") {
var directory = directory var directory = directory
if !directory.hasSuffix("/") { if !directory.hasSuffix("/") {

View File

@@ -1,23 +1,47 @@
/// Class holding a collection of mustache templates.
///
/// Each template can reference the others via a partial using the name the template is registered under
/// ```
/// {{#sequence}}{{>entry}}{{/sequence}}
/// ```
public class HBMustacheLibrary { public class HBMustacheLibrary {
/// Initialize empty library
public init() { public init() {
self.templates = [:] self.templates = [:]
} }
public init(directory: String) { /// Initialize library with contents of folder.
///
/// Each template is registered with the name of the file minus its extension. The search through
/// the folder is recursive and templates in subfolders will be registered with the name `subfolder/template`.
/// - Parameter directory: Directory to look for mustache templates
/// - Parameter extension: Extension of files to look for
public init(directory: String, withExtension extension: String = "mustache") {
self.templates = [:] self.templates = [:]
self.loadTemplates(from: directory) self.loadTemplates(from: directory)
} }
/// Register template under name
/// - Parameters:
/// - template: Template
/// - name: Name of template
public func register(_ template: HBMustacheTemplate, named name: String) { public func register(_ template: HBMustacheTemplate, named name: String) {
template.setLibrary(self) template.setLibrary(self)
templates[name] = template templates[name] = template
} }
/// Return template registed with name
/// - Parameter name: name to search for
/// - Returns: Template
public func getTemplate(named name: String) -> HBMustacheTemplate? { public func getTemplate(named name: String) -> HBMustacheTemplate? {
templates[name] templates[name]
} }
/// Render object using templated with name
/// - Parameters:
/// - object: Object to render
/// - name: Name of template
/// - Returns: Rendered text
public func render(_ object: Any, withTemplateNamed name: String) -> String? { public func render(_ object: Any, withTemplateNamed name: String) -> String? {
guard let template = templates[name] else { return nil } guard let template = templates[name] else { return nil }
return template.render(object) return template.render(object)

View File

@@ -1,13 +1,7 @@
func unwrapOptional(_ object: Any) -> Any? {
let mirror = Mirror(reflecting: object)
guard mirror.displayStyle == .optional else { return object }
guard let first = mirror.children.first else { return nil }
return first.value
}
extension Mirror { extension Mirror {
/// Return value from Mirror given name
func getValue(forKey key: String) -> Any? { func getValue(forKey key: String) -> Any? {
guard let matched = children.filter({ $0.label == key }).first else { guard let matched = children.filter({ $0.label == key }).first else {
return nil return nil
@@ -15,3 +9,12 @@ extension Mirror {
return unwrapOptional(matched.value) return unwrapOptional(matched.value)
} }
} }
/// Return object and if it is an Optional return object Optional holds
private func unwrapOptional(_ object: Any) -> Any? {
let mirror = Mirror(reflecting: object)
guard mirror.displayStyle == .optional else { return object }
guard let first = mirror.children.first else { return nil }
return first.value
}

View File

@@ -1,13 +1,12 @@
/// Protocol for object that has a custom method for accessing their children, instead
/// of using Mirror
protocol HBMustacheParent { protocol HBMustacheParent {
func child(named: String) -> Any? func child(named: String) -> Any?
} }
extension HBMustacheParent { /// Extend dictionary where the key is a string so that it uses the key values to access
// default child to nil /// it values
func child(named: String) -> Any? { return nil }
}
extension Dictionary: HBMustacheParent where Key == String { extension Dictionary: HBMustacheParent where Key == String {
func child(named: String) -> Any? { return self[named] } func child(named: String) -> Any? { return self[named] }
} }

View File

@@ -3,7 +3,7 @@
// Half inspired by Reader class from John Sundell's Ink project // Half inspired by Reader class from John Sundell's Ink project
// https://github.com/JohnSundell/Ink/blob/master/Sources/Ink/Internal/Reader.swift // https://github.com/JohnSundell/Ink/blob/master/Sources/Ink/Internal/Reader.swift
// with optimisation working ie removing String and doing my own UTF8 processing inspired by Fabian Fett's work in // with optimisation working ie removing String and doing my own UTF8 processing inspired by Fabian Fett's work in
// https://github.com/fabianfett/pure-swift-json/blob/master/Sources/PureSwiftJSONParsing/DocumentReader.swift // https://github.com/swift-extras/swift-extras-json/blob/main/Sources/ExtrasJSON/Parsing/DocumentReader.swift
// //
// This is a copy of the parser from Hummingbird. I am not using the version in Hummingbird to avoid the dependency // This is a copy of the parser from Hummingbird. I am not using the version in Hummingbird to avoid the dependency
import Foundation import Foundation

View File

@@ -1,11 +1,15 @@
protocol HBMustacheSequence { /// Protocol for objects that can be rendered as a sequence in Mustache
public protocol HBMustacheSequence {
/// Render section using template
func renderSection(with template: HBMustacheTemplate) -> String func renderSection(with template: HBMustacheTemplate) -> String
/// Render inverted section using template
func renderInvertedSection(with template: HBMustacheTemplate) -> String func renderInvertedSection(with template: HBMustacheTemplate) -> String
} }
extension Sequence { extension Sequence {
func renderSection(with template: HBMustacheTemplate) -> String { /// Render section using template
public func renderSection(with template: HBMustacheTemplate) -> String {
var string = "" var string = ""
var context = HBMustacheContext(first: true) var context = HBMustacheContext(first: true)
var iterator = self.makeIterator() var iterator = self.makeIterator()
@@ -24,7 +28,8 @@ extension Sequence {
return string return string
} }
func renderInvertedSection(with template: HBMustacheTemplate) -> String { /// Render inverted section using template
public func renderInvertedSection(with template: HBMustacheTemplate) -> String {
var iterator = makeIterator() var iterator = makeIterator()
if iterator.next() == nil { if iterator.next() == nil {
return template.render(self) return template.render(self)
@@ -35,4 +40,5 @@ extension Sequence {
} }
extension Array: HBMustacheSequence {} extension Array: HBMustacheSequence {}
extension Set: HBMustacheSequence {}
extension ReversedCollection: HBMustacheSequence {} extension ReversedCollection: HBMustacheSequence {}

View File

@@ -6,11 +6,13 @@ extension HBMustacheTemplate {
case expectedSectionEnd case expectedSectionEnd
} }
/// parse mustache text to generate a list of tokens
static func parse(_ string: String) throws -> [Token] { static func parse(_ string: String) throws -> [Token] {
var parser = HBParser(string) var parser = HBParser(string)
return try parse(&parser, sectionName: nil) return try parse(&parser, sectionName: nil)
} }
/// parse section in mustache text
static func parse(_ parser: inout HBParser, sectionName: String?) throws -> [Token] { static func parse(_ parser: inout HBParser, sectionName: String?) throws -> [Token] {
var tokens: [Token] = [] var tokens: [Token] = []
while !parser.reachedEnd() { while !parser.reachedEnd() {
@@ -78,6 +80,7 @@ extension HBMustacheTemplate {
return tokens return tokens
} }
/// parse variable name
static func parseName(_ parser: inout HBParser) throws -> (String, String?) { static func parseName(_ parser: inout HBParser) throws -> (String, String?) {
parser.read(while: \.isWhitespace) parser.read(while: \.isWhitespace)
var text = parser.read(while: sectionNameChars ) var text = parser.read(while: sectionNameChars )
@@ -88,6 +91,7 @@ extension HBMustacheTemplate {
if text.reachedEnd() { if text.reachedEnd() {
return (text.string, nil) return (text.string, nil)
} else { } else {
// parse function parameter, as we have just parsed a function name
guard text.current() == "(" else { throw Error.unfinishedName } guard text.current() == "(" else { throw Error.unfinishedName }
text.unsafeAdvance() text.unsafeAdvance()
let string2 = text.read(while: sectionNameCharsWithoutBrackets) let string2 = text.read(while: sectionNameCharsWithoutBrackets)

View File

@@ -1,17 +1,23 @@
/// Class holding Mustache template
public class HBMustacheTemplate { public class HBMustacheTemplate {
/// Initialize template
/// - Parameter string: Template text
/// - Throws: HBMustacheTemplate.Error
public init(string: String) throws { public init(string: String) throws {
self.tokens = try Self.parse(string) self.tokens = try Self.parse(string)
} }
internal init(_ tokens: [Token]) { /// Render object using this template
self.tokens = tokens /// - Parameter object: Object to render
} /// - Returns: Rendered text
public func render(_ object: Any) -> String { public func render(_ object: Any) -> String {
self.render(object, context: nil) self.render(object, context: nil)
} }
internal init(_ tokens: [Token]) {
self.tokens = tokens
}
internal func setLibrary(_ library: HBMustacheLibrary) { internal func setLibrary(_ library: HBMustacheLibrary) {
self.library = library self.library = library
for token in tokens { for token in tokens {