From 370ab78dd4641e833093516c8630f309b7bff1e5 Mon Sep 17 00:00:00 2001 From: Denil Chungath Date: Mon, 17 Jul 2023 18:33:20 +0530 Subject: [PATCH 1/6] Add dry run option to CLI --- .../GenerateCommand.swift | 9 ++- .../GenerateOptions+runGenerator.swift | 8 +- .../runGenerator.swift | 79 ++++++++++++++++--- 3 files changed, 83 insertions(+), 13 deletions(-) diff --git a/Sources/swift-openapi-generator/GenerateCommand.swift b/Sources/swift-openapi-generator/GenerateCommand.swift index fc1ac08f..4d63afc7 100644 --- a/Sources/swift-openapi-generator/GenerateCommand.swift +++ b/Sources/swift-openapi-generator/GenerateCommand.swift @@ -47,11 +47,18 @@ struct _GenerateCommand: AsyncParsableCommand { "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." ) var isPluginInvocation: Bool = false + + @Flag( + 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, - isPluginInvocation: isPluginInvocation + isPluginInvocation: isPluginInvocation, + isDryRun: isDryRun ) } } diff --git a/Sources/swift-openapi-generator/GenerateOptions+runGenerator.swift b/Sources/swift-openapi-generator/GenerateOptions+runGenerator.swift index 01314929..1ab3120c 100644 --- a/Sources/swift-openapi-generator/GenerateOptions+runGenerator.swift +++ b/Sources/swift-openapi-generator/GenerateOptions+runGenerator.swift @@ -27,9 +27,13 @@ extension _GenerateOptions { /// Server.swift) regardless of which generator mode was requested, with /// the caveat that the not requested files are empty. This is due to /// a limitation of the build system used by SwiftPM under the hood. + /// - 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, - isPluginInvocation: Bool + isPluginInvocation: Bool, + isDryRun: Bool ) throws { let config = try loadedConfig() let sortedModes = try resolvedModes(config) @@ -63,6 +67,7 @@ extension _GenerateOptions { - Diagnostics output path: \(diagnosticsOutputPath?.path ?? "") - Current directory: \(FileManager.default.currentDirectoryPath) - Is plugin invocation: \(isPluginInvocation) + - Is dry run: \(isDryRun) - Additional imports: \(resolvedAdditionalImports.isEmpty ? "" : resolvedAdditionalImports.joined(separator: ", ")) """ ) @@ -72,6 +77,7 @@ extension _GenerateOptions { configs: configs, isPluginInvocation: isPluginInvocation, outputDirectory: outputDirectory, + isDryRun: isDryRun, diagnostics: diagnostics ) } catch let error as Diagnostic { diff --git a/Sources/swift-openapi-generator/runGenerator.swift b/Sources/swift-openapi-generator/runGenerator.swift index 7fc2f56a..642025c5 100644 --- a/Sources/swift-openapi-generator/runGenerator.swift +++ b/Sources/swift-openapi-generator/runGenerator.swift @@ -16,6 +16,30 @@ import ArgumentParser import _OpenAPIGeneratorCore extension _Tool { + + /// A structure that defines ANSI escape codes for different colors in the terminal output. + /// + /// 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. + /// + /// For example, to print "Hello" in red and "World" in green, you can use: + /// ```swift + /// print( + /// CommandLineColors.red + + /// "Hello" + + /// CommandLineColors.green + + /// "World" + + /// CommandLineColors.reset + /// ) + /// ``` + struct CommandLineColors { + /// The ANSI escape code for resetting the text color and style to the default. + static let reset = "\u{001B}[0;0m" + /// The ANSI escape code for changing the text color to red. + static let red = "\u{001B}[0;31m" + /// The ANSI escape code for changing the text color to green. + static let green = "\u{001B}[0;32m" + } + /// Runs the generator with the specified configuration values. /// - Parameters: @@ -25,12 +49,15 @@ extension _Tool { /// generator invocation is coming from a SwiftPM plugin. /// - 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], isPluginInvocation: Bool, outputDirectory: URL, + isDryRun: Bool, diagnostics: any DiagnosticCollector ) throws { let docData: Data @@ -42,12 +69,17 @@ extension _Tool { let filePathForMode: (GeneratorMode) -> URL = { mode in outputDirectory.appendingPathComponent(mode.outputFileName) } + if isDryRun { + print("--------------------------------") + print("Dry run mode: No files will be created or modified") + } for config in configs { try runGenerator( doc: doc, docData: docData, config: config, outputFilePath: filePathForMode(config.mode), + isDryRun: isDryRun, diagnostics: diagnostics ) } @@ -55,7 +87,7 @@ extension _Tool { let nonGeneratedModes = Set(GeneratorMode.allCases).subtracting(configs.map(\.mode)) for mode in nonGeneratedModes.sorted() { let path = filePathForMode(mode) - try replaceFileContents(at: path, with: { Data() }) + try replaceFileContents(at: path, with: { Data() }, isDryRun: isDryRun) } } } @@ -67,23 +99,32 @@ extension _Tool { /// - config: A set of configuration values for the generator. /// - outputFilePath: 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, docData: Data, config: Config, outputFilePath: URL, + isDryRun: Bool, diagnostics: any DiagnosticCollector ) throws { - let didChange = try replaceFileContents(at: outputFilePath) { - let output = try _OpenAPIGeneratorCore.runGenerator( - input: .init(absolutePath: doc, contents: docData), - config: config, - diagnostics: diagnostics - ) - return output.contents + let didChange = try replaceFileContents( + at: outputFilePath, + with: { + let output = try _OpenAPIGeneratorCore.runGenerator( + input: .init(absolutePath: doc, contents: docData), + config: config, + diagnostics: diagnostics + ) + return output.contents + }, + isDryRun: isDryRun + ) + if !isDryRun { + print("File \(outputFilePath.lastPathComponent): \(didChange ? "changed" : "unchanged")") } - print("File \(outputFilePath.lastPathComponent): \(didChange ? "changed" : "unchanged")") } /// Evaluates a closure to generate file data and writes the data to disk @@ -91,20 +132,36 @@ extension _Tool { /// - 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(at path: URL, with contents: () throws -> Data) throws -> Bool { + static func replaceFileContents( + at path: URL, + with contents: () throws -> Data, + isDryRun: Bool + ) throws -> Bool { let data = try contents() let didChange: Bool if FileManager.default.fileExists(atPath: path.path) { let existingData = try? Data(contentsOf: path) didChange = existingData != data + if didChange { + print(CommandLineColors.red + "File \(path.lastPathComponent) will be overwritten." + CommandLineColors.reset) + } else { + print(CommandLineColors.green + "File \(path.lastPathComponent) will remain unchanged." + CommandLineColors.reset) + } } else { + print(CommandLineColors.green + "File \(path.lastPathComponent) does not exist.\nCreating new file..." + CommandLineColors.reset) didChange = true } if didChange { - try data.write(to: path) + if isDryRun { + print(CommandLineColors.green + "Writing data to \(path.lastPathComponent)..." + CommandLineColors.reset) + } else { + try data.write(to: path) + } } return didChange } From 00a846218c4652d60a299477e3ef92210f47dd95 Mon Sep 17 00:00:00 2001 From: Denil Chungath Date: Mon, 17 Jul 2023 19:38:16 +0530 Subject: [PATCH 2/6] Resolve comments --- .../GenerateCommand.swift | 2 +- .../runGenerator.swift | 38 +++---------------- 2 files changed, 6 insertions(+), 34 deletions(-) diff --git a/Sources/swift-openapi-generator/GenerateCommand.swift b/Sources/swift-openapi-generator/GenerateCommand.swift index 4d63afc7..e74ce1e5 100644 --- a/Sources/swift-openapi-generator/GenerateCommand.swift +++ b/Sources/swift-openapi-generator/GenerateCommand.swift @@ -47,7 +47,7 @@ struct _GenerateCommand: AsyncParsableCommand { "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." ) var isPluginInvocation: Bool = false - + @Flag( help: "Simulate the command and print the operations, without actually affecting the file system." diff --git a/Sources/swift-openapi-generator/runGenerator.swift b/Sources/swift-openapi-generator/runGenerator.swift index 642025c5..6cd89ea7 100644 --- a/Sources/swift-openapi-generator/runGenerator.swift +++ b/Sources/swift-openapi-generator/runGenerator.swift @@ -16,31 +16,6 @@ import ArgumentParser import _OpenAPIGeneratorCore extension _Tool { - - /// A structure that defines ANSI escape codes for different colors in the terminal output. - /// - /// 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. - /// - /// For example, to print "Hello" in red and "World" in green, you can use: - /// ```swift - /// print( - /// CommandLineColors.red + - /// "Hello" + - /// CommandLineColors.green + - /// "World" + - /// CommandLineColors.reset - /// ) - /// ``` - struct CommandLineColors { - /// The ANSI escape code for resetting the text color and style to the default. - static let reset = "\u{001B}[0;0m" - /// The ANSI escape code for changing the text color to red. - static let red = "\u{001B}[0;31m" - /// The ANSI escape code for changing the text color to green. - static let green = "\u{001B}[0;32m" - } - - /// Runs the generator with the specified configuration values. /// - Parameters: /// - doc: A path to the OpenAPI document. @@ -110,7 +85,7 @@ extension _Tool { isDryRun: Bool, diagnostics: any DiagnosticCollector ) throws { - let didChange = try replaceFileContents( + try replaceFileContents( at: outputFilePath, with: { let output = try _OpenAPIGeneratorCore.runGenerator( @@ -122,9 +97,6 @@ extension _Tool { }, isDryRun: isDryRun ) - if !isDryRun { - print("File \(outputFilePath.lastPathComponent): \(didChange ? "changed" : "unchanged")") - } } /// Evaluates a closure to generate file data and writes the data to disk @@ -148,17 +120,17 @@ extension _Tool { let existingData = try? Data(contentsOf: path) didChange = existingData != data if didChange { - print(CommandLineColors.red + "File \(path.lastPathComponent) will be overwritten." + CommandLineColors.reset) + print("File \(path.lastPathComponent) will be overwritten.") } else { - print(CommandLineColors.green + "File \(path.lastPathComponent) will remain unchanged." + CommandLineColors.reset) + print("File \(path.lastPathComponent) will remain unchanged.") } } else { - print(CommandLineColors.green + "File \(path.lastPathComponent) does not exist.\nCreating new file..." + CommandLineColors.reset) + print("File \(path.lastPathComponent) does not exist.\nCreating new file...") didChange = true } if didChange { if isDryRun { - print(CommandLineColors.green + "Writing data to \(path.lastPathComponent)..." + CommandLineColors.reset) + print("Writing data to \(path.lastPathComponent)...") } else { try data.write(to: path) } From e06451119f6af7e0d3b5a372036fc27bc667d250 Mon Sep 17 00:00:00 2001 From: Denil Chungath Date: Wed, 19 Jul 2023 00:59:26 +0530 Subject: [PATCH 3/6] resolve comments --- .../runGenerator.swift | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/Sources/swift-openapi-generator/runGenerator.swift b/Sources/swift-openapi-generator/runGenerator.swift index 6cd89ea7..68cd4cbd 100644 --- a/Sources/swift-openapi-generator/runGenerator.swift +++ b/Sources/swift-openapi-generator/runGenerator.swift @@ -44,10 +44,6 @@ extension _Tool { let filePathForMode: (GeneratorMode) -> URL = { mode in outputDirectory.appendingPathComponent(mode.outputFileName) } - if isDryRun { - print("--------------------------------") - print("Dry run mode: No files will be created or modified") - } for config in configs { try runGenerator( doc: doc, @@ -85,7 +81,7 @@ extension _Tool { isDryRun: Bool, diagnostics: any DiagnosticCollector ) throws { - try replaceFileContents( + let result = try replaceFileContents( at: outputFilePath, with: { let output = try _OpenAPIGeneratorCore.runGenerator( @@ -97,44 +93,49 @@ extension _Tool { }, isDryRun: isDryRun ) + if result.didCreate && isDryRun { + print("File \(outputFilePath.lastPathComponent) does not exist.\nCreating new file...") + } + if result.didChange { + print("File \(outputFilePath.lastPathComponent) will be overwritten.") + } else { + print("File \(outputFilePath.lastPathComponent) will remain unchanged.") + } } /// 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`. + /// - Returns: A tuple of two booleans didChange and didCreate. + /// The former will be `true` if the generated contents changed, otherwise `false` and + /// the latter will be `true` if the file did not exist before, otherwise `false`. @discardableResult static func replaceFileContents( at path: URL, with contents: () throws -> Data, isDryRun: Bool - ) throws -> Bool { + ) throws -> (didChange: Bool, didCreate: Bool) { let data = try contents() let didChange: Bool + var didCreate = false if FileManager.default.fileExists(atPath: path.path) { let existingData = try? Data(contentsOf: path) didChange = existingData != data - if didChange { - print("File \(path.lastPathComponent) will be overwritten.") - } else { - print("File \(path.lastPathComponent) will remain unchanged.") - } } else { - print("File \(path.lastPathComponent) does not exist.\nCreating new file...") didChange = true + didCreate = true } if didChange { - if isDryRun { - print("Writing data to \(path.lastPathComponent)...") - } else { + if !isDryRun { try data.write(to: path) } } - return didChange + return (didChange, didCreate) } } From 26804ba84daadda1e577d69c24b5919786b3252f Mon Sep 17 00:00:00 2001 From: Denil Chungath Date: Wed, 19 Jul 2023 23:39:06 +0530 Subject: [PATCH 4/6] resolve comments --- .../runGenerator.swift | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/Sources/swift-openapi-generator/runGenerator.swift b/Sources/swift-openapi-generator/runGenerator.swift index 68cd4cbd..4eed5dbe 100644 --- a/Sources/swift-openapi-generator/runGenerator.swift +++ b/Sources/swift-openapi-generator/runGenerator.swift @@ -81,7 +81,7 @@ extension _Tool { isDryRun: Bool, diagnostics: any DiagnosticCollector ) throws { - let result = try replaceFileContents( + try replaceFileContents( at: outputFilePath, with: { let output = try _OpenAPIGeneratorCore.runGenerator( @@ -93,14 +93,6 @@ extension _Tool { }, isDryRun: isDryRun ) - if result.didCreate && isDryRun { - print("File \(outputFilePath.lastPathComponent) does not exist.\nCreating new file...") - } - if result.didChange { - print("File \(outputFilePath.lastPathComponent) will be overwritten.") - } else { - print("File \(outputFilePath.lastPathComponent) will remain unchanged.") - } } /// Evaluates a closure to generate file data and writes the data to disk @@ -112,30 +104,26 @@ extension _Tool { /// - 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: A tuple of two booleans didChange and didCreate. - /// The former will be `true` if the generated contents changed, otherwise `false` and - /// the latter will be `true` if the file did not exist before, otherwise `false`. - @discardableResult static func replaceFileContents( at path: URL, with contents: () throws -> Data, isDryRun: Bool - ) throws -> (didChange: Bool, didCreate: Bool) { + ) throws { let data = try contents() let didChange: Bool - var didCreate = false if FileManager.default.fileExists(atPath: path.path) { let existingData = try? Data(contentsOf: path) didChange = existingData != data } else { didChange = true - didCreate = true } if didChange { + print("File \(path.lastPathComponent) will be overwritten.") if !isDryRun { try data.write(to: path) } + } else { + print("File \(path.lastPathComponent) will remain unchanged.") } - return (didChange, didCreate) } } From a5a83cbc48adb6a2582253d24c94dd97dd5d880f Mon Sep 17 00:00:00 2001 From: Denil Chungath Date: Sat, 22 Jul 2023 15:03:46 +0530 Subject: [PATCH 5/6] change flag name --- Sources/swift-openapi-generator/GenerateCommand.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/swift-openapi-generator/GenerateCommand.swift b/Sources/swift-openapi-generator/GenerateCommand.swift index 136c7b78..3f72aa28 100644 --- a/Sources/swift-openapi-generator/GenerateCommand.swift +++ b/Sources/swift-openapi-generator/GenerateCommand.swift @@ -49,8 +49,8 @@ struct _GenerateCommand: AsyncParsableCommand { var pluginSource: PluginSource? @Flag( - help: - "Simulate the command and print the operations, without actually affecting the file system." + name: .customLong("dry-run"), + help: "Simulate the command and print the operations, without actually affecting the file system." ) var isDryRun: Bool = false From 3a2d117105477b451dc5f4d53643fad125520fcc Mon Sep 17 00:00:00 2001 From: Denil Chungath Date: Sat, 22 Jul 2023 15:07:20 +0530 Subject: [PATCH 6/6] Fix indentation --- Sources/swift-openapi-generator/runGenerator.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/swift-openapi-generator/runGenerator.swift b/Sources/swift-openapi-generator/runGenerator.swift index a28decd3..0ff5b188 100644 --- a/Sources/swift-openapi-generator/runGenerator.swift +++ b/Sources/swift-openapi-generator/runGenerator.swift @@ -94,12 +94,12 @@ extension _Tool { inDirectory: outputDirectory, fileName: outputFileName, with: { - let output = try _OpenAPIGeneratorCore.runGenerator( - input: .init(absolutePath: doc, contents: docData), - config: config, - diagnostics: diagnostics - ) - return output.contents + let output = try _OpenAPIGeneratorCore.runGenerator( + input: .init(absolutePath: doc, contents: docData), + config: config, + diagnostics: diagnostics + ) + return output.contents }, isDryRun: isDryRun )