diff --git a/FileOtter.xcodeproj/project.pbxproj b/FileOtter.xcodeproj/project.pbxproj index 645606a..819ecad 100644 --- a/FileOtter.xcodeproj/project.pbxproj +++ b/FileOtter.xcodeproj/project.pbxproj @@ -11,6 +11,9 @@ 7B5064BF2BD9F236009CEFF9 /* FileOtter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B5064B42BD9F236009CEFF9 /* FileOtter.framework */; }; 7B5064C42BD9F236009CEFF9 /* FileOtterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5064C32BD9F236009CEFF9 /* FileOtterTests.swift */; }; 7B5064C52BD9F236009CEFF9 /* FileOtter.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B5064B72BD9F236009CEFF9 /* FileOtter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7B5064CF2BD9F2C0009CEFF9 /* Readme.md in Resources */ = {isa = PBXBuildFile; fileRef = 7B5064CE2BD9F2C0009CEFF9 /* Readme.md */; }; + 7B5064D12BD9F322009CEFF9 /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5064D02BD9F322009CEFF9 /* File.swift */; }; + 7B5064D32BD9F339009CEFF9 /* Dir.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5064D22BD9F339009CEFF9 /* Dir.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -29,6 +32,9 @@ 7B5064B82BD9F236009CEFF9 /* FileOtter.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = FileOtter.docc; sourceTree = ""; }; 7B5064BE2BD9F236009CEFF9 /* FileOtterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FileOtterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 7B5064C32BD9F236009CEFF9 /* FileOtterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileOtterTests.swift; sourceTree = ""; }; + 7B5064CE2BD9F2C0009CEFF9 /* Readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Readme.md; sourceTree = ""; }; + 7B5064D02BD9F322009CEFF9 /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = ""; }; + 7B5064D22BD9F339009CEFF9 /* Dir.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dir.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -53,6 +59,7 @@ 7B5064AA2BD9F235009CEFF9 = { isa = PBXGroup; children = ( + 7B5064CE2BD9F2C0009CEFF9 /* Readme.md */, 7B5064B62BD9F236009CEFF9 /* FileOtter */, 7B5064C22BD9F236009CEFF9 /* FileOtterTests */, 7B5064B52BD9F236009CEFF9 /* Products */, @@ -73,6 +80,8 @@ children = ( 7B5064B72BD9F236009CEFF9 /* FileOtter.h */, 7B5064B82BD9F236009CEFF9 /* FileOtter.docc */, + 7B5064D02BD9F322009CEFF9 /* File.swift */, + 7B5064D22BD9F339009CEFF9 /* Dir.swift */, ); path = FileOtter; sourceTree = ""; @@ -177,6 +186,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7B5064CF2BD9F2C0009CEFF9 /* Readme.md in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -195,6 +205,8 @@ buildActionMask = 2147483647; files = ( 7B5064B92BD9F236009CEFF9 /* FileOtter.docc in Sources */, + 7B5064D12BD9F322009CEFF9 /* File.swift in Sources */, + 7B5064D32BD9F339009CEFF9 /* Dir.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -423,6 +435,7 @@ isa = XCBuildConfiguration; buildSettings = { ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = X45WPY5JFZ; @@ -444,6 +457,7 @@ isa = XCBuildConfiguration; buildSettings = { ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = X45WPY5JFZ; diff --git a/FileOtter/Dir.swift b/FileOtter/Dir.swift new file mode 100644 index 0000000..abdaa72 --- /dev/null +++ b/FileOtter/Dir.swift @@ -0,0 +1,150 @@ +// +// Dir.swift +// FileOtter +// +// Created by Sami Samhuri on 2024-04-24. +// + +import Foundation + +public struct Dir: Equatable, Hashable, RandomAccessCollection { + public let startIndex: Int + + public let endIndex: Int + + public let url: URL + + public init(url: URL) throws { + self.init(url: url, children: try Dir.children(url)) + } + + private let children: [URL] + + private init(url: URL, children: [URL]) { + self.url = url + self.children = children + startIndex = children.startIndex + endIndex = children.endIndex + } +} + +// MARK: - Well-known Directories +extension Dir { + public static var caches: URL { + URL.cachesDirectory + } + + public static var current: URL { + URL.currentDirectory() + } + + public static var documents: URL { + URL.documentsDirectory + } + + public static var home: URL { + URL.homeDirectory + } + + public static var library: URL { + URL.libraryDirectory + } + + public static var pwd: URL { + .currentDirectory() + } + + public static var getwd: URL { + .currentDirectory() + } +} + +// MARK: - Mutations +extension Dir { + @discardableResult + public static func chdir(_ url: URL) -> Bool { + FileManager.default.changeCurrentDirectoryPath(url.path) + } + + @discardableResult + public static func chdir(_ url: URL, block: (URL) -> T) -> T { + let previousDir = pwd + FileManager.default.changeCurrentDirectoryPath(url.path) + defer { + FileManager.default.changeCurrentDirectoryPath(previousDir.path) + } + return block(url) + } + + @discardableResult + public static func unlink(_ url: URL) -> Bool { + do { + try FileManager.default.removeItem(at: url) + return true + } catch { + return false + } + } + + @discardableResult + public static func rmdir(_ url: URL) -> Bool { + unlink(url) + } + + @discardableResult + public static func delete(_ url: URL) -> Bool { + unlink(url) + } +} + +// MARK: - Reading Contents +extension Dir { + public static func children(_ url: URL) throws -> [URL] { + try FileManager.default + .contentsOfDirectory(at: url, includingPropertiesForKeys: nil) + } + + public static func entries(_ url: URL) throws -> [String] { + #warning("TODO: implement this ... maybe, it's dumb") + return [] + } + + public static func exists(_ url: URL) throws -> Bool { + var isDirectory: ObjCBool = false + let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) + return exists && isDirectory.boolValue + } + + public static func isEmpty(_ url: URL) throws -> Bool { + try FileManager.default + .contentsOfDirectory(at: url, includingPropertiesForKeys: nil) + .isEmpty + } +} + +// MARK: - Globbing +extension Dir { + public static func glob(base: URL? = nil, _ patterns: String...) -> [URL] { + _glob(base: base, patterns: patterns) + } + + public static subscript(base: URL? = nil, _ patterns: String...) -> [URL] { + _glob(base: base, patterns: patterns) + } + + private static func _glob(base: URL?, patterns: [String]) -> [URL] { + #warning("TODO: implement me") + return [] + } +} + +// MARK: - RandomAccessCollection +extension Dir { + public func makeIterator() -> any IteratorProtocol { + children.makeIterator() + } + + public subscript(position: Int) -> URL { + children[position] + } +} diff --git a/FileOtter/File.swift b/FileOtter/File.swift new file mode 100644 index 0000000..8fbccad --- /dev/null +++ b/FileOtter/File.swift @@ -0,0 +1,10 @@ +// +// File.swift +// FileOtter +// +// Created by Sami Samhuri on 2024-04-24. +// + +import Foundation + +public struct File {} diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..857192d --- /dev/null +++ b/Readme.md @@ -0,0 +1,6 @@ +# FileOtter + +A sleek way to interact with the filesystem in Swift. Heavily inspired by Ruby's `[Dir][]` and `[File][]` classes from the standard library. + +[Dir]: https://docs.ruby-lang.org/en/3.3/Dir.html +[File]: https://docs.ruby-lang.org/en/3.3/File.html