Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
matrix:
image:
- 'swift:6.0'
- 'swift:6.1'
container:
image: ${{ matrix.image }}
steps:
Expand Down
15 changes: 11 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ let package = Package(
targets: ["FileManagerKit"]
),
.library(
name: "FileManagerKitTesting",
targets: ["FileManagerKitTesting"]
name: "FileManagerKitBuilder",
targets: ["FileManagerKitBuilder"]
),
],
dependencies: [
Expand All @@ -34,7 +34,7 @@ let package = Package(
]
),
.target(
name: "FileManagerKitTesting",
name: "FileManagerKitBuilder",
dependencies: [

],
Expand All @@ -46,7 +46,14 @@ let package = Package(
name: "FileManagerKitTests",
dependencies: [
.target(name: "FileManagerKit"),
.target(name: "FileManagerKitTesting")
.target(name: "FileManagerKitBuilder")
]
),
.testTarget(
name: "FileManagerKitBuilderTests",
dependencies: [
.target(name: "FileManagerKit"),
.target(name: "FileManagerKitBuilder")
]
),
]
Expand Down
163 changes: 139 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,161 @@
# FileManagerKit

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

## Getting started
This package contains two products:

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

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

### Adding the dependency
---

To add a dependency on the package, declare it in your `Package.swift`:
## Installation

Add the package to your `Package.swift`:

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

and to your application target, add `FileManagerKit` to your dependencies:
Then declare the product you want to use in your target dependencies:

```swift
.product(name: "FileManagerKit", package: "file-manager-kit")
.product(name: "FileManagerKitBuilder", package: "file-manager-kit")
```

Example `Package.swift` file with `FileManagerKit` as a dependency:
---

# FileManagerKit

A set of ergonomic, safe extensions for working with FileManager.

## Common Operations

### Check if File or Directory Exists

```swift
// swift-tools-version:6.0
import PackageDescription
let fileURL = URL(filePath: "/path/to/file")
if fileManager.exists(at: fileURL) {
print("Exists!")
}
```

### Create a Directory

```swift
let dirURL = URL(filePath: "/path/to/new-dir")
try fileManager.createDirectory(at: dirURL)
```

### Create a File

```swift
let fileURL = URL(filePath: "/path/to/file.txt")
let data = "Hello".data(using: .utf8)
try fileManager.createFile(at: fileURL, contents: data)
```

let package = Package(
name: "my-application",
dependencies: [
.package(url: "https://github.com/binarybirds/file-manager-kit", .upToNextMinor(from: "0.2.0")),
],
targets: [
.target(name: "MyApplication", dependencies: [
.product(name: "FileManagerKit", package: "file-manager-kit")
]),
.testTarget(name: "MyApplicationTests", dependencies: [
.target(name: "MyApplication"),
]),
]
)
### Delete a File or Directory

```swift
let targetURL = URL(filePath: "/path/to/delete")
try fileManager.delete(at: targetURL)
```

### List Directory Contents

```swift
let contents = fileManager.listDirectory(at: URL(filePath: "/path/to/dir"))
print(contents)
```

### Copy / Move

```swift
try fileManager.copy(from: URL(filePath: "/from"), to: URL(filePath: "/to"))
try fileManager.move(from: URL(filePath: "/from"), to: URL(filePath: "/to"))
```

### Get File Size

```swift
let size = try fileManager.size(at: URL(filePath: "/path/to/file"))
print("\(size) bytes")
```

---

# FileManagerKitBuilder

A Swift DSL to declaratively build, inspect, and tear down file system structures — great for testing.

## Installation

To use FileManagerKitBuilder, add this line to your dependencies:

```swift
.product(name: "FileManagerKitBuilder", package: "file-manager-kit")
```

## Simple Example

Create and clean up a file structure:

```swift
import FileManagerKitBuilder

let playground = FileManagerPlayground {
Directory(name: "foo") {
File(name: "bar.txt", string: "Hello, world!")
}
}

let _ = try playground.build()
try playground.remove()
```

