Template support for input parameters (#4)
This PR contains the work done to support input parameters for the `create` command of the executable target, and to render content dynamically for the newly-generated project. Reviewed-on: #4 Co-authored-by: Javier Cicchelli <javier@rock-n-code.com> Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
This commit was merged in pull request #4.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
public struct FileService: FileServicing {
|
||||
|
||||
public struct FileService {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
private let fileManager: FileManager
|
||||
@@ -12,6 +12,12 @@ public struct FileService: FileServicing {
|
||||
self.fileManager = fileManager
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - FileServicing
|
||||
|
||||
extension FileService: FileServicing {
|
||||
|
||||
// MARK: Computed
|
||||
|
||||
public var currentFolder: URL {
|
||||
@@ -24,7 +30,7 @@ public struct FileService: FileServicing {
|
||||
|
||||
public func copyFile(from source: URL, to destination: URL) async throws (FileServiceError) {
|
||||
guard try await !isItemExists(at: destination) else {
|
||||
throw FileServiceError.itemAlreadyExists
|
||||
throw .itemAlreadyExists
|
||||
}
|
||||
|
||||
var itemData: Data?
|
||||
@@ -32,43 +38,59 @@ public struct FileService: FileServicing {
|
||||
do {
|
||||
itemData = try Data(contentsOf: source)
|
||||
} catch {
|
||||
throw FileServiceError.itemEmptyData
|
||||
throw .itemEmptyData
|
||||
}
|
||||
|
||||
do {
|
||||
try itemData?.write(to: destination, options: .atomic)
|
||||
} catch {
|
||||
throw FileServiceError.itemNotCopied
|
||||
throw .itemNotCopied
|
||||
}
|
||||
}
|
||||
|
||||
public func createFile(at location: URL, with data: Data) async throws (FileServiceError) {
|
||||
guard try await !isItemExists(at: location) else {
|
||||
throw .itemAlreadyExists
|
||||
}
|
||||
|
||||
guard !data.isEmpty else {
|
||||
throw .fileDataIsEmpty
|
||||
}
|
||||
|
||||
do {
|
||||
try data.write(to: location, options: .atomic)
|
||||
} catch {
|
||||
throw .fileNotCreated
|
||||
}
|
||||
}
|
||||
|
||||
public func createFolder(at location: URL) async throws (FileServiceError) {
|
||||
guard try await !isItemExists(at: location) else {
|
||||
throw FileServiceError.itemAlreadyExists
|
||||
throw .itemAlreadyExists
|
||||
}
|
||||
|
||||
do {
|
||||
try fileManager.createDirectory(at: location, withIntermediateDirectories: true)
|
||||
} catch {
|
||||
throw FileServiceError.folderNotCreated
|
||||
throw .folderNotCreated
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteItem(at location: URL) async throws (FileServiceError) {
|
||||
guard try await isItemExists(at: location) else {
|
||||
throw FileServiceError.itemNotExists
|
||||
throw .itemNotExists
|
||||
}
|
||||
|
||||
do {
|
||||
try fileManager.removeItem(at: location)
|
||||
} catch {
|
||||
throw FileServiceError.itemNotDeleted
|
||||
throw .itemNotDeleted
|
||||
}
|
||||
}
|
||||
|
||||
public func isItemExists(at location: URL) async throws (FileServiceError) -> Bool {
|
||||
guard location.isFileURL else {
|
||||
throw FileServiceError.itemNotFileURL
|
||||
throw .itemNotFileURL
|
||||
}
|
||||
|
||||
let filePath = location.pathString
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import Foundation
|
||||
import Mustache
|
||||
|
||||
public struct TemplateService {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
private let mustacheRenderer: MustacheLibrary
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
public init(
|
||||
bundle: Bundleable? = nil,
|
||||
templateFolder: String
|
||||
) async throws (TemplateServiceError) {
|
||||
guard let pathResources = (bundle ?? Bundle.module).resourcePath else {
|
||||
throw .resourcePathNotFound
|
||||
}
|
||||
|
||||
let pathTemplates = pathResources + "/" + templateFolder
|
||||
|
||||
do {
|
||||
self.mustacheRenderer = try await MustacheLibrary(directory: pathTemplates)
|
||||
} catch {
|
||||
throw .serviceNotInitialized
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - TemplateServicing
|
||||
|
||||
extension TemplateService: TemplateServicing {
|
||||
|
||||
// MARK: Functions
|
||||
|
||||
public func render(_ object: Any, on template: String) async throws (TemplateServiceError) -> String {
|
||||
guard mustacheRenderer.getTemplate(named: template) != nil else {
|
||||
throw .templateNotFound
|
||||
}
|
||||
|
||||
guard let content = mustacheRenderer.render(object, withTemplate: template) else {
|
||||
throw .contentNotRendered
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import Foundation
|
||||
|
||||
public struct TerminalService {
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
public init() {}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - TerminalServicing
|
||||
|
||||
extension TerminalService: TerminalServicing {
|
||||
|
||||
// MARK: Functions
|
||||
|
||||
public func run(_ executableURL: URL, arguments: [String]) async throws (TerminalServiceError) -> String {
|
||||
let process = Process()
|
||||
let standardError = Pipe()
|
||||
let standardOutput = Pipe()
|
||||
|
||||
process.executableURL = executableURL
|
||||
process.arguments = arguments
|
||||
process.standardError = standardError
|
||||
process.standardOutput = standardOutput
|
||||
|
||||
async let streamOutput = standardOutput.availableData.append()
|
||||
async let streamError = standardError.availableData.append()
|
||||
|
||||
do {
|
||||
try process.run()
|
||||
|
||||
let dataOutput = await streamOutput
|
||||
let dataError = await streamError
|
||||
|
||||
guard dataError.isEmpty else {
|
||||
guard let errorOutput = String(data: dataError, encoding: .utf8) else {
|
||||
throw TerminalServiceError.unexpected
|
||||
}
|
||||
|
||||
throw TerminalServiceError.output(errorOutput)
|
||||
}
|
||||
|
||||
guard let output = String(data: dataOutput, encoding: .utf8) else {
|
||||
throw TerminalServiceError.unexpected
|
||||
}
|
||||
|
||||
return await withCheckedContinuation { continuation in
|
||||
process.terminationHandler = { _ in
|
||||
continuation.resume(returning: output)
|
||||
}
|
||||
}
|
||||
} catch let error as TerminalServiceError {
|
||||
throw error
|
||||
} catch {
|
||||
throw .captured(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user