Comments
This commit is contained in:
@@ -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("/") {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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] }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user