| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381 |
- //
- // Path.swift
- // FileKit
- //
- // The MIT License (MIT)
- //
- // Copyright (c) 2015-2017 Nikolai Vazquez
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- //
- // swiftlint:disable file_length
- //
- import Foundation
- /// A representation of a filesystem path.
- ///
- /// An Path instance lets you manage files in a much easier way.
- ///
- public struct Path {
- // MARK: - Static Methods and Properties
- /// The standard separator for path components.
- public static let separator = "/"
- /// The root path.
- public static let root = Path(separator)
- /// The path of the program's current working directory.
- public static var current: Path {
- get {
- return Path(FileManager.default.currentDirectoryPath)
- }
- set {
- FileManager.default.changeCurrentDirectoryPath(newValue._safeRawValue)
- }
- }
- /// The paths of the mounted volumes available.
- public static func volumes(_ options: FileManager.VolumeEnumerationOptions = []) -> [Path] {
- let volumes = FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: nil,
- options: options)
- return (volumes ?? []).compactMap { Path(url: $0) }
- }
- // MARK: - Properties
- fileprivate var _fmWraper = _FMWrapper()
- fileprivate class _FMWrapper {
- let unsafeFileManager = FileManager()
- weak var delegate: FileManagerDelegate?
- /// Safe way to use fileManager
- var fileManager: FileManager {
- // if delegate == nil {
- // print("\n\nDelegate is nil\n\n")
- // }
- unsafeFileManager.delegate = delegate
- return unsafeFileManager
- }
- }
- /// The delegate for the file manager used by the path.
- ///
- /// **Note:** no strong reference stored in path, so make sure keep the delegate or it will be `nil`
- public var fileManagerDelegate: FileManagerDelegate? {
- get {
- return _fmWraper.delegate
- }
- set {
- if !isKnownUniquelyReferenced(&_fmWraper) {
- _fmWraper = _FMWrapper()
- }
- _fmWraper.delegate = newValue
- }
- }
- /// The stored path string value.
- public fileprivate(set) var rawValue: String
- /// The non-empty path string value. For internal use only.
- ///
- /// Some NSAPI may throw `NSInvalidArgumentException` when path is `""`, which can't catch in swift
- /// and cause crash
- internal var _safeRawValue: String {
- return rawValue.isEmpty ? "." : rawValue
- }
- /// The standardized path string value
- public var standardRawValue: String {
- return (self.rawValue as NSString).standardizingPath
- }
- /// The standardized path string value without expanding tilde
- public var standardRawValueWithTilde: String {
- let comps = components
- if comps.isEmpty {
- return ""
- } else {
- return self[comps.count - 1].rawValue
- }
- }
- /// The components of the path.
- ///
- /// Return [] if path is `.` or `""`
- public var components: [Path] {
- if rawValue == "" || rawValue == "." {
- return []
- }
- if isAbsolute {
- return (absolute.rawValue as NSString).pathComponents.enumerated().compactMap {
- (($0 == 0 || $1 != "/") && $1 != ".") ? Path($1) : nil
- }
- } else {
- let comps = (self.rawValue as NSString).pathComponents.enumerated()
- // remove extraneous `/` and `.`
- let cleanComps = comps.compactMap {
- (($0 == 0 || $1 != "/") && $1 != ".") ? Path($1) : nil
- }
- return _cleanComponents(cleanComps)
- }
- }
- /// resolving `..` if possible
- fileprivate func _cleanComponents(_ comps: [Path]) -> [Path] {
- var isContinue = false
- let count = comps.count
- let cleanComps: [Path] = comps.enumerated().compactMap {
- if ($1.rawValue != ".." && $0 < count - 1 && comps[$0 + 1].rawValue == "..") || ($1.rawValue == ".." && $0 > 0 && comps[$0 - 1].rawValue != "..") {
- isContinue = true
- return nil
- } else {
- return $1
- }
- }
- return isContinue ? _cleanComponents(cleanComps) : cleanComps
- }
- /// The name of the file at `self`.
- public var fileName: String {
- return self.absolute.components.last?.rawValue ?? ""
- }
- /// The name of the file without extension.
- public var fileNameWithoutExtension: String {
- return ((rawValue as NSString).lastPathComponent as NSString).deletingPathExtension
- }
- /// A new path created by removing extraneous components from the path.
- public var standardized: Path {
- return Path((self.rawValue as NSString).standardizingPath)
- }
- /// The standardized path string value without expanding tilde
- public var standardWithTilde: Path {
- let comps = components
- if comps.isEmpty {
- return Path("")
- } else {
- return self[comps.count - 1]
- }
- }
- /// A new path created by resolving all symlinks and standardizing the path.
- public var resolved: Path {
- return Path((self.rawValue as NSString).resolvingSymlinksInPath)
- }
- /// A new path created by making the path absolute.
- ///
- /// - Returns: If `self` begins with "/", then the standardized path is
- /// returned. Otherwise, the path is assumed to be relative to
- /// the current working directory and the standardized version of
- /// the path added to the current working directory is returned.
- ///
- public var absolute: Path {
- return self.isAbsolute
- ? self.standardized
- : (Path.current + self).standardized
- }
- /// Returns `true` if the path is equal to "/".
- public var isRoot: Bool {
- return resolved.rawValue == Path.separator
- }
- /// Returns `true` if the path begins with "/".
- public var isAbsolute: Bool {
- return rawValue.hasPrefix(Path.separator)
- }
- /// Returns `true` if the path does not begin with "/".
- public var isRelative: Bool {
- return !isAbsolute
- }
- /// Returns `true` if a file or directory exists at the path.
- ///
- /// this method does follow links.
- public var exists: Bool {
- return _fmWraper.fileManager.fileExists(atPath: _safeRawValue)
- }
- /// Returns `true` if a file or directory or symbolic link exists at the path
- ///
- /// this method does **not** follow links.
- // public var existsOrLink: Bool {
- // return self.isSymbolicLink || _fmWraper.fileManager.fileExistsAtPath(_safeRawValue)
- // }
- /// Returns `true` if the current process has write privileges for the file
- /// at the path.
- ///
- /// this method does follow links.
- public var isWritable: Bool {
- return _fmWraper.fileManager.isWritableFile(atPath: _safeRawValue)
- }
- /// Returns `true` if the current process has read privileges for the file
- /// at the path.
- ///
- /// this method does follow links.
- public var isReadable: Bool {
- return _fmWraper.fileManager.isReadableFile(atPath: _safeRawValue)
- }
- /// Returns `true` if the current process has execute privileges for the
- /// file at the path.
- ///
- /// this method does follow links.
- public var isExecutable: Bool {
- return _fmWraper.fileManager.isExecutableFile(atPath: _safeRawValue)
- }
- /// Returns `true` if the current process has delete privileges for the file
- /// at the path.
- ///
- /// this method does **not** follow links.
- public var isDeletable: Bool {
- return _fmWraper.fileManager.isDeletableFile(atPath: _safeRawValue)
- }
- /// Returns `true` if the path points to a directory.
- ///
- /// this method does follow links.
- public var isDirectory: Bool {
- var isDirectory: ObjCBool = false
- return _fmWraper.fileManager.fileExists(atPath: _safeRawValue, isDirectory: &isDirectory)
- && isDirectory.boolValue
- }
- /// Returns `true` if the path is a directory file.
- ///
- /// this method does not follow links.
- public var isDirectoryFile: Bool {
- return fileType == .directory
- }
- /// Returns `true` if the path is a symbolic link.
- ///
- /// this method does not follow links.
- public var isSymbolicLink: Bool {
- return fileType == .symbolicLink
- }
- /// Returns `true` if the path is a regular file.
- ///
- /// this method does not follow links.
- public var isRegular: Bool {
- return fileType == .regular
- }
- /// Returns `true` if the path exists any fileType item.
- ///
- /// this method does not follow links.
- public var isAny: Bool {
- return fileType != nil
- }
- /// The path's extension.
- public var pathExtension: String {
- get {
- return (rawValue as NSString).pathExtension
- }
- set {
- let path = (rawValue as NSString).deletingPathExtension
- rawValue = path + ".\(newValue)"
- }
- }
- mutating func appendToExtension(_ toAppend: String) {
- self.pathExtension = pathExtension + toAppend
- }
- /// The path's parent path.
- public var parent: Path {
- if isAbsolute {
- return Path((absolute.rawValue as NSString).deletingLastPathComponent)
- } else {
- let comps = components
- if comps.isEmpty {
- return Path("..")
- } else if comps.last!.rawValue == ".." {
- return ".." + self[comps.count - 1]
- } else if comps.count == 1 {
- return Path("")
- } else {
- return self[comps.count - 2]
- }
- }
- }
- // MARK: - Initialization
- /// Initializes a path to root.
- public init() {
- self = .root
- }
- /// Initializes a path to the string's value.
- public init(_ path: String, expandingTilde: Bool = false) {
- // empty path may cause crash
- if expandingTilde {
- self.rawValue = (path as NSString).expandingTildeInPath
- } else {
- self.rawValue = path
- }
- }
- }
- extension Path {
- // MARK: - Methods
- /// Runs `closure` with `self` as its current working directory.
- ///
- /// - Parameter closure: The block to run while `Path.Current` is changed.
- ///
- public func changeDirectory(_ closure: () throws -> Void) rethrows {
- let previous = Path.current
- defer { Path.current = previous }
- if _fmWraper.fileManager.changeCurrentDirectoryPath(_safeRawValue) {
- try closure()
- }
- }
- /// Returns the path's children paths.
- ///
- /// - Parameter recursive: Whether to obtain the paths recursively.
- /// Default value is `false`.
- ///
- /// this method follow links if recursive is `false`, otherwise not follow links
- public func children(recursive: Bool = false) -> [Path] {
- let obtainFunc = recursive
- ? _fmWraper.fileManager.subpathsOfDirectory(atPath:)
- : _fmWraper.fileManager.contentsOfDirectory(atPath:)
- return (try? obtainFunc(_safeRawValue))?.map { self + Path($0) } ?? []
- }
- /// Returns true if `path` is a child of `self`.
- ///
- /// - Parameter recursive: Whether to check the paths recursively.
- /// Default value is `true`.
- ///
- public func isChildOfPath(_ path: Path, recursive: Bool = true) -> Bool {
- if !(isRelative && path.isRelative) && !(isAbsolute && path.isAbsolute) {
- return self.absolute.isChildOfPath(path.absolute)
- }
- if isRoot {
- return true
- }
- if recursive {
- return path.isAncestorOfPath(self)
- } else {
- return path.parent == self
- }
- }
- /// Returns true if `path` is a parent of `self`.
- ///
- /// Relative paths can't be compared return `false`. like `../../path1/path2` and `../path2`
- ///
- public func isAncestorOfPath(_ path: Path) -> Bool {
- if !(isRelative && path.isRelative) && !(isAbsolute && path.isAbsolute) {
- return self.absolute.isAncestorOfPath(path.absolute)
- }
- if path.isRoot {
- return true
- }
- if self != path && self.commonAncestor(path) == path {
- return true
- }
- return false
- }
- /// Returns the common ancestor between `self` and `path`.
- ///
- /// Relative path return the most precise path if possible
- ///
- public func commonAncestor(_ path: Path) -> Path {
- if !(isRelative && path.isRelative) && !(isAbsolute && path.isAbsolute) {
- return self.absolute.commonAncestor(path.absolute)
- }
- let selfComponents = self.components
- let pathComponents = path.components
- let minCount = Swift.min(selfComponents.count, pathComponents.count)
- var total = minCount
- for index in 0 ..< total
- where selfComponents[index].rawValue != pathComponents[index].rawValue {
- total = index
- break
- }
- let ancestorComponents = selfComponents[0..<total]
- let common = ancestorComponents.reduce("") { $0 + $1 }
- switch (self.relativePathType, path.relativePathType) {
- case (.absolute, .absolute), (.normal, .normal), (.normal, .current), (.current, .normal), (.current, .current):
- return common
- case (.normal, .parent), (.current, .parent), (.parent, .normal), (.parent, .current), (.parent, .parent):
- return Path("..")
- default:
- // count for prefix ".." in components
- var n1 = 0, n2 = 0
- for elem in selfComponents {
- if elem.rawValue == ".." {
- n1 += 1
- } else {
- break
- }
- }
- for elem in pathComponents {
- if elem.rawValue == ".." {
- n2 += 1
- } else {
- break
- }
- }
- if n1 == n2 {
- // paths like "../../common/path1" and "../../common/path2"
- return common
- } else { // paths like "../path" and "../../path2/path1"
- let maxCount = Swift.max(n1, n2)
- var dotPath: Path = ""
- for _ in 0..<maxCount {
- dotPath += ".."
- }
- return dotPath
- }
- }
- }
- /// Returns the relative path type.
- ///
- public var relativePathType: RelativePathType {
- if isAbsolute {
- return .absolute
- } else {
- let comp = self.components
- switch comp.first?.rawValue {
- case nil:
- return .current
- case ".."? where comp.count > 1:
- return .ancestor
- case ".."?:
- return .parent
- default:
- return .normal
- }
- }
- }
- /// Returns paths in `self` that match a condition.
- ///
- /// - Parameter searchDepth: How deep to search before exiting. A negative
- /// value will cause the search to exit only when
- /// every subdirectory has been searched through.
- /// Default value is `-1`.
- /// - Parameter condition: If `true`, the path is added.
- ///
- /// - Returns: An Array containing the paths in `self` that match the
- /// condition.
- ///
- public func find(searchDepth depth: Int = -1, condition: (Path) throws -> Bool) rethrows -> [Path] {
- return try self.find(searchDepth: depth) { path in
- try condition(path) ? path : nil
- }
- }
- /// Returns non-nil values for paths found in `self`.
- ///
- /// - Parameter searchDepth: How deep to search before exiting. A negative
- /// value will cause the search to exit only when
- /// every subdirectory has been searched through.
- /// Default value is `-1`.
- /// - Parameter transform: The transform run on each path found.
- ///
- /// - Returns: An Array containing the non-nil values for paths found in
- /// `self`.
- ///
- public func find<T>(searchDepth depth: Int = -1, transform: (Path) throws -> T?) rethrows -> [T] {
- return try self.children().reduce([]) { values, child in
- if let value = try transform(child) {
- return values + [value]
- } else if depth != 0 {
- return try values + child.find(searchDepth: depth - 1, transform: transform)
- } else {
- return values
- }
- }
- }
- // swiftlint:enable line_length
- /// Standardizes the path.
- public mutating func standardize() {
- self = self.standardized
- }
- /// Resolves the path's symlinks and standardizes it.
- public mutating func resolve() {
- self = self.resolved
- }
- /// Creates a symbolic link at a path that points to `self`.
- ///
- /// - Parameter path: The Path to which at which the link of the file at
- /// `self` will be created.
- /// If `path` exists and is a directory, then the link
- /// will be made inside of `path`. Otherwise, an error
- /// will be thrown.
- ///
- /// - Throws:
- /// `FileKitError.fileAlreadyExists`,
- /// `FileKitError.createSymlinkFail`
- ///
- public func symlinkFile(to path: Path) throws {
- // it's possible to create symbolic links to locations that do not yet exist.
- // guard self.exists else {
- // throw FileKitError.FileDoesNotExist(path: self)
- // }
- let linkPath = path.isDirectory ? path + self.fileName : path
- // Throws if linking to an existing non-directory file.
- guard !linkPath.isAny else {
- throw FileKitError.fileAlreadyExists(path: linkPath)
- }
- do {
- try _fmWraper.fileManager.createSymbolicLink(
- atPath: linkPath._safeRawValue, withDestinationPath: self._safeRawValue)
- } catch {
- throw FileKitError.createSymlinkFail(from: self, to: linkPath, error: error)
- }
- }
- /// Creates a hard link at a path that points to `self`.
- ///
- /// - Parameter path: The Path to which the link of the file at
- /// `self` will be created.
- /// If `path` exists and is a directory, then the link
- /// will be made inside of `path`. Otherwise, an error
- /// will be thrown.
- ///
- /// - Throws:
- /// `FileKitError.fileAlreadyExists`,
- /// `FileKitError.createHardlinkFail`
- ///
- public func hardlinkFile(to path: Path) throws {
- let linkPath = path.isDirectory ? path + self.fileName : path
- guard !linkPath.isAny else {
- throw FileKitError.fileAlreadyExists(path: linkPath)
- }
- do {
- try _fmWraper.fileManager.linkItem(atPath: self._safeRawValue, toPath: linkPath._safeRawValue)
- } catch {
- throw FileKitError.createHardlinkFail(from: self, to: path, error: error)
- }
- }
- /// Creates a file at path.
- ///
- /// Throws an error if the file cannot be created.
- ///
- /// - Throws: `FileKitError.CreateFileFail`
- ///
- /// this method does not follow links.
- ///
- /// If a file or symlink exists, this method removes the file or symlink and create regular file
- public func createFile() throws {
- if !_fmWraper.fileManager.createFile(atPath: _safeRawValue, contents: nil, attributes: nil) {
- throw FileKitError.createFileFail(path: self)
- }
- }
- /// Creates a file at path if not exist
- /// or update the modification date.
- ///
- /// Throws an error if the file cannot be created
- /// or if modification date could not be modified.
- ///
- /// - Throws:
- /// `FileKitError.CreateFileFail`,
- /// `FileKitError.AttributesChangeFail`
- ///
- public func touch(_ updateModificationDate: Bool = true) throws {
- if self.exists {
- if updateModificationDate {
- try _setAttribute(FileAttributeKey.modificationDate, value: Date())
- }
- } else {
- try createFile()
- }
- }
- /// Creates a directory at the path.
- ///
- /// Throws an error if the directory cannot be created.
- ///
- /// - Parameter createIntermediates: If `true`, any non-existent parent
- /// directories are created along with that
- /// of `self`. Default value is `true`.
- ///
- /// - Throws: `FileKitError.CreateDirectoryFail`
- ///
- /// this method does not follow links.
- ///
- public func createDirectory(withIntermediateDirectories createIntermediates: Bool = true) throws {
- do {
- let manager = _fmWraper.fileManager
- try manager.createDirectory(atPath: _safeRawValue,
- withIntermediateDirectories: createIntermediates,
- attributes: nil)
- } catch {
- throw FileKitError.createDirectoryFail(path: self, error: error)
- }
- }
- // swiftlint:enable line_length
- /// Deletes the file or directory at the path.
- ///
- /// Throws an error if the file or directory cannot be deleted.
- ///
- /// - Throws: `FileKitError.DeleteFileFail`
- ///
- /// this method does not follow links.
- public func deleteFile() throws {
- do {
- try _fmWraper.fileManager.removeItem(atPath: _safeRawValue)
- } catch {
- throw FileKitError.deleteFileFail(path: self, error: error)
- }
- }
- /// Moves the file at `self` to a path.
- ///
- /// Throws an error if the file cannot be moved.
- ///
- /// - Throws: `FileKitError.fileDoesNotExist`, `FileKitError.fileAlreadyExists`, `FileKitError.moveFileFail`
- ///
- /// this method does not follow links.
- public func moveFile(to path: Path) throws {
- if self.isAny {
- if !path.isAny {
- do {
- try _fmWraper.fileManager.moveItem(atPath: self._safeRawValue, toPath: path._safeRawValue)
- } catch {
- throw FileKitError.moveFileFail(from: self, to: path, error: error)
- }
- } else {
- throw FileKitError.fileAlreadyExists(path: path)
- }
- } else {
- throw FileKitError.fileDoesNotExist(path: self)
- }
- }
- /// Copies the file at `self` to a path.
- ///
- /// Throws an error if the file at `self` could not be copied or if a file
- /// already exists at the destination path.
- ///
- /// - Throws: `FileKitError.fileDoesNotExist`, `FileKitError.fileAlreadyExists`, `FileKitError.copyFileFail`
- ///
- /// this method does not follow links.
- public func copyFile(to path: Path) throws {
- if self.isAny {
- if !path.isAny {
- do {
- try _fmWraper.fileManager.copyItem(atPath: self._safeRawValue, toPath: path._safeRawValue)
- } catch {
- throw FileKitError.copyFileFail(from: self, to: path, error: error)
- }
- } else {
- throw FileKitError.fileAlreadyExists(path: path)
- }
- } else {
- throw FileKitError.fileDoesNotExist(path: self)
- }
- }
- }
- extension Path: ExpressibleByStringLiteral {
- // MARK: - ExpressibleByStringLiteral
- public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType
- public typealias UnicodeScalarLiteralType = StringLiteralType
- /// Initializes a path to the literal.
- public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
- self.rawValue = value
- }
- /// Initializes a path to the literal.
- public init(stringLiteral value: StringLiteralType) {
- self.rawValue = value
- }
- /// Initializes a path to the literal.
- public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
- self.rawValue = value
- }
- }
- extension Path: RawRepresentable {
- // MARK: - RawRepresentable
- /// Initializes a path to the string value.
- ///
- /// - Parameter rawValue: The raw value to initialize from.
- public init(rawValue: String) {
- self.rawValue = rawValue
- }
- }
- extension Path: Hashable {
- // MARK: - Hashable
- /// To compute the hash value of the path.
- public func hash(into hasher: inout Hasher) {
- hasher.combine(rawValue)
- }
- }
- extension Path { // : Indexable {
- // MARK: - Indexable
- /// The path's start index.
- public var startIndex: Int {
- return components.startIndex
- }
- /// The path's end index; the successor of the last valid subscript argument.
- public var endIndex: Int {
- return components.endIndex
- }
- /// The path's subscript. (read-only)
- ///
- /// - Returns: All of the path's elements up to and including the index.
- ///
- public subscript(position: Int) -> Path {
- let components = self.components
- if position < 0 || position >= components.count {
- fatalError("Path index out of range")
- } else {
- var result = components.first!
- for i in 1 ..< position + 1 {
- result += components[i]
- }
- return result
- }
- }
- public subscript(bounds: Range<Int>) -> Path {
- let components = self.components
- if bounds.lowerBound < 0 || bounds.upperBound >= components.count {
- fatalError("Path bounds out of range")
- }
- var result = components[bounds.lowerBound]
- for i in (bounds.lowerBound + 1) ..< bounds.upperBound {
- result += components[i]
- }
- return result
- }
- public func index(after i: Int) -> Int {
- return components.index(after: i)
- }
- }
- extension Path {
- // MARK: - Attributes
- /// Returns the path's attributes.
- ///
- /// this method does not follow links.
- public var attributes: [FileAttributeKey: Any] {
- return (try? _fmWraper.fileManager.attributesOfItem(atPath: _safeRawValue)) ?? [:]
- }
- /// Modify attributes
- ///
- /// this method does not follow links.
- fileprivate func _setAttributes(_ attributes: [FileAttributeKey: Any]) throws {
- do {
- try _fmWraper.fileManager.setAttributes(attributes, ofItemAtPath: self._safeRawValue)
- } catch {
- throw FileKitError.attributesChangeFail(path: self, error: error)
- }
- }
- /// Modify one attribute
- fileprivate func _setAttribute(_ key: FileAttributeKey, value: Any) throws {
- try _setAttributes([key: value])
- }
- /// The creation date of the file at the path.
- public var creationDate: Date? {
- return attributes[FileAttributeKey.creationDate] as? Date
- }
- /// The modification date of the file at the path.
- public var modificationDate: Date? {
- return attributes[FileAttributeKey.modificationDate] as? Date
- }
- /// The name of the owner of the file at the path.
- public var ownerName: String? {
- return attributes[FileAttributeKey.ownerAccountName] as? String
- }
- /// The ID of the owner of the file at the path.
- public var ownerID: UInt? {
- if let value = attributes[FileAttributeKey.ownerAccountID] as? NSNumber {
- return value.uintValue
- }
- return nil
- }
- /// The group name of the owner of the file at the path.
- public var groupName: String? {
- return attributes[FileAttributeKey.groupOwnerAccountName] as? String
- }
- /// The group ID of the owner of the file at the path.
- public var groupID: UInt? {
- if let value = attributes[FileAttributeKey.groupOwnerAccountID] as? NSNumber {
- return value.uintValue
- }
- return nil
- }
- /// Indicates whether the extension of the file at the path is hidden.
- public var extensionIsHidden: Bool? {
- if let value = attributes[FileAttributeKey.extensionHidden] as? NSNumber {
- return value.boolValue
- }
- return nil
- }
- /// The POSIX permissions of the file at the path.
- public var posixPermissions: Int16? {
- if let value = attributes[FileAttributeKey.posixPermissions] as? NSNumber {
- return value.int16Value
- }
- return nil
- }
- /// The number of hard links to a file.
- public var fileReferenceCount: UInt? {
- if let value = attributes[FileAttributeKey.referenceCount] as? NSNumber {
- return value.uintValue
- }
- return nil
- }
- /// The size of the file at the path in bytes.
- public var fileSize: UInt64? {
- if let value = attributes[FileAttributeKey.size] as? NSNumber {
- return value.uint64Value
- }
- return nil
- }
- /// The filesystem number of the file at the path.
- public var filesystemFileNumber: UInt? {
- if let value = attributes[FileAttributeKey.systemFileNumber] as? NSNumber {
- return value.uintValue
- }
- return nil
- }
- }
- extension Path {
- // MARK: - FileType
- /// The FileType attribute for the file at the path.
- public var fileType: FileType? {
- guard let value = attributes[FileAttributeKey.type] as? String else {
- return nil
- }
- return FileType(rawValue: value)
- }
- }
- extension Path {
- // MARK: - FilePermissions
- /// The permissions for the file at the path.
- public var filePermissions: FilePermissions {
- return FilePermissions(forPath: self)
- }
- }
- extension Path {
- // MARK: - NSURL
- /// Creates a new path with given url if possible.
- ///
- /// - Parameter url: The url to create a path for.
- public init?(url: URL) {
- guard url.isFileURL else {
- return nil
- }
- rawValue = url.path
- }
- /// - Returns: The `Path` objects url.
- public var url: URL {
- return URL(fileURLWithPath: _safeRawValue, isDirectory: self.isDirectory)
- }
- }
- extension Path {
- // MARK: - BookmarkData
- /// Creates a new path with given url if possible.
- ///
- /// - Parameter bookmarkData: The bookmark data to create a path for.
- public init?(bookmarkData bookData: Data) {
- var isStale: ObjCBool = false
- let url = try? (NSURL(
- resolvingBookmarkData: bookData,
- options: [],
- relativeTo: nil,
- bookmarkDataIsStale: &isStale) as URL)
- guard let fullURL = url else {
- return nil
- }
- self.init(url: fullURL)
- }
- /// - Returns: The `Path` objects bookmarkData.
- public var bookmarkData: Data? {
- return try? url.bookmarkData(
- options: .suitableForBookmarkFile,
- includingResourceValuesForKeys: nil,
- relativeTo: nil)
- }
- }
- extension Path {
- // MARK: - Ubiquity Container
- /// Create a path for the iCloud container associated with the specified identifier and establishes access to that container.
- public init?(ubiquityContainerIdentifier containerIdentifier: String) {
- guard let url = FileManager.default.url(forUbiquityContainerIdentifier: containerIdentifier) else {
- return nil
- }
- self.init(url: url)
- }
- /// - Returns: a Boolean indicating whether the item is targeted for storage in iCloud.
- public var isUbiquitousItem: Bool {
- return _fmWraper.fileManager.isUbiquitousItem(at: self.url)
- }
- /// Removes the local copy of the specified item that’s stored in iCloud.
- public func evictUbiquitousItem() throws {
- do {
- return try _fmWraper.fileManager.evictUbiquitousItem(at: self.url)
- } catch {
- throw FileKitError.deleteFileFail(path: self, error: error)
- }
- }
- /// Returns a URL that can be emailed to users to allow them to download a copy of a flat file item from iCloud.
- /// It return also the expiration date.
- func publicUbiquitousURL() throws -> (URL, Date?) {
- var expiration: NSDate?
- let url = try _fmWraper.fileManager.url(forPublishingUbiquitousItemAt: self.url, expiration: &expiration)
- guard let date = expiration else {
- return (url, nil)
- }
- // TODO need to encapsulate error before exposing it
- return (url, date as Date)
- }
- /// Indicates whether the current file should be stored in iCloud.
- public func setUbiquitous(destination: Path) throws {
- do {
- try _fmWraper.fileManager.setUbiquitous(true, itemAt: self.url, destinationURL: destination.url)
- } catch {
- throw FileKitError.attributesChangeFail(path: self, error: error)
- }
- }
- /// Indicates whether the current file should no more be stored in iCloud.
- public func unsetUbiquitous() throws {
- do {
- try _fmWraper.fileManager.setUbiquitous(false, itemAt: self.url, destinationURL: self.url)
- } catch {
- throw FileKitError.attributesChangeFail(path: self, error: error)
- }
- }
- /// Starts downloading (if necessary) the specified item to the local system.
- func startDownloadingUbiquitous() throws {
- try _fmWraper.fileManager.startDownloadingUbiquitousItem(at: self.url)
- // TODO need to encapsulate error before exposing it
- }
- }
- extension Path {
- // MARK: - SecurityApplicationGroupIdentifier
- /// Returns the container directory associated with the specified security application group ID.
- ///
- /// - Parameter groupIdentifier: The group identifier.
- public init?(groupIdentifier: String) {
- guard let url = FileManager().containerURL(forSecurityApplicationGroupIdentifier: groupIdentifier) else {
- return nil
- }
- self.init(url: url)
- }
- }
- extension Path {
- // MARK: - NSFileHandle
- /// Returns a file handle for reading the file at the path, or `nil` if no
- /// file exists.
- public var fileHandleForReading: FileHandle? {
- return FileHandle(forReadingAtPath: absolute._safeRawValue)
- }
- /// Returns a file handle for writing to the file at the path, or `nil` if
- /// no file exists.
- public var fileHandleForWriting: FileHandle? {
- return FileHandle(forWritingAtPath: absolute._safeRawValue)
- }
- /// Returns a file handle for reading and writing to the file at the path,
- /// or `nil` if no file exists.
- public var fileHandleForUpdating: FileHandle? {
- return FileHandle(forUpdatingAtPath: absolute._safeRawValue)
- }
- }
- extension FileHandle {
- /// Specifies how the operating system should open a file.
- public enum Mode {
- case read
- case write
- case update
- }
- }
- extension Path {
- /// Returns a file handle for specific mode to the file at the path,
- /// or `nil` if no file exists.
- /// - Parameter mode: How the operating system should open the file.
- public func fileHandle(for mode: FileHandle.Mode) throws -> FileHandle {
- switch mode {
- case .read:
- do {
- return try FileHandle(forReadingFrom: absolute.url)
- } catch {
- throw FileKitError.readFromFileFail(path: self, error: error)
- }
- case .write:
- do {
- return try FileHandle(forWritingTo: absolute.url)
- } catch {
- throw FileKitError.writeToFileFail(path: self, error: error)
- }
- case .update:
- do {
- return try FileHandle(forUpdating: absolute.url)
- } catch {
- throw FileKitError.writeToFileFail(path: self, error: error)
- }
- }
- }
- }
- extension Path {
- // MARK: - NSStream
- /// Returns an input stream that reads data from the file at the path, or
- /// `nil` if no file exists.
- public func inputStream() -> InputStream? {
- return InputStream(fileAtPath: absolute._safeRawValue)
- }
- /// Returns an output stream for writing to the file at the path, or `nil`
- /// if no file exists.
- ///
- /// - Parameter shouldAppend: `true` if newly written data should be
- /// appended to any existing file contents,
- /// `false` otherwise. Default value is `false`.
- ///
- public func outputStream(append shouldAppend: Bool = false) -> OutputStream? {
- return OutputStream(toFileAtPath: absolute._safeRawValue, append: shouldAppend)
- }
- }
- extension Path: ExpressibleByStringInterpolation {
- // MARK: - StringInterpolationConvertible
- /// Initializes a path from the string interpolation paths.
- public init(stringInterpolation paths: Path...) {
- self.init(paths.reduce("", { $0 + $1.rawValue }))
- }
- /// Initializes a path from the string interpolation segment.
- public init<T>(stringInterpolationSegment expr: T) {
- if let path = expr as? Path {
- self = path
- } else {
- self = Path(String(describing: expr))
- }
- }
- }
- extension Path: CustomStringConvertible {
- // MARK: - CustomStringConvertible
- /// A textual representation of `self`.
- public var description: String {
- return rawValue
- }
- }
- extension Path: CustomDebugStringConvertible {
- // MARK: - CustomDebugStringConvertible
- /// A textual representation of `self`, suitable for debugging.
- public var debugDescription: String {
- return "Path(\(rawValue.debugDescription))"
- }
- }
- extension Path: Sequence {
- // MARK: - Sequence
- /// - Returns: An *iterator* over the contents of the path.
- public func makeIterator() -> DirectoryEnumerator {
- return DirectoryEnumerator(path: self)
- }
- }
- extension Path {
- // MARK: - Paths
- /// Returns the path to the user's or application's home directory,
- /// depending on the platform.
- public static var userHome: Path {
- // same as FileManager.default.homeDirectoryForCurrentUser
- return Path(NSHomeDirectory()).standardized
- }
- /// Returns the path to the user's temporary directory.
- public static var userTemporary: Path {
- // same as FileManager.default.temporaryDirectory
- return Path(NSTemporaryDirectory()).standardized
- }
- /// Returns a temporary path for the process.
- public static var processTemporary: Path {
- return Path.userTemporary + ProcessInfo.processInfo.globallyUniqueString
- }
- /// Returns a unique temporary path.
- public static var uniqueTemporary: Path {
- return Path.processTemporary + UUID().uuidString
- }
- /// Returns the path to the user's caches directory.
- public static var userCaches: Path {
- return _pathInUserDomain(.cachesDirectory)
- }
- /// Returns the path to the user's applications directory.
- public static var userApplications: Path {
- return _pathInUserDomain(.applicationDirectory)
- }
- /// Returns the path to the user's application support directory.
- public static var userApplicationSupport: Path {
- return _pathInUserDomain(.applicationSupportDirectory)
- }
- /// Returns the path to the user's desktop directory.
- public static var userDesktop: Path {
- return _pathInUserDomain(.desktopDirectory)
- }
- /// Returns the path to the user's documents directory.
- public static var userDocuments: Path {
- return _pathInUserDomain(.documentDirectory)
- }
- /// Returns the path to the user's autosaved documents directory.
- public static var userAutosavedInformation: Path {
- return _pathInUserDomain(.autosavedInformationDirectory)
- }
- /// Returns the path to the user's downloads directory.
- public static var userDownloads: Path {
- return _pathInUserDomain(.downloadsDirectory)
- }
- /// Returns the path to the user's library directory.
- public static var userLibrary: Path {
- return _pathInUserDomain(.libraryDirectory)
- }
- /// Returns the path to the user's movies directory.
- public static var userMovies: Path {
- return _pathInUserDomain(.moviesDirectory)
- }
- /// Returns the path to the user's music directory.
- public static var userMusic: Path {
- return _pathInUserDomain(.musicDirectory)
- }
- /// Returns the path to the user's pictures directory.
- public static var userPictures: Path {
- return _pathInUserDomain(.picturesDirectory)
- }
- /// Returns the path to the user's Public sharing directory.
- public static var userSharedPublic: Path {
- return _pathInUserDomain(.sharedPublicDirectory)
- }
- #if os(OSX)
- /// Returns the path to the user scripts folder for the calling application
- public static var userApplicationScripts: Path {
- return _pathInUserDomain(.applicationScriptsDirectory)
- }
- /// Returns the path to the user's trash directory
- public static var userTrash: Path {
- return _pathInUserDomain(.trashDirectory)
- }
- #endif
- /// Returns the path to the system's applications directory.
- public static var systemApplications: Path {
- return _pathInSystemDomain(.applicationDirectory)
- }
- /// Returns the path to the system's application support directory.
- public static var systemApplicationSupport: Path {
- return _pathInSystemDomain(.applicationSupportDirectory)
- }
- /// Returns the path to the system's library directory.
- public static var systemLibrary: Path {
- return _pathInSystemDomain(.libraryDirectory)
- }
- /// Returns the path to the system's core services directory.
- public static var systemCoreServices: Path {
- return _pathInSystemDomain(.coreServiceDirectory)
- }
- /// Returns the path to the system's PPDs directory.
- public static var systemPrinterDescription: Path {
- return _pathInSystemDomain(.printerDescriptionDirectory)
- }
- /// Returns the path to the system's PreferencePanes directory.
- public static var systemPreferencePanes: Path {
- return _pathInSystemDomain(.preferencePanesDirectory)
- }
- /// Returns the paths where resources can occur.
- public static var allLibraries: [Path] {
- return _pathsInDomains(.allLibrariesDirectory, .allDomainsMask)
- }
- /// Returns the paths where applications can occur
- public static var allApplications: [Path] {
- return _pathsInDomains(.allApplicationsDirectory, .allDomainsMask)
- }
- fileprivate static func _pathInUserDomain(_ directory: FileManager.SearchPathDirectory) -> Path {
- return _pathsInDomains(directory, .userDomainMask)[0]
- }
- fileprivate static func _pathInSystemDomain(_ directory: FileManager.SearchPathDirectory) -> Path {
- return _pathsInDomains(directory, .systemDomainMask)[0]
- }
- fileprivate static func _pathsInDomains(_ directory: FileManager.SearchPathDirectory,
- _ domainMask: FileManager.SearchPathDomainMask) -> [Path] {
- return NSSearchPathForDirectoriesInDomains(directory, domainMask, true)
- .map({ Path($0).standardized })
- }
- }
|