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 { public var loadFile: @Sendable (URL, inout [String: String], JSONDecoder) throws -> Void public var homeDir: @Sendable () -> URL = { URL(string: "~/")! } public var isDirectory: @Sendable (URL) -> Bool = { _ in false } public var isReadable: @Sendable (URL) -> Bool = { _ in false } public var fileExists: @Sendable (String) -> Bool = { _ in false } public var write: @Sendable (String, Data) throws -> Void } @_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) }, 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 loadFile(at url: URL, into env: inout [String: String], decoder: JSONDecoder) throws { @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 } 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]) } } } }