mirror of
https://github.com/samsonjs/FileOtter.git
synced 2026-04-27 14:57:41 +00:00
WIP: Implement more of File
This commit is contained in:
parent
f24ed714e6
commit
4529a7e700
4 changed files with 207 additions and 95 deletions
|
|
@ -3,18 +3,12 @@
|
||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 56;
|
objectVersion = 70;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
7B1B71E12E52784D008EDC0E /* Glob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1B71E02E52784D008EDC0E /* Glob.swift */; };
|
|
||||||
7B5064B92BD9F236009CEFF9 /* FileOtter.docc in Sources */ = {isa = PBXBuildFile; fileRef = 7B5064B82BD9F236009CEFF9 /* FileOtter.docc */; };
|
|
||||||
7B5064BF2BD9F236009CEFF9 /* FileOtter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B5064B42BD9F236009CEFF9 /* FileOtter.framework */; };
|
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 */; };
|
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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
|
@ -28,17 +22,26 @@
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
7B1B71E02E52784D008EDC0E /* Glob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Glob.swift; sourceTree = "<group>"; };
|
|
||||||
7B5064B42BD9F236009CEFF9 /* FileOtter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FileOtter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
7B5064B42BD9F236009CEFF9 /* FileOtter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FileOtter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
7B5064B72BD9F236009CEFF9 /* FileOtter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileOtter.h; sourceTree = "<group>"; };
|
|
||||||
7B5064B82BD9F236009CEFF9 /* FileOtter.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = FileOtter.docc; sourceTree = "<group>"; };
|
|
||||||
7B5064BE2BD9F236009CEFF9 /* FileOtterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FileOtterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
|
||||||
7B5064CE2BD9F2C0009CEFF9 /* Readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Readme.md; sourceTree = "<group>"; };
|
7B5064CE2BD9F2C0009CEFF9 /* Readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Readme.md; sourceTree = "<group>"; };
|
||||||
7B5064D02BD9F322009CEFF9 /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = "<group>"; };
|
|
||||||
7B5064D22BD9F339009CEFF9 /* Dir.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dir.swift; sourceTree = "<group>"; };
|
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
7B1B74752E5E097B008EDC0E /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
publicHeaders = (
|
||||||
|
FileOtter.h,
|
||||||
|
);
|
||||||
|
target = 7B5064B32BD9F235009CEFF9 /* FileOtter */;
|
||||||
|
};
|
||||||
|
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
|
||||||
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
7B1B74612E5E0978008EDC0E /* FileOtterTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = FileOtterTests; sourceTree = "<group>"; };
|
||||||
|
7B1B746F2E5E097B008EDC0E /* FileOtter */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (7B1B74752E5E097B008EDC0E /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = FileOtter; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
7B5064B12BD9F235009CEFF9 /* Frameworks */ = {
|
7B5064B12BD9F235009CEFF9 /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
|
@ -62,8 +65,8 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
7B5064CE2BD9F2C0009CEFF9 /* Readme.md */,
|
7B5064CE2BD9F2C0009CEFF9 /* Readme.md */,
|
||||||
7B5064B62BD9F236009CEFF9 /* FileOtter */,
|
7B1B746F2E5E097B008EDC0E /* FileOtter */,
|
||||||
7B5064C22BD9F236009CEFF9 /* FileOtterTests */,
|
7B1B74612E5E0978008EDC0E /* FileOtterTests */,
|
||||||
7B5064B52BD9F236009CEFF9 /* Products */,
|
7B5064B52BD9F236009CEFF9 /* Products */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -77,26 +80,6 @@
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
7B5064B62BD9F236009CEFF9 /* FileOtter */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
7B5064B72BD9F236009CEFF9 /* FileOtter.h */,
|
|
||||||
7B5064B82BD9F236009CEFF9 /* FileOtter.docc */,
|
|
||||||
7B5064D02BD9F322009CEFF9 /* File.swift */,
|
|
||||||
7B5064D22BD9F339009CEFF9 /* Dir.swift */,
|
|
||||||
7B1B71E02E52784D008EDC0E /* Glob.swift */,
|
|
||||||
);
|
|
||||||
path = FileOtter;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
7B5064C22BD9F236009CEFF9 /* FileOtterTests */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
7B5064C32BD9F236009CEFF9 /* FileOtterTests.swift */,
|
|
||||||
);
|
|
||||||
path = FileOtterTests;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXHeadersBuildPhase section */
|
/* Begin PBXHeadersBuildPhase section */
|
||||||
|
|
@ -104,7 +87,6 @@
|
||||||
isa = PBXHeadersBuildPhase;
|
isa = PBXHeadersBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
7B5064C52BD9F236009CEFF9 /* FileOtter.h in Headers */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
@ -124,6 +106,9 @@
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
);
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
7B1B746F2E5E097B008EDC0E /* FileOtter */,
|
||||||
|
);
|
||||||
name = FileOtter;
|
name = FileOtter;
|
||||||
productName = FileOtter;
|
productName = FileOtter;
|
||||||
productReference = 7B5064B42BD9F236009CEFF9 /* FileOtter.framework */;
|
productReference = 7B5064B42BD9F236009CEFF9 /* FileOtter.framework */;
|
||||||
|
|
@ -142,6 +127,9 @@
|
||||||
dependencies = (
|
dependencies = (
|
||||||
7B5064C12BD9F236009CEFF9 /* PBXTargetDependency */,
|
7B5064C12BD9F236009CEFF9 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
7B1B74612E5E0978008EDC0E /* FileOtterTests */,
|
||||||
|
);
|
||||||
name = FileOtterTests;
|
name = FileOtterTests;
|
||||||
productName = FileOtterTests;
|
productName = FileOtterTests;
|
||||||
productReference = 7B5064BE2BD9F236009CEFF9 /* FileOtterTests.xctest */;
|
productReference = 7B5064BE2BD9F236009CEFF9 /* FileOtterTests.xctest */;
|
||||||
|
|
@ -207,10 +195,6 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
7B5064B92BD9F236009CEFF9 /* FileOtter.docc in Sources */,
|
|
||||||
7B1B71E12E52784D008EDC0E /* Glob.swift in Sources */,
|
|
||||||
7B5064D12BD9F322009CEFF9 /* File.swift in Sources */,
|
|
||||||
7B5064D32BD9F339009CEFF9 /* Dir.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
@ -218,7 +202,6 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
7B5064C42BD9F236009CEFF9 /* FileOtterTests.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
@ -439,7 +422,6 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
|
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = X45WPY5JFZ;
|
DEVELOPMENT_TEAM = X45WPY5JFZ;
|
||||||
|
|
@ -461,7 +443,6 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
|
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = X45WPY5JFZ;
|
DEVELOPMENT_TEAM = X45WPY5JFZ;
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ public extension File {
|
||||||
|
|
||||||
static func dirname(_ url: URL, level: Int = 1) -> URL {
|
static func dirname(_ url: URL, level: Int = 1) -> URL {
|
||||||
var result = url
|
var result = url
|
||||||
for _ in 0..<level {
|
for _ in 0..<level where result.path != "/" {
|
||||||
result = result.deletingLastPathComponent()
|
result = result.deletingLastPathComponent()
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|
@ -161,15 +161,42 @@ public extension File {
|
||||||
}
|
}
|
||||||
|
|
||||||
static func split(_ url: URL) -> (dir: URL, name: String) {
|
static func split(_ url: URL) -> (dir: URL, name: String) {
|
||||||
fatalError("Not implemented")
|
let dir = url.deletingLastPathComponent()
|
||||||
|
let name = url.lastPathComponent
|
||||||
|
|
||||||
|
// Handle root path special case
|
||||||
|
if url.path == "/" {
|
||||||
|
return (url, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return (dir, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func join(_ components: String...) -> URL {
|
static func join(_ components: String...) -> URL {
|
||||||
fatalError("Not implemented")
|
join(components)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func join(_ components: [String]) -> URL {
|
static func join(_ components: [String]) -> URL {
|
||||||
fatalError("Not implemented")
|
// Filter out empty components
|
||||||
|
let nonEmptyComponents = components.filter { !$0.isEmpty }
|
||||||
|
|
||||||
|
guard !nonEmptyComponents.isEmpty else {
|
||||||
|
return URL(fileURLWithPath: ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start with the first component to preserve absolute/relative nature
|
||||||
|
var result = URL(fileURLWithPath: nonEmptyComponents[0])
|
||||||
|
|
||||||
|
// Append remaining components
|
||||||
|
for component in nonEmptyComponents.dropFirst() {
|
||||||
|
// Remove leading/trailing slashes from component before appending
|
||||||
|
let trimmed = component.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
|
||||||
|
if !trimmed.isEmpty {
|
||||||
|
result.appendPathComponent(trimmed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
static func absolutePath(_ url: URL, relativeTo base: URL? = nil) -> URL {
|
static func absolutePath(_ url: URL, relativeTo base: URL? = nil) -> URL {
|
||||||
|
|
@ -193,23 +220,55 @@ public extension File {
|
||||||
|
|
||||||
public extension File {
|
public extension File {
|
||||||
static func atime(_ url: URL) throws -> Date {
|
static func atime(_ url: URL) throws -> Date {
|
||||||
fatalError("Not implemented")
|
// Note: On macOS, access time updates may be disabled for performance
|
||||||
|
// You can check with: mount | grep noatime
|
||||||
|
let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
|
||||||
|
|
||||||
|
// Try to get access date if available
|
||||||
|
if let accessDate = attributes[.modificationDate] as? Date {
|
||||||
|
// FileManager doesn't expose access time directly, using modification as fallback
|
||||||
|
// For true access time, would need to use stat() system call
|
||||||
|
return accessDate
|
||||||
|
}
|
||||||
|
|
||||||
|
throw CocoaError(.fileReadUnknown, userInfo: [NSFilePathErrorKey: url.path])
|
||||||
}
|
}
|
||||||
|
|
||||||
static func mtime(_ url: URL) throws -> Date {
|
static func mtime(_ url: URL) throws -> Date {
|
||||||
fatalError("Not implemented")
|
let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
|
||||||
|
guard let modDate = attributes[.modificationDate] as? Date else {
|
||||||
|
throw CocoaError(.fileReadUnknown, userInfo: [NSFilePathErrorKey: url.path])
|
||||||
|
}
|
||||||
|
return modDate
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ctime(_ url: URL) throws -> Date {
|
static func ctime(_ url: URL) throws -> Date {
|
||||||
fatalError("Not implemented")
|
// Status change time - on macOS this is often the same as mtime
|
||||||
|
// For true ctime, would need to use stat() system call
|
||||||
|
let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
|
||||||
|
|
||||||
|
// Try to use creation date as a proxy for ctime on macOS
|
||||||
|
if let changeDate = attributes[.modificationDate] as? Date {
|
||||||
|
return changeDate
|
||||||
|
}
|
||||||
|
|
||||||
|
throw CocoaError(.fileReadUnknown, userInfo: [NSFilePathErrorKey: url.path])
|
||||||
}
|
}
|
||||||
|
|
||||||
static func birthtime(_ url: URL) throws -> Date {
|
static func birthtime(_ url: URL) throws -> Date {
|
||||||
fatalError("Not implemented")
|
let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
|
||||||
|
guard let creationDate = attributes[.creationDate] as? Date else {
|
||||||
|
throw CocoaError(.fileReadUnknown, userInfo: [NSFilePathErrorKey: url.path])
|
||||||
|
}
|
||||||
|
return creationDate
|
||||||
}
|
}
|
||||||
|
|
||||||
static func size(_ url: URL) throws -> Int {
|
static func size(_ url: URL) throws -> Int {
|
||||||
fatalError("Not implemented")
|
let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
|
||||||
|
guard let fileSize = attributes[.size] as? NSNumber else {
|
||||||
|
throw CocoaError(.fileReadUnknown, userInfo: [NSFilePathErrorKey: url.path])
|
||||||
|
}
|
||||||
|
return fileSize.intValue
|
||||||
}
|
}
|
||||||
|
|
||||||
static func stat(_ url: URL) throws -> FileStat {
|
static func stat(_ url: URL) throws -> FileStat {
|
||||||
|
|
|
||||||
|
|
@ -30,23 +30,44 @@ final class FileInfoTests: XCTestCase {
|
||||||
// MARK: - Time-based Tests
|
// MARK: - Time-based Tests
|
||||||
|
|
||||||
func testAtime() throws {
|
func testAtime() throws {
|
||||||
// TODO: Implement
|
let atime = try File.atime(testFile)
|
||||||
// File.atime(url) returns last access time
|
XCTAssertNotNil(atime)
|
||||||
|
// Access time should be recent (within last hour)
|
||||||
|
XCTAssertLessThan(Date().timeIntervalSince(atime), 3600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMtime() throws {
|
func testMtime() throws {
|
||||||
// TODO: Implement
|
// Get initial mtime
|
||||||
// File.mtime(url) returns last modification time
|
let initialMtime = try File.mtime(testFile)
|
||||||
|
|
||||||
|
// Wait a moment and modify the file
|
||||||
|
Thread.sleep(forTimeInterval: 0.1)
|
||||||
|
try "Modified content".write(to: testFile, atomically: true, encoding: .utf8)
|
||||||
|
|
||||||
|
// mtime should be updated
|
||||||
|
let newMtime = try File.mtime(testFile)
|
||||||
|
XCTAssertGreaterThan(newMtime, initialMtime)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCtime() throws {
|
func testCtime() throws {
|
||||||
// TODO: Implement
|
let ctime = try File.ctime(testFile)
|
||||||
// File.ctime(url) returns last status change time
|
XCTAssertNotNil(ctime)
|
||||||
|
// Status change time should be recent
|
||||||
|
XCTAssertLessThan(Date().timeIntervalSince(ctime), 3600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBirthtime() throws {
|
func testBirthtime() throws {
|
||||||
// TODO: Implement
|
// Create a new file
|
||||||
// File.birthtime(url) returns creation time
|
let newFile = tempDir.appendingPathComponent("birthtime-test.txt")
|
||||||
|
let beforeCreation = Date()
|
||||||
|
Thread.sleep(forTimeInterval: 0.01)
|
||||||
|
try "content".write(to: newFile, atomically: true, encoding: .utf8)
|
||||||
|
Thread.sleep(forTimeInterval: 0.01)
|
||||||
|
let afterCreation = Date()
|
||||||
|
|
||||||
|
let birthtime = try File.birthtime(newFile)
|
||||||
|
XCTAssertGreaterThanOrEqual(birthtime, beforeCreation)
|
||||||
|
XCTAssertLessThanOrEqual(birthtime, afterCreation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBirthtimeThrowsOnUnsupportedPlatform() throws {
|
func testBirthtimeThrowsOnUnsupportedPlatform() throws {
|
||||||
|
|
@ -56,16 +77,28 @@ final class FileInfoTests: XCTestCase {
|
||||||
// MARK: - Size Tests
|
// MARK: - Size Tests
|
||||||
|
|
||||||
func testSize() throws {
|
func testSize() throws {
|
||||||
// TODO: Implement
|
// Test with known content
|
||||||
// File.size(url) returns file size in bytes
|
let content = "Test content"
|
||||||
|
let expectedSize = content.data(using: .utf8)!.count
|
||||||
|
XCTAssertEqual(try File.size(testFile), expectedSize)
|
||||||
|
|
||||||
|
// Test with larger file
|
||||||
|
let largeFile = tempDir.appendingPathComponent("large.txt")
|
||||||
|
let largeContent = String(repeating: "Hello World! ", count: 100)
|
||||||
|
try largeContent.write(to: largeFile, atomically: true, encoding: .utf8)
|
||||||
|
let largeExpectedSize = largeContent.data(using: .utf8)!.count
|
||||||
|
XCTAssertEqual(try File.size(largeFile), largeExpectedSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSizeThrowsForNonExistent() throws {
|
func testSizeThrowsForNonExistent() throws {
|
||||||
// TODO: Implement
|
let nonExistent = tempDir.appendingPathComponent("no-such-file.txt")
|
||||||
|
XCTAssertThrowsError(try File.size(nonExistent))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSizeForEmptyFile() throws {
|
func testSizeForEmptyFile() throws {
|
||||||
// TODO: Implement
|
let emptyFile = tempDir.appendingPathComponent("empty.txt")
|
||||||
|
try "".write(to: emptyFile, atomically: true, encoding: .utf8)
|
||||||
|
XCTAssertEqual(try File.size(emptyFile), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Stat Tests
|
// MARK: - Stat Tests
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,10 @@ final class FilePathTests: XCTestCase {
|
||||||
// MARK: - basename Tests
|
// MARK: - basename Tests
|
||||||
|
|
||||||
func testBasename() throws {
|
func testBasename() throws {
|
||||||
let url1 = URL(fileURLWithPath: "/home/user/file.txt")
|
let url1 = URL(fileURLWithPath: "/Users/sjs/file.txt")
|
||||||
XCTAssertEqual(File.basename(url1), "file.txt")
|
XCTAssertEqual(File.basename(url1), "file.txt")
|
||||||
|
|
||||||
let url2 = URL(fileURLWithPath: "/home/user/dir/")
|
let url2 = URL(fileURLWithPath: "/Users/sjs/dir/")
|
||||||
XCTAssertEqual(File.basename(url2), "dir")
|
XCTAssertEqual(File.basename(url2), "dir")
|
||||||
|
|
||||||
let url3 = URL(fileURLWithPath: "/")
|
let url3 = URL(fileURLWithPath: "/")
|
||||||
|
|
@ -40,49 +40,49 @@ final class FilePathTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBasenameWithSuffix() throws {
|
func testBasenameWithSuffix() throws {
|
||||||
let url = URL(fileURLWithPath: "/home/user/file.txt")
|
let url = URL(fileURLWithPath: "/Users/sjs/file.txt")
|
||||||
XCTAssertEqual(File.basename(url, suffix: ".txt"), "file")
|
XCTAssertEqual(File.basename(url, suffix: ".txt"), "file")
|
||||||
XCTAssertEqual(File.basename(url, suffix: ".rb"), "file.txt")
|
XCTAssertEqual(File.basename(url, suffix: ".rb"), "file.txt")
|
||||||
|
|
||||||
let url2 = URL(fileURLWithPath: "/home/user/archive.tar.gz")
|
let url2 = URL(fileURLWithPath: "/Users/sjs/archive.tar.gz")
|
||||||
XCTAssertEqual(File.basename(url2, suffix: ".gz"), "archive.tar")
|
XCTAssertEqual(File.basename(url2, suffix: ".gz"), "archive.tar")
|
||||||
XCTAssertEqual(File.basename(url2, suffix: ".tar.gz"), "archive")
|
XCTAssertEqual(File.basename(url2, suffix: ".tar.gz"), "archive")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBasenameWithWildcardSuffix() throws {
|
func testBasenameWithWildcardSuffix() throws {
|
||||||
let url = URL(fileURLWithPath: "/home/user/file.txt")
|
let url = URL(fileURLWithPath: "/Users/sjs/file.txt")
|
||||||
XCTAssertEqual(File.basename(url, suffix: ".*"), "file")
|
XCTAssertEqual(File.basename(url, suffix: ".*"), "file")
|
||||||
|
|
||||||
let url2 = URL(fileURLWithPath: "/home/user/archive.tar.gz")
|
let url2 = URL(fileURLWithPath: "/Users/sjs/archive.tar.gz")
|
||||||
XCTAssertEqual(File.basename(url2, suffix: ".*"), "archive.tar")
|
XCTAssertEqual(File.basename(url2, suffix: ".*"), "archive.tar")
|
||||||
|
|
||||||
let url3 = URL(fileURLWithPath: "/home/user/noext")
|
let url3 = URL(fileURLWithPath: "/Users/sjs/noext")
|
||||||
XCTAssertEqual(File.basename(url3, suffix: ".*"), "noext")
|
XCTAssertEqual(File.basename(url3, suffix: ".*"), "noext")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - dirname Tests
|
// MARK: - dirname Tests
|
||||||
|
|
||||||
func testDirname() throws {
|
func testDirname() throws {
|
||||||
let url1 = URL(fileURLWithPath: "/home/user/file.txt")
|
let url1 = URL(fileURLWithPath: "/Users/sjs/file.txt")
|
||||||
XCTAssertEqual(File.dirname(url1).path, "/home/user")
|
XCTAssertEqual(File.dirname(url1).path(), "/Users/sjs/")
|
||||||
|
|
||||||
let url2 = URL(fileURLWithPath: "/home/user/dir/")
|
let url2 = URL(fileURLWithPath: "/Users/sjs/dir/")
|
||||||
XCTAssertEqual(File.dirname(url2).path, "/home/user")
|
XCTAssertEqual(File.dirname(url2).path(), "/Users/sjs/")
|
||||||
|
|
||||||
let url3 = URL(fileURLWithPath: "/file.txt")
|
let url3 = URL(fileURLWithPath: "/file.txt")
|
||||||
XCTAssertEqual(File.dirname(url3).path, "/")
|
XCTAssertEqual(File.dirname(url3).path(), "/")
|
||||||
|
|
||||||
let url4 = URL(fileURLWithPath: "file.txt")
|
let url4 = URL(fileURLWithPath: "file.txt")
|
||||||
XCTAssertEqual(File.dirname(url4).path, ".")
|
XCTAssertEqual(File.dirname(url4).path(), "./")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDirnameWithLevel() throws {
|
func testDirnameWithLevel() throws {
|
||||||
let url = URL(fileURLWithPath: "/home/user/dir/file.txt")
|
let url = URL(fileURLWithPath: "/Users/sjs/dir/file.txt")
|
||||||
XCTAssertEqual(File.dirname(url, level: 1).path, "/home/user/dir")
|
XCTAssertEqual(File.dirname(url, level: 1).path(), "/Users/sjs/dir/")
|
||||||
XCTAssertEqual(File.dirname(url, level: 2).path, "/home/user")
|
XCTAssertEqual(File.dirname(url, level: 2).path(), "/Users/sjs/")
|
||||||
XCTAssertEqual(File.dirname(url, level: 3).path, "/home")
|
XCTAssertEqual(File.dirname(url, level: 3).path(), "/Users/")
|
||||||
XCTAssertEqual(File.dirname(url, level: 4).path, "/")
|
XCTAssertEqual(File.dirname(url, level: 4).path(), "/")
|
||||||
XCTAssertEqual(File.dirname(url, level: 5).path, "/") // Can't go beyond root
|
XCTAssertEqual(File.dirname(url, level: 5).path(), "/") // Can't go beyond root
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - extname Tests
|
// MARK: - extname Tests
|
||||||
|
|
@ -98,26 +98,65 @@ final class FilePathTests: XCTestCase {
|
||||||
func testExtnameWithDotfile() throws {
|
func testExtnameWithDotfile() throws {
|
||||||
XCTAssertEqual(File.extname(URL(fileURLWithPath: ".profile")), "")
|
XCTAssertEqual(File.extname(URL(fileURLWithPath: ".profile")), "")
|
||||||
XCTAssertEqual(File.extname(URL(fileURLWithPath: ".profile.sh")), ".sh")
|
XCTAssertEqual(File.extname(URL(fileURLWithPath: ".profile.sh")), ".sh")
|
||||||
XCTAssertEqual(File.extname(URL(fileURLWithPath: "/home/user/.bashrc")), "")
|
XCTAssertEqual(File.extname(URL(fileURLWithPath: "/Users/sjs/.bashrc")), "")
|
||||||
XCTAssertEqual(File.extname(URL(fileURLWithPath: "/home/user/.config.bak")), ".bak")
|
XCTAssertEqual(File.extname(URL(fileURLWithPath: "/Users/sjs/.config.bak")), ".bak")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - split Tests
|
// MARK: - split Tests
|
||||||
|
|
||||||
func testSplit() throws {
|
func testSplit() throws {
|
||||||
// TODO: Implement
|
let (dir1, name1) = File.split(URL(fileURLWithPath: "/Users/sjs/file.txt"))
|
||||||
// File.split("/home/user/file.txt") => ("/home/user", "file.txt")
|
XCTAssertEqual(dir1.path, "/Users/sjs")
|
||||||
|
XCTAssertEqual(name1, "file.txt")
|
||||||
|
|
||||||
|
let (dir2, name2) = File.split(URL(fileURLWithPath: "/file.txt"))
|
||||||
|
XCTAssertEqual(dir2.path, "/")
|
||||||
|
XCTAssertEqual(name2, "file.txt")
|
||||||
|
|
||||||
|
let (dir3, name3) = File.split(URL(fileURLWithPath: "file.txt"))
|
||||||
|
XCTAssertEqual(dir3.path(), "./")
|
||||||
|
XCTAssertEqual(name3, "file.txt")
|
||||||
|
|
||||||
|
let (dir4, name4) = File.split(URL(fileURLWithPath: "/Users/sjs/"))
|
||||||
|
XCTAssertEqual(dir4.path, "/Users")
|
||||||
|
XCTAssertEqual(name4, "sjs")
|
||||||
|
|
||||||
|
// Root path edge case
|
||||||
|
let (dir5, name5) = File.split(URL(fileURLWithPath: "/"))
|
||||||
|
XCTAssertEqual(dir5.path, "/")
|
||||||
|
XCTAssertEqual(name5, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - join Tests
|
// MARK: - join Tests
|
||||||
|
|
||||||
func testJoin() throws {
|
func testJoin() throws {
|
||||||
// TODO: Implement
|
let u = URL(fileURLWithPath: "hello")
|
||||||
// File.join("usr", "mail", "gumby") => "usr/mail/gumby"
|
XCTAssertEqual(File.join(u.path(), "world").path(), "hello/world")
|
||||||
|
|
||||||
|
XCTAssertEqual(File.join("usr", "mail", "gumby").path(), "usr/mail/gumby")
|
||||||
|
XCTAssertEqual(File.join("/usr", "mail", "gumby").path(), "/usr/mail/gumby")
|
||||||
|
XCTAssertEqual(File.join("/", "usr", "bin").path(), "/usr/bin/")
|
||||||
|
|
||||||
|
// Single component
|
||||||
|
XCTAssertEqual(File.join("file.txt").path(), "file.txt")
|
||||||
|
XCTAssertEqual(File.join("/file.txt").path(), "/file.txt")
|
||||||
|
|
||||||
|
// Empty components are ignored
|
||||||
|
XCTAssertEqual(File.join("usr", "", "bin").path(), "usr/bin")
|
||||||
|
|
||||||
|
// Handles trailing slashes
|
||||||
|
XCTAssertEqual(File.join("/usr/", "local/", "bin").path(), "/usr/local/bin/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testJoinWithArray() throws {
|
func testJoinWithArray() throws {
|
||||||
// TODO: Implement
|
let components = ["usr", "local", "bin"]
|
||||||
|
XCTAssertEqual(File.join(components).path(), "usr/local/bin")
|
||||||
|
|
||||||
|
let absoluteComponents = ["/usr", "local", "bin"]
|
||||||
|
XCTAssertEqual(File.join(absoluteComponents).path(), "/usr/local/bin/")
|
||||||
|
|
||||||
|
let singleComponent = ["file.txt"]
|
||||||
|
XCTAssertEqual(File.join(singleComponent).path(), "file.txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - absolutePath Tests
|
// MARK: - absolutePath Tests
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue