From 25aa1563ea0c06084c918ed6309a7728980ba934 Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Tue, 6 Feb 2018 11:23:57 +0100 Subject: [PATCH] Allow using JS templates with SPM (#508) * allow js templates for spm builds * do not print misleading warning * updated docs * fallback to executable path * renamed ejsbundle.js to ejs.js --- CHANGELOG.md | 1 + Package.swift | 5 +++ Sourcery.xcodeproj/project.pbxproj | 10 +++--- .../JavaScript/JavaScriptTemplate.swift | 3 -- Sourcery/Sourcery.swift | 31 ++++++++++--------- Sourcery/main.swift | 24 +++++++++----- SourceryJS/Sources/EJSTemplate.swift | 29 +++++++++++------ SourceryJS/Sources/{ejsbundle.js => ejs.js} | 0 guides/Writing templates.md | 4 +++ 9 files changed, 66 insertions(+), 41 deletions(-) rename SourceryJS/Sources/{ejsbundle.js => ejs.js} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bb22623f..55f599a90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 ** diff --git a/Package.swift b/Package.swift index a99c55eec..1d1b591dd 100644 --- a/Package.swift +++ b/Package.swift @@ -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"), @@ -21,6 +22,7 @@ let package = Package( targets: [ .target(name: "Sourcery", dependencies: [ "SourceryRuntime", + "SourceryJS", "Commander", "PathKit", "SourceKittenFramework", @@ -30,5 +32,8 @@ let package = Package( "SwiftTryCatch", ]), .target(name: "SourceryRuntime"), + .target(name: "SourceryJS", dependencies: [ + "PathKit" + ]) ] ) diff --git a/Sourcery.xcodeproj/project.pbxproj b/Sourcery.xcodeproj/project.pbxproj index 7dc84e319..fd8a03eda 100644 --- a/Sourcery.xcodeproj/project.pbxproj +++ b/Sourcery.xcodeproj/project.pbxproj @@ -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 */; }; @@ -175,7 +174,7 @@ 09346DC71E02195300D6358C /* StructSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StructSpec.swift; sourceTree = ""; }; 094415C71E78A46A00ABE04A /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; 09618B3B1E61EEE100B49D5F /* EJSTemplate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EJSTemplate.swift; sourceTree = ""; }; - 09618B411E620D8600B49D5F /* ejsbundle.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ejsbundle.js; sourceTree = ""; }; + 09618B411E620D8600B49D5F /* ejs.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ejs.js; sourceTree = ""; }; 09618B431E620E3200B49D5F /* JSExport.ejs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = JSExport.ejs; sourceTree = ""; }; 09618B491E6626EA00B49D5F /* JavaScriptTemplateSpecs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JavaScriptTemplateSpecs.swift; sourceTree = ""; }; 09618B4E1E662B0A00B49D5F /* JavaScriptTemplates */ = {isa = PBXFileReference; lastKnownFileType = folder; path = JavaScriptTemplates; sourceTree = ""; }; @@ -285,7 +284,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 6393BC7B2019F6990044E8CC /* SourceryRuntime.framework in Frameworks */, B54CFEA7116B0F372DC7D901 /* Pods_SourceryJS.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -432,7 +430,7 @@ isa = PBXGroup; children = ( 09618B3B1E61EEE100B49D5F /* EJSTemplate.swift */, - 09618B411E620D8600B49D5F /* ejsbundle.js */, + 09618B411E620D8600B49D5F /* ejs.js */, ); path = Sources; sourceTree = ""; @@ -896,7 +894,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 6393BC93201B55230044E8CC /* ejsbundle.js in Resources */, + 6393BC93201B55230044E8CC /* ejs.js in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sourcery/Generating/Template/JavaScript/JavaScriptTemplate.swift b/Sourcery/Generating/Template/JavaScript/JavaScriptTemplate.swift index 54980c3ec..f2fed4d24 100644 --- a/Sourcery/Generating/Template/JavaScript/JavaScriptTemplate.swift +++ b/Sourcery/Generating/Template/JavaScript/JavaScriptTemplate.swift @@ -1,5 +1,3 @@ -#if SWIFT_PACKAGE -#else import SourceryJS import SourceryRuntime import JavaScriptCore @@ -53,4 +51,3 @@ private extension JSContext { } } -#endif diff --git a/Sourcery/Sourcery.swift b/Sourcery/Sourcery.swift index 3b6383576..b416750b2 100644 --- a/Sourcery/Sourcery.swift +++ b/Sourcery/Sourcery.swift @@ -7,10 +7,10 @@ import Foundation import PathKit import SwiftTryCatch import SourceryRuntime +import SourceryJS #if SWIFT_PACKAGE #else -import SourceryJS import SourcerySwift #endif @@ -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) + } } } diff --git a/Sourcery/main.swift b/Sourcery/main.swift index b9bdb447d..8d31d35c6 100644 --- a/Sourcery/main.swift +++ b/Sourcery/main.swift @@ -11,6 +11,7 @@ import Commander import PathKit import XcodeEdit import SourceryRuntime +import SourceryJS extension Path: ArgumentConvertible { /// :nodoc: @@ -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("sources", description: "Path to a source swift files"), + VariadicOption("sources", description: "Path to a source swift files."), VariadicOption("templates", description: "Path to templates. File or Directory."), - Option("output", ".", description: "Path to output. File or Directory. Default is current path."), + Option("output", "", description: "Path to output. File or Directory. Default is current path."), Option("config", ".", description: "Path to config file. File or Directory. Default is current path."), - VariadicOption("force-parse", description: "extensions that this run of Sorcery should parse"), - VariadicOption("args", description: "Custom values to pass to templates.") - ) { watcherEnabled, disableCache, verboseLogging, quiet, prune, sources, templates, output, configPath, forceParse, args in + VariadicOption("force-parse", description: "File extensions that Sourcery will be forced to parse, even if they were generated by Sourcery."), + VariadicOption("args", description: "Custom values to pass to templates."), + Option("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 @@ -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 { @@ -135,7 +142,8 @@ func runCLI() { !sources.isEmpty || !templates.isEmpty || !forceParse.isEmpty || - output != "" + output != "" || + !args.isEmpty ) if hasAnyYamlDuplicatedParameter { diff --git a/SourceryJS/Sources/EJSTemplate.swift b/SourceryJS/Sources/EJSTemplate.swift index 7b5dab2f7..97a08ff48 100644 --- a/SourceryJS/Sources/EJSTemplate.swift +++ b/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()! @@ -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() diff --git a/SourceryJS/Sources/ejsbundle.js b/SourceryJS/Sources/ejs.js similarity index 100% rename from SourceryJS/Sources/ejsbundle.js rename to SourceryJS/Sources/ejs.js diff --git a/guides/Writing templates.md b/guides/Writing templates.md index 311ce278c..d03169d49 100644 --- a/guides/Writing templates.md +++ b/guides/Writing templates.md @@ -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