A type safe swift ResultBuilder DSL for structured directories
0

Configure Feed

Select the types of activity you want to include in your feed.

Remove FileTree writing

+152 -124
+7 -7
Package.resolved
··· 1 1 { 2 - "originHash" : "3bee63a89a4fa8f240e4114f8d87c9eeba8362e426c4eb499967e05e8ef3c9b8", 2 + "originHash" : "8d8d86fb5c65d5509471dbe0c52ac821e1db4d31a2a7d1a275c978281b0f5847", 3 3 "pins" : [ 4 4 { 5 5 "identity" : "swift-case-paths", 6 6 "kind" : "remoteSourceControl", 7 7 "location" : "https://github.com/pointfreeco/swift-case-paths", 8 8 "state" : { 9 - "revision" : "bc92c4b27f9a84bfb498cdbfdf35d5a357e9161f", 10 - "version" : "1.5.6" 9 + "revision" : "19b7263bacb9751f151ec0c93ec816fe1ef67c7b", 10 + "version" : "1.6.1" 11 11 } 12 12 }, 13 13 { ··· 24 24 "kind" : "remoteSourceControl", 25 25 "location" : "https://github.com/woodymelling/swift-parsing", 26 26 "state" : { 27 - "revision" : "c0249cbebab111490fcf0735b8f05fc51911f084", 28 - "version" : "0.1.0" 27 + "branch" : "android-support", 28 + "revision" : "1d74dcf9fe3b3918067b519447c66c0088b644b2" 29 29 } 30 30 }, 31 31 { ··· 42 42 "kind" : "remoteSourceControl", 43 43 "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", 44 44 "state" : { 45 - "revision" : "27d767d643fa2cf083d0a73d74fa84cacb53e85c", 46 - "version" : "1.4.1" 45 + "revision" : "39de59b2d47f7ef3ca88a039dff3084688fe27f4", 46 + "version" : "1.5.2" 47 47 } 48 48 } 49 49 ],
+1 -1
Package.swift
··· 13 13 dependencies: [ 14 14 .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.4.0"), 15 15 .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.3"), 16 - .package(url: "https://github.com/woodymelling/swift-parsing", from: "0.1.0") 16 + .package(url: "https://github.com/woodymelling/swift-parsing", branch: "android-support") 17 17 ], 18 18 targets: [ 19 19 // Targets are the basic building blocks of a package, defining a module or a test suite.
+49 -49
Sources/FileTree/Components/Directory.swift
··· 23 23 return try component.read(from: directoryURL) 24 24 } 25 25 26 - public func write(_ data: Component.Content, to url: URL) throws { 27 - let directoryPath = url.appending(component: path.description) 28 - 29 - if !FileManager.default.fileExists(atPath: directoryPath.path()) { 30 - try FileManager.default.createDirectory(at: directoryPath, withIntermediateDirectories: false) 31 - } 32 - 33 - try component.write(data, to: directoryPath) 34 - } 26 + // public func write(_ data: Component.Content, to url: URL) throws { 27 + // let directoryPath = url.appending(component: path.description) 28 + // 29 + // if !FileManager.default.fileExists(atPath: directoryPath.path()) { 30 + // try FileManager.default.createDirectory(at: directoryPath, withIntermediateDirectories: false) 31 + // } 32 + // 33 + // try component.write(data, to: directoryPath) 34 + // } 35 35 } 36 36 37 37 ··· 63 63 }.sorted(by: { $0.directoryName < $1.directoryName }) 64 64 } 65 65 66 - public func write(_ data: [DirectoryContent<Component.Content>], to url: URL) throws { 67 - guard writingToEmptyDirectory 68 - else { 69 - reportIssue(""" 70 - Writing a `Many` to a directory that may already have contents currently unsupported. 71 - 72 - This is because it is difficult to determine if a value that does not exist in the array of values getting written should be deleted because it was removed, 73 - or if it exists outside of the purview of the `Many { }` block and should be left alone. 74 - 75 - The semantics of Many may need to be tweaked to make this determination more clear. 76 - 77 - To allow writing to the directory, use: 78 - 79 - ``` 80 - $writingToEmptyDirectory.withValue(true) { 81 - Directories { StaticFile($0, "txt") }.write(...) 82 - } 83 - ``` 84 - 85 - which will naively write all the contents to the directory, and not delete anything that is already there. 86 - """) 87 - return 88 - } 89 - 90 - 91 - 92 - if !FileManager.default.fileExists(atPath: url.path()) { 93 - try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true) 94 - } 95 - 96 - for directoryContent in data { 97 - let directoryURL = url.appendingPathComponent(directoryContent.directoryName) 98 - 99 - if !FileManager.default.fileExists(atPath: directoryURL.path()) { 100 - try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: false) 101 - } 102 - 103 - try component.write(directoryContent.components, to: directoryURL) 104 - } 105 - } 66 + // public func write(_ data: [DirectoryContent<Component.Content>], to url: URL) throws { 67 + // guard writingToEmptyDirectory 68 + // else { 69 + // reportIssue(""" 70 + // Writing a `Many` to a directory that may already have contents currently unsupported. 71 + // 72 + // This is because it is difficult to determine if a value that does not exist in the array of values getting written should be deleted because it was removed, 73 + // or if it exists outside of the purview of the `Many { }` block and should be left alone. 74 + // 75 + // The semantics of Many may need to be tweaked to make this determination more clear. 76 + // 77 + // To allow writing to the directory, use: 78 + // 79 + // ``` 80 + // $writingToEmptyDirectory.withValue(true) { 81 + // Directories { StaticFile($0, "txt") }.write(...) 82 + // } 83 + // ``` 84 + // 85 + // which will naively write all the contents to the directory, and not delete anything that is already there. 86 + // """) 87 + // return 88 + // } 89 + // 90 + // 91 + // 92 + // if !FileManager.default.fileExists(atPath: url.path()) { 93 + // try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true) 94 + // } 95 + // 96 + // for directoryContent in data { 97 + // let directoryURL = url.appendingPathComponent(directoryContent.directoryName) 98 + // 99 + // if !FileManager.default.fileExists(atPath: directoryURL.path()) { 100 + // try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: false) 101 + // } 102 + // 103 + // try component.write(directoryContent.components, to: directoryURL) 104 + // } 105 + // } 106 106 } 107 107 108 108 }
+7 -16
Sources/FileTree/Components/File.swift
··· 5 5 // Created by Woodrow Melling on 12/5/24. 6 6 // 7 7 8 - import UniformTypeIdentifiers 9 8 import Foundation 10 9 import IssueReporting 11 10 12 11 public struct File: FileTreeComponent { 13 12 let fileName: StaticString 14 - let fileType: UTFileExtension 15 - 16 - public init(_ fileName: StaticString, _ fileType: UTType) { 17 - self.fileName = fileName 18 - self.fileType = .utType(fileType) 19 - } 13 + let fileType: FileExtension 20 14 21 15 public init(_ fileName: StaticString, _ fileType: FileExtension) { 22 16 self.fileName = fileName 23 - self.fileType = .extension(fileType) 17 + self.fileType = fileType 24 18 } 25 19 26 20 public func read(from url: URL) throws -> Data { ··· 39 33 extension File { 40 34 public struct Many: FileTreeComponent { 41 35 public typealias Content = [FileContent<Data>] 42 - let fileType: UTFileExtension? 36 + let fileType: FileExtension? 43 37 44 38 public init() { 45 39 self.fileType = nil 46 40 } 47 41 48 - public init(withExtension content: UTType) { 49 - self.fileType = .utType(content) 50 - } 51 - 52 42 public init(withExtension content: FileExtension) { 53 - self.fileType = .extension(content) 54 - } 43 + self.fileType = content 44 + } 55 45 56 46 public func read(from url: URL) throws -> [FileContent<Data>] { 57 47 var paths = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: []) 58 48 59 49 if let fileType { 60 - paths = paths.filter { $0.pathExtension == fileType.identifier } 50 + paths = paths.filter { $0.pathExtension == fileType.rawValue } 61 51 } 62 52 63 53 // let filteredPaths = paths.filter { $0.pathExtension == self.fileType.identifier } ··· 128 118 ) 129 119 } 130 120 } 121 +
+23 -2
Sources/FileTree/Components/Tuple.swift
··· 20 20 try (repeat (each value).read(from: url)) 21 21 } 22 22 23 - public func write(_ data: (repeat (each T).Content), to url: URL) throws { 24 - try (repeat (each value).write((each data), to: url)) 23 + // public func write(_ data: Content, to url: URL) throws { 24 + // fatalError() 25 + // // try (repeat (each value).write((each data), to: url)) 26 + // } 27 + } 28 + 29 + public struct PairFileTreeComponent<F1: FileTreeComponent, F2: FileTreeComponent>: FileTreeComponent { 30 + public var value: (F1, F2) 31 + public typealias Content = (F1.Content, F2.Content) 32 + 33 + @inlinable public init(_ value: (F1, F2)) { 34 + self.value = value 25 35 } 36 + 37 + public func read(from url: URL) throws -> Content { 38 + try (value.0.read(from: url), (value.1.read(from: url))) 39 + } 40 + 41 + 42 + // public func write(_ data: (F1.Content, F2.Content), to url: URL) throws { 43 + // try value.0.write(data.0, to: url) 44 + // try value.1.write(data.1, to: url) 45 + // } 26 46 } 47 +
+8 -6
Sources/FileTree/Conversions.swift
··· 26 26 try self.downstream.apply(upstream.read(from: url)) 27 27 } 28 28 29 - @inlinable 30 - @inline(__always) 31 - public func write(_ data: Downstream.Output, to url: URL) throws { 32 - try self.upstream.write(downstream.unapply(data), to: url) 33 - } 29 + // @inlinable 30 + // @inline(__always) 31 + // public func write(_ data: Downstream.Output, to url: URL) throws { 32 + // try self.upstream.write(downstream.unapply(data), to: url) 33 + // } 34 34 } 35 35 36 36 ··· 70 70 // let originalData = try data.map { fileContent in 71 71 // return try conversion.unapply(fileContent) 72 72 // } 73 - // try original.write(originalData, to: url) 73 + // try original. write(originalData, to: url) 74 74 // } 75 75 //} 76 76 ··· 152 152 153 153 154 154 // MARK: SwiftUI 155 + #if canImport(SwiftUI) 155 156 import SwiftUI 156 157 157 158 extension _ConvertedFileTreeComponent: FileTreeViewable where Upstream: FileTreeViewable { ··· 198 199 } 199 200 } 200 201 } 202 + #endif 201 203 // 202 204 //extension _ManyFileMapConversion: FileTreeViewable where File.Many: FileTreeViewable { 203 205 // @MainActor
+24 -14
Sources/FileTree/Core.swift
··· 1 1 import Foundation 2 - import UniformTypeIdentifiers 3 2 import IssueReporting 4 3 5 4 // MARK: Protocol 6 5 public protocol FileTreeComponent<Content> { 7 - associatedtype Content 6 + associatedtype Content 8 7 9 8 associatedtype Body 10 9 11 10 func read(from url: URL) throws -> Content 12 11 13 - func write(_ data: Content, to url: URL) throws 12 + // func write(_ data: Content, to url: URL) throws 14 13 15 14 @FileTreeBuilder 16 15 var body: Body { get } ··· 27 26 try body.read(from: url) 28 27 } 29 28 30 - public func write(_ data: Content, to url: URL) throws { 31 - try body.write(data, to: url) 32 - } 29 + // public func write(_ data: Content, to url: URL) throws { 30 + // try body.write(data, to: url) 31 + // } 33 32 } 34 - 35 33 36 34 // MARK: Result Builder 37 35 @resultBuilder 38 36 public struct FileTreeBuilder { 39 - public static func buildExpression<Component>(_ component: Component) -> Component where Component: FileTreeComponent { 37 + public static func buildExpression<Component>(_ component: Component) -> Component 38 + where Component: FileTreeComponent { 40 39 component 41 40 } 42 41 43 - public static func buildBlock<Component>(_ component: Component) -> Component where Component: FileTreeComponent { 42 + public static func buildBlock<Component>(_ component: Component) -> Component 43 + where Component: FileTreeComponent { 44 44 component 45 45 } 46 46 47 - public static func buildBlock<each Component>(_ component: repeat each Component) -> TupleFileSystemComponent<repeat each Component> where repeat each Component: FileTreeComponent { 48 - return TupleFileSystemComponent(repeat each component) 47 + // public static func buildBlock<each Component>(_ component: repeat each Component) -> TupleFileSystemComponent<repeat each Component> where repeat each Component: FileTreeComponent { 48 + // return TupleFileSystemComponent(repeat each component) 49 + // } 50 + 51 + public static func buildPartialBlock<F: FileTreeComponent>(first content: F) -> F { 52 + content 53 + } 54 + 55 + public static func buildPartialBlock<F0, F1>(accumulated: F0, next: F1) 56 + -> PairFileTreeComponent<F0, F1> where F0: FileTreeComponent, F1: FileTreeComponent 57 + { 58 + return PairFileTreeComponent((accumulated, next)) 49 59 } 50 60 } 51 61 ··· 62 72 try self.component.read(from: url) 63 73 } 64 74 65 - public func write(_ data: Component.Content, to url: URL) throws { 66 - try self.component.write(data, to: url) 67 - } 75 + // public func write(_ data: Component.Content, to url: URL) throws { 76 + // try self.component.write(data, to: url) 77 + // } 68 78 }
+2 -22
Sources/FileTree/FileExtension.swift
··· 5 5 // Created by Woodrow Melling on 12/5/24. 6 6 // 7 7 8 - import UniformTypeIdentifiers 9 8 import Foundation 10 9 11 - enum UTFileExtension: Sendable, Hashable { 12 - case utType(UTType) 13 - case `extension`(FileExtension) 14 - 15 - var identifier: String { 16 - switch self { 17 - case .utType(let utType): 18 - utType.identifier 19 - case .extension(let e): 20 - e.rawValue 21 - } 22 - } 23 - } 24 - 25 10 public struct FileExtension: ExpressibleByStringLiteral, Sendable, Hashable { 26 11 var rawValue: String 27 12 ··· 31 16 } 32 17 33 18 extension URL { 34 - func appendingPathComponent(_ partialName: String, withType type: UTFileExtension) -> URL { 35 - switch type { 36 - case .utType(let utType): 37 - self.appendingPathComponent(partialName, conformingTo: utType) 38 - case .extension(let string): 39 - self.appending(path: partialName).appendingPathExtension(string.rawValue) 40 - } 19 + func appendingPathComponent(_ partialName: String, withType type: FileExtension) -> URL { 20 + self.appending(path: partialName).appendingPathExtension(type.rawValue) 41 21 } 42 22 }
+13 -7
Sources/FileTree/FileTree+SwiftUI.swift
··· 50 50 } 51 51 } 52 52 53 - 54 - extension TupleFileSystemComponent: FileTreeViewable where repeat (each T): FileTreeViewable { 55 - 53 + // 54 + // extension TupleFileSystemComponent: FileTreeViewable where repeat (each T): FileTreeViewable { 55 + // 56 + // @MainActor 57 + // public func view(for content: (repeat (each T).Content)) -> some 58 + // View { 59 + // TupleView((repeat (each value).view(for: (each content)))) 60 + // } 61 + // } 62 + extension PairFileTreeComponent: FileTreeViewable where F1: FileTreeViewable, F2: FileTreeViewable { 56 63 @MainActor 57 - public func view(for content: (repeat (each T).Content)) -> some 58 - View { 59 - TupleView((repeat (each value).view(for: (each content)))) 64 + public func view(for content: (F1.Content, F2.Content)) -> some View { 65 + TupleView((value.0.view(for: (content.0)), value.1.view(for: (content.1)))) 60 66 } 61 67 } 62 68 ··· 348 354 var body: some FileTreeViewable<([FileContent<Data>])> { 349 355 Directory("Dir") { 350 356 351 - File.Many(withExtension: .text) 357 + File.Many(withExtension: "txt") 352 358 .map(FileContentConversion(Conversions.Identity<Data>())) 353 359 .tag { Tag.list($0.fileName) } 354 360 // .tag(Tag.contents)
+18
Tests/FileTreeTests/BuilderTests.swift
··· 1 + // 2 + // BuilderTests.swift 3 + // swift-file-tree 4 + // 5 + // Created by Woodrow Melling on 3/17/25. 6 + // 7 + 8 + import FileTree 9 + import Testing 10 + 11 + struct Builder { 12 + func builder() { 13 + let result = FileTree { 14 + File("blah", "txt") 15 + File("bleh", "txt") 16 + } 17 + } 18 + }