diff --git a/.gitignore b/.gitignore index d32735f..a0090b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,67 +1,2 @@ -# Xcode -# -# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore - -## Build generated -build/ -DerivedData/ - -## Various settings -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata/ - -## Other -*.moved-aside -*.xcuserstate -*.xcscmblueprint - -## Obj-C/Swift specific -*.hmap -*.ipa -*.dSYM.zip -*.dSYM - -## Playgrounds -timeline.xctimeline -playground.xcworkspace - -# Swift Package Manager -# -# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. -# Packages/ -.build/ -.swiftpm/ - -# CocoaPods -# -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control -# -# Pods/ - -# Carthage -# -# Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts - -Carthage/Build - -# fastlane -# -# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the -# screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md - -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots -fastlane/test_output +# macOS +.DS_Store diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/andrew.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/andrew.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..8a8324b Binary files /dev/null and b/.swiftpm/xcode/package.xcworkspace/xcuserdata/andrew.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/.swiftpm/xcode/xcuserdata/andrew.xcuserdatad/xcschemes/xcschememanagement.plist b/.swiftpm/xcode/xcuserdata/andrew.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..8b91339 --- /dev/null +++ b/.swiftpm/xcode/xcuserdata/andrew.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + Files.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/Package.swift b/Package.swift index cc7dc34..0557dbb 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.0 +// swift-tools-version:6.2 /** * Files diff --git a/Sources/Files.swift b/Sources/Files.swift index aede19e..1b94cf2 100644 --- a/Sources/Files.swift +++ b/Sources/Files.swift @@ -47,6 +47,17 @@ public protocol Location: Equatable, CustomStringConvertible { init(storage: Storage) } +/// Protocol adopted by locations that represent files. +public protocol FileLocation: Location {} + +public extension FileLocation { + /// The file size, in bytes. Returns `0` if the size couldn't be determined. + var size: UInt64 { + let number = storage.attributes[.size] as? NSNumber + return number?.uint64Value ?? 0 + } +} + public extension Location { static func ==(lhs: Self, rhs: Self) -> Bool { return lhs.storage.path == rhs.storage.path @@ -79,6 +90,19 @@ public extension Location { return components.dropLast().joined() } + /// Disco: The name of the location, excluding its `extension`. + var baseName: String { + get { + let components = name.split(separator: ".") + guard components.count > 1 else { return name } + return components.dropLast().joined() + } + set { + try? rename(to: newValue, keepExtension: true) + } + } + + /// The file extension of the item at the location. var `extension`: String? { let components = name.split(separator: ".") @@ -364,7 +388,7 @@ private extension Storage where LocationType == Folder { /// Type that represents a file on disk. You can either reference an existing /// file by initializing an instance with a `path`, or you can create new files /// using the various `createFile...` APIs available on `Folder`. -public struct File: Location { +public struct File: Location, FileLocation { public let storage: Storage public init(storage: Storage) { @@ -406,9 +430,15 @@ public extension File { func append(_ data: Data) throws { do { let handle = try FileHandle(forWritingTo: url) - handle.seekToEndOfFile() - handle.write(data) - handle.closeFile() + if #available(macOS 10.15.4, *) { + _ = try handle.seekToEnd() + try handle.write(contentsOf: data) + try handle.close() + } else { + handle.seekToEndOfFile() + handle.write(data) + handle.closeFile() + } } catch { throw WriteError(path: path, reason: .writeFailed(error)) } @@ -466,7 +496,7 @@ import AppKit public extension File { /// Open the file. func open() { - NSWorkspace.shared.openFile(path) + NSWorkspace.shared.open(url) } } diff --git a/Tests/FilesTests/FilesTests.swift b/Tests/FilesTests/FilesTests.swift index 040f60e..b32ae73 100644 --- a/Tests/FilesTests/FilesTests.swift +++ b/Tests/FilesTests/FilesTests.swift @@ -197,6 +197,14 @@ class FilesTests: XCTestCase { try assert(nonIntFile.readAsInt(), throwsErrorOfType: ReadError.self) } } + + func testFileSize() { + performTest { + let data = Data("Hello".utf8) + let file = try folder.createFile(named: "size.txt", contents: data) + XCTAssertEqual(file.size, UInt64(data.count)) + } + } func testRenamingFile() { performTest {