feat: Begin working on configuration client.
This commit is contained in:
38
Sources/ConfigurationClient/Coders.swift
Normal file
38
Sources/ConfigurationClient/Coders.swift
Normal file
@@ -0,0 +1,38 @@
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
import Foundation
|
||||
import TOMLKit
|
||||
|
||||
public extension DependencyValues {
|
||||
var coders: Coders {
|
||||
get { self[Coders.self] }
|
||||
set { self[Coders.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
@DependencyClient
|
||||
public struct Coders: Sendable {
|
||||
public var jsonDecoder: @Sendable () -> JSONDecoder = { .init() }
|
||||
public var jsonEncoder: @Sendable () -> JSONEncoder = { .init() }
|
||||
public var tomlDecoder: @Sendable () -> TOMLDecoder = { .init() }
|
||||
public var tomlEncoder: @Sendable () -> TOMLEncoder = { .init() }
|
||||
}
|
||||
|
||||
extension Coders: DependencyKey {
|
||||
public static var testValue: Coders {
|
||||
.init(
|
||||
jsonDecoder: { .init() },
|
||||
jsonEncoder: { defaultJsonEncoder },
|
||||
tomlDecoder: { .init() },
|
||||
tomlEncoder: { .init() }
|
||||
)
|
||||
}
|
||||
|
||||
public static var liveValue: Coders { .testValue }
|
||||
|
||||
private static let defaultJsonEncoder: JSONEncoder = {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
|
||||
return encoder
|
||||
}()
|
||||
}
|
||||
75
Sources/ConfigurationClient/Configuration.swift
Normal file
75
Sources/ConfigurationClient/Configuration.swift
Normal file
@@ -0,0 +1,75 @@
|
||||
import Foundation
|
||||
|
||||
public struct Configuration: Codable, Sendable {
|
||||
public let target: Target?
|
||||
public let strategy: VersionStrategy?
|
||||
|
||||
public init(
|
||||
target: Target? = nil,
|
||||
strategy: VersionStrategy? = .semvar()
|
||||
) {
|
||||
self.target = target
|
||||
self.strategy = strategy
|
||||
}
|
||||
}
|
||||
|
||||
public extension Configuration {
|
||||
|
||||
enum VersionStrategy: Codable, Equatable, Sendable {
|
||||
case branch(Branch = .init())
|
||||
case semvar(SemVar = .init())
|
||||
|
||||
public struct Branch: Codable, Equatable, Sendable {
|
||||
let includeCommitSha: Bool
|
||||
|
||||
public init(includeCommitSha: Bool = true) {
|
||||
self.includeCommitSha = includeCommitSha
|
||||
}
|
||||
}
|
||||
|
||||
public enum PreReleaseStrategy: Codable, Equatable, Sendable {
|
||||
/// Use output of tag, with branch and commit sha.
|
||||
case branch(Branch = .init())
|
||||
|
||||
/// Provide a custom pre-release tag.
|
||||
indirect case custom(String, PreReleaseStrategy? = nil)
|
||||
|
||||
/// Use the output of `git describe --tags`
|
||||
case gitTag
|
||||
}
|
||||
|
||||
public struct SemVar: Codable, Equatable, Sendable {
|
||||
let preReleaseStrategy: PreReleaseStrategy?
|
||||
let requireExistingFile: Bool
|
||||
let requireExistingSemVar: Bool
|
||||
|
||||
public init(
|
||||
preReleaseStrategy: PreReleaseStrategy? = nil,
|
||||
requireExistingFile: Bool = true,
|
||||
requireExistingSemVar: Bool = true
|
||||
) {
|
||||
self.preReleaseStrategy = preReleaseStrategy
|
||||
self.requireExistingFile = requireExistingFile
|
||||
self.requireExistingSemVar = requireExistingSemVar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Target: Codable, Equatable, Sendable {
|
||||
case path(String)
|
||||
case module(Module)
|
||||
|
||||
public struct Module: Codable, Equatable, Sendable {
|
||||
public let name: String
|
||||
public let fileName: String
|
||||
|
||||
public init(
|
||||
_ name: String,
|
||||
fileName: String = "Version.swift"
|
||||
) {
|
||||
self.name = name
|
||||
self.fileName = fileName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
72
Sources/ConfigurationClient/ConfigurationClient.swift
Normal file
72
Sources/ConfigurationClient/ConfigurationClient.swift
Normal file
@@ -0,0 +1,72 @@
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
import FileClient
|
||||
import Foundation
|
||||
|
||||
public extension DependencyValues {
|
||||
var configurationClient: ConfigurationClient {
|
||||
get { self[ConfigurationClient.self] }
|
||||
set { self[ConfigurationClient.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
@DependencyClient
|
||||
public struct ConfigurationClient: Sendable {
|
||||
public var find: @Sendable (URL?) async throws -> ConfigruationFile?
|
||||
public var load: @Sendable (ConfigruationFile) async throws -> Configuration
|
||||
public var write: @Sendable (Configuration, ConfigruationFile) async throws -> Void
|
||||
|
||||
public func findAndLoad(_ url: URL? = nil) async throws -> Configuration {
|
||||
guard let url = try await find(url) else {
|
||||
throw ConfigurationClientError.configurationNotFound
|
||||
}
|
||||
return try await load(url)
|
||||
}
|
||||
}
|
||||
|
||||
extension ConfigurationClient: DependencyKey {
|
||||
public static let testValue: ConfigurationClient = Self()
|
||||
|
||||
public static var liveValue: ConfigurationClient {
|
||||
.init(
|
||||
find: { try await findConfiguration($0) },
|
||||
load: { try await $0.load() ?? .init() },
|
||||
write: { try await $1.write($0) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func findConfiguration(_ url: URL?) async throws -> ConfigruationFile? {
|
||||
@Dependency(\.fileClient) var fileClient
|
||||
|
||||
var url: URL! = url
|
||||
if url == nil {
|
||||
url = try await URL(filePath: fileClient.currentDirectory())
|
||||
}
|
||||
|
||||
// Check if url is a valid configuration url.
|
||||
var configurationFile = ConfigruationFile(url: url)
|
||||
if let configurationFile { return configurationFile }
|
||||
|
||||
guard try await fileClient.isDirectory(url.cleanFilePath) else {
|
||||
throw ConfigurationClientError.invalidConfigurationDirectory(path: url.cleanFilePath)
|
||||
}
|
||||
|
||||
// Check for toml file.
|
||||
let tomlUrl = url.appending(path: "\(ConfigurationClient.Constants.defaultFileNameWithoutExtension).toml")
|
||||
configurationFile = ConfigruationFile(url: tomlUrl)
|
||||
if let configurationFile { return configurationFile }
|
||||
|
||||
// Check for json file.
|
||||
let jsonUrl = url.appending(path: "\(ConfigurationClient.Constants.defaultFileNameWithoutExtension).json")
|
||||
configurationFile = ConfigruationFile(url: jsonUrl)
|
||||
if let configurationFile { return configurationFile }
|
||||
|
||||
// Couldn't find valid configuration file.
|
||||
return nil
|
||||
}
|
||||
|
||||
enum ConfigurationClientError: Error {
|
||||
case configurationNotFound
|
||||
case invalidConfigurationDirectory(path: String)
|
||||
}
|
||||
58
Sources/ConfigurationClient/ConfigurationFile.swift
Normal file
58
Sources/ConfigurationClient/ConfigurationFile.swift
Normal file
@@ -0,0 +1,58 @@
|
||||
import Dependencies
|
||||
import FileClient
|
||||
import Foundation
|
||||
|
||||
public enum ConfigruationFile: Equatable, Sendable {
|
||||
case json(URL)
|
||||
case toml(URL)
|
||||
|
||||
public init?(url: URL) {
|
||||
if url.pathExtension == "toml" {
|
||||
self = .toml(url)
|
||||
} else if url.pathExtension == "json" {
|
||||
self = .json(url)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var url: URL {
|
||||
switch self {
|
||||
case let .json(url): return url
|
||||
case let .toml(url): return url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ConfigruationFile {
|
||||
|
||||
func load() async throws -> Configuration? {
|
||||
@Dependency(\.coders) var coders
|
||||
@Dependency(\.fileClient) var fileClient
|
||||
|
||||
switch self {
|
||||
case .json:
|
||||
let data = try await Data(fileClient.read(url.cleanFilePath).utf8)
|
||||
return try? coders.jsonDecoder().decode(Configuration.self, from: data)
|
||||
case .toml:
|
||||
let string = try await fileClient.read(url.cleanFilePath)
|
||||
return try? coders.tomlDecoder().decode(Configuration.self, from: string)
|
||||
}
|
||||
}
|
||||
|
||||
func write(_ configuration: Configuration) async throws {
|
||||
@Dependency(\.coders) var coders
|
||||
@Dependency(\.fileClient) var fileClient
|
||||
|
||||
let data: Data
|
||||
|
||||
switch self {
|
||||
case .json:
|
||||
data = try coders.jsonEncoder().encode(configuration)
|
||||
case .toml:
|
||||
data = try Data(coders.tomlEncoder().encode(configuration).utf8)
|
||||
}
|
||||
|
||||
try await fileClient.write(data, url)
|
||||
}
|
||||
}
|
||||
7
Sources/ConfigurationClient/Constants.swift
Normal file
7
Sources/ConfigurationClient/Constants.swift
Normal file
@@ -0,0 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
extension ConfigurationClient {
|
||||
enum Constants {
|
||||
static let defaultFileNameWithoutExtension = ".bump-version"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user