Files
swift-hpa/Sources/CliClient/FileClient.swift

104 lines
3.2 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 {
public var contentsOfDirectory: @Sendable (URL) throws -> [URL]
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 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(
contentsOfDirectory: { try client.contentsOfDirectory(url: $0) },
loadFile: { try client.loadFile(at: $0, into: &$1, decoder: $2) },
homeDir: { client.homeDir },
isDirectory: { client.isDirectory(url: $0) },
isReadable: { client.isReadable(url: $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 contentsOfDirectory(url: URL) throws -> [URL] {
try fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
}
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.contains(".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])
}
}
}
}