Skip to content

Commit 370ab78

Browse files
committed
Add dry run option to CLI
1 parent 89b9d7b commit 370ab78

File tree

3 files changed

+83
-13
lines changed

3 files changed

+83
-13
lines changed

Sources/swift-openapi-generator/GenerateCommand.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,18 @@ struct _GenerateCommand: AsyncParsableCommand {
4747
"Whether this invocation is from the SwiftPM plugin. We always need to produce all files when invoked from the plugin. Non-requested modes produce empty files."
4848
)
4949
var isPluginInvocation: Bool = false
50+
51+
@Flag(
52+
help:
53+
"Simulate the command and print the operations, without actually affecting the file system."
54+
)
55+
var isDryRun: Bool = false
5056

5157
func run() async throws {
5258
try generate.runGenerator(
5359
outputDirectory: outputDirectory,
54-
isPluginInvocation: isPluginInvocation
60+
isPluginInvocation: isPluginInvocation,
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
@@ -27,9 +27,13 @@ extension _GenerateOptions {
2727
/// Server.swift) regardless of which generator mode was requested, with
2828
/// the caveat that the not requested files are empty. This is due to
2929
/// a limitation of the build system used by SwiftPM under the hood.
30+
/// - isDryRun: A Boolean value that indicates whether this invocation should
31+
/// be run in a testing mode to preview all the operations being carried out without
32+
/// making any actual changes.
3033
func runGenerator(
3134
outputDirectory: URL,
32-
isPluginInvocation: Bool
35+
isPluginInvocation: Bool,
36+
isDryRun: Bool
3337
) throws {
3438
let config = try loadedConfig()
3539
let sortedModes = try resolvedModes(config)
@@ -63,6 +67,7 @@ extension _GenerateOptions {
6367
- Diagnostics output path: \(diagnosticsOutputPath?.path ?? "<none - logs to stderr>")
6468
- Current directory: \(FileManager.default.currentDirectoryPath)
6569
- Is plugin invocation: \(isPluginInvocation)
70+
- Is dry run: \(isDryRun)
6671
- Additional imports: \(resolvedAdditionalImports.isEmpty ? "<none>" : resolvedAdditionalImports.joined(separator: ", "))
6772
"""
6873
)
@@ -72,6 +77,7 @@ extension _GenerateOptions {
7277
configs: configs,
7378
isPluginInvocation: isPluginInvocation,
7479
outputDirectory: outputDirectory,
80+
isDryRun: isDryRun,
7581
diagnostics: diagnostics
7682
)
7783
} catch let error as Diagnostic {

Sources/swift-openapi-generator/runGenerator.swift

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,30 @@ import ArgumentParser
1616
import _OpenAPIGeneratorCore
1717

1818
extension _Tool {
19+
20+
/// A structure that defines ANSI escape codes for different colors in the terminal output.
21+
///
22+
/// You can use these codes to change the color of the text printed by the `print` function or other methods that write to the standard output. To reset the text color to the default, use the `reset` code.
23+
///
24+
/// For example, to print "Hello" in red and "World" in green, you can use:
25+
/// ```swift
26+
/// print(
27+
/// CommandLineColors.red +
28+
/// "Hello" +
29+
/// CommandLineColors.green +
30+
/// "World" +
31+
/// CommandLineColors.reset
32+
/// )
33+
/// ```
34+
struct CommandLineColors {
35+
/// The ANSI escape code for resetting the text color and style to the default.
36+
static let reset = "\u{001B}[0;0m"
37+
/// The ANSI escape code for changing the text color to red.
38+
static let red = "\u{001B}[0;31m"
39+
/// The ANSI escape code for changing the text color to green.
40+
static let green = "\u{001B}[0;32m"
41+
}
42+
1943

2044
/// Runs the generator with the specified configuration values.
2145
/// - Parameters:
@@ -25,12 +49,15 @@ extension _Tool {
2549
/// generator invocation is coming from a SwiftPM plugin.
2650
/// - outputDirectory: The directory to which the generator writes
2751
/// the generated Swift files.
52+
/// - isDryRun: A Boolean value that indicates whether this invocation should
53+
/// be a dry run.
2854
/// - diagnostics: A collector for diagnostics emitted by the generator.
2955
static func runGenerator(
3056
doc: URL,
3157
configs: [Config],
3258
isPluginInvocation: Bool,
3359
outputDirectory: URL,
60+
isDryRun: Bool,
3461
diagnostics: any DiagnosticCollector
3562
) throws {
3663
let docData: Data
@@ -42,20 +69,25 @@ extension _Tool {
4269
let filePathForMode: (GeneratorMode) -> URL = { mode in
4370
outputDirectory.appendingPathComponent(mode.outputFileName)
4471
}
72+
if isDryRun {
73+
print("--------------------------------")
74+
print("Dry run mode: No files will be created or modified")
75+
}
4576
for config in configs {
4677
try runGenerator(
4778
doc: doc,
4879
docData: docData,
4980
config: config,
5081
outputFilePath: filePathForMode(config.mode),
82+
isDryRun: isDryRun,
5183
diagnostics: diagnostics
5284
)
5385
}
5486
if isPluginInvocation {
5587
let nonGeneratedModes = Set(GeneratorMode.allCases).subtracting(configs.map(\.mode))
5688
for mode in nonGeneratedModes.sorted() {
5789
let path = filePathForMode(mode)
58-
try replaceFileContents(at: path, with: { Data() })
90+
try replaceFileContents(at: path, with: { Data() }, isDryRun: isDryRun)
5991
}
6092
}
6193
}
@@ -67,44 +99,69 @@ extension _Tool {
6799
/// - config: A set of configuration values for the generator.
68100
/// - outputFilePath: The directory to which the generator writes
69101
/// the generated Swift files.
102+
/// - isDryRun: A Boolean value that indicates whether this invocation should
103+
/// be a dry run.
70104
/// - diagnostics: A collector for diagnostics emitted by the generator.
71105
static func runGenerator(
72106
doc: URL,
73107
docData: Data,
74108
config: Config,
75109
outputFilePath: URL,
110+
isDryRun: Bool,
76111
diagnostics: any DiagnosticCollector
77112
) throws {
78-
let didChange = try replaceFileContents(at: outputFilePath) {
79-
let output = try _OpenAPIGeneratorCore.runGenerator(
80-
input: .init(absolutePath: doc, contents: docData),
81-
config: config,
82-
diagnostics: diagnostics
83-
)
84-
return output.contents
113+
let didChange = try replaceFileContents(
114+
at: outputFilePath,
115+
with: {
116+
let output = try _OpenAPIGeneratorCore.runGenerator(
117+
input: .init(absolutePath: doc, contents: docData),
118+
config: config,
119+
diagnostics: diagnostics
120+
)
121+
return output.contents
122+
},
123+
isDryRun: isDryRun
124+
)
125+
if !isDryRun {
126+
print("File \(outputFilePath.lastPathComponent): \(didChange ? "changed" : "unchanged")")
85127
}
86-
print("File \(outputFilePath.lastPathComponent): \(didChange ? "changed" : "unchanged")")
87128
}
88129

89130
/// Evaluates a closure to generate file data and writes the data to disk
90131
/// if the data is different than the current file contents.
91132
/// - Parameters:
92133
/// - path: A path to the file.
93134
/// - contents: A closure evaluated to produce the file contents data.
135+
/// - isDryRun: A Boolean value that indicates whether this invocation should
136+
/// be a dry run. File system changes will not be written to disk in this mode.
94137
/// - Throws: When writing to disk fails.
95138
/// - Returns: `true` if the generated contents changed, otherwise `false`.
96139
@discardableResult
97-
static func replaceFileContents(at path: URL, with contents: () throws -> Data) throws -> Bool {
140+
static func replaceFileContents(
141+
at path: URL,
142+
with contents: () throws -> Data,
143+
isDryRun: Bool
144+
) throws -> Bool {
98145
let data = try contents()
99146
let didChange: Bool
100147
if FileManager.default.fileExists(atPath: path.path) {
101148
let existingData = try? Data(contentsOf: path)
102149
didChange = existingData != data
150+
if didChange {
151+
print(CommandLineColors.red + "File \(path.lastPathComponent) will be overwritten." + CommandLineColors.reset)
152+
} else {
153+
print(CommandLineColors.green + "File \(path.lastPathComponent) will remain unchanged." + CommandLineColors.reset)
154+
}
103155
} else {
156+
print(CommandLineColors.green + "File \(path.lastPathComponent) does not exist.\nCreating new file..." + CommandLineColors.reset)
104157
didChange = true
105158
}
106159
if didChange {
107-
try data.write(to: path)
160+
if isDryRun {
161+
print(CommandLineColors.green + "Writing data to \(path.lastPathComponent)..." + CommandLineColors.reset)
162+
} else {
163+
try data.write(to: path)
164+
}
108165
}
109166
return didChange
110167
}

0 commit comments

Comments
 (0)