feat: Updates logging configuration.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import ConfigurationClient
|
||||
import CustomDump
|
||||
import Dependencies
|
||||
import FileClient
|
||||
import Foundation
|
||||
@@ -7,6 +8,40 @@ import GitClient
|
||||
@_spi(Internal)
|
||||
public extension CliClient.SharedOptions {
|
||||
|
||||
/// All cli-client calls should run through this, it set's up logging,
|
||||
/// loads configuration, and generates the current version based on the
|
||||
/// configuration.
|
||||
@discardableResult
|
||||
func run(
|
||||
_ operation: (CurrentVersionContainer) async throws -> Void
|
||||
) async rethrows -> String {
|
||||
try await loggingOptions.withLogger {
|
||||
// Load the default configuration, if it exists.
|
||||
try await withMergedConfiguration { configuration in
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
var configurationString = ""
|
||||
customDump(configuration, to: &configurationString)
|
||||
logger.trace("\nConfiguration: \(configurationString)")
|
||||
|
||||
// This will fail if the target url is not set properly.
|
||||
let targetUrl = try configuration.targetUrl(gitDirectory: gitDirectory)
|
||||
logger.debug("Target: \(targetUrl.cleanFilePath)")
|
||||
|
||||
// Perform the operation, which generates the new version and writes it.
|
||||
try await operation(
|
||||
configuration.currentVersion(
|
||||
targetUrl: targetUrl,
|
||||
gitDirectory: gitDirectory
|
||||
)
|
||||
)
|
||||
|
||||
// Return the file path we wrote the version to.
|
||||
return targetUrl.cleanFilePath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merges any configuration set via the passed in options.
|
||||
@discardableResult
|
||||
func withMergedConfiguration<T>(
|
||||
@@ -26,37 +61,6 @@ public extension CliClient.SharedOptions {
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func run(
|
||||
_ operation: (CurrentVersionContainer) async throws -> Void
|
||||
) async rethrows -> String {
|
||||
try await withDependencies {
|
||||
$0.logger.logLevel = logLevel
|
||||
} operation: {
|
||||
// Load the default configuration, if it exists.
|
||||
try await withMergedConfiguration { configuration in
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
logger.debug("Configuration: \(configuration)")
|
||||
|
||||
// This will fail if the target url is not set properly.
|
||||
let targetUrl = try configuration.targetUrl(gitDirectory: gitDirectory)
|
||||
logger.debug("Target: \(targetUrl.cleanFilePath)")
|
||||
|
||||
// Perform the operation, which generates the new version and writes it.
|
||||
try await operation(
|
||||
configuration.currentVersion(
|
||||
targetUrl: targetUrl,
|
||||
gitDirectory: gitDirectory
|
||||
)
|
||||
)
|
||||
|
||||
// Return the file path we wrote the version to.
|
||||
return targetUrl.cleanFilePath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func write(_ string: String, to url: URL) async throws {
|
||||
@Dependency(\.fileClient) var fileClient
|
||||
@Dependency(\.logger) var logger
|
||||
@@ -64,7 +68,7 @@ public extension CliClient.SharedOptions {
|
||||
try await fileClient.write(string: string, to: url)
|
||||
} else {
|
||||
logger.debug("Skipping, due to dry-run being passed.")
|
||||
logger.debug("\(string)")
|
||||
logger.debug("\n\(string)\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,15 @@ extension Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -29,7 +38,7 @@ extension Configuration.Branch {
|
||||
extension Configuration.SemVar {
|
||||
func merging(_ other: Self?) -> Self {
|
||||
.init(
|
||||
preRelease: other?.preRelease ?? preRelease,
|
||||
preRelease: preRelease?.merging(other?.preRelease),
|
||||
requireExistingFile: other?.requireExistingFile ?? requireExistingFile,
|
||||
requireExistingSemVar: other?.requireExistingSemVar ?? requireExistingSemVar
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ import ConfigurationClient
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import GitClient
|
||||
import ShellClient
|
||||
|
||||
extension Configuration {
|
||||
func targetUrl(gitDirectory: String?) throws -> URL {
|
||||
@@ -58,26 +59,46 @@ extension Configuration.Target {
|
||||
}
|
||||
|
||||
extension GitClient {
|
||||
func version(branch: Configuration.Branch, gitDirectory: String?) async throws -> String {
|
||||
func version(includeCommitSha: Bool, gitDirectory: String?) async throws -> String {
|
||||
@Dependency(\.gitClient) var gitClient
|
||||
|
||||
return try await gitClient.version(.init(
|
||||
gitDirectory: gitDirectory,
|
||||
style: .branch(commitSha: branch.includeCommitSha)
|
||||
style: .branch(commitSha: includeCommitSha)
|
||||
)).description
|
||||
}
|
||||
}
|
||||
|
||||
extension Configuration.PreRelease {
|
||||
|
||||
func preReleaseString(gitDirectory: String?) async throws -> String {
|
||||
// FIX: This needs to handle the pre-release type appropriatly.
|
||||
func preReleaseString(gitDirectory: String?) async throws -> PreReleaseString? {
|
||||
guard let strategy else { return nil }
|
||||
|
||||
@Dependency(\.asyncShellClient) var asyncShellClient
|
||||
@Dependency(\.gitClient) var gitClient
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
let preReleaseString: String
|
||||
var preReleaseString: String
|
||||
var suffix = true
|
||||
var allowsPrefix = true
|
||||
|
||||
if let branch = strategy?.branch {
|
||||
preReleaseString = try await gitClient.version(branch: branch, gitDirectory: gitDirectory)
|
||||
} else {
|
||||
switch strategy {
|
||||
case let .branch(includeCommitSha: includeCommitSha):
|
||||
logger.trace("Branch pre-release strategy, includeCommitSha: \(includeCommitSha).")
|
||||
preReleaseString = try await gitClient.version(
|
||||
includeCommitSha: includeCommitSha,
|
||||
gitDirectory: gitDirectory
|
||||
)
|
||||
case let .command(arguments: arguments):
|
||||
logger.trace("Command pre-release strategy, arguments: \(arguments).")
|
||||
// TODO: What to do with allows prefix? Need a configuration setting for commands.
|
||||
preReleaseString = try await asyncShellClient.background(.init(arguments))
|
||||
case .gitTag:
|
||||
logger.trace("Git tag pre-release strategy.")
|
||||
logger.trace("This will ignore any set prefix.")
|
||||
suffix = false
|
||||
allowsPrefix = false
|
||||
preReleaseString = try await gitClient.version(.init(
|
||||
gitDirectory: gitDirectory,
|
||||
style: .tag(exactMatch: false)
|
||||
@@ -85,9 +106,22 @@ extension Configuration.PreRelease {
|
||||
}
|
||||
|
||||
if let prefix {
|
||||
return "\(prefix)-\(preReleaseString)"
|
||||
if allowsPrefix {
|
||||
preReleaseString = "\(prefix)-\(preReleaseString)"
|
||||
} else {
|
||||
logger.warning("Found prefix, but pre-release strategy may not work properly, ignoring prefix.")
|
||||
}
|
||||
}
|
||||
return preReleaseString
|
||||
|
||||
guard suffix else { return .semvar(preReleaseString) }
|
||||
return .suffix(preReleaseString)
|
||||
|
||||
// return preReleaseString
|
||||
}
|
||||
|
||||
enum PreReleaseString: Sendable {
|
||||
case suffix(String)
|
||||
case semvar(String)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,15 +131,28 @@ public extension Configuration.SemVar {
|
||||
private func applyingPreRelease(_ semVar: SemVar, _ gitDirectory: String?) async throws -> SemVar {
|
||||
@Dependency(\.logger) var logger
|
||||
logger.trace("Start apply pre-release to: \(semVar)")
|
||||
guard let preReleaseStrategy = self.preRelease else {
|
||||
|
||||
guard let preReleaseStrategy = self.preRelease,
|
||||
let preRelease = try await preReleaseStrategy.preReleaseString(gitDirectory: gitDirectory)
|
||||
else {
|
||||
logger.trace("No pre-release strategy, returning original semvar.")
|
||||
return semVar
|
||||
}
|
||||
|
||||
let preRelease = try await preReleaseStrategy.preReleaseString(gitDirectory: gitDirectory)
|
||||
// let preRelease = try await preReleaseStrategy.preReleaseString(gitDirectory: gitDirectory)
|
||||
logger.trace("Pre-release string: \(preRelease)")
|
||||
|
||||
return semVar.applyingPreRelease(preRelease)
|
||||
switch preRelease {
|
||||
case let .suffix(string):
|
||||
return semVar.applyingPreRelease(string)
|
||||
case let .semvar(string):
|
||||
guard let semvar = SemVar(string: string) else {
|
||||
throw CliClientError.preReleaseParsingError(string)
|
||||
}
|
||||
return semvar
|
||||
}
|
||||
|
||||
// return semVar.applyingPreRelease(preRelease)
|
||||
}
|
||||
|
||||
func currentVersion(file: URL, gitDirectory: String? = nil) async throws -> CurrentVersionContainer.Version {
|
||||
@@ -181,7 +228,7 @@ extension Configuration.VersionStrategy {
|
||||
return try await .init(
|
||||
targetUrl: targetUrl,
|
||||
version: .string(
|
||||
gitClient.version(branch: branch, gitDirectory: gitDirectory)
|
||||
gitClient.version(includeCommitSha: branch.includeCommitSha, gitDirectory: gitDirectory)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -28,13 +28,13 @@ public extension FileClient {
|
||||
logger.debug("Version line: \(versionLine)")
|
||||
|
||||
let isOptional = versionLine.contains("String?")
|
||||
logger.debug("Uses optional: \(isOptional)")
|
||||
logger.trace("Uses optional: \(isOptional)")
|
||||
|
||||
let versionString = versionLine.split(separator: "let VERSION: \(isOptional ? "String?" : "String") = ").last
|
||||
guard let versionString else {
|
||||
throw CliClientError.failedToParseVersionFile
|
||||
}
|
||||
logger.debug("Parsed version string: \(versionString)")
|
||||
logger.trace("Parsed version string: \(versionString)")
|
||||
return (String(versionString), isOptional)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import Logging
|
||||
|
||||
// TODO: Move.
|
||||
@_spi(Internal)
|
||||
public extension Logger.Level {
|
||||
|
||||
init(verbose: Int) {
|
||||
switch verbose {
|
||||
case 1: self = .warning
|
||||
case 2: self = .debug
|
||||
case 3...: self = .trace
|
||||
default: self = .info
|
||||
}
|
||||
}
|
||||
}
|
||||
172
Sources/CliClient/Internal/Logging.swift
Normal file
172
Sources/CliClient/Internal/Logging.swift
Normal file
@@ -0,0 +1,172 @@
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import Logging
|
||||
import LoggingFormatAndPipe
|
||||
import Rainbow
|
||||
import ShellClient
|
||||
|
||||
extension String {
|
||||
var orange: Self {
|
||||
bit24(255, 165, 0)
|
||||
}
|
||||
|
||||
var magena: Self {
|
||||
// bit24(186, 85, 211)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
var coloredString: String {
|
||||
switch self {
|
||||
case .info:
|
||||
return "\(self)".cyan
|
||||
case .warning:
|
||||
return "\(self)".orange.bold
|
||||
case .debug:
|
||||
return "\(self)".green
|
||||
case .trace:
|
||||
return "\(self)".yellow
|
||||
case .error:
|
||||
return "\(self)".red.bold
|
||||
default:
|
||||
return "\(self)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LevelFormatter: LoggingFormatAndPipe.Formatter {
|
||||
|
||||
let basic: BasicFormatter
|
||||
|
||||
var timestampFormatter: DateFormatter { basic.timestampFormatter }
|
||||
|
||||
// swiftlint:disable function_parameter_count
|
||||
func processLog(
|
||||
level: Logger.Level,
|
||||
message: Logger.Message,
|
||||
prettyMetadata: String?,
|
||||
file: String,
|
||||
function: String,
|
||||
line: UInt
|
||||
) -> String {
|
||||
let now = Date()
|
||||
|
||||
return basic.format.map { component -> String in
|
||||
return processComponent(
|
||||
component,
|
||||
now: now,
|
||||
level: level,
|
||||
message: message,
|
||||
prettyMetadata: prettyMetadata,
|
||||
file: file,
|
||||
function: function,
|
||||
line: line
|
||||
)
|
||||
}
|
||||
.filter { $0.count > 0 }
|
||||
.joined(separator: basic.separator ?? "")
|
||||
}
|
||||
|
||||
public func processComponent(
|
||||
_ component: LogComponent,
|
||||
now: Date,
|
||||
level: Logger.Level,
|
||||
message: Logger.Message,
|
||||
prettyMetadata: String?,
|
||||
file: String,
|
||||
function: String,
|
||||
line: UInt
|
||||
) -> String {
|
||||
switch component {
|
||||
case .level:
|
||||
let maxLen = "\(Logger.Level.warning)".count
|
||||
let paddingCount = (maxLen - "\(level)".count) / 2
|
||||
var padding = ""
|
||||
for _ in 0 ... paddingCount {
|
||||
padding += " "
|
||||
}
|
||||
return "\(padding)\(level.coloredString)\(padding)"
|
||||
case let .group(components):
|
||||
return components.map { component -> String in
|
||||
self.processComponent(
|
||||
component,
|
||||
now: now,
|
||||
level: level,
|
||||
message: message,
|
||||
prettyMetadata: prettyMetadata,
|
||||
file: file,
|
||||
function: function,
|
||||
line: line
|
||||
)
|
||||
}.joined()
|
||||
case .message:
|
||||
return basic.processComponent(
|
||||
component,
|
||||
now: now,
|
||||
level: level,
|
||||
message: message,
|
||||
prettyMetadata: prettyMetadata,
|
||||
file: file,
|
||||
function: function,
|
||||
line: line
|
||||
).italic
|
||||
default:
|
||||
return basic.processComponent(
|
||||
component,
|
||||
now: now,
|
||||
level: level,
|
||||
message: message,
|
||||
prettyMetadata: prettyMetadata,
|
||||
file: file,
|
||||
function: function,
|
||||
line: line
|
||||
)
|
||||
}
|
||||
}
|
||||
// swiftlint:enable function_parameter_count
|
||||
|
||||
}
|
||||
|
||||
extension CliClient.LoggingOptions {
|
||||
|
||||
func makeLogger() -> Logger {
|
||||
let formatters: [LogComponent] = [
|
||||
.text(executableName.magena),
|
||||
.text(command.blue),
|
||||
.level,
|
||||
.group([
|
||||
.text("-> "),
|
||||
.message
|
||||
])
|
||||
]
|
||||
return Logger(label: executableName) { _ in
|
||||
LoggingFormatAndPipe.Handler(
|
||||
formatter: LevelFormatter(basic: BasicFormatter(
|
||||
formatters,
|
||||
separator: " | "
|
||||
)),
|
||||
pipe: LoggerTextOutputStreamPipe.standardOutput
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user