Skip to content

Commit

Permalink
[BREAKING CHANGE] Define Configuration.default to remove access to …
Browse files Browse the repository at this point in the history
…non-thread safe objects.

This change prevents configuration from being shared globally. Therefore, an interface has been added to each function that can receive the configuration.

If no configuration is added, `Configuration.default` is used.
  • Loading branch information
fumito-ito committed Aug 18, 2024
1 parent ec5ace1 commit 80ad9fc
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 140 deletions.
16 changes: 7 additions & 9 deletions Sources/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,13 @@
/// Templates can also be configured individually. See the documentation of each
/// Configuration method for more details.
public struct Configuration {


/// The default configuration that is used unless specified otherwise by a
/// TemplateRepository.
public static var `default`: Configuration {
Configuration()
}

// =========================================================================
// MARK: - Factory Configuration

Expand Down Expand Up @@ -296,11 +302,3 @@ public struct Configuration {
/// Delimiter tag": `{{=[[ ]]=}}` changes delimiters to `[[` and `]]`.
public var tagDelimiterPair: TagDelimiterPair
}


// =============================================================================
// MARK: - Default Configuration

/// The default configuration that is used unless specified otherwise by a
/// TemplateRepository.
nonisolated(unsafe) public var DefaultConfiguration = Configuration()
12 changes: 7 additions & 5 deletions Sources/CoreFunctions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -470,8 +470,9 @@ public typealias RenderFunction = (_ info: RenderingInfo) throws -> Rendering
/// let rendering = try! template.render(data)
///
/// - parameter lambda: A `String -> String` function.
/// - parameter configuration: The configuration for rendering. If the configuration is not specified, `Configuration.default` is used.
/// - returns: A RenderFunction.
public func Lambda(_ lambda: @escaping (String) -> String) -> RenderFunction {
public func Lambda(_ lambda: @escaping (String) -> String, configuration: Configuration = .default) -> RenderFunction {
return { (info: RenderingInfo) in
switch info.tag.type {
case .variable:
Expand All @@ -483,7 +484,7 @@ public func Lambda(_ lambda: @escaping (String) -> String) -> RenderFunction {
// https://github.com/mustache/spec/blob/83b0721610a4e11832e83df19c73ace3289972b9/specs/%7Elambdas.yml#L117
// > Lambdas used for sections should parse with the current delimiters

let templateRepository = TemplateRepository()
let templateRepository = TemplateRepository(configuration: configuration)
templateRepository.configuration.tagDelimiterPair = info.tag.tagDelimiterPair

let templateString = lambda(info.tag.innerTemplateString)
Expand Down Expand Up @@ -585,8 +586,9 @@ public func Lambda(_ lambda: @escaping (String) -> String) -> RenderFunction {
/// let rendering = try! template.render(data)
///
/// - parameter lambda: A `() -> String` function.
/// - parameter configuration: The configuration for rendering. If the configuration is not specified, `Configuration.default` is used.
/// - returns: A RenderFunction.
public func Lambda(_ lambda: @escaping () -> String) -> RenderFunction {
public func Lambda(_ lambda: @escaping () -> String, configuration: Configuration = .default) -> RenderFunction {
return { (info: RenderingInfo) in
switch info.tag.type {
case .variable:
Expand All @@ -597,7 +599,7 @@ public func Lambda(_ lambda: @escaping () -> String) -> RenderFunction {
//
// Let's render a text template:

let templateRepository = TemplateRepository()
let templateRepository = TemplateRepository(configuration: configuration)
templateRepository.configuration.contentType = .text

let templateString = lambda()
Expand Down Expand Up @@ -658,7 +660,7 @@ public func Lambda(_ lambda: @escaping () -> String) -> RenderFunction {
// {{# lambda }}...{{/ lambda }}
//
// Behave as a true object, and render the section.
let context = info.context.extendedContext(Lambda(lambda))
let context = info.context.extendedContext(Lambda(lambda, configuration: configuration))
return try info.tag.render(context)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/ExpressionGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ final class ExpressionGenerator {
let configuration: Configuration

init(configuration: Configuration? = nil) {
self.configuration = configuration ?? DefaultConfiguration
self.configuration = configuration ?? .default
}

func stringFromExpression(_ expression: Expression) -> String {
Expand Down
20 changes: 12 additions & 8 deletions Sources/Template.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ final public class Template {
/// Creates a template from a template string.
///
/// - parameter string: The template string.
/// - parameter configuration: The configuration for rendering. If the configuration is not specified, `Configuration.default` is used.
/// - throws: MustacheError
public convenience init(string: String) throws {
let repository = TemplateRepository()
public convenience init(string: String, configuration: Configuration = .default) throws {
let repository = TemplateRepository(configuration: configuration)
let templateAST = try repository.templateAST(string: string)
self.init(repository: repository, templateAST: templateAST, baseContext: repository.configuration.baseContext)
}
Expand All @@ -49,13 +50,14 @@ final public class Template {
///
/// - parameter path: The path to the template file.
/// - parameter encoding: The encoding of the template file.
/// - parameter configuration: The configuration for rendering. If the configuration is not specified, `Configuration.default` is used.
/// - throws: MustacheError
public convenience init(path: String, encoding: String.Encoding = String.Encoding.utf8) throws {
public convenience init(path: String, encoding: String.Encoding = String.Encoding.utf8, configuration: Configuration = .default) throws {
let nsPath = path as NSString
let directoryPath = nsPath.deletingLastPathComponent
let templateExtension = nsPath.pathExtension
let templateName = (nsPath.lastPathComponent as NSString).deletingPathExtension
let repository = TemplateRepository(directoryPath: directoryPath, templateExtension: templateExtension, encoding: encoding)
let repository = TemplateRepository(directoryPath: directoryPath, templateExtension: templateExtension, encoding: encoding, configuration: configuration)
let templateAST = try repository.templateAST(named: templateName)
self.init(repository: repository, templateAST: templateAST, baseContext: repository.configuration.baseContext)
}
Expand All @@ -70,12 +72,13 @@ final public class Template {
///
/// - parameter URL: The URL of the template.
/// - parameter encoding: The encoding of the template resource.
/// - parameter configuration: The configuration for rendering. If the configuration is not specified, `Configuration.default` is used.
/// - throws: MustacheError
public convenience init(URL: Foundation.URL, encoding: String.Encoding = String.Encoding.utf8) throws {
public convenience init(URL: Foundation.URL, encoding: String.Encoding = String.Encoding.utf8, configuration: Configuration = .default) throws {
let baseURL = URL.deletingLastPathComponent()
let templateExtension = URL.pathExtension
let templateName = (URL.lastPathComponent as NSString).deletingPathExtension
let repository = TemplateRepository(baseURL: baseURL, templateExtension: templateExtension, encoding: encoding)
let repository = TemplateRepository(baseURL: baseURL, templateExtension: templateExtension, encoding: encoding, configuration: configuration)
let templateAST = try repository.templateAST(named: templateName)
self.init(repository: repository, templateAST: templateAST, baseContext: repository.configuration.baseContext)
}
Expand All @@ -95,9 +98,10 @@ final public class Template {
/// the extension is assumed not to exist and the template file should
/// exactly match name.
/// - parameter encoding: The encoding of template resource.
/// - parameter configuration: The configuration for rendering. If the configuration is not specified, `Configuration.default` is used.
/// - throws: MustacheError
public convenience init(named name: String, bundle: Bundle? = nil, templateExtension: String? = "mustache", encoding: String.Encoding = String.Encoding.utf8) throws {
let repository = TemplateRepository(bundle: bundle, templateExtension: templateExtension, encoding: encoding)
public convenience init(named name: String, bundle: Bundle? = nil, templateExtension: String? = "mustache", encoding: String.Encoding = String.Encoding.utf8, configuration: Configuration = .default) throws {
let repository = TemplateRepository(bundle: bundle, templateExtension: templateExtension, encoding: encoding, configuration: configuration)
let templateAST = try repository.templateAST(named: name)
self.init(repository: repository, templateAST: templateAST, baseContext: repository.configuration.baseContext)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/TemplateGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ final class TemplateGenerator {
let configuration: Configuration

init(configuration: Configuration? = nil) {
self.configuration = configuration ?? DefaultConfiguration
self.configuration = configuration ?? .default
}

func stringFromTemplateAST(_ templateAST: TemplateAST) -> String {
Expand Down
24 changes: 14 additions & 10 deletions Sources/TemplateRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ final public class TemplateRepository {
///
/// let repository = TemplateRepository()
/// let template = try! repository.template(string: "Hello {{name}}")
public init(dataSource: TemplateRepositoryDataSource? = nil) {
configuration = DefaultConfiguration
public init(dataSource: TemplateRepositoryDataSource? = nil, configuration: Configuration = .default) {
self.configuration = configuration
templateASTCache = [:]
self.dataSource = dataSource
}
Expand All @@ -123,8 +123,9 @@ final public class TemplateRepository {
///
/// - parameter templates: A dictionary whose keys are template names and
/// values template strings.
convenience public init(templates: [String: String]) {
self.init(dataSource: DictionaryDataSource(templates: templates))
/// - parameter configuration: The configuration for rendering. If the configuration is not specified, `Configuration.default` is used.
convenience public init(templates: [String: String], configuration: Configuration = .default) {
self.init(dataSource: DictionaryDataSource(templates: templates), configuration: configuration)
}

/// Creates a TemplateRepository that loads templates from a directory.
Expand Down Expand Up @@ -160,8 +161,9 @@ final public class TemplateRepository {
/// extension is "mustache".
/// - parameter encoding: The encoding of template files. Default encoding
/// is UTF-8.
convenience public init(directoryPath: String, templateExtension: String? = "mustache", encoding: String.Encoding = String.Encoding.utf8) {
self.init(dataSource: URLDataSource(baseURL: URL(fileURLWithPath: directoryPath, isDirectory: true), templateExtension: templateExtension, encoding: encoding))
/// - parameter configuration: The configuration for rendering. If the configuration is not specified, `Configuration.default` is used.
convenience public init(directoryPath: String, templateExtension: String? = "mustache", encoding: String.Encoding = String.Encoding.utf8, configuration: Configuration = .default) {
self.init(dataSource: URLDataSource(baseURL: URL(fileURLWithPath: directoryPath, isDirectory: true), templateExtension: templateExtension, encoding: encoding), configuration: configuration)
}

/// Creates a TemplateRepository that loads templates from a URL.
Expand Down Expand Up @@ -197,8 +199,9 @@ final public class TemplateRepository {
/// Default extension is "mustache".
/// - parameter encoding: The encoding of template resources. Default
/// encoding is UTF-8.
convenience public init(baseURL: URL, templateExtension: String? = "mustache", encoding: String.Encoding = String.Encoding.utf8) {
self.init(dataSource: URLDataSource(baseURL: baseURL, templateExtension: templateExtension, encoding: encoding))
/// - parameter configuration: The configuration for rendering. If the configuration is not specified, `Configuration.default` is used.
convenience public init(baseURL: URL, templateExtension: String? = "mustache", encoding: String.Encoding = String.Encoding.utf8, configuration: Configuration = .default) {
self.init(dataSource: URLDataSource(baseURL: baseURL, templateExtension: templateExtension, encoding: encoding), configuration: configuration)
}

/// Creates a TemplateRepository that loads templates stored as resources in
Expand All @@ -215,8 +218,9 @@ final public class TemplateRepository {
/// Default extension is "mustache".
/// - parameter encoding: The encoding of template resources. Default
/// encoding is UTF-8.
convenience public init(bundle: Bundle?, templateExtension: String? = "mustache", encoding: String.Encoding = String.Encoding.utf8) {
self.init(dataSource: BundleDataSource(bundle: bundle ?? Bundle.main, templateExtension: templateExtension, encoding: encoding))
/// - parameter configuration: The configuration for rendering. If the configuration is not specified, `Configuration.default` is used.
convenience public init(bundle: Bundle?, templateExtension: String? = "mustache", encoding: String.Encoding = String.Encoding.utf8, configuration: Configuration = .default) {
self.init(dataSource: BundleDataSource(bundle: bundle ?? Bundle.main, templateExtension: templateExtension, encoding: encoding), configuration: configuration)
}


Expand Down
38 changes: 17 additions & 21 deletions Tests/Public/ConfigurationTests/ConfigurationBaseContextTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,33 +25,31 @@ import XCTest
import Mustache

class ConfigurationBaseContextTests: XCTestCase {

override func tearDown() {
super.tearDown()
DefaultConfiguration = Configuration()
}


func testDefaultConfigurationCustomBaseContext() {
DefaultConfiguration.baseContext = Context(["foo": "success"])

let template = try! Template(string: "{{foo}}")
var configuration = Configuration.default
configuration.baseContext = Context(["foo": "success"])

let template = try! Template(string: "{{foo}}", configuration: configuration)
let rendering = try! template.render()
XCTAssertEqual(rendering, "success")
}

func testTemplateBaseContextOverridesDefaultConfigurationBaseContext() {
DefaultConfiguration.baseContext = Context(["foo": "failure"])

let template = try! Template(string: "{{foo}}")
var configuration = Configuration.default
configuration.baseContext = Context(["foo": "failure"])

let template = try! Template(string: "{{foo}}", configuration: configuration)
template.baseContext = Context(["foo": "success"])
let rendering = try! template.render()
XCTAssertEqual(rendering, "success")
}

func testDefaultRepositoryConfigurationHasDefaultConfigurationBaseContext() {
DefaultConfiguration.baseContext = Context(["foo": "success"])

let repository = TemplateRepository()
var configuration = Configuration.default
configuration.baseContext = Context(["foo": "success"])

let repository = TemplateRepository(configuration: configuration)
let template = try! repository.template(string: "{{foo}}")
let rendering = try! template.render()
XCTAssertEqual(rendering, "success")
Expand Down Expand Up @@ -79,8 +77,6 @@ class ConfigurationBaseContextTests: XCTestCase {
}

func testRepositoryConfigurationBaseContextOverridesDefaultConfigurationBaseContextWhenSettingTheWholeConfiguration() {
DefaultConfiguration.baseContext = Context(["foo": "failure"])

var configuration = Configuration()
configuration.baseContext = Context(["foo": "success"])

Expand All @@ -93,8 +89,6 @@ class ConfigurationBaseContextTests: XCTestCase {
}

func testRepositoryConfigurationBaseContextOverridesDefaultConfigurationBaseContextWhenUpdatingRepositoryConfiguration() {
DefaultConfiguration.baseContext = Context(["foo": "failure"])

let repository = TemplateRepository()
repository.configuration.baseContext = Context(["foo": "success"])

Expand Down Expand Up @@ -133,8 +127,10 @@ class ConfigurationBaseContextTests: XCTestCase {

var rendering = try! repository.template(string: "{{^foo}}success{{/foo}}").render()
XCTAssertEqual(rendering, "success")

DefaultConfiguration.baseContext = Context(["foo": "failure"])

var configuration = Configuration.default
configuration.baseContext = Context(["foo": "failure"])
repository.configuration = configuration
rendering = try! repository.template(string: "{{^foo}}success{{/foo}}").render()
XCTAssertEqual(rendering, "success")
}
Expand Down
Loading

0 comments on commit 80ad9fc

Please sign in to comment.