Skip to content

Commit

Permalink
Don't clean up output directory (#36)
Browse files Browse the repository at this point in the history
* prevent to remove root folder and grouped module arguments

* migrate files type from String to URL

* output directory already exist error

* fix CI

minor changes

* typo error in linux - swagger generator
  • Loading branch information
miguelangel-dev authored May 11, 2020
1 parent dfa0392 commit 9ab4f59
Show file tree
Hide file tree
Showing 18 changed files with 412 additions and 292 deletions.
5 changes: 2 additions & 3 deletions BowOpenAPI/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ extension BowOpenAPICommand {
"""
Could not generate API client:
• SCHEMA '\(self.schema)'
\(apiError)
\(self.verbose ?
" • LOG \n\n\(prodEnv.logPath.contentOfFile)\n" :
" • LOG: \(prodEnv.logPath)")
"• LOG \n\n\(prodEnv.logPath.contentOfFile)\n" :
"• LOG: \(prodEnv.logPath)")
"""
},
{ success in
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ macos: clean structure install
.PHONY: fixtures
fixtures:
@rm -rf ./Tests/Fixtures/FixturesAPI
$(TOOL_NAME) --name FixturesAPI --schema ./Tests/Fixtures/petstore.yaml --output ./Tests/Fixtures/FixturesAPI --verbose
$(TOOL_NAME) --name FixturesAPI --schema ./Tests/Fixtures/petstore.yaml --output ./Tests/Fixtures --verbose

.PHONY: xcode
xcode: macos fixtures
Expand Down
120 changes: 65 additions & 55 deletions OpenApiGenerator/APIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,80 +8,90 @@ public enum APIClient {

public static func bow(moduleName: String, schema: String, output: String) -> EnvIO<Environment, APIClientError, String> {
let env = EnvIO<Environment, APIClientError, Environment>.var()
let validated = EnvIO<Environment, APIClientError, String>.var()
let template = EnvIO<Environment, APIClientError, URL>.var()
let schema = schema.expandingTildeInPath
let output = output.expandingTildeInPath
let templates = EnvIO<Environment, APIClientError, URL>.var()
let generated = EnvIO<Environment, APIClientError, String>.var()

return binding(
env <- .ask(),
validated <- validate(schema: schema),
template <- env.get.generator.getTemplates().contramap(\.fileSystem),
|<-bow(moduleName: moduleName, scheme: validated.get, output: output, template: template.get),
yield: "RENDER SUCCEEDED")^
env <- .ask(),
templates <- env.get.generator.getTemplates(),
generated <- bow(moduleName: moduleName, scheme: schema, output: output, templates: templates.get),
yield: generated.get)^
}

public static func bow(moduleName: String, scheme: String, output: String, template: URL) -> EnvIO<Environment, APIClientError, String> {
EnvIO { env in
let outputPath = OutputPath(sources: "\(output)/Sources",
tests: "\(output)/XCTest")

return binding(
|<-createStructure(outputPath: outputPath).provide(env.fileSystem),
|<-env.generator.generate(moduleName: moduleName,
schemePath: scheme,
outputPath: outputPath,
template: template,
logPath: env.logPath).provide(env.fileSystem),
|<-createSwiftPackage(moduleName: moduleName, outputPath: output, template: template).provide(env.fileSystem),
yield: "RENDER SUCCEEDED")^
}
public static func bow(moduleName: String, scheme: String, output: String, templates: URL) -> EnvIO<Environment, APIClientError, String> {
let outputURL = URL(fileURLWithPath: output.expandingTildeInPath)
let schemeURL = URL(fileURLWithPath: scheme.expandingTildeInPath)
let module = OpenAPIModule(name: moduleName,
url: outputURL,
schema: schemeURL,
templates: templates)

return bow(module: module)
}

public static func bow(module: OpenAPIModule) -> EnvIO<Environment, APIClientError, String> {
let env = EnvIO<Environment, APIClientError, Environment>.var()
let validated = EnvIO<Environment, APIClientError, OpenAPIModule>.var()

return binding(
env <- .ask(),
validated <- validate(module: module),
|<-createStructure(module: validated.get),
|<-env.get.generator.generate(module: validated.get),
|<-createSwiftPackage(module: validated.get),
yield: "RENDER SUCCEEDED")^
}

// MARK: attributes
private static func validate(schema: String) -> EnvIO<Environment, APIClientError, String> {
EnvIO.invoke { _ in
guard FileManager.default.fileExists(atPath: schema) else {
private static func validate(module: OpenAPIModule) -> EnvIO<Environment, APIClientError, OpenAPIModule> {
EnvIO.invoke { env in
guard env.fileSystem.exist(item: module.schema) else {
throw APIClientError(operation: "validate(schema:output:)",
error: GeneratorError.invalidParameters)
}

return schema
return module
}
}

// MARK: steps
internal static func createStructure(outputPath: OutputPath) -> EnvIO<FileSystem, APIClientError, ()> {
EnvIO { fileSystem in
let parentPath = outputPath.sources.parentPath
internal static func createStructure(module: OpenAPIModule) -> EnvIO<Environment, APIClientError, Void> {
EnvIO { env in
guard !env.fileSystem.exist(item: module.url) else {
return .raiseError(APIClientError(operation: "createStructure(atPath:)", error: GeneratorError.existOutput(directory: module.url)))^
}

return fileSystem.removeDirectory(parentPath).handleError({ _ in })^
.followedBy(fileSystem.createDirectory(atPath: parentPath))^
.followedBy(fileSystem.createDirectory(atPath: outputPath.sources))^
.followedBy(fileSystem.createDirectory(atPath: outputPath.tests))^
.mapError { _ in APIClientError(operation: "createStructure(atPath:)", error: GeneratorError.structure) }
return env.fileSystem.createDirectory(at: module.url, withIntermediateDirectories: true)^
.followedBy(env.fileSystem.createDirectory(at: module.sources))^
.followedBy(env.fileSystem.createDirectory(at: module.tests))^
.mapError { _ in APIClientError(operation: "createStructure(atPath:)", error: GeneratorError.structure) }
}
}

internal static func createSwiftPackage(moduleName: String, outputPath: String, template: URL) -> EnvIO<FileSystem, APIClientError, ()> {
EnvIO { fileSystem in
fileSystem.copy(item: "Package.swift", from: template.path, to: outputPath)^
}.followedBy(package(moduleName: moduleName, outputPath: outputPath))^
.mapError(FileSystemError.toAPIClientError)
}

internal static func package(moduleName: String, outputPath: String) -> EnvIO<FileSystem, FileSystemError, ()> {
EnvIO { fileSystem in
let content = IO<FileSystemError, String>.var()
let fixedContent = IO<FileSystemError, String>.var()
let path = outputPath + "/Package.swift"

return binding(
content <- fileSystem.readFile(atPath: path),
fixedContent <- IO.pure(content.get.replacingOccurrences(of: "{{ moduleName }}", with: moduleName)),
|<-fileSystem.write(content: fixedContent.get, toFile: path),
yield: ()
)^
internal static func createSwiftPackage(module: OpenAPIModule) -> EnvIO<Environment, APIClientError, Void> {
func installPackage(module: OpenAPIModule) -> EnvIO<FileSystem, FileSystemError, Void> {
EnvIO { fileSystem in
fileSystem.copy(item: "Package.swift", from: module.templates, to: module.url)^
}
}

func updatePackageName(module: OpenAPIModule) -> EnvIO<FileSystem, FileSystemError, Void> {
EnvIO { fileSystem in
let content = IO<FileSystemError, String>.var()
let fixedContent = IO<FileSystemError, String>.var()
let output = module.url.appendingPathComponent("Package.swift")

return binding(
content <- fileSystem.readFile(at: output),
fixedContent <- IO.pure(content.get.replacingOccurrences(of: "{{ moduleName }}", with: module.name)),
|<-fileSystem.write(content: fixedContent.get, toFile: output),
yield: ())^
}
}

return installPackage(module: module)
.followedBy(updatePackageName(module: module))^
.mapError(FileSystemError.toAPIClientError)
.contramap(\.fileSystem)^
}
}
9 changes: 2 additions & 7 deletions OpenApiGenerator/Algebra/ClientGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@ import Foundation
import Bow
import BowEffects

public struct OutputPath {
let sources: String
let tests: String
}

public protocol ClientGenerator {
func getTemplates() -> EnvIO<FileSystem, APIClientError, URL>
func generate(moduleName: String, schemePath: String, outputPath: OutputPath, template: URL, logPath: String) -> EnvIO<FileSystem, APIClientError, Void>
func getTemplates() -> EnvIO<Environment, APIClientError, URL>
func generate(module: OpenAPIModule) -> EnvIO<Environment, APIClientError, Void>
}
60 changes: 33 additions & 27 deletions OpenApiGenerator/Algebra/FileSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,56 +5,62 @@ import Bow
import BowEffects

public protocol FileSystem {
func createDirectory(atPath: String) -> IO<FileSystemError, ()>
func copy(itemPath: String, toPath: String) -> IO<FileSystemError, ()>
func remove(itemPath: String) -> IO<FileSystemError, ()>
func items(atPath path: String) -> IO<FileSystemError, [String]>
func readFile(atPath path: String) -> IO<FileSystemError, String>
func write(content: String, toFile path: String) -> IO<FileSystemError, ()>
func createDirectory(at: URL, withIntermediateDirectories: Bool) -> IO<FileSystemError, Void>
func copy(item: URL, to: URL) -> IO<FileSystemError, Void>
func remove(item: URL) -> IO<FileSystemError, Void>
func items(at: URL) -> IO<FileSystemError, [URL]>
func readFile(at: URL) -> IO<FileSystemError, String>
func write(content: String, toFile: URL) -> IO<FileSystemError, Void>
func exist(item: URL) -> Bool
}

public extension FileSystem {
func copy(item: String, from input: String, to output: String) -> IO<FileSystemError, ()> {
copy(itemPath: "\(input)/\(item)", toPath: "\(output)/\(item)")
func createDirectory(at directory: URL) -> IO<FileSystemError, Void> {
createDirectory(at: directory, withIntermediateDirectories: false)
}

func copy(items: [String], from input: String, to output: String) -> IO<FileSystemError, ()> {
items.traverse { (itemPath: String) in
self.copy(item: itemPath.filename, from: input, to: output)
func copy(item: String, from input: URL, to output: URL) -> IO<FileSystemError, Void> {
copy(item: input.appendingPathComponent(item), to: output.appendingPathComponent(item))
}

func copy(items: [String], from input: URL, to output: URL) -> IO<FileSystemError, Void> {
items.traverse { (item: String) in
self.copy(item: item.filename, from: input, to: output)
}.void()^
}

func remove(from folder: String, files: String...) -> IO<FileSystemError, ()> {
files.traverse { file in self.remove(itemPath: "\(folder)/\(file)") }.void()^
func remove(in directory: URL, files: [String]) -> IO<FileSystemError, Void> {
files.traverse { file in self.remove(item: directory.appendingPathComponent(file)) }.void()^
}

func removeDirectory(_ output: String) -> IO<FileSystemError, ()> {
let outputURL = URL(fileURLWithPath: output, isDirectory: true)
return remove(itemPath: outputURL.path)
func removeDirectory(_ directory: URL) -> IO<FileSystemError, Void> {
let outputURL = URL(fileURLWithPath: directory.path, isDirectory: true)
return remove(item: outputURL)
}

func removeFiles(_ files: String...) -> IO<FileSystemError, ()> {
files.traverse(remove(itemPath:)).void()^
func removeFiles(_ files: [URL]) -> IO<FileSystemError, Void> {
files.traverse(remove(item:)).void()^
}

func moveFile(from origin: String, to destination: String) -> IO<FileSystemError, Void> {
copy(itemPath: origin, toPath: destination)
.followedBy(removeFiles(origin))^
func moveFile(from origin: URL, to destination: URL) -> IO<FileSystemError, Void> {
copy(item: origin, to: destination)
.followedBy(removeFiles([origin]))^
.mapError { _ in .move(from: origin, to: destination) }
}

func moveFiles(in input: String, to output: String) -> IO<FileSystemError, ()> {
let items = IO<FileSystemError, [String]>.var()
func moveFiles(in input: URL, to output: URL) -> IO<FileSystemError, Void> {
let items = IO<FileSystemError, [URL]>.var()

return binding(
items <- self.items(atPath: input),
|<-self.copy(items: items.get, from: input, to: output),
items <- self.items(at: input),
|<-self.copy(items: items.get.map(\.path), from: input, to: output),
|<-self.removeDirectory(input),
yield: ()
)^.mapError { _ in .move(from: input, to: output) }
}

func rename(_ newName: String, itemAt: String) -> IO<FileSystemError, ()> {
moveFile(from: itemAt, to: "\(itemAt.parentPath)/\(newName)")
func rename(with newName: String, item: URL) -> IO<FileSystemError, Void> {
let newItem = item.deletingLastPathComponent().appendingPathComponent(newName)
return moveFile(from: item, to: newItem)
}
}
31 changes: 17 additions & 14 deletions OpenApiGenerator/Error/FileSystemError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,35 @@
import Foundation

public enum FileSystemError: Error {
case create(item: String)
case copy(from: String, to: String)
case remove(item: String)
case move(from: String, to: String)
case get(from: String)
case read(file: String)
case write(file: String)
case create(item: URL)
case copy(from: URL, to: URL)
case remove(item: URL)
case move(from: URL, to: URL)
case get(from: URL)
case read(file: URL)
case invalidContent(info: String)
case write(file: URL)
}

extension FileSystemError: CustomStringConvertible {
public var description: String {
switch self {
case .create(let item):
return "cannot create item '\(item)'"
return "cannot create item '\(item.path)'"
case .copy(let from, let to):
return "cannot copy item at '\(from)' to '\(to)'"
return "cannot copy item at '\(from.path)' to '\(to.path)'"
case .remove(let item):
return "cannot remove item at '\(item)'"
return "cannot remove item at '\(item.path)'"
case .move(let from, let to):
return "cannot move item from '\(from)' to '\(to)'"
return "cannot move item from '\(from.path)' to '\(to.path)'"
case .get(let from):
return "cannot get items from '\(from)'"
return "cannot get items from '\(from.path)'"
case .read(let file):
return "cannot read content of file '\(file)'"
return "cannot read content of file '\(file.path)'"
case .invalidContent(let info):
return "invalid content file \(info)"
case .write(let file):
return "cannot write in file '\(file)'"
return "cannot write in file '\(file.path)'"
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions OpenApiGenerator/Error/GeneratorError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public enum GeneratorError: Error {
case invalidParameters
case templateNotFound
case structure
case existOutput(directory: URL)
case generator
}

Expand All @@ -19,6 +20,8 @@ extension GeneratorError: CustomStringConvertible {
return "templates for generating Bow client have not been found"
case .structure:
return "could not create project structure"
case .existOutput(let directory):
return "output directory '\(directory.path)' already exists"
case .generator:
return "command 'swagger-codegen' failed"
}
Expand Down
Loading

0 comments on commit 9ab4f59

Please sign in to comment.