Skip to content

Commit

Permalink
Allow using JS templates with SPM (#508)
Browse files Browse the repository at this point in the history
* allow js templates for spm builds

* do not print misleading warning

* updated docs

* fallback to executable path

* renamed ejsbundle.js to ejs.js
  • Loading branch information
ilyapuchka committed Feb 6, 2018
1 parent 73aca81 commit 25aa156
Show file tree
Hide file tree
Showing 9 changed files with 66 additions and 41 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -14,6 +14,7 @@
- Improved support for generic types. Now you can access basic generic type information with `TypeName.generic` property
- added `@objcMembers` attribute
- Moved EJS and Swift templates to separate framework targets
- EJS templates now can be used when building Sourcery with SPM

** Breaking **

Expand Down
5 changes: 5 additions & 0 deletions Package.swift
Expand Up @@ -7,6 +7,7 @@ let package = Package(
products: [
.executable(name: "sourcery", targets: ["Sourcery"]),
.library(name: "SourceryRuntime", targets: ["SourceryRuntime"]),
.library(name: "SourceryJS", targets: ["SourceryJS"]),
],
dependencies: [
.package(url: "https://github.com/kylef/Commander.git", from: "0.6.1"),
Expand All @@ -21,6 +22,7 @@ let package = Package(
targets: [
.target(name: "Sourcery", dependencies: [
"SourceryRuntime",
"SourceryJS",
"Commander",
"PathKit",
"SourceKittenFramework",
Expand All @@ -30,5 +32,8 @@ let package = Package(
"SwiftTryCatch",
]),
.target(name: "SourceryRuntime"),
.target(name: "SourceryJS", dependencies: [
"PathKit"
])
]
)
10 changes: 4 additions & 6 deletions Sourcery.xcodeproj/project.pbxproj
Expand Up @@ -57,10 +57,9 @@
6393BC6D2019F58D0044E8CC /* SourceryJS.h in Headers */ = {isa = PBXBuildFile; fileRef = 6393BC6B2019F58D0044E8CC /* SourceryJS.h */; settings = {ATTRIBUTES = (Public, ); }; };
6393BC762019F59E0044E8CC /* SourceryJS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6393BC692019F58D0044E8CC /* SourceryJS.framework */; settings = {ATTRIBUTES = (Required, ); }; };
6393BC792019F5C90044E8CC /* EJSTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09618B3B1E61EEE100B49D5F /* EJSTemplate.swift */; };
6393BC7B2019F6990044E8CC /* SourceryRuntime.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64E201A31EAE8FBF00EAD8A2 /* SourceryRuntime.framework */; };
6393BC7D201A7C280044E8CC /* Template.swift in Sources */ = {isa = PBXBuildFile; fileRef = E291DC7064748459115A1660 /* Template.swift */; };
6393BC92201B50910044E8CC /* JavaScriptTemplateSpecs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09618B491E6626EA00B49D5F /* JavaScriptTemplateSpecs.swift */; };
6393BC93201B55230044E8CC /* ejsbundle.js in Resources */ = {isa = PBXBuildFile; fileRef = 09618B411E620D8600B49D5F /* ejsbundle.js */; };
6393BC93201B55230044E8CC /* ejs.js in Resources */ = {isa = PBXBuildFile; fileRef = 09618B411E620D8600B49D5F /* ejs.js */; };
6393BC98201B57620044E8CC /* JavaScriptTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6393BC97201B57620044E8CC /* JavaScriptTemplate.swift */; };
63D25BE71FD0356E007592AA /* Subscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D25BE61FD0356E007592AA /* Subscript.swift */; };
63E1AD0E1F4093EB00D01FED /* Configs in Resources */ = {isa = PBXBuildFile; fileRef = 63E1AD0D1F4093EB00D01FED /* Configs */; };
Expand Down Expand Up @@ -175,7 +174,7 @@
09346DC71E02195300D6358C /* StructSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StructSpec.swift; sourceTree = "<group>"; };
094415C71E78A46A00ABE04A /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
09618B3B1E61EEE100B49D5F /* EJSTemplate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EJSTemplate.swift; sourceTree = "<group>"; };
09618B411E620D8600B49D5F /* ejsbundle.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ejsbundle.js; sourceTree = "<group>"; };
09618B411E620D8600B49D5F /* ejs.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ejs.js; sourceTree = "<group>"; };
09618B431E620E3200B49D5F /* JSExport.ejs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = JSExport.ejs; sourceTree = "<group>"; };
09618B491E6626EA00B49D5F /* JavaScriptTemplateSpecs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JavaScriptTemplateSpecs.swift; sourceTree = "<group>"; };
09618B4E1E662B0A00B49D5F /* JavaScriptTemplates */ = {isa = PBXFileReference; lastKnownFileType = folder; path = JavaScriptTemplates; sourceTree = "<group>"; };
Expand Down Expand Up @@ -285,7 +284,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
6393BC7B2019F6990044E8CC /* SourceryRuntime.framework in Frameworks */,
B54CFEA7116B0F372DC7D901 /* Pods_SourceryJS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -432,7 +430,7 @@
isa = PBXGroup;
children = (
09618B3B1E61EEE100B49D5F /* EJSTemplate.swift */,
09618B411E620D8600B49D5F /* ejsbundle.js */,
09618B411E620D8600B49D5F /* ejs.js */,
);
path = Sources;
sourceTree = "<group>";
Expand Down Expand Up @@ -896,7 +894,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6393BC93201B55230044E8CC /* ejsbundle.js in Resources */,
6393BC93201B55230044E8CC /* ejs.js in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
@@ -1,5 +1,3 @@
#if SWIFT_PACKAGE
#else
import SourceryJS
import SourceryRuntime
import JavaScriptCore
Expand Down Expand Up @@ -53,4 +51,3 @@ private extension JSContext {
}

}
#endif
31 changes: 16 additions & 15 deletions Sourcery/Sourcery.swift
Expand Up @@ -7,10 +7,10 @@ import Foundation
import PathKit
import SwiftTryCatch
import SourceryRuntime
import SourceryJS

#if SWIFT_PACKAGE
#else
import SourceryJS
import SourcerySwift
#endif

Expand Down Expand Up @@ -190,23 +190,24 @@ class Sourcery {
}

fileprivate func templates(from: Paths) throws -> [Template] {
return try templatePaths(from: from).map {
#if SWIFT_PACKAGE
if $0.extension == "swifttemplate" || $0.extension == "ejs" {
throw "Swift and JavaScript templates are not supported when using Sourcery built with Swift Package Manager yet. Please use only Stencil templates. See https://github.com/krzysztofzablocki/Sourcery/issues/244 for details."
} else {
return try StencilTemplate(path: $0)
}
#else
if $0.extension == "swifttemplate" {
return try templatePaths(from: from).flatMap {
if $0.extension == "swifttemplate" {
#if SWIFT_PACKAGE
Log.warning("Skipping template \($0). Swift templates are not supported when using Sourcery built with Swift Package Manager yet. Please use only Stencil or EJS templates. See https://github.com/krzysztofzablocki/Sourcery/issues/244 for details.")
return nil
#else
let cachePath = cacheDisabled ? nil : Path.cachesDir(sourcePath: $0)
return try SwiftTemplate(path: $0, cachePath: cachePath)
} else if $0.extension == "ejs" {
return try JavaScriptTemplate(path: $0)
} else {
return try StencilTemplate(path: $0)
#endif
} else if $0.extension == "ejs" {
guard EJSTemplate.ejsPath != nil else {
Log.warning("Skipping template \($0). JavaScript templates require EJS path to be set manually when using Sourcery built with Swift Package Manager. Use `--ejsPath` command line argument to set it.")
return nil
}
#endif
return try JavaScriptTemplate(path: $0)
} else {
return try StencilTemplate(path: $0)
}
}
}

Expand Down
24 changes: 16 additions & 8 deletions Sourcery/main.swift
Expand Up @@ -11,6 +11,7 @@ import Commander
import PathKit
import XcodeEdit
import SourceryRuntime
import SourceryJS

extension Path: ArgumentConvertible {
/// :nodoc:
Expand Down Expand Up @@ -95,20 +96,26 @@ func runCLI() {
description: "Turn on verbose logging"),
Flag("quiet",
flag: "q",
description: "Turn off any logging, only emmit errors"),
description: "Turn off any logging, only emmit errors."),
Flag("prune",
flag: "p",
description: "Remove empty generated files"),
VariadicOption<Path>("sources", description: "Path to a source swift files"),
VariadicOption<Path>("sources", description: "Path to a source swift files."),
VariadicOption<Path>("templates", description: "Path to templates. File or Directory."),
Option<Path>("output", ".", description: "Path to output. File or Directory. Default is current path."),
Option<Path>("output", "", description: "Path to output. File or Directory. Default is current path."),
Option<Path>("config", ".", description: "Path to config file. File or Directory. Default is current path."),
VariadicOption<String>("force-parse", description: "extensions that this run of Sorcery should parse"),
VariadicOption<String>("args", description: "Custom values to pass to templates.")
) { watcherEnabled, disableCache, verboseLogging, quiet, prune, sources, templates, output, configPath, forceParse, args in
VariadicOption<String>("force-parse", description: "File extensions that Sourcery will be forced to parse, even if they were generated by Sourcery."),
VariadicOption<String>("args", description: "Custom values to pass to templates."),
Option<Path>("ejsPath", "", description: "Path to EJS file for JavaScript templates.")
) { watcherEnabled, disableCache, verboseLogging, quiet, prune, sources, templates, output, configPath, forceParse, args, ejsPath in
do {
Log.level = verboseLogging ? .verbose : quiet ? .errors : .info

// if ejsPath is not provided use default value or executable path
EJSTemplate.ejsPath = ejsPath.string.isEmpty
? (EJSTemplate.ejsPath ?? Path(ProcessInfo.processInfo.arguments[0]).parent() + "ejs.js")
: ejsPath

let configuration: Configuration
let yamlPath: Path = configPath.isDirectory ? configPath + ".sourcery.yml" : configPath

Expand All @@ -118,7 +125,7 @@ func runCLI() {
let arguments = AnnotationsParser.parse(line: args)
configuration = Configuration(sources: sources,
templates: templates,
output: output,
output: output.string.isEmpty ? "." : output,
forceParse: forceParse,
args: arguments)
} else {
Expand All @@ -135,7 +142,8 @@ func runCLI() {
!sources.isEmpty ||
!templates.isEmpty ||
!forceParse.isEmpty ||
output != ""
output != "" ||
!args.isEmpty
)

if hasAnyYamlDuplicatedParameter {
Expand Down
29 changes: 20 additions & 9 deletions SourceryJS/Sources/EJSTemplate.swift
@@ -1,11 +1,23 @@
import JavaScriptCore
import PathKit
import SourceryRuntime

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 ejsbundle.js from framework bundle.
/// If framework is built with SPM this property should be set manually.
public static var ejsPath: Path! = Bundle(for: EJSTemplate.self).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()!
Expand All @@ -23,31 +35,30 @@ open class EJSTemplate {
}
}

public init(path: Path, context: JSContext = JSContext()) throws {
public init(path: Path, ejsPath: Path = EJSTemplate.ejsPath) throws {
templateString = try path.read()
sourcePath = path
self.ejs = try ejsPath.read(.utf8)
}

public init(templateString: String) {
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: Swift.Error?
var error: Error?
jsContext.exceptionHandler = {
error = error ?? $1?.toString() ?? "Unknown JavaScript error"
error = error ?? $1.map({ Error($0.toString()) }) ?? Error("Unknown JavaScript error")
}

// swiftlint:disable:next force_unwrapping
let path = Bundle(for: EJSTemplate.self).path(forResource: "ejsbundle", ofType: "js")!
let ejs = try String(contentsOfFile: path, encoding: .utf8)
jsContext.evaluateScript("var window = this; \(ejs)")

if let error = error {
throw "\(sourcePath): \(error)"
throw Error("\(sourcePath): \(error)")
}

let content = jsContext.objectForKeyedSubscript("content").toString()
Expand Down
File renamed without changes.
4 changes: 4 additions & 0 deletions guides/Writing templates.md
Expand Up @@ -85,6 +85,10 @@ JavaScript templates are powered by [EJS](http://ejs.co) and support all the fea

**Example**: [JSExport.ejs](https://github.com/krzysztofzablocki/Sourcery/blob/master/Sourcery/Templates/JSExport.ejs)

> Note: when using JavaScript templates with Sourcery built using Swift Package Manager you must provide path to EJS source code using `--ejsPath` command line argument. Download EJS source code [here](https://github.com/krzysztofzablocki/Sourcery/blob/master/SourceryJS/Sources/ejs.js), put it in some path and pass it when running Sourcery. Otherwise JavaScript templates will be ignored (you will see a warning in the console output).
You can also use `SourceryJS` framework independently of Sourcery. You can add it as a Carthge or SPM dependency.

## Using Source Annotations

Sourcery supports annotating your classes and variables with special annotations, similar to how attributes work in Rust / Java
Expand Down

0 comments on commit 25aa156

Please sign in to comment.