160 lines
4.8 KiB
Swift
160 lines
4.8 KiB
Swift
import Dependencies
|
|
import DependenciesMacros
|
|
import Foundation
|
|
|
|
@_spi(Internal)
|
|
public extension DependencyValues {
|
|
var fileClient: FileClient {
|
|
get { self[FileClient.self] }
|
|
set { self[FileClient.self] = newValue }
|
|
}
|
|
}
|
|
|
|
@_spi(Internal)
|
|
@DependencyClient
|
|
public struct FileClient: Sendable {
|
|
/// 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 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)
|
|
extension FileClient: DependencyKey {
|
|
public static let testValue: FileClient = Self()
|
|
|
|
public static func live(fileManager: FileManager = .default) -> Self {
|
|
let client = LiveFileClient(fileManager: fileManager)
|
|
return Self(
|
|
loadFile: { try client.loadFile(at: $0, into: &$1, decoder: $2) },
|
|
homeDir: { client.homeDir },
|
|
isDirectory: { client.isDirectory(url: $0) },
|
|
isReadable: { client.isReadable(url: $0) },
|
|
fileExists: { client.fileExists(at: $0) },
|
|
findVaultFile: { try client.findVaultFile(in: $0) },
|
|
write: { path, data in
|
|
try data.write(to: URL(filePath: path))
|
|
}
|
|
)
|
|
}
|
|
|
|
public static let liveValue = Self.live()
|
|
}
|
|
|
|
private struct LiveFileClient: @unchecked Sendable {
|
|
|
|
private let fileManager: FileManager
|
|
|
|
init(fileManager: FileManager) {
|
|
self.fileManager = fileManager
|
|
}
|
|
|
|
var homeDir: URL { fileManager.homeDirectoryForCurrentUser }
|
|
|
|
func isDirectory(url: URL) -> Bool {
|
|
var isDirectory: ObjCBool = false
|
|
fileManager.fileExists(atPath: path(for: url), isDirectory: &isDirectory)
|
|
return isDirectory.boolValue
|
|
}
|
|
|
|
func isReadable(url: URL) -> Bool {
|
|
fileManager.isReadableFile(atPath: path(for: url))
|
|
}
|
|
|
|
func fileExists(at path: String) -> Bool {
|
|
fileManager.fileExists(atPath: path)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// check for folders that end with "vars" and search those next.
|
|
for folder in urls.filter({ $0.absoluteString.hasSuffix("vars/") }) {
|
|
let files = try fileManager.contentsOfDirectory(at: folder, includingPropertiesForKeys: nil)
|
|
if let vault = files.firstVaultFile {
|
|
return vault
|
|
}
|
|
}
|
|
|
|
// Fallback to check all sub-folders
|
|
for folder in urls.filter({ self.isDirectory(url: $0) && !$0.absoluteString.hasSuffix("vars/") }) {
|
|
let files = try fileManager.contentsOfDirectory(at: folder, includingPropertiesForKeys: nil)
|
|
if let vault = files.firstVaultFile {
|
|
return vault
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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)
|
|
return try decoder.decode(Configuration.self, from: data)
|
|
}
|
|
|
|
let string = try String(contentsOfFile: path(for: url), encoding: .utf8)
|
|
|
|
logger.trace("Loaded file contents: \(string)")
|
|
|
|
let lines = string.split(separator: "\n")
|
|
for line in lines {
|
|
logger.trace("Line: \(line)")
|
|
let strippedLine = line.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let splitLine = strippedLine.split(separator: "=").map {
|
|
$0.replacingOccurrences(of: "\"", with: "")
|
|
}
|
|
logger.trace("Split Line: \(splitLine)")
|
|
guard splitLine.count >= 2 else { continue }
|
|
|
|
if splitLine.count > 2 {
|
|
let rest = splitLine.dropFirst()
|
|
env[String(splitLine[0])] = String(rest.joined(separator: "="))
|
|
} else {
|
|
env[String(splitLine[0])] = String(splitLine[1])
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private extension Array where Element == URL {
|
|
var firstVaultFile: URL? {
|
|
first { $0.absoluteString.hasSuffix("vault.yml") }
|
|
}
|
|
}
|