diff --git a/Package.swift b/Package.swift index 289f06f..33013b9 100644 --- a/Package.swift +++ b/Package.swift @@ -13,6 +13,7 @@ let package = Package( .library(name: "ConfigurationClient", targets: ["ConfigurationClient"]), .library(name: "FileClient", targets: ["FileClient"]), .library(name: "GitClient", targets: ["GitClient"]), + .library(name: "LoggingExtensions", targets: ["LoggingExtensions"]), .plugin(name: "BuildWithVersionPlugin", targets: ["BuildWithVersionPlugin"]), .plugin(name: "GenerateVersionPlugin", targets: ["GenerateVersionPlugin"]), .plugin(name: "UpdateVersionPlugin", targets: ["UpdateVersionPlugin"]) @@ -40,6 +41,7 @@ let package = Package( "ConfigurationClient", "FileClient", "GitClient", + "LoggingExtensions", .product(name: "Logging", package: "swift-log"), .product(name: "CustomDump", package: "swift-custom-dump") ] @@ -81,6 +83,13 @@ let package = Package( name: "GitClientTests", dependencies: ["GitClient"] ), + .target( + name: "LoggingExtensions", + dependencies: [ + .product(name: "Dependencies", package: "swift-dependencies"), + .product(name: "ShellClient", package: "swift-shell-client") + ] + ), .target(name: "TestSupport"), .plugin( name: "BuildWithVersionPlugin", diff --git a/Sources/CliClient/CliClient.swift b/Sources/CliClient/CliClient.swift index 61d4c09..1b530a1 100644 --- a/Sources/CliClient/CliClient.swift +++ b/Sources/CliClient/CliClient.swift @@ -4,6 +4,7 @@ import DependenciesMacros import FileClient import Foundation import GitClient +import LoggingExtensions import ShellClient public extension DependencyValues { @@ -34,24 +35,6 @@ public struct CliClient: Sendable { case major, minor, patch, preRelease } - // TODO: Need a quiet option, as default log level is warning, need a way to set it to ignore logs. - public struct LoggingOptions: Equatable, Sendable { - - let command: String - let executableName: String - let verbose: Int - - public init( - executableName: String = "bump-version", - command: String, - verbose: Int - ) { - self.executableName = executableName - self.command = command - self.verbose = verbose - } - } - public struct SharedOptions: Equatable, Sendable { let allowPreReleaseTag: Bool @@ -95,7 +78,9 @@ extension CliClient: DependencyKey { bump: { try await $1.bump($0) }, generate: { try await $0.generate() }, parsedConfiguration: { options in - try await options.withMergedConfiguration { $0 } + try await options.loggingOptions.withLogger { + try await options.withMergedConfiguration { $0 } + } } ) } diff --git a/Sources/CliClient/Internal/CliClient+run.swift b/Sources/CliClient/Internal/CliClient+run.swift index f94fa61..4ebcabb 100644 --- a/Sources/CliClient/Internal/CliClient+run.swift +++ b/Sources/CliClient/Internal/CliClient+run.swift @@ -47,18 +47,32 @@ public extension CliClient.SharedOptions { func withMergedConfiguration( operation: (Configuration) async throws -> T ) async throws -> T { - try await withConfiguration(path: configurationFile) { configuration in - var configuration = configuration - configuration = configuration.mergingTarget(target) + @Dependency(\.configurationClient) var configurationClient + @Dependency(\.logger) var logger - if configuration.strategy?.branch != nil, let branch { - configuration = configuration.mergingStrategy(.branch(branch)) - } else if let semvar { - configuration = configuration.mergingStrategy(.semvar(semvar)) - } + var strategy: Configuration.VersionStrategy? - return try await operation(configuration) + if let branch { + logger.trace("Merging branch strategy.") + strategy = .branch(branch) + } else if let semvar { + logger.trace("Merging semvar strategy.") + var semvarString = "" + customDump(semvar, to: &semvarString) + logger.trace("\(semvarString)") + strategy = .semvar(semvar) } + + let configuration = Configuration( + target: target, + strategy: strategy + ) + + return try await configurationClient.withConfiguration( + path: configurationFile, + merging: configuration, + operation: operation + ) } func write(_ string: String, to url: URL) async throws { diff --git a/Sources/CliClient/Internal/Configuration+merging.swift b/Sources/CliClient/Internal/Configuration+merging.swift index 8fbf6b5..21a1adf 100644 --- a/Sources/CliClient/Internal/Configuration+merging.swift +++ b/Sources/CliClient/Internal/Configuration+merging.swift @@ -1,82 +1,82 @@ -import ConfigurationClient -import Dependencies -import FileClient -import Foundation - -extension Configuration { - - func mergingTarget(_ otherTarget: Configuration.Target?) -> Self { - .init( - target: otherTarget ?? target, - strategy: strategy - ) - } - - func mergingStrategy(_ otherStrategy: Configuration.VersionStrategy?) -> Self { - .init( - target: target, - strategy: strategy?.merging(otherStrategy) - ) - } -} - -extension Configuration.PreRelease { - func merging(_ other: Self?) -> Self { - .init( - prefix: other?.prefix ?? prefix, - strategy: other?.strategy ?? strategy - ) - } -} - -extension Configuration.Branch { - func merging(_ other: Self?) -> Self { - return .init(includeCommitSha: other?.includeCommitSha ?? includeCommitSha) - } -} - -extension Configuration.SemVar { - // TODO: Merge strategy ?? - func merging(_ other: Self?) -> Self { - .init( - allowPreRelease: other?.allowPreRelease ?? allowPreRelease, - preRelease: preRelease?.merging(other?.preRelease), - requireExistingFile: other?.requireExistingFile ?? requireExistingFile, - requireExistingSemVar: other?.requireExistingSemVar ?? requireExistingSemVar, - strategy: other?.strategy ?? strategy - ) - } -} - -extension Configuration.VersionStrategy { - func merging(_ other: Self?) -> Self { - guard let branch else { - guard let semvar else { return self } - return .semvar(semvar.merging(other?.semvar)) - } - return .branch(branch.merging(other?.branch)) - } -} - -extension Configuration { - func merging(_ other: Self?) -> Self { - var output = self - output = output.mergingTarget(other?.target) - output = output.mergingStrategy(other?.strategy) - return output - } -} - -@discardableResult -func withConfiguration( - path: String?, - _ operation: (Configuration) async throws -> T -) async throws -> T { - @Dependency(\.configurationClient) var configurationClient - - let configuration = try await configurationClient.findAndLoad( - path != nil ? URL(filePath: path!) : nil - ) - - return try await operation(configuration) -} +// import ConfigurationClient +// import Dependencies +// import FileClient +// import Foundation +// +// extension Configuration { +// +// func mergingTarget(_ otherTarget: Configuration.Target?) -> Self { +// .init( +// target: otherTarget ?? target, +// strategy: strategy +// ) +// } +// +// func mergingStrategy(_ otherStrategy: Configuration.VersionStrategy?) -> Self { +// .init( +// target: target, +// strategy: strategy?.merging(otherStrategy) +// ) +// } +// } +// +// extension Configuration.PreRelease { +// func merging(_ other: Self?) -> Self { +// .init( +// prefix: other?.prefix ?? prefix, +// strategy: other?.strategy ?? strategy +// ) +// } +// } +// +// extension Configuration.Branch { +// func merging(_ other: Self?) -> Self { +// return .init(includeCommitSha: other?.includeCommitSha ?? includeCommitSha) +// } +// } +// +// extension Configuration.SemVar { +// // TODO: Merge strategy ?? +// func merging(_ other: Self?) -> Self { +// .init( +// allowPreRelease: other?.allowPreRelease ?? allowPreRelease, +// preRelease: preRelease?.merging(other?.preRelease), +// requireExistingFile: other?.requireExistingFile ?? requireExistingFile, +// requireExistingSemVar: other?.requireExistingSemVar ?? requireExistingSemVar, +// strategy: other?.strategy ?? strategy +// ) +// } +// } +// +// extension Configuration.VersionStrategy { +// func merging(_ other: Self?) -> Self { +// guard let branch else { +// guard let semvar else { return self } +// return .semvar(semvar.merging(other?.semvar)) +// } +// return .branch(branch.merging(other?.branch)) +// } +// } +// +// extension Configuration { +// func merging(_ other: Self?) -> Self { +// var output = self +// output = output.mergingTarget(other?.target) +// output = output.mergingStrategy(other?.strategy) +// return output +// } +// } +// +// @discardableResult +// func withConfiguration( +// path: String?, +// _ operation: (Configuration) async throws -> T +// ) async throws -> T { +// @Dependency(\.configurationClient) var configurationClient +// +// let configuration = try await configurationClient.findAndLoad( +// path != nil ? URL(filePath: path!) : nil +// ) +// +// return try await operation(configuration) +// } diff --git a/Sources/ConfigurationClient/Configuration+merge.swift b/Sources/ConfigurationClient/Configuration+merge.swift new file mode 100644 index 0000000..8f355f5 --- /dev/null +++ b/Sources/ConfigurationClient/Configuration+merge.swift @@ -0,0 +1,76 @@ +import Dependencies +import FileClient +import Foundation +import ShellClient + +@_spi(Internal) +public extension Configuration { + + func merging(_ other: Self?) -> Self { + mergingTarget(other?.target).mergingStrategy(other?.strategy) + } + + private func mergingTarget(_ otherTarget: Configuration.Target?) -> Self { + .init( + target: otherTarget ?? target, + strategy: strategy + ) + } + + private func mergingStrategy(_ otherStrategy: Configuration.VersionStrategy?) -> Self { + .init( + target: target, + strategy: strategy?.merging(otherStrategy) + ) + } +} + +@_spi(Internal) +public extension Configuration.PreRelease { + func merging(_ other: Self?) -> Self { + return .init( + prefix: other?.prefix ?? prefix, + strategy: other?.strategy ?? strategy + ) + } +} + +@_spi(Internal) +public extension Configuration.Branch { + func merging(_ other: Self?) -> Self { + return .init(includeCommitSha: other?.includeCommitSha ?? includeCommitSha) + } +} + +@_spi(Internal) +public extension Configuration.SemVar { + func merging(_ other: Self?) -> Self { + .init( + allowPreRelease: other?.allowPreRelease ?? allowPreRelease, + preRelease: preRelease == nil ? other?.preRelease : preRelease!.merging(other?.preRelease), + requireExistingFile: other?.requireExistingFile ?? requireExistingFile, + requireExistingSemVar: other?.requireExistingSemVar ?? requireExistingSemVar, + strategy: other?.strategy ?? strategy + ) + } +} + +@_spi(Internal) +public extension Configuration.VersionStrategy { + func merging(_ other: Self?) -> Self { + if let otherBranch = other?.branch { + guard let branch else { + return .branch(otherBranch) + } + return .branch(branch.merging(otherBranch)) + } + + guard let branch else { + guard let semvar else { + return other ?? self + } + return .semvar(semvar.merging(other?.semvar)) + } + return .branch(branch.merging(other?.branch)) + } +} diff --git a/Sources/ConfigurationClient/ConfigurationClient.swift b/Sources/ConfigurationClient/ConfigurationClient.swift index f2ae1eb..f84db98 100644 --- a/Sources/ConfigurationClient/ConfigurationClient.swift +++ b/Sources/ConfigurationClient/ConfigurationClient.swift @@ -16,6 +16,9 @@ public extension DependencyValues { @DependencyClient public struct ConfigurationClient: Sendable { + /// The default file name for a configuration file. + public var defaultFileName: @Sendable () -> String = { "test.json" } + /// Find a configuration file in the given directory or in current working directory. public var find: @Sendable (URL?) async throws -> URL? @@ -32,6 +35,25 @@ public struct ConfigurationClient: Sendable { } return (try? await load(url)) ?? .default } + + /// Loads configuration from the given path, or searches for the default file and loads it. + /// Optionally merges other configuration, then perform an operation with the loaded configuration. + /// + /// - Parameters: + /// - path: Optional file path of the configuration to load. + /// - other: Optional configuration to merge with the loaded configuration. + /// - operation: The operation to perform with the loaded configuration. + @discardableResult + public func withConfiguration( + path: String?, + merging other: Configuration? = nil, + operation: (Configuration) async throws -> T + ) async throws -> T { + let configuration = try await findAndLoad( + path != nil ? URL(filePath: path!) : nil + ) + return try await operation(configuration.merging(other)) + } } extension ConfigurationClient: DependencyKey { @@ -39,6 +61,7 @@ extension ConfigurationClient: DependencyKey { public static var liveValue: ConfigurationClient { .init( + defaultFileName: { "\(Constants.defaultFileNameWithoutExtension).json" }, find: { try await findConfiguration($0) }, load: { try await loadConfiguration($0) }, write: { try await writeConfiguration($0, to: $1) } diff --git a/Sources/CliClient/Internal/Logging.swift b/Sources/LoggingExtensions/Formatter.swift similarity index 83% rename from Sources/CliClient/Internal/Logging.swift rename to Sources/LoggingExtensions/Formatter.swift index ef521d8..256488c 100644 --- a/Sources/CliClient/Internal/Logging.swift +++ b/Sources/LoggingExtensions/Formatter.swift @@ -5,27 +5,19 @@ import LoggingFormatAndPipe import Rainbow import ShellClient +// MARK: Custom colors. + extension String { var orange: Self { bit24(255, 165, 0) } - var magena: Self { - // bit24(186, 85, 211) + var magenta: Self { bit24(238, 130, 238) } } -@_spi(Internal) -public extension Logger.Level { - - init(verbose: Int) { - switch verbose { - case 1: self = .debug - case 2...: self = .trace - default: self = .warning - } - } +extension Logger.Level { var coloredString: String { switch self { @@ -45,7 +37,7 @@ public extension Logger.Level { } } -struct LevelFormatter: LoggingFormatAndPipe.Formatter { +private struct LevelFormatter: LoggingFormatAndPipe.Formatter { let basic: BasicFormatter @@ -138,11 +130,11 @@ struct LevelFormatter: LoggingFormatAndPipe.Formatter { } -extension CliClient.LoggingOptions { +extension LoggingOptions { func makeLogger() -> Logger { let formatters: [LogComponent] = [ - .text(executableName.magena), + .text(executableName.magenta), .text(command.blue), .level, .group([ @@ -161,12 +153,4 @@ extension CliClient.LoggingOptions { } } - func withLogger(_ operation: () async throws -> T) async rethrows -> T { - try await withDependencies { - $0.logger = makeLogger() - $0.logger.logLevel = .init(verbose: verbose) - } operation: { - try await operation() - } - } } diff --git a/Sources/LoggingExtensions/LogLevel+verbose.swift b/Sources/LoggingExtensions/LogLevel+verbose.swift new file mode 100644 index 0000000..b907636 --- /dev/null +++ b/Sources/LoggingExtensions/LogLevel+verbose.swift @@ -0,0 +1,13 @@ +import Logging + +@_spi(Internal) +public extension Logger.Level { + + init(verbose: Int) { + switch verbose { + case 1: self = .debug + case 2...: self = .trace + default: self = .warning + } + } +} diff --git a/Sources/LoggingExtensions/LoggingOptions.swift b/Sources/LoggingExtensions/LoggingOptions.swift new file mode 100644 index 0000000..db4b929 --- /dev/null +++ b/Sources/LoggingExtensions/LoggingOptions.swift @@ -0,0 +1,28 @@ +import Dependencies +import ShellClient + +public struct LoggingOptions: Equatable, Sendable { + + let command: String + let executableName: String + let verbose: Int + + public init( + executableName: String = "bump-version", + command: String, + verbose: Int + ) { + self.executableName = executableName + self.command = command + self.verbose = verbose + } + + public func withLogger(_ operation: () async throws -> T) async rethrows -> T { + try await withDependencies { + $0.logger = makeLogger() + $0.logger.logLevel = .init(verbose: verbose) + } operation: { + try await operation() + } + } +} diff --git a/Sources/bump-version/Commands/ConfigCommand.swift b/Sources/bump-version/Commands/ConfigCommand.swift index 281adc0..0268519 100644 --- a/Sources/bump-version/Commands/ConfigCommand.swift +++ b/Sources/bump-version/Commands/ConfigCommand.swift @@ -105,6 +105,7 @@ extension ConfigCommand { } } + // TODO: Add verbose. @dynamicMemberLookup struct ConfigCommandOptions: ParsableArguments { @@ -133,7 +134,7 @@ extension ConfigCommand { private extension ConfigCommand.ConfigCommandOptions { func shared(command: String) throws -> CliClient.SharedOptions { - try configOptions.shared(command: command, extraOptions: extraOptions) + try configOptions.shared(command: command, extraOptions: extraOptions, verbose: 2) } func handlePrintJson(_ configuration: Configuration) throws { diff --git a/Sources/bump-version/Helpers/GlobalOptions+run.swift b/Sources/bump-version/Helpers/GlobalOptions+run.swift index 72edb9b..063eab3 100644 --- a/Sources/bump-version/Helpers/GlobalOptions+run.swift +++ b/Sources/bump-version/Helpers/GlobalOptions+run.swift @@ -167,6 +167,7 @@ extension ConfigurationOptions { ) } + // TODO: Need to potentially do something different with passing branch. func shared( command: String, dryRun: Bool = true, @@ -180,7 +181,7 @@ extension ConfigurationOptions { gitDirectory: gitDirectory, loggingOptions: .init(command: command, verbose: verbose), target: target(), - branch: .init(includeCommitSha: commitSha), + branch: semvarOptions.gitTag ? nil : .init(includeCommitSha: commitSha), semvar: semvarOptions(extraOptions: extraOptions), configurationFile: configurationFile )