From 66f3e6ed47af724305ec5d0fb5db2c82b29ab426 Mon Sep 17 00:00:00 2001 From: Ruslan Alikhamov Date: Mon, 18 Sep 2023 22:18:58 +0400 Subject: [PATCH] Separate EJSTemplate.swift for swift test and for swift build -c release (#1203) * Attempt to resolve runtime issue * Applied workaround for swift test --- SourceryJS/Sources/EJSTemplate+Tests.swift | 147 +++++++++++++++++++++ SourceryJS/Sources/EJSTemplate.swift | 38 ++---- 2 files changed, 155 insertions(+), 30 deletions(-) create mode 100644 SourceryJS/Sources/EJSTemplate+Tests.swift diff --git a/SourceryJS/Sources/EJSTemplate+Tests.swift b/SourceryJS/Sources/EJSTemplate+Tests.swift new file mode 100644 index 000000000..e03ff019b --- /dev/null +++ b/SourceryJS/Sources/EJSTemplate+Tests.swift @@ -0,0 +1,147 @@ +// swift test runs tests in DEBUG, thus this file will only be compiled for `swift test` +#if canImport(ObjectiveC) && DEBUG +import JavaScriptCore +import PathKit + +// (c) https://forums.swift.org/t/swift-5-3-spm-resources-in-tests-uses-wrong-bundle-path/37051/46 +private extension Bundle { + static let mypackageResources: Bundle = { + #if DEBUG + if let moduleName = Bundle(for: BundleFinder.self).bundleIdentifier, + let testBundlePath = ProcessInfo.processInfo.environment["XCTestBundlePath"] { + if let resourceBundle = Bundle(path: testBundlePath + "/\(moduleName)_\(moduleName).bundle") { + return resourceBundle + } + } + #endif + return Bundle.module + }() + + private final class BundleFinder {} +} + +public extension Foundation.Bundle { + /// Returns the resource bundle associated with the current Swift module. + static var jsModule: Bundle = { + let bundleName = "Sourcery_SourceryJS" + + let candidates = [ + // Bundle should be present here when the package is linked into an App. + Bundle.main.resourceURL, + + // Bundle should be present here when the package is linked into a framework. + Bundle(for: EJSTemplate.self).resourceURL, + + // For command-line tools. + Bundle.main.bundleURL, + ] + + for candidate in candidates { + let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle") + if let bundle = bundlePath.flatMap(Bundle.init(url:)) { + return bundle + } + } + return Bundle.mypackageResources + }() +} + +open class EJSTemplate { + + public struct Error: Swift.Error, CustomStringConvertible { + public let description: String + public init(_ value: String) { + self.description = value + } + } + + /// Should be set to the path of EJS before rendering any template. + /// By default reads ejs.js from framework bundle. + #if SWIFT_PACKAGE + static let bundle = Bundle.jsModule + #else + static let bundle = Bundle(for: EJSTemplate.self) + #endif + public static var ejsPath: Path? = { + let bundle = EJSTemplate.bundle + guard let path = bundle.path(forResource: "ejs", ofType: "js") else { + return nil + } + return Path(path) + }() + + public let sourcePath: Path + public let templateString: String + let ejs: String + + public private(set) lazy var jsContext: JSContext = { + let jsContext = JSContext()! + jsContext.setObject(self.templateString, forKeyedSubscript: "template" as NSString) + jsContext.setObject(self.sourcePath.lastComponent, forKeyedSubscript: "templateName" as NSString) + jsContext.setObject(self.context, forKeyedSubscript: "templateContext" as NSString) + let include: @convention(block) (String) -> [String:String] = { [unowned self] in self.includeFile($0) } + jsContext.setObject(include, forKeyedSubscript: "include" as NSString) + return jsContext + }() + + open var context: [String: Any] = [:] { + didSet { + jsContext.setObject(context, forKeyedSubscript: "templateContext" as NSString) + } + } + + public convenience init(path: Path, ejsPath: Path) throws { + try self.init(path: path, templateString: try path.read(), ejsPath: ejsPath) + } + + public init(path: Path, templateString: String, ejsPath: Path) throws { + self.templateString = templateString + sourcePath = path + self.ejs = try ejsPath.read(.utf8) + } + + public init(templateString: String, ejsPath: Path) throws { + self.templateString = templateString + sourcePath = "" + self.ejs = try ejsPath.read(.utf8) + } + + public func render(_ context: [String: Any]) throws -> String { + self.context = context + + var error: Error? + jsContext.exceptionHandler = { + error = error ?? $1.map({ Error($0.toString()) }) ?? Error("Unknown JavaScript error") + } + + jsContext.evaluateScript("var window = this; \(ejs)") + + if let error = error { + throw Error("\(sourcePath): \(error)") + } + + let content = jsContext.objectForKeyedSubscript("content").toString() + return content ?? "" + } + + func includeFile(_ requestedPath: String) -> [String: String] { + let requestedPath = Path(requestedPath) + let path = sourcePath.parent() + requestedPath + var includedTemplate: String? = try? path.read() + + /// The template extension may be omitted, so try to read again by adding it if a template was not found + if includedTemplate == nil, path.extension != "ejs" { + includedTemplate = try? Path(path.string + ".ejs").read() + } + + var templateDictionary = [String: String]() + templateDictionary["template"] = includedTemplate + if requestedPath.components.count > 1 { + templateDictionary["basePath"] = Path(components: requestedPath.components.dropLast()).string + } + + return templateDictionary + } + +} +#endif diff --git a/SourceryJS/Sources/EJSTemplate.swift b/SourceryJS/Sources/EJSTemplate.swift index 082615dd7..723fb64d0 100644 --- a/SourceryJS/Sources/EJSTemplate.swift +++ b/SourceryJS/Sources/EJSTemplate.swift @@ -1,25 +1,8 @@ -#if canImport(ObjectiveC) +#if canImport(ObjectiveC) && !DEBUG import JavaScriptCore import PathKit -// (c) https://forums.swift.org/t/swift-5-3-spm-resources-in-tests-uses-wrong-bundle-path/37051/46 -private extension Bundle { - static let mypackageResources: Bundle = { - #if DEBUG - if let moduleName = Bundle(for: BundleFinder.self).bundleIdentifier, - let testBundlePath = ProcessInfo.processInfo.environment["XCTestBundlePath"] { - if let resourceBundle = Bundle(path: testBundlePath + "/\(moduleName)_\(moduleName).bundle") { - return resourceBundle - } - } - #endif - return Bundle.module - }() - - private final class BundleFinder {} -} - -public extension Foundation.Bundle { +private extension Foundation.Bundle { /// Returns the resource bundle associated with the current Swift module. static var jsModule: Bundle = { let bundleName = "Sourcery_SourceryJS" @@ -41,7 +24,8 @@ public extension Foundation.Bundle { return bundle } } - return Bundle.mypackageResources + + return Bundle(for: EJSTemplate.self) }() } @@ -61,13 +45,7 @@ open class EJSTemplate { #else static let bundle = Bundle(for: EJSTemplate.self) #endif - public static var ejsPath: Path? = { - let bundle = EJSTemplate.bundle - guard let path = bundle.path(forResource: "ejs", ofType: "js") else { - return nil - } - return Path(path) - }() + public static var ejsPath: Path! = bundle.path(forResource: "ejs", ofType: "js").map({ Path($0) }) public let sourcePath: Path public let templateString: String @@ -89,17 +67,17 @@ open class EJSTemplate { } } - public convenience init(path: Path, ejsPath: Path) throws { + public convenience init(path: Path, ejsPath: Path = EJSTemplate.ejsPath) throws { try self.init(path: path, templateString: try path.read(), ejsPath: ejsPath) } - public init(path: Path, templateString: String, ejsPath: Path) throws { + public init(path: Path, templateString: String, ejsPath: Path = EJSTemplate.ejsPath) throws { self.templateString = templateString sourcePath = path self.ejs = try ejsPath.read(.utf8) } - public init(templateString: String, ejsPath: Path) throws { + public init(templateString: String, ejsPath: Path = EJSTemplate.ejsPath) throws { self.templateString = templateString sourcePath = "" self.ejs = try ejsPath.read(.utf8)