feat: Moves logging extensions into it's own module, moves configuration merging into config-client, fixes merging bug.
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,18 +47,32 @@ public extension CliClient.SharedOptions {
|
||||
func withMergedConfiguration<T>(
|
||||
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 {
|
||||
|
||||
@@ -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<T>(
|
||||
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<T>(
|
||||
// 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)
|
||||
// }
|
||||
|
||||
76
Sources/ConfigurationClient/Configuration+merge.swift
Normal file
76
Sources/ConfigurationClient/Configuration+merge.swift
Normal file
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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<T>(
|
||||
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) }
|
||||
|
||||
@@ -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<T>(_ operation: () async throws -> T) async rethrows -> T {
|
||||
try await withDependencies {
|
||||
$0.logger = makeLogger()
|
||||
$0.logger.logLevel = .init(verbose: verbose)
|
||||
} operation: {
|
||||
try await operation()
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Sources/LoggingExtensions/LogLevel+verbose.swift
Normal file
13
Sources/LoggingExtensions/LogLevel+verbose.swift
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Sources/LoggingExtensions/LoggingOptions.swift
Normal file
28
Sources/LoggingExtensions/LoggingOptions.swift
Normal file
@@ -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<T>(_ operation: () async throws -> T) async rethrows -> T {
|
||||
try await withDependencies {
|
||||
$0.logger = makeLogger()
|
||||
$0.logger.logLevel = .init(verbose: verbose)
|
||||
} operation: {
|
||||
try await operation()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user