## Custom Type Example

Use a BuildableItem to generate structured files (e.g., JSON).

```swift
public struct JSON<T: Encodable>: BuildableItem {
public let name: String
public let contents: T

public func buildItem() -> FileManagerPlayground.Item {
let data = try! JSONEncoder().encode(contents)
let string = String(data: data, encoding: .utf8)!
return .file(File(name: "\(name).json", string: string))
}
}

struct User: Codable { let name: String }

let playground = FileManagerPlayground {
Directory(name: "data") {
JSON(name: "user", contents: User(name: "Deku"))
}
}

try playground.build()
```

## Test Example

Use `.test` to run assertions in a temporary sandbox:

```swift
try FileManagerPlayground {
Directory(name: "foo") {
"bar.txt"
}
}
.test { fileManager, rootUrl in
let fileURL = rootUrl.appendingPathComponent("foo/bar.txt")
#expect(fileManager.fileExists(at: fileURL))
}
```
13 changes: 13 additions & 0 deletions Sources/FileManagerKitBuilder/Buildable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Buildable.swift
// file-manager-kit
//
// Created by Viasz-Kádi Ferenc on 2025. 05. 30..
//

import Foundation

protocol Buildable {

func build(in path: URL, using fileManager: FileManager) throws
}
10 changes: 10 additions & 0 deletions Sources/FileManagerKitBuilder/BuildableItem.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//
// BuildableItem.swift
// file-manager-kit
//
// Created by Viasz-Kádi Ferenc on 2025. 05. 30..
//

public protocol BuildableItem {
func buildItem() -> FileManagerPlayground.Item
}
49 changes: 49 additions & 0 deletions Sources/FileManagerKitBuilder/Directory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// Directory.swift
// file-manager-kit
//
// Created by Viasz-Kádi Ferenc on 2025. 05. 30..
//

import Foundation

public struct Directory: Buildable {
let name: String
let attributes: [FileAttributeKey: Any]?
let contents: [FileManagerPlayground.Item]

public init(
name: String,
attributes: [FileAttributeKey: Any]? = nil,
@FileManagerPlayground.DirectoryBuilder _ contentsClosure: () ->
[FileManagerPlayground.Item]
) {
self.name = name
self.attributes = attributes
self.contents = contentsClosure()
}

public init(
name: String,
attributes: [FileAttributeKey: Any]? = nil
) {
self.name = name
self.attributes = attributes
self.contents = []
}

func build(
in url: URL,
using fileManager: FileManager
) throws {
let dirUrl = url.appendingPathComponent(name)
try fileManager.createDirectory(
atPath: dirUrl.path(),
withIntermediateDirectories: true,
attributes: attributes
)
for item in contents {
try item.build(in: dirUrl, using: fileManager)
}
}
}
49 changes: 49 additions & 0 deletions Sources/FileManagerKitBuilder/File.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// File.swift
// file-manager-kit
//
// Created by Viasz-Kádi Ferenc on 2025. 05. 30..
//

import Foundation

public struct File: ExpressibleByStringLiteral, Buildable {
private let name: String
private let attributes: [FileAttributeKey: Any]?
private let contents: Data?

public init(
name: String,
attributes: [FileAttributeKey: Any]? = nil,
contents: Data? = nil
) {
self.name = name
self.attributes = attributes
self.contents = contents
}

public init(
name: String,
attributes: [FileAttributeKey: Any]? = nil,
string: String? = nil
) {
self.name = name
self.attributes = attributes
self.contents = string?.data(using: .utf8)
}

public init(stringLiteral value: String) {
self.init(name: value, contents: nil)
}

func build(
in url: URL,
using fileManager: FileManager
) throws {
fileManager.createFile(
atPath: url.appendingPathComponent(name).path(),
contents: contents,
attributes: attributes
)
}
}
Loading
Loading