Sapling/Sources/SaplingWorkspace/WorkspaceManager.swift

96 lines
3.2 KiB
Swift

import Foundation
import SaplingCore
public protocol WorkspaceManaging: Sendable {
func openWorkspace(at url: URL) async throws -> Workspace
func sampleWorkspace() -> Workspace
}
public final class LocalWorkspaceManager: WorkspaceManaging, @unchecked Sendable {
private let fileManager: FileManager
public init(fileManager: FileManager = .default) {
self.fileManager = fileManager
}
public func openWorkspace(at url: URL) async throws -> Workspace {
let items = try scanItems(at: url, relativeTo: url)
let workspace = Workspace(name: url.lastPathComponent, rootURL: url, items: items)
try SaplingRules.validateWorkspace(workspace)
return workspace
}
public func sampleWorkspace() -> Workspace {
SaplingSampleData.workspace
}
private func scanItems(at url: URL, relativeTo rootURL: URL) throws -> [WorkspaceItem] {
guard let children = try? fileManager.contentsOfDirectory(
at: url,
includingPropertiesForKeys: [.isDirectoryKey],
options: directoryOptions
) else {
return []
}
return try children
.sorted { $0.lastPathComponent.localizedStandardCompare($1.lastPathComponent) == .orderedAscending }
.map { childURL in
let values = try childURL.resourceValues(forKeys: [.isDirectoryKey])
if values.isDirectory == true {
if isGitRepository(at: childURL) {
return .project(try project(at: childURL, relativeTo: rootURL))
}
return .folder(
WorkspaceFolder(
name: childURL.lastPathComponent,
url: childURL,
children: try scanItems(at: childURL, relativeTo: rootURL)
)
)
}
return .file(
WorkspaceFile(
name: childURL.lastPathComponent,
url: childURL,
kind: fileKind(for: childURL)
)
)
}
}
private var directoryOptions: FileManager.DirectoryEnumerationOptions {
[.skipsHiddenFiles, .skipsPackageDescendants]
}
private func isGitRepository(at url: URL) -> Bool {
fileManager.fileExists(atPath: url.appendingPathComponent(".git").path)
}
private func project(at url: URL, relativeTo rootURL: URL) throws -> Project {
let repository = GitRepository(
name: url.lastPathComponent,
rootURL: url,
statusSummary: .unknown
)
return Project(
name: url.lastPathComponent,
repositoryURL: url,
children: try scanItems(at: url, relativeTo: rootURL),
gitRepository: repository
)
}
private func fileKind(for url: URL) -> WorkspaceFileKind {
switch url.pathExtension.lowercased() {
case "md", "markdown":
return .markdown
case "png", "jpg", "jpeg", "gif", "webp", "pdf", "mp3", "mp4":
return .attachment
default:
return .other
}
}
}