feat: Begins work on supporting toml for configuration.

This commit is contained in:
2024-12-09 17:00:52 -05:00
parent a8d35fed37
commit 87390c4b63
14 changed files with 354 additions and 196 deletions

View File

@@ -3,6 +3,8 @@ import DependenciesMacros
import Foundation
import ShellClient
// TODO: Drop support for non-json configuration.
public extension DependencyValues {
var cliClient: CliClient {
get { self[CliClient.self] }
@@ -38,6 +40,7 @@ public struct CliClient: Sendable {
extension CliClient: DependencyKey {
// swiftlint:disable function_body_length
public static func live(
decoder: JSONDecoder = .init(),
encoder: JSONEncoder = .init(),
@@ -55,9 +58,10 @@ extension CliClient: DependencyKey {
var env = env
logger.trace("Loading configuration from: \(url)")
try fileClient.loadFile(url, &env, decoder)
return try .fromEnv(env)
guard let config = try fileClient.loadFile(url, &env, decoder) else {
return try .fromEnv(env)
}
return config
} runCommand: { args, quiet, shell in
@Dependency(\.asyncShellClient) var shellClient
@@ -105,6 +109,8 @@ extension CliClient: DependencyKey {
}
}
// swiftlint:enable function_body_length
public static var liveValue: CliClient {
.live(env: ProcessInfo.processInfo.environment)
}

View File

@@ -2,8 +2,105 @@ import Dependencies
import Foundation
import ShellClient
/// Represents the configuration.
public struct Configuration: Codable, Sendable {
public struct Configuration2: Codable, Equatable, Sendable {
public let playbook: Playbook
public let template: Template
public let vault: Vault
public init(
playbook: Playbook,
template: Template,
vault: Vault
) {
self.playbook = playbook
self.template = template
self.vault = vault
}
public static var mock: Self {
.init(playbook: .mock, template: .mock, vault: .mock)
}
public struct Playbook: Codable, Equatable, Sendable {
public let directory: String?
public let inventory: String?
public let args: [String]?
public let useVaultArgs: Bool
public init(
directory: String? = nil,
inventory: String? = nil,
args: [String]? = nil,
useVaultArgs: Bool = false
) {
self.directory = directory
self.inventory = inventory
self.args = args
self.useVaultArgs = useVaultArgs
}
public static var mock: Self {
.init(
directory: "/path/to/local/playbook-directory",
inventory: "/path/to/local/inventory.ini",
args: [],
useVaultArgs: true
)
}
}
public struct Template: Codable, Equatable, Sendable {
let url: String?
let version: String?
let directory: String?
public init(
url: String? = nil,
version: String? = nil,
directory: String? = nil
) {
self.url = url
self.version = version
self.directory = directory
}
public static var mock: Self {
.init(
url: "https://git.example.com/consult-template.git",
version: "main",
directory: "/path/to/local/template-directory"
)
}
}
public struct Vault: Codable, Equatable, Sendable {
public let args: [String]?
public let encryptId: String?
public init(
args: [String]? = nil,
encryptId: String? = nil
) {
self.args = args
self.encryptId = encryptId
}
public static var mock: Self {
.init(
args: [
"--vault-id=myId@$SCRIPTS/vault-gopass-client"
],
encryptId: "myId"
)
}
}
}
/// Represents the configurable items for the command line tool.
///
///
public struct Configuration: Codable, Equatable, Sendable {
public let playbookDir: String?
public let inventoryPath: String?

View File

@@ -13,13 +13,30 @@ public extension DependencyValues {
@_spi(Internal)
@DependencyClient
public struct FileClient: Sendable {
public var loadFile: @Sendable (URL, inout [String: String], JSONDecoder) throws -> Void
/// Loads a file at the given path into the environment unless it can decode it as json,
/// at which point it will return the decoded file contents as a ``Configuration`` item.
public var loadFile: @Sendable (URL, inout [String: String], JSONDecoder) throws -> Configuration?
/// Returns the user's home directory path.
public var homeDir: @Sendable () -> URL = { URL(string: "~/")! }
/// Check if a path is a directory.
public var isDirectory: @Sendable (URL) -> Bool = { _ in false }
/// Check if a path is a readable.
public var isReadable: @Sendable (URL) -> Bool = { _ in false }
/// Check if a file exists at the path.
public var fileExists: @Sendable (String) -> Bool = { _ in false }
public var findVaultFileInCurrentDirectory: @Sendable () throws -> URL?
public var findVaultFile: @Sendable (String) throws -> URL?
/// Write data to a file.
public var write: @Sendable (String, Data) throws -> Void
public func findVaultFileInCurrentDirectory() throws -> URL? {
try findVaultFile(".")
}
}
@_spi(Internal)
@@ -34,7 +51,7 @@ extension FileClient: DependencyKey {
isDirectory: { client.isDirectory(url: $0) },
isReadable: { client.isReadable(url: $0) },
fileExists: { client.fileExists(at: $0) },
findVaultFileInCurrentDirectory: { try client.findVaultFileInCurrentDirectory() },
findVaultFile: { try client.findVaultFile(in: $0) },
write: { path, data in
try data.write(to: URL(filePath: path))
}
@@ -68,8 +85,11 @@ private struct LiveFileClient: @unchecked Sendable {
fileManager.fileExists(atPath: path)
}
func findVaultFileInCurrentDirectory() throws -> URL? {
let urls = try fileManager.contentsOfDirectory(at: URL(filePath: "."), includingPropertiesForKeys: nil)
// func findVaultFileInCurrentDirectory() throws -> URL? {
func findVaultFile(in filePath: String) throws -> URL? {
let urls = try fileManager
.contentsOfDirectory(at: URL(filePath: filePath), includingPropertiesForKeys: nil)
if let vault = urls.firstVaultFile {
return vault
@@ -93,16 +113,18 @@ private struct LiveFileClient: @unchecked Sendable {
return nil
}
func loadFile(at url: URL, into env: inout [String: String], decoder: JSONDecoder) throws {
func loadFile(
at url: URL,
into env: inout [String: String],
decoder: JSONDecoder
) throws -> Configuration? {
@Dependency(\.logger) var logger
logger.trace("Begin load file for: \(path(for: url))")
if url.absoluteString.hasSuffix(".json") {
// Handle json file.
let data = try Data(contentsOf: url)
let dict = (try? decoder.decode([String: String].self, from: data)) ?? [:]
env.merge(dict, uniquingKeysWith: { $1 })
return
return try decoder.decode(Configuration.self, from: data)
}
let string = try String(contentsOfFile: path(for: url), encoding: .utf8)
@@ -126,6 +148,7 @@ private struct LiveFileClient: @unchecked Sendable {
env[String(splitLine[0])] = String(splitLine[1])
}
}
return nil
}
}

View File

@@ -57,7 +57,8 @@ public func findConfigurationFiles(
throw ConfigurationError.configurationNotFound
}
func path(for url: URL) -> String {
@_spi(Internal)
public func path(for url: URL) -> String {
url.absoluteString.replacing("file://", with: "")
}