156 lines
4.4 KiB
Swift
156 lines
4.4 KiB
Swift
import Dependencies
|
|
import DependenciesMacros
|
|
import Foundation
|
|
|
|
public extension DependencyValues {
|
|
/// Represents interactions with the file system.
|
|
///
|
|
var fileClient: FileClient {
|
|
get { self[FileClient.self] }
|
|
set { self[FileClient.self] = newValue }
|
|
}
|
|
}
|
|
|
|
/// Represents interactions with the file system.
|
|
///
|
|
///
|
|
@DependencyClient
|
|
public struct FileClient: Sendable {
|
|
|
|
/// Copy an item from one location to another.
|
|
public var copy: @Sendable (URL, URL) async throws -> Void
|
|
|
|
/// Create a directory at the given location.
|
|
public var createDirectory: @Sendable (URL) async throws -> Void
|
|
|
|
/// Delete the item at the given location.
|
|
public var delete: @Sendable (URL) async throws -> Void
|
|
|
|
/// Check if a file exists at the given location.
|
|
public var fileExists: @Sendable (URL) -> Bool = { _ in true }
|
|
|
|
/// Find an ansible-vault file in the given location, checking up to 1 level deep
|
|
/// in subfolders.
|
|
public var findVaultFile: @Sendable (URL) async throws -> URL?
|
|
|
|
/// Return the user's home directory.
|
|
public var homeDirectory: @Sendable () -> URL = { URL(filePath: "~/") }
|
|
|
|
/// Check if an item is a directory or not.
|
|
public var isDirectory: @Sendable (URL) async throws -> Bool
|
|
|
|
/// Load a file from the given location.
|
|
public var load: @Sendable (URL) async throws -> Data
|
|
|
|
/// Write data to a file at the given location.
|
|
public var write: @Sendable (Data, URL) async throws -> Void
|
|
|
|
/// Find an ansible-vault file in the current directory, checking up to 1 level
|
|
/// deep in subfolders.
|
|
public func findVaultFileInCurrentDirectory() async throws -> URL? {
|
|
try await findVaultFile(URL(filePath: "./"))
|
|
}
|
|
}
|
|
|
|
extension FileClient: DependencyKey {
|
|
public static let testValue: FileClient = Self()
|
|
|
|
public static var liveValue: Self {
|
|
let manager = LiveFileClient()
|
|
return .init(
|
|
copy: { try await manager.copy($0, to: $1) },
|
|
createDirectory: { try await manager.creatDirectory($0) },
|
|
delete: { try await manager.delete($0) },
|
|
fileExists: { manager.fileExists(at: $0) },
|
|
findVaultFile: { try await manager.findVaultFile(in: $0) },
|
|
homeDirectory: { manager.homeDirectory() },
|
|
isDirectory: { manager.isDirectory($0) },
|
|
load: { try await manager.load(from: $0) },
|
|
write: { try await manager.write($0, to: $1) }
|
|
)
|
|
}
|
|
}
|
|
|
|
struct LiveFileClient: Sendable {
|
|
|
|
private var manager: FileManager { FileManager.default }
|
|
|
|
func copy(_ url: URL, to toUrl: URL) async throws {
|
|
try manager.copyItem(at: url, to: toUrl)
|
|
}
|
|
|
|
func creatDirectory(_ url: URL) async throws {
|
|
try manager.createDirectory(at: url, withIntermediateDirectories: true)
|
|
}
|
|
|
|
func delete(_ url: URL) async throws {
|
|
try manager.removeItem(at: url)
|
|
}
|
|
|
|
func fileExists(at url: URL) -> Bool {
|
|
manager.fileExists(atPath: url.cleanFilePath)
|
|
}
|
|
|
|
func findVaultFile(in url: URL) async throws -> URL? {
|
|
guard isDirectory(url) else { return nil }
|
|
let urls = try manager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
|
|
|
|
guard let vault = urls.firstVaultFile else {
|
|
// check subfolders, 1 layer deep.
|
|
let subfolders = urls.filter { isDirectory($0) }
|
|
for folder in subfolders {
|
|
let vault = try manager.contentsOfDirectory(
|
|
at: folder,
|
|
includingPropertiesForKeys: nil
|
|
)
|
|
.firstVaultFile
|
|
|
|
if let vault { return vault }
|
|
}
|
|
// Didn't find a file.
|
|
return nil
|
|
}
|
|
|
|
return vault
|
|
}
|
|
|
|
func homeDirectory() -> URL {
|
|
manager.homeDirectoryForCurrentUser
|
|
}
|
|
|
|
func isDirectory(_ url: URL) -> Bool {
|
|
var isDirectory: ObjCBool = false
|
|
#if os(Linux)
|
|
_ = manager.fileExists(atPath: url.cleanFilePath, isDirectory: &isDirectory)
|
|
#else
|
|
manager.fileExists(atPath: url.cleanFilePath, isDirectory: &isDirectory)
|
|
#endif
|
|
return isDirectory.boolValue
|
|
}
|
|
|
|
func load(from url: URL) async throws -> Data {
|
|
try Data(contentsOf: url)
|
|
}
|
|
|
|
func write(_ data: Data, to url: URL) async throws {
|
|
try data.write(to: url)
|
|
}
|
|
}
|
|
|
|
private extension Array where Element == URL {
|
|
var firstVaultFile: URL? {
|
|
first { url in
|
|
let string = url.absoluteString
|
|
return string.hasSuffix("vault.yml") || string.hasSuffix("vault.yaml")
|
|
}
|
|
}
|
|
}
|
|
|
|
public extension URL {
|
|
var cleanFilePath: String {
|
|
absoluteString
|
|
.replacing("file://", with: "")
|
|
.replacing("/private", with: "")
|
|
}
|
|
}
|