Skip to content

Commit df5a24a

Browse files
authored
New BuildableItem protocol (#4)
- new BuildableItem protocol to make custom types possible in the @directorybuilder - new build and remove functions to gain control over the created structure - visible parameter labels - swift 6.1 tests
1 parent 508cc7e commit df5a24a

File tree

14 files changed

+593
-306
lines changed

14 files changed

+593
-306
lines changed

.github/workflows/run-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ jobs:
3535
matrix:
3636
image:
3737
- 'swift:6.0'
38+
- 'swift:6.1'
3839
container:
3940
image: ${{ matrix.image }}
4041
steps:

Package.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ let package = Package(
1616
targets: ["FileManagerKit"]
1717
),
1818
.library(
19-
name: "FileManagerKitTesting",
20-
targets: ["FileManagerKitTesting"]
19+
name: "FileManagerKitBuilder",
20+
targets: ["FileManagerKitBuilder"]
2121
),
2222
],
2323
dependencies: [
@@ -34,7 +34,7 @@ let package = Package(
3434
]
3535
),
3636
.target(
37-
name: "FileManagerKitTesting",
37+
name: "FileManagerKitBuilder",
3838
dependencies: [
3939

4040
],
@@ -46,7 +46,14 @@ let package = Package(
4646
name: "FileManagerKitTests",
4747
dependencies: [
4848
.target(name: "FileManagerKit"),
49-
.target(name: "FileManagerKitTesting")
49+
.target(name: "FileManagerKitBuilder")
50+
]
51+
),
52+
.testTarget(
53+
name: "FileManagerKitBuilderTests",
54+
dependencies: [
55+
.target(name: "FileManagerKit"),
56+
.target(name: "FileManagerKitBuilder")
5057
]
5158
),
5259
]

README.md

Lines changed: 139 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,161 @@
11
# FileManagerKit
22

