diff --git a/Sources/Mustache/Context.swift b/Sources/Mustache/Context.swift
index ab48e3e..03488a9 100644
--- a/Sources/Mustache/Context.swift
+++ b/Sources/Mustache/Context.swift
@@ -2,7 +2,7 @@
//
// This source file is part of the Hummingbird server framework project
//
-// Copyright (c) 2021-2021 the Hummingbird authors
+// Copyright (c) 2021-2024 the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
@@ -20,15 +20,17 @@ struct MustacheContext {
let inherited: [String: MustacheTemplate]?
let contentType: MustacheContentType
let library: MustacheLibrary?
+ let reloadPartials: Bool
/// initialize context with a single objectt
- init(_ object: Any, library: MustacheLibrary? = nil) {
+ init(_ object: Any, library: MustacheLibrary? = nil, reloadPartials: Bool = false) {
self.stack = [object]
self.sequenceContext = nil
self.indentation = nil
self.inherited = nil
self.contentType = HTMLContentType()
self.library = library
+ self.reloadPartials = reloadPartials
}
private init(
@@ -37,7 +39,8 @@ struct MustacheContext {
indentation: String?,
inherited: [String: MustacheTemplate]?,
contentType: MustacheContentType,
- library: MustacheLibrary? = nil
+ library: MustacheLibrary? = nil,
+ reloadPartials: Bool
) {
self.stack = stack
self.sequenceContext = sequenceContext
@@ -45,6 +48,7 @@ struct MustacheContext {
self.inherited = inherited
self.contentType = contentType
self.library = library
+ self.reloadPartials = reloadPartials
}
/// return context with object add to stack
@@ -57,7 +61,8 @@ struct MustacheContext {
indentation: self.indentation,
inherited: self.inherited,
contentType: self.contentType,
- library: self.library
+ library: self.library,
+ reloadPartials: self.reloadPartials
)
}
@@ -83,7 +88,8 @@ struct MustacheContext {
indentation: indentation,
inherited: inherits,
contentType: HTMLContentType(),
- library: self.library
+ library: self.library,
+ reloadPartials: self.reloadPartials
)
}
@@ -100,7 +106,8 @@ struct MustacheContext {
indentation: indentation,
inherited: self.inherited,
contentType: self.contentType,
- library: self.library
+ library: self.library,
+ reloadPartials: self.reloadPartials
)
}
@@ -114,7 +121,8 @@ struct MustacheContext {
indentation: self.indentation,
inherited: self.inherited,
contentType: self.contentType,
- library: self.library
+ library: self.library,
+ reloadPartials: self.reloadPartials
)
}
@@ -126,7 +134,8 @@ struct MustacheContext {
indentation: self.indentation,
inherited: self.inherited,
contentType: contentType,
- library: self.library
+ library: self.library,
+ reloadPartials: self.reloadPartials
)
}
}
diff --git a/Sources/Mustache/Library+FileSystem.swift b/Sources/Mustache/Library+FileSystem.swift
index 06a95e8..ccdf0c8 100644
--- a/Sources/Mustache/Library+FileSystem.swift
+++ b/Sources/Mustache/Library+FileSystem.swift
@@ -2,7 +2,7 @@
//
// This source file is part of the Hummingbird server framework project
//
-// Copyright (c) 2021-2021 the Hummingbird authors
+// Copyright (c) 2021-2024 the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
@@ -27,17 +27,14 @@ extension MustacheLibrary {
var templates: [String: MustacheTemplate] = [:]
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)
- var template: MustacheTemplate
do {
- template = try MustacheTemplate(string: string)
+ guard let template = try MustacheTemplate(filename: directory + path) else { continue }
+ // drop ".mustache" from path to get name
+ let name = String(path.dropLast(extWithDot.count))
+ templates[name] = template
} catch let error as MustacheTemplate.ParserError {
throw ParserError(filename: path, context: error.context, error: error.error)
}
- // drop ".mustache" from path to get name
- let name = String(path.dropLast(extWithDot.count))
- templates[name] = template
}
return templates
}
diff --git a/Sources/Mustache/Library.swift b/Sources/Mustache/Library.swift
index 6e8be10..0cdd408 100644
--- a/Sources/Mustache/Library.swift
+++ b/Sources/Mustache/Library.swift
@@ -2,7 +2,7 @@
//
// This source file is part of the Hummingbird server framework project
//
-// Copyright (c) 2021-2021 the Hummingbird authors
+// Copyright (c) 2021-2024 the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
@@ -78,6 +78,21 @@ public struct MustacheLibrary: Sendable {
return template.render(object, library: self)
}
+ /// Render object using templated with name
+ /// - Parameters:
+ /// - object: Object to render
+ /// - name: Name of template
+ /// - reload: Reload templates when rendering. This is only available in debug builds
+ /// - Returns: Rendered text
+ public func render(_ object: Any, withTemplate name: String, reload: Bool) -> String? {
+ guard let template = templates[name] else { return nil }
+ #if DEBUG
+ return template.render(object, library: self, reload: reload)
+ #else
+ return template.render(object, library: self)
+ #endif
+ }
+
/// Error returned by init() when parser fails
public struct ParserError: Swift.Error {
/// File error occurred in
diff --git a/Sources/Mustache/Template+FileSystem.swift b/Sources/Mustache/Template+FileSystem.swift
new file mode 100644
index 0000000..5e2afb5
--- /dev/null
+++ b/Sources/Mustache/Template+FileSystem.swift
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Hummingbird server framework project
+//
+// Copyright (c) 2021-2024 the Hummingbird authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+import Foundation
+
+extension MustacheTemplate {
+ /// Internal function to load a template from a file
+ /// - Parameters
+ /// - string: Template text
+ /// - filename: File template was loaded from
+ /// - Throws: MustacheTemplate.Error
+ init?(filename: String) throws {
+ let fs = FileManager()
+ guard let data = fs.contents(atPath: filename) else { return nil }
+ let string = String(decoding: data, as: Unicode.UTF8.self)
+ self.tokens = try Self.parse(string)
+ self.filename = filename
+ }
+}
diff --git a/Sources/Mustache/Template+Render.swift b/Sources/Mustache/Template+Render.swift
index 6be73eb..6862d23 100644
--- a/Sources/Mustache/Template+Render.swift
+++ b/Sources/Mustache/Template+Render.swift
@@ -2,7 +2,7 @@
//
// This source file is part of the Hummingbird server framework project
//
-// Copyright (c) 2021-2021 the Hummingbird authors
+// Copyright (c) 2021-2024 the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
@@ -84,7 +84,20 @@ extension MustacheTemplate {
}
case .partial(let name, let indentation, let overrides):
- if let template = context.library?.getTemplate(named: name) {
+ if var template = context.library?.getTemplate(named: name) {
+ #if DEBUG
+ if context.reloadPartials {
+ guard let filename = template.filename else {
+ preconditionFailure("Can only use reload if template was generated from a file")
+ }
+ do {
+ guard let partialTemplate = try MustacheTemplate(filename: filename) else { return "Cannot find template at \(filename)" }
+ template = partialTemplate
+ } catch {
+ return "\(error)"
+ }
+ }
+ #endif
return template.render(context: context.withPartial(indented: indentation, inheriting: overrides))
}
diff --git a/Sources/Mustache/Template.swift b/Sources/Mustache/Template.swift
index fe152ac..26a1211 100644
--- a/Sources/Mustache/Template.swift
+++ b/Sources/Mustache/Template.swift
@@ -2,7 +2,7 @@
//
// This source file is part of the Hummingbird server framework project
//
-// Copyright (c) 2021-2021 the Hummingbird authors
+// Copyright (c) 2021-2024 the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
@@ -19,17 +19,44 @@ public struct MustacheTemplate: Sendable {
/// - Throws: MustacheTemplate.Error
public init(string: String) throws {
self.tokens = try Self.parse(string)
+ self.filename = nil
}
/// Render object using this template
- /// - Parameter object: Object to render
+ /// - Parameters
+ /// - object: Object to render
+ /// - library: library template uses to access partials
/// - Returns: Rendered text
public func render(_ object: Any, library: MustacheLibrary? = nil) -> String {
self.render(context: .init(object, library: library))
}
+ /// Render object using this template
+ /// - Parameters
+ /// - object: Object to render
+ /// - library: library template uses to access partials
+ /// - reload: Should I reload this template when rendering. This is only available in debug builds
+ /// - Returns: Rendered text
+ public func render(_ object: Any, library: MustacheLibrary? = nil, reload: Bool) -> String {
+ #if DEBUG
+ if reload {
+ guard let filename else {
+ preconditionFailure("Can only use reload if template was generated from a file")
+ }
+ do {
+ guard let template = try MustacheTemplate(filename: filename) else { return "Cannot find template at \(filename)" }
+ return template.render(context: .init(object, library: library, reloadPartials: reload))
+ } catch {
+ return "\(error)"
+ }
+ }
+ #endif
+ return self.render(context: .init(object, library: library))
+ }
+
internal init(_ tokens: [Token]) {
self.tokens = tokens
+ self.filename = nil
}
enum Token: Sendable {
@@ -45,4 +72,5 @@ public struct MustacheTemplate: Sendable {
}
var tokens: [Token]
+ let filename: String?
}
diff --git a/Tests/MustacheTests/LibraryTests.swift b/Tests/MustacheTests/LibraryTests.swift
index 10f062d..c2cc38f 100644
--- a/Tests/MustacheTests/LibraryTests.swift
+++ b/Tests/MustacheTests/LibraryTests.swift
@@ -2,7 +2,7 @@
//
// This source file is part of the Hummingbird server framework project
//
-// Copyright (c) 2021-2021 the Hummingbird authors
+// Copyright (c) 2021-2024 the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
@@ -71,4 +71,43 @@ final class LibraryTests: XCTestCase {
XCTAssertEqual(parserError.context.columnNumber, 10)
}
}
+
+ #if DEBUG
+ func testReload() async throws {
+ let fs = FileManager()
+ try? fs.createDirectory(atPath: "templates", withIntermediateDirectories: false)
+ defer { XCTAssertNoThrow(try fs.removeItem(atPath: "templates")) }
+ let mustache = Data("{{#value}}{{.}}{{/value}}".utf8)
+ try mustache.write(to: URL(fileURLWithPath: "templates/test.mustache"))
+ defer { XCTAssertNoThrow(try fs.removeItem(atPath: "templates/test.mustache")) }
+
+ let library = try await MustacheLibrary(directory: "./templates")
+ let object = ["value": ["value1", "value2"]]
+ XCTAssertEqual(library.render(object, withTemplate: "test"), "value1value2")
+ let mustache2 = Data("{{#value}}{{.}}{{/value}}".utf8)
+ try mustache2.write(to: URL(fileURLWithPath: "templates/test.mustache"))
+ XCTAssertEqual(library.render(object, withTemplate: "test", reload: true), "value1value2")
+ }
+
+ func testReloadPartial() async throws {
+ let fs = FileManager()
+ try? fs.createDirectory(atPath: "templates", withIntermediateDirectories: false)
+ let mustache = Data("{{#value}}{{.}}{{/value}}".utf8)
+ try mustache.write(to: URL(fileURLWithPath: "templates/test-partial.mustache"))
+ let mustache2 = Data("{{>test-partial}}".utf8)
+ try mustache2.write(to: URL(fileURLWithPath: "templates/test.mustache"))
+ defer {
+ XCTAssertNoThrow(try fs.removeItem(atPath: "templates/test-partial.mustache"))
+ XCTAssertNoThrow(try fs.removeItem(atPath: "templates/test.mustache"))
+ XCTAssertNoThrow(try fs.removeItem(atPath: "templates"))
+ }
+
+ let library = try await MustacheLibrary(directory: "./templates")
+ let object = ["value": ["value1", "value2"]]
+ XCTAssertEqual(library.render(object, withTemplate: "test"), "value1value2")
+ let mustache3 = Data("{{#value}}{{.}}{{/value}}".utf8)
+ try mustache3.write(to: URL(fileURLWithPath: "templates/test-partial.mustache"))
+ XCTAssertEqual(library.render(object, withTemplate: "test", reload: true), "value1value2")
+ }
+ #endif
}