Skip to content

Commit 7bb32a9

Browse files
authored
Add dry run option to CLI (#123)
1 parent 6a6ddf8 commit 7bb32a9

File tree

3 files changed

+51
-33
lines changed

3 files changed

+51
-33
lines changed

Sources/swift-openapi-generator/GenerateCommand.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,17 @@ struct _GenerateCommand: AsyncParsableCommand {
4848
)
4949
var pluginSource: PluginSource?
5050

51+
@Flag(
52+
name: .customLong("dry-run"),
53+
help: "Simulate the command and print the operations, without actually affecting the file system."
54+
)
55+
var isDryRun: Bool = false
56+
5157
func run() async throws {
5258
try generate.runGenerator(
5359
outputDirectory: outputDirectory,
54-
pluginSource: pluginSource
60+
pluginSource: pluginSource,
61+
isDryRun: isDryRun
5562
)
5663
}
5764
}

Sources/swift-openapi-generator/GenerateOptions+runGenerator.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ extension _GenerateOptions {
2222
/// - outputDirectory: The directory path to which the generator writes
2323
/// the generated Swift files.
2424
/// - pluginSource: The source of the generator invocation if from a plugin.
25+
/// - isDryRun: A Boolean value that indicates whether this invocation should
26+
/// be run in a testing mode to preview all the operations being carried out without
27+
/// making any actual changes.
2528
func runGenerator(
2629
outputDirectory: URL,
27-
pluginSource: PluginSource?
30+
pluginSource: PluginSource?,
31+
isDryRun: Bool
2832
) throws {
2933
let config = try loadedConfig()
3034
let sortedModes = try resolvedModes(config)
@@ -58,6 +62,7 @@ extension _GenerateOptions {
5862
- Diagnostics output path: \(diagnosticsOutputPath?.path ?? "<none - logs to stderr>")
5963
- Current directory: \(FileManager.default.currentDirectoryPath)
6064
- Plugin source: \(pluginSource?.rawValue ?? "<none>")
65+
- Is dry run: \(isDryRun)
6166
- Additional imports: \(resolvedAdditionalImports.isEmpty ? "<none>" : resolvedAdditionalImports.joined(separator: ", "))
6267
"""
6368
)
@@ -67,6 +72,7 @@ extension _GenerateOptions {
6772
configs: configs,
6873
pluginSource: pluginSource,
6974
outputDirectory: outputDirectory,
75+
isDryRun: isDryRun,
7076
diagnostics: diagnostics
7177
)
7278
} catch let error as Diagnostic {

Sources/swift-openapi-generator/runGenerator.swift

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,22 @@ import ArgumentParser
1616
import _OpenAPIGeneratorCore
1717

1818
extension _Tool {
19-
2019
/// Runs the generator with the specified configuration values.
2120
/// - Parameters:
2221
/// - doc: A path to the OpenAPI document.
2322
/// - configs: A list of generator configurations.
2423
/// - pluginSource: The source of the generator invocation.
2524
/// - outputDirectory: The directory to which the generator writes
2625
/// the generated Swift files.
26+
/// - isDryRun: A Boolean value that indicates whether this invocation should
27+
/// be a dry run.
2728
/// - diagnostics: A collector for diagnostics emitted by the generator.
2829
static func runGenerator(
2930
doc: URL,
3031
configs: [Config],
3132
pluginSource: PluginSource?,
3233
outputDirectory: URL,
34+
isDryRun: Bool,
3335
diagnostics: any DiagnosticCollector
3436
) throws {
3537
let docData: Data
@@ -45,6 +47,7 @@ extension _Tool {
4547
config: config,
4648
outputDirectory: outputDirectory,
4749
outputFileName: config.mode.outputFileName,
50+
isDryRun: isDryRun,
4851
diagnostics: diagnostics
4952
)
5053
}
@@ -59,7 +62,8 @@ extension _Tool {
5962
try replaceFileContents(
6063
inDirectory: outputDirectory,
6164
fileName: mode.outputFileName,
62-
with: { Data() }
65+
with: { Data() },
66+
isDryRun: isDryRun
6367
)
6468
}
6569
}
@@ -74,62 +78,63 @@ extension _Tool {
7478
/// the generated Swift file.
7579
/// - outputFileName: The file name to which the generator writes
7680
/// the generated Swift file.
81+
/// - isDryRun: A Boolean value that indicates whether this invocation should
82+
/// be a dry run.
7783
/// - diagnostics: A collector for diagnostics emitted by the generator.
7884
static func runGenerator(
7985
doc: URL,
8086
docData: Data,
8187
config: Config,
8288
outputDirectory: URL,
8389
outputFileName: String,
90+
isDryRun: Bool,
8491
diagnostics: any DiagnosticCollector
8592
) throws {
86-
let didChange = try replaceFileContents(
93+
try replaceFileContents(
8794
inDirectory: outputDirectory,
88-
fileName: outputFileName
89-
) {
90-
let output = try _OpenAPIGeneratorCore.runGenerator(
91-
input: .init(absolutePath: doc, contents: docData),
92-
config: config,
93-
diagnostics: diagnostics
94-
)
95-
return output.contents
96-
}
97-
print("File \(outputFileName): \(didChange ? "changed" : "unchanged")")
95+
fileName: outputFileName,
96+
with: {
97+
let output = try _OpenAPIGeneratorCore.runGenerator(
98+
input: .init(absolutePath: doc, contents: docData),
99+
config: config,
100+
diagnostics: diagnostics
101+
)
102+
return output.contents
103+
},
104+
isDryRun: isDryRun
105+
)
98106
}
99107

100108
/// Evaluates a closure to generate file data and writes the data to disk
101-
/// if the data is different than the current file contents.
109+
/// if the data is different than the current file contents. Will write to disk
110+
/// only if `isDryRun` is set as `false`.
102111
/// - Parameters:
103112
/// - path: A path to the file.
104113
/// - contents: A closure evaluated to produce the file contents data.
114+
/// - isDryRun: A Boolean value that indicates whether this invocation should
115+
/// be a dry run. File system changes will not be written to disk in this mode.
105116
/// - Throws: When writing to disk fails.
106-
/// - Returns: `true` if the generated contents changed, otherwise `false`.
107-
@discardableResult
108117
static func replaceFileContents(
109118
inDirectory outputDirectory: URL,
110119
fileName: String,
111-
with contents: () throws -> Data
112-
) throws -> Bool {
120+
with contents: () throws -> Data,
121+
isDryRun: Bool
122+
) throws {
113123
let fileManager = FileManager.default
124+
let path = outputDirectory.appendingPathComponent(fileName)
125+
let data = try contents()
114126

115-
// Create directory if it doesn't exist.
116-
if !fileManager.fileExists(atPath: outputDirectory.path) {
127+
if let existingData = try? Data(contentsOf: path), existingData == data {
128+
print("File \(path.lastPathComponent) already up to date.")
129+
return
130+
}
131+
print("Writing data to file \(path.lastPathComponent)...")
132+
if !isDryRun {
117133
try fileManager.createDirectory(
118134
at: outputDirectory,
119135
withIntermediateDirectories: true
120136
)
121-
}
122-
123-
let path = outputDirectory.appendingPathComponent(fileName)
124-
let data = try contents()
125-
guard fileManager.fileExists(atPath: path.path) else {
126-
return fileManager.createFile(atPath: path.path, contents: data)
127-
}
128-
let existingData = try? Data(contentsOf: path)
129-
guard existingData == data else {
130137
try data.write(to: path)
131-
return true
132138
}
133-
return false
134139
}
135140
}

0 commit comments

Comments
 (0)