diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f19dae3..b1e6290 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -40,9 +40,10 @@ jobs:
strategy:
matrix:
image:
- - 'swift:5.6'
- 'swift:5.7'
- 'swift:5.8'
+ - 'swift:5.9'
+ - 'swiftlang/swift:nightly-5.10-jammy'
container:
image: ${{ matrix.image }}
diff --git a/Package.swift b/Package.swift
index 692780d..7aaedd8 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,10 +1,11 @@
-// swift-tools-version:5.3
+// swift-tools-version:5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "hummingbird-mustache",
+ platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6)],
products: [
.library(name: "HummingbirdMustache", targets: ["HummingbirdMustache"]),
],
diff --git a/Sources/HummingbirdMustache/ContentType.swift b/Sources/HummingbirdMustache/ContentType.swift
index 4625d68..1a578a1 100644
--- a/Sources/HummingbirdMustache/ContentType.swift
+++ b/Sources/HummingbirdMustache/ContentType.swift
@@ -13,7 +13,7 @@
//===----------------------------------------------------------------------===//
/// Protocol for content types
-public protocol HBMustacheContentType {
+public protocol HBMustacheContentType: Sendable {
/// escape text for this content type eg for HTML replace "<" with "<"
func escapeText(_ text: String) -> String
}
diff --git a/Sources/HummingbirdMustache/Context.swift b/Sources/HummingbirdMustache/Context.swift
index 1815c0d..83e1f63 100644
--- a/Sources/HummingbirdMustache/Context.swift
+++ b/Sources/HummingbirdMustache/Context.swift
@@ -19,14 +19,16 @@ struct HBMustacheContext {
let indentation: String?
let inherited: [String: HBMustacheTemplate]?
let contentType: HBMustacheContentType
+ let library: HBMustacheLibrary?
/// initialize context with a single objectt
- init(_ object: Any) {
+ init(_ object: Any, library: HBMustacheLibrary? = nil) {
self.stack = [object]
self.sequenceContext = nil
self.indentation = nil
self.inherited = nil
self.contentType = HBHTMLContentType()
+ self.library = library
}
private init(
@@ -34,13 +36,15 @@ struct HBMustacheContext {
sequenceContext: HBMustacheSequenceContext?,
indentation: String?,
inherited: [String: HBMustacheTemplate]?,
- contentType: HBMustacheContentType
+ contentType: HBMustacheContentType,
+ library: HBMustacheLibrary? = nil
) {
self.stack = stack
self.sequenceContext = sequenceContext
self.indentation = indentation
self.inherited = inherited
self.contentType = contentType
+ self.library = library
}
/// return context with object add to stack
@@ -52,7 +56,8 @@ struct HBMustacheContext {
sequenceContext: nil,
indentation: self.indentation,
inherited: self.inherited,
- contentType: self.contentType
+ contentType: self.contentType,
+ library: self.library
)
}
@@ -79,7 +84,8 @@ struct HBMustacheContext {
sequenceContext: nil,
indentation: indentation,
inherited: inherits,
- contentType: HBHTMLContentType()
+ contentType: HBHTMLContentType(),
+ library: self.library
)
}
@@ -92,7 +98,8 @@ struct HBMustacheContext {
sequenceContext: sequenceContext,
indentation: self.indentation,
inherited: self.inherited,
- contentType: self.contentType
+ contentType: self.contentType,
+ library: self.library
)
}
@@ -103,7 +110,8 @@ struct HBMustacheContext {
sequenceContext: self.sequenceContext,
indentation: self.indentation,
inherited: self.inherited,
- contentType: contentType
+ contentType: contentType,
+ library: self.library
)
}
}
diff --git a/Sources/HummingbirdMustache/Library+FileSystem.swift b/Sources/HummingbirdMustache/Library+FileSystem.swift
index c878993..9503eed 100644
--- a/Sources/HummingbirdMustache/Library+FileSystem.swift
+++ b/Sources/HummingbirdMustache/Library+FileSystem.swift
@@ -16,19 +16,20 @@ import Foundation
extension HBMustacheLibrary {
/// Load templates from a folder
- func loadTemplates(from directory: String, withExtension extension: String = "mustache") throws {
+ static func loadTemplates(from directory: String, withExtension extension: String = "mustache") async throws -> [String: HBMustacheTemplate] {
var directory = directory
if !directory.hasSuffix("/") {
directory += "/"
}
let extWithDot = ".\(`extension`)"
let fs = FileManager()
- guard let enumerator = fs.enumerator(atPath: directory) else { return }
+ guard let enumerator = fs.enumerator(atPath: directory) else { return [:] }
+ var templates: [String: HBMustacheTemplate] = [:]
for case let path as String in enumerator {
guard path.hasSuffix(extWithDot) else { continue }
guard let data = fs.contents(atPath: directory + path) else { continue }
let string = String(decoding: data, as: Unicode.UTF8.self)
- let template: HBMustacheTemplate
+ var template: HBMustacheTemplate
do {
template = try HBMustacheTemplate(string: string)
} catch let error as HBMustacheTemplate.ParserError {
@@ -36,7 +37,8 @@ extension HBMustacheLibrary {
}
// drop ".mustache" from path to get name
let name = String(path.dropLast(extWithDot.count))
- register(template, named: name)
+ templates[name] = template
}
+ return templates
}
}
diff --git a/Sources/HummingbirdMustache/Library.swift b/Sources/HummingbirdMustache/Library.swift
index f016ead..515292e 100644
--- a/Sources/HummingbirdMustache/Library.swift
+++ b/Sources/HummingbirdMustache/Library.swift
@@ -18,7 +18,7 @@
/// ```
/// {{#sequence}}{{>entry}}{{/sequence}}
/// ```
-public final class HBMustacheLibrary {
+public struct HBMustacheLibrary: Sendable {
/// Initialize empty library
public init() {
self.templates = [:]
@@ -30,17 +30,25 @@ public final class HBMustacheLibrary {
/// 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") throws {
- self.templates = [:]
- try loadTemplates(from: directory, withExtension: `extension`)
+ public init(templates: [String: HBMustacheTemplate]) {
+ self.templates = templates
+ }
+
+ /// 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") async throws {
+ self.templates = try await Self.loadTemplates(from: directory, withExtension: `extension`)
}
/// Register template under name
/// - Parameters:
/// - template: Template
/// - name: Name of template
- public func register(_ template: HBMustacheTemplate, named name: String) {
- template.setLibrary(self)
+ public mutating func register(_ template: HBMustacheTemplate, named name: String) {
self.templates[name] = template
}
@@ -48,9 +56,8 @@ public final class HBMustacheLibrary {
/// - Parameters:
/// - mustache: Mustache text
/// - name: Name of template
- public func register(_ mustache: String, named name: String) throws {
+ public mutating func register(_ mustache: String, named name: String) throws {
let template = try HBMustacheTemplate(string: mustache)
- template.setLibrary(self)
self.templates[name] = template
}
@@ -68,7 +75,7 @@ public final class HBMustacheLibrary {
/// - Returns: Rendered text
public func render(_ object: Any, withTemplate name: String) -> String? {
guard let template = templates[name] else { return nil }
- return template.render(object)
+ return template.render(object, library: self)
}
/// Error returned by init() when parser fails
diff --git a/Sources/HummingbirdMustache/Template+Render.swift b/Sources/HummingbirdMustache/Template+Render.swift
index d7ef5b2..dca1acf 100644
--- a/Sources/HummingbirdMustache/Template+Render.swift
+++ b/Sources/HummingbirdMustache/Template+Render.swift
@@ -80,7 +80,7 @@ extension HBMustacheTemplate {
}
case .partial(let name, let indentation, let overrides):
- if let template = library?.getTemplate(named: name) {
+ if let template = context.library?.getTemplate(named: name) {
return template.render(context: context.withPartial(indented: indentation, inheriting: overrides))
}
diff --git a/Sources/HummingbirdMustache/Template.swift b/Sources/HummingbirdMustache/Template.swift
index 2400a95..a82d8c1 100644
--- a/Sources/HummingbirdMustache/Template.swift
+++ b/Sources/HummingbirdMustache/Template.swift
@@ -13,7 +13,7 @@
//===----------------------------------------------------------------------===//
/// Class holding Mustache template
-public final class HBMustacheTemplate {
+public struct HBMustacheTemplate: Sendable {
/// Initialize template
/// - Parameter string: Template text
/// - Throws: HBMustacheTemplate.Error
@@ -24,29 +24,15 @@ public final class HBMustacheTemplate {
/// Render object using this template
/// - Parameter object: Object to render
/// - Returns: Rendered text
- public func render(_ object: Any) -> String {
- self.render(context: .init(object))
+ public func render(_ object: Any, library: HBMustacheLibrary? = nil) -> String {
+ self.render(context: .init(object, library: library))
}
internal init(_ tokens: [Token]) {
self.tokens = tokens
}
- internal func setLibrary(_ library: HBMustacheLibrary) {
- self.library = library
- for token in self.tokens {
- switch token {
- case .section(_, _, let template), .invertedSection(_, _, let template), .inheritedSection(_, let template):
- template.setLibrary(library)
- case .partial(_, _, let templates):
- templates?.forEach { $1.setLibrary(library) }
- default:
- break
- }
- }
- }
-
- enum Token {
+ enum Token: Sendable {
case text(String)
case variable(name: String, transform: String? = nil)
case unescapedVariable(name: String, transform: String? = nil)
@@ -57,6 +43,5 @@ public final class HBMustacheTemplate {
case contentType(HBMustacheContentType)
}
- let tokens: [Token]
- var library: HBMustacheLibrary?
+ var tokens: [Token]
}
diff --git a/Tests/HummingbirdMustacheTests/LibraryTests.swift b/Tests/HummingbirdMustacheTests/LibraryTests.swift
index 8f4cee5..5a92bcf 100644
--- a/Tests/HummingbirdMustacheTests/LibraryTests.swift
+++ b/Tests/HummingbirdMustacheTests/LibraryTests.swift
@@ -16,7 +16,7 @@
import XCTest
final class LibraryTests: XCTestCase {
- func testDirectoryLoad() throws {
+ func testDirectoryLoad() async throws {
let fs = FileManager()
try? fs.createDirectory(atPath: "templates", withIntermediateDirectories: false)
defer { XCTAssertNoThrow(try fs.removeItem(atPath: "templates")) }
@@ -24,12 +24,12 @@ final class LibraryTests: XCTestCase {
try mustache.write(to: URL(fileURLWithPath: "templates/test.mustache"))
defer { XCTAssertNoThrow(try fs.removeItem(atPath: "templates/test.mustache")) }
- let library = try HBMustacheLibrary(directory: "./templates")
+ let library = try await HBMustacheLibrary(directory: "./templates")
let object = ["value": ["value1", "value2"]]
XCTAssertEqual(library.render(object, withTemplate: "test"), "