Skip to content

Add dry run option to CLI #123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Sources/swift-openapi-generator/GenerateCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,17 @@ struct _GenerateCommand: AsyncParsableCommand {
)
var pluginSource: PluginSource?

@Flag(
name: .customLong("dry-run"),
help: "Simulate the command and print the operations, without actually affecting the file system."
)
var isDryRun: Bool = false

func run() async throws {
try generate.runGenerator(
outputDirectory: outputDirectory,
pluginSource: pluginSource
pluginSource: pluginSource,
isDryRun: isDryRun
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@ extension _GenerateOptions {
/// - outputDirectory: The directory path to which the generator writes
/// the generated Swift files.
/// - pluginSource: The source of the generator invocation if from a plugin.
/// - isDryRun: A Boolean value that indicates whether this invocation should
/// be run in a testing mode to preview all the operations being carried out without
/// making any actual changes.
func runGenerator(
outputDirectory: URL,
pluginSource: PluginSource?
pluginSource: PluginSource?,
isDryRun: Bool
) throws {
let config = try loadedConfig()
let sortedModes = try resolvedModes(config)
Expand Down Expand Up @@ -58,6 +62,7 @@ extension _GenerateOptions {
- Diagnostics output path: \(diagnosticsOutputPath?.path ?? "<none - logs to stderr>")
- Current directory: \(FileManager.default.currentDirectoryPath)
- Plugin source: \(pluginSource?.rawValue ?? "<none>")
- Is dry run: \(isDryRun)
- Additional imports: \(resolvedAdditionalImports.isEmpty ? "<none>" : resolvedAdditionalImports.joined(separator: ", "))
"""
)
Expand All @@ -67,6 +72,7 @@ extension _GenerateOptions {
configs: configs,
pluginSource: pluginSource,
outputDirectory: outputDirectory,
isDryRun: isDryRun,
diagnostics: diagnostics
)
} catch let error as Diagnostic {
Expand Down
67 changes: 36 additions & 31 deletions Sources/swift-openapi-generator/runGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,22 @@ import ArgumentParser
import _OpenAPIGeneratorCore

extension _Tool {

/// Runs the generator with the specified configuration values.
/// - Parameters:
/// - doc: A path to the OpenAPI document.
/// - configs: A list of generator configurations.
/// - pluginSource: The source of the generator invocation.
/// - outputDirectory: The directory to which the generator writes
/// the generated Swift files.
/// - isDryRun: A Boolean value that indicates whether this invocation should
/// be a dry run.
/// - diagnostics: A collector for diagnostics emitted by the generator.
static func runGenerator(
doc: URL,
configs: [Config],
pluginSource: PluginSource?,
outputDirectory: URL,
isDryRun: Bool,
diagnostics: any DiagnosticCollector
) throws {
let docData: Data
Expand All @@ -45,6 +47,7 @@ extension _Tool {
config: config,
outputDirectory: outputDirectory,
outputFileName: config.mode.outputFileName,
isDryRun: isDryRun,
diagnostics: diagnostics
)
}
Expand All @@ -59,7 +62,8 @@ extension _Tool {
try replaceFileContents(
inDirectory: outputDirectory,
fileName: mode.outputFileName,
with: { Data() }
with: { Data() },
isDryRun: isDryRun
)
}
}
Expand All @@ -74,62 +78,63 @@ extension _Tool {
/// the generated Swift file.
/// - outputFileName: The file name to which the generator writes
/// the generated Swift file.
/// - isDryRun: A Boolean value that indicates whether this invocation should
/// be a dry run.
/// - diagnostics: A collector for diagnostics emitted by the generator.
static func runGenerator(
doc: URL,
docData: Data,
config: Config,
outputDirectory: URL,
outputFileName: String,
isDryRun: Bool,
diagnostics: any DiagnosticCollector
) throws {
let didChange = try replaceFileContents(
try replaceFileContents(
inDirectory: outputDirectory,
fileName: outputFileName
) {
let output = try _OpenAPIGeneratorCore.runGenerator(
input: .init(absolutePath: doc, contents: docData),
config: config,
diagnostics: diagnostics
)
return output.contents
}
print("File \(outputFileName): \(didChange ? "changed" : "unchanged")")
fileName: outputFileName,
with: {
let output = try _OpenAPIGeneratorCore.runGenerator(
input: .init(absolutePath: doc, contents: docData),
config: config,
diagnostics: diagnostics
)
return output.contents
},
isDryRun: isDryRun
)
}

/// Evaluates a closure to generate file data and writes the data to disk
/// if the data is different than the current file contents.
/// if the data is different than the current file contents. Will write to disk
/// only if `isDryRun` is set as `false`.
/// - Parameters:
/// - path: A path to the file.
/// - contents: A closure evaluated to produce the file contents data.
/// - isDryRun: A Boolean value that indicates whether this invocation should
/// be a dry run. File system changes will not be written to disk in this mode.
/// - Throws: When writing to disk fails.
/// - Returns: `true` if the generated contents changed, otherwise `false`.
@discardableResult
static func replaceFileContents(
inDirectory outputDirectory: URL,
fileName: String,
with contents: () throws -> Data
) throws -> Bool {
with contents: () throws -> Data,
isDryRun: Bool
) throws {
let fileManager = FileManager.default
let path = outputDirectory.appendingPathComponent(fileName)
let data = try contents()

// Create directory if it doesn't exist.
if !fileManager.fileExists(atPath: outputDirectory.path) {
if let existingData = try? Data(contentsOf: path), existingData == data {
print("File \(path.lastPathComponent) already up to date.")
return
}
print("Writing data to file \(path.lastPathComponent)...")
if !isDryRun {
try fileManager.createDirectory(
at: outputDirectory,
withIntermediateDirectories: true
)
}

let path = outputDirectory.appendingPathComponent(fileName)
let data = try contents()
guard fileManager.fileExists(atPath: path.path) else {
return fileManager.createFile(atPath: path.path, contents: data)
}
let existingData = try? Data(contentsOf: path)
guard existingData == data else {
try data.write(to: path)
return true
}
return false
}
}