-
-
Notifications
You must be signed in to change notification settings - Fork 624
/
Copy pathEJSTemplate.swift
122 lines (97 loc) · 4.24 KB
/
EJSTemplate.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import JavaScriptCore
import PathKit
private 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(for: EJSTemplate.self)
}()
}
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! = bundle.path(forResource: "ejs", ofType: "js").map({ Path($0) })
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 = EJSTemplate.ejsPath) throws {
try self.init(path: path, templateString: try path.read(), ejsPath: ejsPath)
}
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 = EJSTemplate.ejsPath) 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
}
}