Skip to content

Commit

Permalink
Separate EJSTemplate.swift for swift test and for swift build -c rele…
Browse files Browse the repository at this point in the history
…ase (#1203)

* Attempt to resolve runtime issue

* Applied workaround for swift test
  • Loading branch information
art-divin authored Sep 18, 2023
1 parent b59864c commit 66f3e6e
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 30 deletions.
147 changes: 147 additions & 0 deletions SourceryJS/Sources/EJSTemplate+Tests.swift
Original file line number Diff line number Diff line change
@@ -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
38 changes: 8 additions & 30 deletions SourceryJS/Sources/EJSTemplate.swift
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -41,7 +24,8 @@ public extension Foundation.Bundle {
return bundle
}
}
return Bundle.mypackageResources

return Bundle(for: EJSTemplate.self)
}()
}

Expand All @@ -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
Expand All @@ -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)
Expand Down

0 comments on commit 66f3e6e

Please sign in to comment.