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
extension HBMustacheLibrary {
/// Load templates from a folder
func loadTemplates(from directory: String, withExtension extension: String = "mustache") {
var directory = directory
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 {
/// Initialize empty library
public init() {
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.loadTemplates(from: directory)
}
/// Register template under name
/// - Parameters:
/// - template: Template
/// - name: Name of template
public func register(_ template: HBMustacheTemplate, named name: String) {
template.setLibrary(self)
templates[name] = template
}
/// Return template registed with name
/// - Parameter name: name to search for
/// - Returns: Template
public func getTemplate(named name: String) -> HBMustacheTemplate? {
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? {
guard let template = templates[name] else { return nil }
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 {
/// Return value from Mirror given name
func getValue(forKey key: String) -> Any? {
guard let matched = children.filter({ $0.label == key }).first else {
return nil
@@ -15,3 +9,12 @@ extension Mirror {
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 {
func child(named: String) -> Any?
}
extension HBMustacheParent {
// default child to nil
func child(named: String) -> Any? { return nil }
}
/// Extend dictionary where the key is a string so that it uses the key values to access
/// it values
extension Dictionary: HBMustacheParent where Key == String {
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
// 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
// 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
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
/// Render inverted section using template
func renderInvertedSection(with template: HBMustacheTemplate) -> String
}
extension Sequence {
func renderSection(with template: HBMustacheTemplate) -> String {
/// Render section using template
public func renderSection(with template: HBMustacheTemplate) -> String {
var string = ""
var context = HBMustacheContext(first: true)
var iterator = self.makeIterator()
@@ -24,7 +28,8 @@ extension Sequence {
return string
}
func renderInvertedSection(with template: HBMustacheTemplate) -> String {
/// Render inverted section using template
public func renderInvertedSection(with template: HBMustacheTemplate) -> String {
var iterator = makeIterator()
if iterator.next() == nil {
return template.render(self)
@@ -35,4 +40,5 @@ extension Sequence {
}
extension Array: HBMustacheSequence {}
extension Set: HBMustacheSequence {}
extension ReversedCollection: HBMustacheSequence {}

View File

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

View File

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