Exclude items by shell pattern as well as by name
Docs / docs (push) Canceled after 0s
Docs / deploy (push) Canceled after 0s

Patterns such as `.*` or `@*` are matched against an item's own name
with `fnmatch`, in the same places as excluded names: resolving a
tree, extending a watch to a directory that appears later, and
delivering events. Dependents that prune large trees can now skip
whole families of directories without listing each name.
This commit is contained in:
T. R. Bernstein
2026-09-16 11:21:45 +02:00
parent 442053eae2
commit 8af25be549
10 changed files with 154 additions and 22 deletions
+10 -10
View File
@@ -4,28 +4,28 @@ public struct DirectoryResolver {
static let fileManager = FileSystem.shared
public static func resolve(_ paths: String..., excluding itemNames: Set<String> = []) async throws -> [FilePath] {
try await Self.resolve(paths, excluding: itemNames)
try await Self.resolve(paths, excluding: ExclusionList(names: itemNames))
}
static func resolve(_ paths: [String], excluding itemNames: Set<String> = []) async throws -> [FilePath] {
static func resolve(_ paths: [String], excluding exclusions: ExclusionList = ExclusionList()) async throws -> [FilePath] {
var resolved: [FilePath] = []
for path in paths {
let path = FilePath(path)
resolved.append(path)
try await withSubdirectories(at: path, excluding: itemNames) { resolved.append($0) }
try await withSubdirectories(at: path, excluding: exclusions) { resolved.append($0) }
}
return resolved
}
/// The direct children of `directory`, without the excluded names.
static func entries(of directory: FilePath, excluding itemNames: Set<String> = []) async throws -> [(name: String, isDirectory: Bool)] {
/// The direct children of `directory`, without the excluded items.
static func entries(of directory: FilePath, excluding exclusions: ExclusionList = ExclusionList()) async throws -> [(name: String, isDirectory: Bool)] {
let directoryHandle = try await fileManager.openDirectory(atPath: directory)
var entries: [(name: String, isDirectory: Bool)] = []
for try await childContent in directoryHandle.listContents() {
guard let name = childContent.path.lastComponent?.string else { continue }
guard !itemNames.contains(name) else { continue }
guard !exclusions.excludes(name) else { continue }
entries.append((name: name, isDirectory: childContent.type == .directory))
}
try await directoryHandle.close()
@@ -33,14 +33,14 @@ public struct DirectoryResolver {
}
/// Calls `body` for every subdirectory below `path`, depth first. Excluded
/// names are neither reported nor descended into.
private static func withSubdirectories(at path: FilePath, excluding itemNames: Set<String>, body: (FilePath) async throws -> Void) async throws {
/// directories are neither reported nor descended into.
private static func withSubdirectories(at path: FilePath, excluding exclusions: ExclusionList, body: (FilePath) async throws -> Void) async throws {
let directoryHandle = try await fileManager.openDirectory(atPath: path)
for try await childContent in directoryHandle.listContents() {
guard childContent.type == .directory else { continue }
guard let name = childContent.path.lastComponent?.string, !itemNames.contains(name) else { continue }
guard let name = childContent.path.lastComponent?.string, !exclusions.excludes(name) else { continue }
try await body(childContent.path)
try await withSubdirectories(at: childContent.path, excluding: itemNames, body: body)
try await withSubdirectories(at: childContent.path, excluding: exclusions, body: body)
}
try await directoryHandle.close()
}