3-
Useful extensions for the [FileManager](https://developer.apple.com/documentation/foundation/filemanager) class.
3+
Swift extensions and DSLs for filesystem testing, scripting, and inspection.
44

5-
## Getting started
5+
This package contains two products:
66

7-
⚠️ This repository is a work in progress, things can break until it reaches v1.0.0.
7+
- FileManagerKit – high-level extensions for FileManager
8+
- FileManagerKitBuilder – a DSL for creating filesystem layouts (ideal for tests)
89

9-
Use at your own risk.
10+
Note: This repository is a work in progress. Expect breaking changes before v1.0.0.
1011

11-
### Adding the dependency
12+
---
1213

13-
To add a dependency on the package, declare it in your `Package.swift`:
14+
## Installation
15+
16+
Add the package to your `Package.swift`:
1417

1518
```swift
1619
.package(url: "https://github.com/binarybirds/file-manager-kit", .upToNextMinor(from: "0.2.0")),
1720
```
1821

19-
and to your application target, add `FileManagerKit` to your dependencies:
22+
Then declare the product you want to use in your target dependencies:
2023

2124
```swift
2225
.product(name: "FileManagerKit", package: "file-manager-kit")
26+
.product(name: "FileManagerKitBuilder", package: "file-manager-kit")
2327
```
2428

25-
Example `Package.swift` file with `FileManagerKit` as a dependency:
29+
---
30+
31+
# FileManagerKit
32+
33+
A set of ergonomic, safe extensions for working with FileManager.
34+
35+
## Common Operations
36+
37+
### Check if File or Directory Exists
2638

2739
```swift
28-
// swift-tools-version:6.0
29-
import PackageDescription
40+
let fileURL = URL(filePath: "/path/to/file")
41+
if fileManager.exists(at: fileURL) {
42+
print("Exists!")
43+
}
44+
```
45+
46+
### Create a Directory
47+
48+
```swift
49+
let dirURL = URL(filePath: "/path/to/new-dir")
50+
try fileManager.createDirectory(at: dirURL)
51+
```
52+
53+
### Create a File
54+
55+
```swift
56+
let fileURL = URL(filePath: "/path/to/file.txt")
57+
let data = "Hello".data(using: .utf8)
58+
try fileManager.createFile(at: fileURL, contents: data)
59+
```
3060

31-
let package = Package(
32-
name: "my-application",
33-
dependencies: [
34-
.package(url: "https://github.com/binarybirds/file-manager-kit", .upToNextMinor(from: "0.2.0")),
35-
],
36-
targets: [
37-
.target(name: "MyApplication", dependencies: [
38-
.product(name: "FileManagerKit", package: "file-manager-kit")
39-
]),
40-
.testTarget(name: "MyApplicationTests", dependencies: [
41-
.target(name: "MyApplication"),
42-
]),
43-
]
44-
)
61+
### Delete a File or Directory
62+
63+
```swift
64+
let targetURL = URL(filePath: "/path/to/delete")
65+
try fileManager.delete(at: targetURL)
66+
```
67+
68+
### List Directory Contents
69+
70+
```swift
71+
let contents = fileManager.listDirectory(at: URL(filePath: "/path/to/dir"))
72+
print(contents)
73+
```
74+
75+
### Copy / Move
76+
77+
```swift
78+
try fileManager.copy(from: URL(filePath: "/from"), to: URL(filePath: "/to"))
79+
try fileManager.move(from: URL(filePath: "/from"), to: URL(filePath: "/to"))
80+
```
81+
82+
### Get File Size
83+
84+
```swift
85+
let size = try fileManager.size(at: URL(filePath: "/path/to/file"))
86+
print("\(size) bytes")
4587
```
4688

89+
---
90+
91+
# FileManagerKitBuilder
92+
93+
A Swift DSL to declaratively build, inspect, and tear down file system structures — great for testing.
94+
95+
## Installation
96+
97+
To use FileManagerKitBuilder, add this line to your dependencies:
98+
99+
```swift
100+
.product(name: "FileManagerKitBuilder", package: "file-manager-kit")
101+
```
102+
103+
## Simple Example
104+
105+
Create and clean up a file structure:
106+
107+
```swift
108+
import FileManagerKitBuilder
109+
110+
let playground = FileManagerPlayground {
111+
Directory(name: "foo") {
112+
File(name: "bar.txt", string: "Hello, world!")
113+
}
114+
}
115+
116+
let _ = try playground.build()
117+
try playground.remove()
118+
```
119+
120+
## Custom Type Example
121+
122+
Use a BuildableItem to generate structured files (e.g., JSON).
123+
124+
```swift
125+
public struct JSON<T: Encodable>: BuildableItem {
126+
public let name: String
127+
public let contents: T
128+
129+
public func buildItem() -> FileManagerPlayground.Item {
130+
let data = try! JSONEncoder().encode(contents)
131+
let string = String(data: data, encoding: .utf8)!
132+
return .file(File(name: "\(name).json", string: string))
133+
}
134+
}
135+
136+
struct User: Codable { let name: String }
137+
138+
let playground = FileManagerPlayground {
139+
Directory(name: "data") {
140+
JSON(name: "user", contents: User(name: "Deku"))
141+
}
142+
}
143+
144+
try playground.build()
145+
```
146+
147+
## Test Example
148+
149+
Use `.test` to run assertions in a temporary sandbox:
150+
151+
```swift
152+
try FileManagerPlayground {
153+
Directory(name: "foo") {
154+
"bar.txt"
155+
}
156+
}
157+
.test { fileManager, rootUrl in
158+
let fileURL = rootUrl.appendingPathComponent("foo/bar.txt")
159+
#expect(fileManager.fileExists(at: fileURL))
160+
}
161+
```
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// Buildable.swift
3+
// file-manager-kit
4+
//
5+
// Created by Viasz-Kádi Ferenc on 2025. 05. 30..
6+
//
7+
8+
import Foundation
9+
10+
protocol Buildable {
11+
12+
func build(in path: URL, using fileManager: FileManager) throws
13+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//
2+
// BuildableItem.swift
3+
// file-manager-kit
4+
//
5+
// Created by Viasz-Kádi Ferenc on 2025. 05. 30..
6+
//
7+
8+
public protocol BuildableItem {
9+
func buildItem() -> FileManagerPlayground.Item
10+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//
2+
// Directory.swift
3+
// file-manager-kit
4+
//
5+
// Created by Viasz-Kádi Ferenc on 2025. 05. 30..
6+
//
7+
8+
import Foundation
9+
10+
public struct Directory: Buildable {
11+
let name: String
12+
let attributes: [FileAttributeKey: Any]?
13+
let contents: [FileManagerPlayground.Item]
14+
15+
public init(
16+
name: String,
17+
attributes: [FileAttributeKey: Any]? = nil,
18+
@FileManagerPlayground.DirectoryBuilder _ contentsClosure: () ->
19+
[FileManagerPlayground.Item]
20+
) {
21+
self.name = name
22+
self.attributes = attributes
23+
self.contents = contentsClosure()
24+
}
25+
26+
public init(
27+
name: String,
28+
attributes: [FileAttributeKey: Any]? = nil
29+
) {
30+
self.name = name
31+
self.attributes = attributes
32+
self.contents = []
33+
}
34+
35+
func build(
36+
in url: URL,
37+
using fileManager: FileManager
38+
) throws {
39+
let dirUrl = url.appendingPathComponent(name)
40+
try fileManager.createDirectory(
41+
atPath: dirUrl.path(),
42+
withIntermediateDirectories: true,
43+
attributes: attributes
44+
)
45+
for item in contents {
46+
try item.build(in: dirUrl, using: fileManager)
47+
}
48+
}
49+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//
2+
// File.swift
3+
// file-manager-kit
4+
//
5+
// Created by Viasz-Kádi Ferenc on 2025. 05. 30..
6+
//
7+
8+
import Foundation
9+
10+
public struct File: ExpressibleByStringLiteral, Buildable {
11+
private let name: String
12+
private let attributes: [FileAttributeKey: Any]?
13+
private let contents: Data?
14+
15+
public init(
16+
name: String,
17+
attributes: [FileAttributeKey: Any]? = nil,
18+
contents: Data? = nil
19+
) {
20+
self.name = name
21+
self.attributes = attributes
22+
self.contents = contents
23+
}
24+
25+
public init(
26+
name: String,
27+
attributes: [FileAttributeKey: Any]? = nil,
28+
string: String? = nil
29+
) {
30+
self.name = name
31+
self.attributes = attributes
32+
self.contents = string?.data(using: .utf8)
33+
}
34+
35+
public init(stringLiteral value: String) {
36+
self.init(name: value, contents: nil)
37+
}
38+
39+
func build(
40+
in url: URL,
41+
using fileManager: FileManager
42+
) throws {
43+
fileManager.createFile(
44+
atPath: url.appendingPathComponent(name).path(),
45+
contents: contents,
46+
attributes: attributes
47+
)
48+
}
49+
}

0 commit comments

Comments
 (0)