Path.swift 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381
  1. //
  2. // Path.swift
  3. // FileKit
  4. //
  5. // The MIT License (MIT)
  6. //
  7. // Copyright (c) 2015-2017 Nikolai Vazquez
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. //
  27. // swiftlint:disable file_length
  28. //
  29. import Foundation
  30. /// A representation of a filesystem path.
  31. ///
  32. /// An Path instance lets you manage files in a much easier way.
  33. ///
  34. public struct Path {
  35. // MARK: - Static Methods and Properties
  36. /// The standard separator for path components.
  37. public static let separator = "/"
  38. /// The root path.
  39. public static let root = Path(separator)
  40. /// The path of the program's current working directory.
  41. public static var current: Path {
  42. get {
  43. return Path(FileManager.default.currentDirectoryPath)
  44. }
  45. set {
  46. FileManager.default.changeCurrentDirectoryPath(newValue._safeRawValue)
  47. }
  48. }
  49. /// The paths of the mounted volumes available.
  50. public static func volumes(_ options: FileManager.VolumeEnumerationOptions = []) -> [Path] {
  51. let volumes = FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: nil,
  52. options: options)
  53. return (volumes ?? []).compactMap { Path(url: $0) }
  54. }
  55. // MARK: - Properties
  56. fileprivate var _fmWraper = _FMWrapper()
  57. fileprivate class _FMWrapper {
  58. let unsafeFileManager = FileManager()
  59. weak var delegate: FileManagerDelegate?
  60. /// Safe way to use fileManager
  61. var fileManager: FileManager {
  62. // if delegate == nil {
  63. // print("\n\nDelegate is nil\n\n")
  64. // }
  65. unsafeFileManager.delegate = delegate
  66. return unsafeFileManager
  67. }
  68. }
  69. /// The delegate for the file manager used by the path.
  70. ///
  71. /// **Note:** no strong reference stored in path, so make sure keep the delegate or it will be `nil`
  72. public var fileManagerDelegate: FileManagerDelegate? {
  73. get {
  74. return _fmWraper.delegate
  75. }
  76. set {
  77. if !isKnownUniquelyReferenced(&_fmWraper) {
  78. _fmWraper = _FMWrapper()
  79. }
  80. _fmWraper.delegate = newValue
  81. }
  82. }
  83. /// The stored path string value.
  84. public fileprivate(set) var rawValue: String
  85. /// The non-empty path string value. For internal use only.
  86. ///
  87. /// Some NSAPI may throw `NSInvalidArgumentException` when path is `""`, which can't catch in swift
  88. /// and cause crash
  89. internal var _safeRawValue: String {
  90. return rawValue.isEmpty ? "." : rawValue
  91. }
  92. /// The standardized path string value
  93. public var standardRawValue: String {
  94. return (self.rawValue as NSString).standardizingPath
  95. }
  96. /// The standardized path string value without expanding tilde
  97. public var standardRawValueWithTilde: String {
  98. let comps = components
  99. if comps.isEmpty {
  100. return ""
  101. } else {
  102. return self[comps.count - 1].rawValue
  103. }
  104. }
  105. /// The components of the path.
  106. ///
  107. /// Return [] if path is `.` or `""`
  108. public var components: [Path] {
  109. if rawValue == "" || rawValue == "." {
  110. return []
  111. }
  112. if isAbsolute {
  113. return (absolute.rawValue as NSString).pathComponents.enumerated().compactMap {
  114. (($0 == 0 || $1 != "/") && $1 != ".") ? Path($1) : nil
  115. }
  116. } else {
  117. let comps = (self.rawValue as NSString).pathComponents.enumerated()
  118. // remove extraneous `/` and `.`
  119. let cleanComps = comps.compactMap {
  120. (($0 == 0 || $1 != "/") && $1 != ".") ? Path($1) : nil
  121. }
  122. return _cleanComponents(cleanComps)
  123. }
  124. }
  125. /// resolving `..` if possible
  126. fileprivate func _cleanComponents(_ comps: [Path]) -> [Path] {
  127. var isContinue = false
  128. let count = comps.count
  129. let cleanComps: [Path] = comps.enumerated().compactMap {
  130. if ($1.rawValue != ".." && $0 < count - 1 && comps[$0 + 1].rawValue == "..") || ($1.rawValue == ".." && $0 > 0 && comps[$0 - 1].rawValue != "..") {
  131. isContinue = true
  132. return nil
  133. } else {
  134. return $1
  135. }
  136. }
  137. return isContinue ? _cleanComponents(cleanComps) : cleanComps
  138. }
  139. /// The name of the file at `self`.
  140. public var fileName: String {
  141. return self.absolute.components.last?.rawValue ?? ""
  142. }
  143. /// The name of the file without extension.
  144. public var fileNameWithoutExtension: String {
  145. return ((rawValue as NSString).lastPathComponent as NSString).deletingPathExtension
  146. }
  147. /// A new path created by removing extraneous components from the path.
  148. public var standardized: Path {
  149. return Path((self.rawValue as NSString).standardizingPath)
  150. }
  151. /// The standardized path string value without expanding tilde
  152. public var standardWithTilde: Path {
  153. let comps = components
  154. if comps.isEmpty {
  155. return Path("")
  156. } else {
  157. return self[comps.count - 1]
  158. }
  159. }
  160. /// A new path created by resolving all symlinks and standardizing the path.
  161. public var resolved: Path {
  162. return Path((self.rawValue as NSString).resolvingSymlinksInPath)
  163. }
  164. /// A new path created by making the path absolute.
  165. ///
  166. /// - Returns: If `self` begins with "/", then the standardized path is
  167. /// returned. Otherwise, the path is assumed to be relative to
  168. /// the current working directory and the standardized version of
  169. /// the path added to the current working directory is returned.
  170. ///
  171. public var absolute: Path {
  172. return self.isAbsolute
  173. ? self.standardized
  174. : (Path.current + self).standardized
  175. }
  176. /// Returns `true` if the path is equal to "/".
  177. public var isRoot: Bool {
  178. return resolved.rawValue == Path.separator
  179. }
  180. /// Returns `true` if the path begins with "/".
  181. public var isAbsolute: Bool {
  182. return rawValue.hasPrefix(Path.separator)
  183. }
  184. /// Returns `true` if the path does not begin with "/".
  185. public var isRelative: Bool {
  186. return !isAbsolute
  187. }
  188. /// Returns `true` if a file or directory exists at the path.
  189. ///
  190. /// this method does follow links.
  191. public var exists: Bool {
  192. return _fmWraper.fileManager.fileExists(atPath: _safeRawValue)
  193. }
  194. /// Returns `true` if a file or directory or symbolic link exists at the path
  195. ///
  196. /// this method does **not** follow links.
  197. // public var existsOrLink: Bool {
  198. // return self.isSymbolicLink || _fmWraper.fileManager.fileExistsAtPath(_safeRawValue)
  199. // }
  200. /// Returns `true` if the current process has write privileges for the file
  201. /// at the path.
  202. ///
  203. /// this method does follow links.
  204. public var isWritable: Bool {
  205. return _fmWraper.fileManager.isWritableFile(atPath: _safeRawValue)
  206. }
  207. /// Returns `true` if the current process has read privileges for the file
  208. /// at the path.
  209. ///
  210. /// this method does follow links.
  211. public var isReadable: Bool {
  212. return _fmWraper.fileManager.isReadableFile(atPath: _safeRawValue)
  213. }
  214. /// Returns `true` if the current process has execute privileges for the
  215. /// file at the path.
  216. ///
  217. /// this method does follow links.
  218. public var isExecutable: Bool {
  219. return _fmWraper.fileManager.isExecutableFile(atPath: _safeRawValue)
  220. }
  221. /// Returns `true` if the current process has delete privileges for the file
  222. /// at the path.
  223. ///
  224. /// this method does **not** follow links.
  225. public var isDeletable: Bool {
  226. return _fmWraper.fileManager.isDeletableFile(atPath: _safeRawValue)
  227. }
  228. /// Returns `true` if the path points to a directory.
  229. ///
  230. /// this method does follow links.
  231. public var isDirectory: Bool {
  232. var isDirectory: ObjCBool = false
  233. return _fmWraper.fileManager.fileExists(atPath: _safeRawValue, isDirectory: &isDirectory)
  234. && isDirectory.boolValue
  235. }
  236. /// Returns `true` if the path is a directory file.
  237. ///
  238. /// this method does not follow links.
  239. public var isDirectoryFile: Bool {
  240. return fileType == .directory
  241. }
  242. /// Returns `true` if the path is a symbolic link.
  243. ///
  244. /// this method does not follow links.
  245. public var isSymbolicLink: Bool {
  246. return fileType == .symbolicLink
  247. }
  248. /// Returns `true` if the path is a regular file.
  249. ///
  250. /// this method does not follow links.
  251. public var isRegular: Bool {
  252. return fileType == .regular
  253. }
  254. /// Returns `true` if the path exists any fileType item.
  255. ///
  256. /// this method does not follow links.
  257. public var isAny: Bool {
  258. return fileType != nil
  259. }
  260. /// The path's extension.
  261. public var pathExtension: String {
  262. get {
  263. return (rawValue as NSString).pathExtension
  264. }
  265. set {
  266. let path = (rawValue as NSString).deletingPathExtension
  267. rawValue = path + ".\(newValue)"
  268. }
  269. }
  270. mutating func appendToExtension(_ toAppend: String) {
  271. self.pathExtension = pathExtension + toAppend
  272. }
  273. /// The path's parent path.
  274. public var parent: Path {
  275. if isAbsolute {
  276. return Path((absolute.rawValue as NSString).deletingLastPathComponent)
  277. } else {
  278. let comps = components
  279. if comps.isEmpty {
  280. return Path("..")
  281. } else if comps.last!.rawValue == ".." {
  282. return ".." + self[comps.count - 1]
  283. } else if comps.count == 1 {
  284. return Path("")
  285. } else {
  286. return self[comps.count - 2]
  287. }
  288. }
  289. }
  290. // MARK: - Initialization
  291. /// Initializes a path to root.
  292. public init() {
  293. self = .root
  294. }
  295. /// Initializes a path to the string's value.
  296. public init(_ path: String, expandingTilde: Bool = false) {
  297. // empty path may cause crash
  298. if expandingTilde {
  299. self.rawValue = (path as NSString).expandingTildeInPath
  300. } else {
  301. self.rawValue = path
  302. }
  303. }
  304. }
  305. extension Path {
  306. // MARK: - Methods
  307. /// Runs `closure` with `self` as its current working directory.
  308. ///
  309. /// - Parameter closure: The block to run while `Path.Current` is changed.
  310. ///
  311. public func changeDirectory(_ closure: () throws -> Void) rethrows {
  312. let previous = Path.current
  313. defer { Path.current = previous }
  314. if _fmWraper.fileManager.changeCurrentDirectoryPath(_safeRawValue) {
  315. try closure()
  316. }
  317. }
  318. /// Returns the path's children paths.
  319. ///
  320. /// - Parameter recursive: Whether to obtain the paths recursively.
  321. /// Default value is `false`.
  322. ///
  323. /// this method follow links if recursive is `false`, otherwise not follow links
  324. public func children(recursive: Bool = false) -> [Path] {
  325. let obtainFunc = recursive
  326. ? _fmWraper.fileManager.subpathsOfDirectory(atPath:)
  327. : _fmWraper.fileManager.contentsOfDirectory(atPath:)
  328. return (try? obtainFunc(_safeRawValue))?.map { self + Path($0) } ?? []
  329. }
  330. /// Returns true if `path` is a child of `self`.
  331. ///
  332. /// - Parameter recursive: Whether to check the paths recursively.
  333. /// Default value is `true`.
  334. ///
  335. public func isChildOfPath(_ path: Path, recursive: Bool = true) -> Bool {
  336. if !(isRelative && path.isRelative) && !(isAbsolute && path.isAbsolute) {
  337. return self.absolute.isChildOfPath(path.absolute)
  338. }
  339. if isRoot {
  340. return true
  341. }
  342. if recursive {
  343. return path.isAncestorOfPath(self)
  344. } else {
  345. return path.parent == self
  346. }
  347. }
  348. /// Returns true if `path` is a parent of `self`.
  349. ///
  350. /// Relative paths can't be compared return `false`. like `../../path1/path2` and `../path2`
  351. ///
  352. public func isAncestorOfPath(_ path: Path) -> Bool {
  353. if !(isRelative && path.isRelative) && !(isAbsolute && path.isAbsolute) {
  354. return self.absolute.isAncestorOfPath(path.absolute)
  355. }
  356. if path.isRoot {
  357. return true
  358. }
  359. if self != path && self.commonAncestor(path) == path {
  360. return true
  361. }
  362. return false
  363. }
  364. /// Returns the common ancestor between `self` and `path`.
  365. ///
  366. /// Relative path return the most precise path if possible
  367. ///
  368. public func commonAncestor(_ path: Path) -> Path {
  369. if !(isRelative && path.isRelative) && !(isAbsolute && path.isAbsolute) {
  370. return self.absolute.commonAncestor(path.absolute)
  371. }
  372. let selfComponents = self.components
  373. let pathComponents = path.components
  374. let minCount = Swift.min(selfComponents.count, pathComponents.count)
  375. var total = minCount
  376. for index in 0 ..< total
  377. where selfComponents[index].rawValue != pathComponents[index].rawValue {
  378. total = index
  379. break
  380. }
  381. let ancestorComponents = selfComponents[0..<total]
  382. let common = ancestorComponents.reduce("") { $0 + $1 }
  383. switch (self.relativePathType, path.relativePathType) {
  384. case (.absolute, .absolute), (.normal, .normal), (.normal, .current), (.current, .normal), (.current, .current):
  385. return common
  386. case (.normal, .parent), (.current, .parent), (.parent, .normal), (.parent, .current), (.parent, .parent):
  387. return Path("..")
  388. default:
  389. // count for prefix ".." in components
  390. var n1 = 0, n2 = 0
  391. for elem in selfComponents {
  392. if elem.rawValue == ".." {
  393. n1 += 1
  394. } else {
  395. break
  396. }
  397. }
  398. for elem in pathComponents {
  399. if elem.rawValue == ".." {
  400. n2 += 1
  401. } else {
  402. break
  403. }
  404. }
  405. if n1 == n2 {
  406. // paths like "../../common/path1" and "../../common/path2"
  407. return common
  408. } else { // paths like "../path" and "../../path2/path1"
  409. let maxCount = Swift.max(n1, n2)
  410. var dotPath: Path = ""
  411. for _ in 0..<maxCount {
  412. dotPath += ".."
  413. }
  414. return dotPath
  415. }
  416. }
  417. }
  418. /// Returns the relative path type.
  419. ///
  420. public var relativePathType: RelativePathType {
  421. if isAbsolute {
  422. return .absolute
  423. } else {
  424. let comp = self.components
  425. switch comp.first?.rawValue {
  426. case nil:
  427. return .current
  428. case ".."? where comp.count > 1:
  429. return .ancestor
  430. case ".."?:
  431. return .parent
  432. default:
  433. return .normal
  434. }
  435. }
  436. }
  437. /// Returns paths in `self` that match a condition.
  438. ///
  439. /// - Parameter searchDepth: How deep to search before exiting. A negative
  440. /// value will cause the search to exit only when
  441. /// every subdirectory has been searched through.
  442. /// Default value is `-1`.
  443. /// - Parameter condition: If `true`, the path is added.
  444. ///
  445. /// - Returns: An Array containing the paths in `self` that match the
  446. /// condition.
  447. ///
  448. public func find(searchDepth depth: Int = -1, condition: (Path) throws -> Bool) rethrows -> [Path] {
  449. return try self.find(searchDepth: depth) { path in
  450. try condition(path) ? path : nil
  451. }
  452. }
  453. /// Returns non-nil values for paths found in `self`.
  454. ///
  455. /// - Parameter searchDepth: How deep to search before exiting. A negative
  456. /// value will cause the search to exit only when
  457. /// every subdirectory has been searched through.
  458. /// Default value is `-1`.
  459. /// - Parameter transform: The transform run on each path found.
  460. ///
  461. /// - Returns: An Array containing the non-nil values for paths found in
  462. /// `self`.
  463. ///
  464. public func find<T>(searchDepth depth: Int = -1, transform: (Path) throws -> T?) rethrows -> [T] {
  465. return try self.children().reduce([]) { values, child in
  466. if let value = try transform(child) {
  467. return values + [value]
  468. } else if depth != 0 {
  469. return try values + child.find(searchDepth: depth - 1, transform: transform)
  470. } else {
  471. return values
  472. }
  473. }
  474. }
  475. // swiftlint:enable line_length
  476. /// Standardizes the path.
  477. public mutating func standardize() {
  478. self = self.standardized
  479. }
  480. /// Resolves the path's symlinks and standardizes it.
  481. public mutating func resolve() {
  482. self = self.resolved
  483. }
  484. /// Creates a symbolic link at a path that points to `self`.
  485. ///
  486. /// - Parameter path: The Path to which at which the link of the file at
  487. /// `self` will be created.
  488. /// If `path` exists and is a directory, then the link
  489. /// will be made inside of `path`. Otherwise, an error
  490. /// will be thrown.
  491. ///
  492. /// - Throws:
  493. /// `FileKitError.fileAlreadyExists`,
  494. /// `FileKitError.createSymlinkFail`
  495. ///
  496. public func symlinkFile(to path: Path) throws {
  497. // it's possible to create symbolic links to locations that do not yet exist.
  498. // guard self.exists else {
  499. // throw FileKitError.FileDoesNotExist(path: self)
  500. // }
  501. let linkPath = path.isDirectory ? path + self.fileName : path
  502. // Throws if linking to an existing non-directory file.
  503. guard !linkPath.isAny else {
  504. throw FileKitError.fileAlreadyExists(path: linkPath)
  505. }
  506. do {
  507. try _fmWraper.fileManager.createSymbolicLink(
  508. atPath: linkPath._safeRawValue, withDestinationPath: self._safeRawValue)
  509. } catch {
  510. throw FileKitError.createSymlinkFail(from: self, to: linkPath, error: error)
  511. }
  512. }
  513. /// Creates a hard link at a path that points to `self`.
  514. ///
  515. /// - Parameter path: The Path to which the link of the file at
  516. /// `self` will be created.
  517. /// If `path` exists and is a directory, then the link
  518. /// will be made inside of `path`. Otherwise, an error
  519. /// will be thrown.
  520. ///
  521. /// - Throws:
  522. /// `FileKitError.fileAlreadyExists`,
  523. /// `FileKitError.createHardlinkFail`
  524. ///
  525. public func hardlinkFile(to path: Path) throws {
  526. let linkPath = path.isDirectory ? path + self.fileName : path
  527. guard !linkPath.isAny else {
  528. throw FileKitError.fileAlreadyExists(path: linkPath)
  529. }
  530. do {
  531. try _fmWraper.fileManager.linkItem(atPath: self._safeRawValue, toPath: linkPath._safeRawValue)
  532. } catch {
  533. throw FileKitError.createHardlinkFail(from: self, to: path, error: error)
  534. }
  535. }
  536. /// Creates a file at path.
  537. ///
  538. /// Throws an error if the file cannot be created.
  539. ///
  540. /// - Throws: `FileKitError.CreateFileFail`
  541. ///
  542. /// this method does not follow links.
  543. ///
  544. /// If a file or symlink exists, this method removes the file or symlink and create regular file
  545. public func createFile() throws {
  546. if !_fmWraper.fileManager.createFile(atPath: _safeRawValue, contents: nil, attributes: nil) {
  547. throw FileKitError.createFileFail(path: self)
  548. }
  549. }
  550. /// Creates a file at path if not exist
  551. /// or update the modification date.
  552. ///
  553. /// Throws an error if the file cannot be created
  554. /// or if modification date could not be modified.
  555. ///
  556. /// - Throws:
  557. /// `FileKitError.CreateFileFail`,
  558. /// `FileKitError.AttributesChangeFail`
  559. ///
  560. public func touch(_ updateModificationDate: Bool = true) throws {
  561. if self.exists {
  562. if updateModificationDate {
  563. try _setAttribute(FileAttributeKey.modificationDate, value: Date())
  564. }
  565. } else {
  566. try createFile()
  567. }
  568. }
  569. /// Creates a directory at the path.
  570. ///
  571. /// Throws an error if the directory cannot be created.
  572. ///
  573. /// - Parameter createIntermediates: If `true`, any non-existent parent
  574. /// directories are created along with that
  575. /// of `self`. Default value is `true`.
  576. ///
  577. /// - Throws: `FileKitError.CreateDirectoryFail`
  578. ///
  579. /// this method does not follow links.
  580. ///
  581. public func createDirectory(withIntermediateDirectories createIntermediates: Bool = true) throws {
  582. do {
  583. let manager = _fmWraper.fileManager
  584. try manager.createDirectory(atPath: _safeRawValue,
  585. withIntermediateDirectories: createIntermediates,
  586. attributes: nil)
  587. } catch {
  588. throw FileKitError.createDirectoryFail(path: self, error: error)
  589. }
  590. }
  591. // swiftlint:enable line_length
  592. /// Deletes the file or directory at the path.
  593. ///
  594. /// Throws an error if the file or directory cannot be deleted.
  595. ///
  596. /// - Throws: `FileKitError.DeleteFileFail`
  597. ///
  598. /// this method does not follow links.
  599. public func deleteFile() throws {
  600. do {
  601. try _fmWraper.fileManager.removeItem(atPath: _safeRawValue)
  602. } catch {
  603. throw FileKitError.deleteFileFail(path: self, error: error)
  604. }
  605. }
  606. /// Moves the file at `self` to a path.
  607. ///
  608. /// Throws an error if the file cannot be moved.
  609. ///
  610. /// - Throws: `FileKitError.fileDoesNotExist`, `FileKitError.fileAlreadyExists`, `FileKitError.moveFileFail`
  611. ///
  612. /// this method does not follow links.
  613. public func moveFile(to path: Path) throws {
  614. if self.isAny {
  615. if !path.isAny {
  616. do {
  617. try _fmWraper.fileManager.moveItem(atPath: self._safeRawValue, toPath: path._safeRawValue)
  618. } catch {
  619. throw FileKitError.moveFileFail(from: self, to: path, error: error)
  620. }
  621. } else {
  622. throw FileKitError.fileAlreadyExists(path: path)
  623. }
  624. } else {
  625. throw FileKitError.fileDoesNotExist(path: self)
  626. }
  627. }
  628. /// Copies the file at `self` to a path.
  629. ///
  630. /// Throws an error if the file at `self` could not be copied or if a file
  631. /// already exists at the destination path.
  632. ///
  633. /// - Throws: `FileKitError.fileDoesNotExist`, `FileKitError.fileAlreadyExists`, `FileKitError.copyFileFail`
  634. ///
  635. /// this method does not follow links.
  636. public func copyFile(to path: Path) throws {
  637. if self.isAny {
  638. if !path.isAny {
  639. do {
  640. try _fmWraper.fileManager.copyItem(atPath: self._safeRawValue, toPath: path._safeRawValue)
  641. } catch {
  642. throw FileKitError.copyFileFail(from: self, to: path, error: error)
  643. }
  644. } else {
  645. throw FileKitError.fileAlreadyExists(path: path)
  646. }
  647. } else {
  648. throw FileKitError.fileDoesNotExist(path: self)
  649. }
  650. }
  651. }
  652. extension Path: ExpressibleByStringLiteral {
  653. // MARK: - ExpressibleByStringLiteral
  654. public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType
  655. public typealias UnicodeScalarLiteralType = StringLiteralType
  656. /// Initializes a path to the literal.
  657. public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
  658. self.rawValue = value
  659. }
  660. /// Initializes a path to the literal.
  661. public init(stringLiteral value: StringLiteralType) {
  662. self.rawValue = value
  663. }
  664. /// Initializes a path to the literal.
  665. public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
  666. self.rawValue = value
  667. }
  668. }
  669. extension Path: RawRepresentable {
  670. // MARK: - RawRepresentable
  671. /// Initializes a path to the string value.
  672. ///
  673. /// - Parameter rawValue: The raw value to initialize from.
  674. public init(rawValue: String) {
  675. self.rawValue = rawValue
  676. }
  677. }
  678. extension Path: Hashable {
  679. // MARK: - Hashable
  680. /// To compute the hash value of the path.
  681. public func hash(into hasher: inout Hasher) {
  682. hasher.combine(rawValue)
  683. }
  684. }
  685. extension Path { // : Indexable {
  686. // MARK: - Indexable
  687. /// The path's start index.
  688. public var startIndex: Int {
  689. return components.startIndex
  690. }
  691. /// The path's end index; the successor of the last valid subscript argument.
  692. public var endIndex: Int {
  693. return components.endIndex
  694. }
  695. /// The path's subscript. (read-only)
  696. ///
  697. /// - Returns: All of the path's elements up to and including the index.
  698. ///
  699. public subscript(position: Int) -> Path {
  700. let components = self.components
  701. if position < 0 || position >= components.count {
  702. fatalError("Path index out of range")
  703. } else {
  704. var result = components.first!
  705. for i in 1 ..< position + 1 {
  706. result += components[i]
  707. }
  708. return result
  709. }
  710. }
  711. public subscript(bounds: Range<Int>) -> Path {
  712. let components = self.components
  713. if bounds.lowerBound < 0 || bounds.upperBound >= components.count {
  714. fatalError("Path bounds out of range")
  715. }
  716. var result = components[bounds.lowerBound]
  717. for i in (bounds.lowerBound + 1) ..< bounds.upperBound {
  718. result += components[i]
  719. }
  720. return result
  721. }
  722. public func index(after i: Int) -> Int {
  723. return components.index(after: i)
  724. }
  725. }
  726. extension Path {
  727. // MARK: - Attributes
  728. /// Returns the path's attributes.
  729. ///
  730. /// this method does not follow links.
  731. public var attributes: [FileAttributeKey: Any] {
  732. return (try? _fmWraper.fileManager.attributesOfItem(atPath: _safeRawValue)) ?? [:]
  733. }
  734. /// Modify attributes
  735. ///
  736. /// this method does not follow links.
  737. fileprivate func _setAttributes(_ attributes: [FileAttributeKey: Any]) throws {
  738. do {
  739. try _fmWraper.fileManager.setAttributes(attributes, ofItemAtPath: self._safeRawValue)
  740. } catch {
  741. throw FileKitError.attributesChangeFail(path: self, error: error)
  742. }
  743. }
  744. /// Modify one attribute
  745. fileprivate func _setAttribute(_ key: FileAttributeKey, value: Any) throws {
  746. try _setAttributes([key: value])
  747. }
  748. /// The creation date of the file at the path.
  749. public var creationDate: Date? {
  750. return attributes[FileAttributeKey.creationDate] as? Date
  751. }
  752. /// The modification date of the file at the path.
  753. public var modificationDate: Date? {
  754. return attributes[FileAttributeKey.modificationDate] as? Date
  755. }
  756. /// The name of the owner of the file at the path.
  757. public var ownerName: String? {
  758. return attributes[FileAttributeKey.ownerAccountName] as? String
  759. }
  760. /// The ID of the owner of the file at the path.
  761. public var ownerID: UInt? {
  762. if let value = attributes[FileAttributeKey.ownerAccountID] as? NSNumber {
  763. return value.uintValue
  764. }
  765. return nil
  766. }
  767. /// The group name of the owner of the file at the path.
  768. public var groupName: String? {
  769. return attributes[FileAttributeKey.groupOwnerAccountName] as? String
  770. }
  771. /// The group ID of the owner of the file at the path.
  772. public var groupID: UInt? {
  773. if let value = attributes[FileAttributeKey.groupOwnerAccountID] as? NSNumber {
  774. return value.uintValue
  775. }
  776. return nil
  777. }
  778. /// Indicates whether the extension of the file at the path is hidden.
  779. public var extensionIsHidden: Bool? {
  780. if let value = attributes[FileAttributeKey.extensionHidden] as? NSNumber {
  781. return value.boolValue
  782. }
  783. return nil
  784. }
  785. /// The POSIX permissions of the file at the path.
  786. public var posixPermissions: Int16? {
  787. if let value = attributes[FileAttributeKey.posixPermissions] as? NSNumber {
  788. return value.int16Value
  789. }
  790. return nil
  791. }
  792. /// The number of hard links to a file.
  793. public var fileReferenceCount: UInt? {
  794. if let value = attributes[FileAttributeKey.referenceCount] as? NSNumber {
  795. return value.uintValue
  796. }
  797. return nil
  798. }
  799. /// The size of the file at the path in bytes.
  800. public var fileSize: UInt64? {
  801. if let value = attributes[FileAttributeKey.size] as? NSNumber {
  802. return value.uint64Value
  803. }
  804. return nil
  805. }
  806. /// The filesystem number of the file at the path.
  807. public var filesystemFileNumber: UInt? {
  808. if let value = attributes[FileAttributeKey.systemFileNumber] as? NSNumber {
  809. return value.uintValue
  810. }
  811. return nil
  812. }
  813. }
  814. extension Path {
  815. // MARK: - FileType
  816. /// The FileType attribute for the file at the path.
  817. public var fileType: FileType? {
  818. guard let value = attributes[FileAttributeKey.type] as? String else {
  819. return nil
  820. }
  821. return FileType(rawValue: value)
  822. }
  823. }
  824. extension Path {
  825. // MARK: - FilePermissions
  826. /// The permissions for the file at the path.
  827. public var filePermissions: FilePermissions {
  828. return FilePermissions(forPath: self)
  829. }
  830. }
  831. extension Path {
  832. // MARK: - NSURL
  833. /// Creates a new path with given url if possible.
  834. ///
  835. /// - Parameter url: The url to create a path for.
  836. public init?(url: URL) {
  837. guard url.isFileURL else {
  838. return nil
  839. }
  840. rawValue = url.path
  841. }
  842. /// - Returns: The `Path` objects url.
  843. public var url: URL {
  844. return URL(fileURLWithPath: _safeRawValue, isDirectory: self.isDirectory)
  845. }
  846. }
  847. extension Path {
  848. // MARK: - BookmarkData
  849. /// Creates a new path with given url if possible.
  850. ///
  851. /// - Parameter bookmarkData: The bookmark data to create a path for.
  852. public init?(bookmarkData bookData: Data) {
  853. var isStale: ObjCBool = false
  854. let url = try? (NSURL(
  855. resolvingBookmarkData: bookData,
  856. options: [],
  857. relativeTo: nil,
  858. bookmarkDataIsStale: &isStale) as URL)
  859. guard let fullURL = url else {
  860. return nil
  861. }
  862. self.init(url: fullURL)
  863. }
  864. /// - Returns: The `Path` objects bookmarkData.
  865. public var bookmarkData: Data? {
  866. return try? url.bookmarkData(
  867. options: .suitableForBookmarkFile,
  868. includingResourceValuesForKeys: nil,
  869. relativeTo: nil)
  870. }
  871. }
  872. extension Path {
  873. // MARK: - Ubiquity Container
  874. /// Create a path for the iCloud container associated with the specified identifier and establishes access to that container.
  875. public init?(ubiquityContainerIdentifier containerIdentifier: String) {
  876. guard let url = FileManager.default.url(forUbiquityContainerIdentifier: containerIdentifier) else {
  877. return nil
  878. }
  879. self.init(url: url)
  880. }
  881. /// - Returns: a Boolean indicating whether the item is targeted for storage in iCloud.
  882. public var isUbiquitousItem: Bool {
  883. return _fmWraper.fileManager.isUbiquitousItem(at: self.url)
  884. }
  885. /// Removes the local copy of the specified item that’s stored in iCloud.
  886. public func evictUbiquitousItem() throws {
  887. do {
  888. return try _fmWraper.fileManager.evictUbiquitousItem(at: self.url)
  889. } catch {
  890. throw FileKitError.deleteFileFail(path: self, error: error)
  891. }
  892. }
  893. /// Returns a URL that can be emailed to users to allow them to download a copy of a flat file item from iCloud.
  894. /// It return also the expiration date.
  895. func publicUbiquitousURL() throws -> (URL, Date?) {
  896. var expiration: NSDate?
  897. let url = try _fmWraper.fileManager.url(forPublishingUbiquitousItemAt: self.url, expiration: &expiration)
  898. guard let date = expiration else {
  899. return (url, nil)
  900. }
  901. // TODO need to encapsulate error before exposing it
  902. return (url, date as Date)
  903. }
  904. /// Indicates whether the current file should be stored in iCloud.
  905. public func setUbiquitous(destination: Path) throws {
  906. do {
  907. try _fmWraper.fileManager.setUbiquitous(true, itemAt: self.url, destinationURL: destination.url)
  908. } catch {
  909. throw FileKitError.attributesChangeFail(path: self, error: error)
  910. }
  911. }
  912. /// Indicates whether the current file should no more be stored in iCloud.
  913. public func unsetUbiquitous() throws {
  914. do {
  915. try _fmWraper.fileManager.setUbiquitous(false, itemAt: self.url, destinationURL: self.url)
  916. } catch {
  917. throw FileKitError.attributesChangeFail(path: self, error: error)
  918. }
  919. }
  920. /// Starts downloading (if necessary) the specified item to the local system.
  921. func startDownloadingUbiquitous() throws {
  922. try _fmWraper.fileManager.startDownloadingUbiquitousItem(at: self.url)
  923. // TODO need to encapsulate error before exposing it
  924. }
  925. }
  926. extension Path {
  927. // MARK: - SecurityApplicationGroupIdentifier
  928. /// Returns the container directory associated with the specified security application group ID.
  929. ///
  930. /// - Parameter groupIdentifier: The group identifier.
  931. public init?(groupIdentifier: String) {
  932. guard let url = FileManager().containerURL(forSecurityApplicationGroupIdentifier: groupIdentifier) else {
  933. return nil
  934. }
  935. self.init(url: url)
  936. }
  937. }
  938. extension Path {
  939. // MARK: - NSFileHandle
  940. /// Returns a file handle for reading the file at the path, or `nil` if no
  941. /// file exists.
  942. public var fileHandleForReading: FileHandle? {
  943. return FileHandle(forReadingAtPath: absolute._safeRawValue)
  944. }
  945. /// Returns a file handle for writing to the file at the path, or `nil` if
  946. /// no file exists.
  947. public var fileHandleForWriting: FileHandle? {
  948. return FileHandle(forWritingAtPath: absolute._safeRawValue)
  949. }
  950. /// Returns a file handle for reading and writing to the file at the path,
  951. /// or `nil` if no file exists.
  952. public var fileHandleForUpdating: FileHandle? {
  953. return FileHandle(forUpdatingAtPath: absolute._safeRawValue)
  954. }
  955. }
  956. extension FileHandle {
  957. /// Specifies how the operating system should open a file.
  958. public enum Mode {
  959. case read
  960. case write
  961. case update
  962. }
  963. }
  964. extension Path {
  965. /// Returns a file handle for specific mode to the file at the path,
  966. /// or `nil` if no file exists.
  967. /// - Parameter mode: How the operating system should open the file.
  968. public func fileHandle(for mode: FileHandle.Mode) throws -> FileHandle {
  969. switch mode {
  970. case .read:
  971. do {
  972. return try FileHandle(forReadingFrom: absolute.url)
  973. } catch {
  974. throw FileKitError.readFromFileFail(path: self, error: error)
  975. }
  976. case .write:
  977. do {
  978. return try FileHandle(forWritingTo: absolute.url)
  979. } catch {
  980. throw FileKitError.writeToFileFail(path: self, error: error)
  981. }
  982. case .update:
  983. do {
  984. return try FileHandle(forUpdating: absolute.url)
  985. } catch {
  986. throw FileKitError.writeToFileFail(path: self, error: error)
  987. }
  988. }
  989. }
  990. }
  991. extension Path {
  992. // MARK: - NSStream
  993. /// Returns an input stream that reads data from the file at the path, or
  994. /// `nil` if no file exists.
  995. public func inputStream() -> InputStream? {
  996. return InputStream(fileAtPath: absolute._safeRawValue)
  997. }
  998. /// Returns an output stream for writing to the file at the path, or `nil`
  999. /// if no file exists.
  1000. ///
  1001. /// - Parameter shouldAppend: `true` if newly written data should be
  1002. /// appended to any existing file contents,
  1003. /// `false` otherwise. Default value is `false`.
  1004. ///
  1005. public func outputStream(append shouldAppend: Bool = false) -> OutputStream? {
  1006. return OutputStream(toFileAtPath: absolute._safeRawValue, append: shouldAppend)
  1007. }
  1008. }
  1009. extension Path: ExpressibleByStringInterpolation {
  1010. // MARK: - StringInterpolationConvertible
  1011. /// Initializes a path from the string interpolation paths.
  1012. public init(stringInterpolation paths: Path...) {
  1013. self.init(paths.reduce("", { $0 + $1.rawValue }))
  1014. }
  1015. /// Initializes a path from the string interpolation segment.
  1016. public init<T>(stringInterpolationSegment expr: T) {
  1017. if let path = expr as? Path {
  1018. self = path
  1019. } else {
  1020. self = Path(String(describing: expr))
  1021. }
  1022. }
  1023. }
  1024. extension Path: CustomStringConvertible {
  1025. // MARK: - CustomStringConvertible
  1026. /// A textual representation of `self`.
  1027. public var description: String {
  1028. return rawValue
  1029. }
  1030. }
  1031. extension Path: CustomDebugStringConvertible {
  1032. // MARK: - CustomDebugStringConvertible
  1033. /// A textual representation of `self`, suitable for debugging.
  1034. public var debugDescription: String {
  1035. return "Path(\(rawValue.debugDescription))"
  1036. }
  1037. }
  1038. extension Path: Sequence {
  1039. // MARK: - Sequence
  1040. /// - Returns: An *iterator* over the contents of the path.
  1041. public func makeIterator() -> DirectoryEnumerator {
  1042. return DirectoryEnumerator(path: self)
  1043. }
  1044. }
  1045. extension Path {
  1046. // MARK: - Paths
  1047. /// Returns the path to the user's or application's home directory,
  1048. /// depending on the platform.
  1049. public static var userHome: Path {
  1050. // same as FileManager.default.homeDirectoryForCurrentUser
  1051. return Path(NSHomeDirectory()).standardized
  1052. }
  1053. /// Returns the path to the user's temporary directory.
  1054. public static var userTemporary: Path {
  1055. // same as FileManager.default.temporaryDirectory
  1056. return Path(NSTemporaryDirectory()).standardized
  1057. }
  1058. /// Returns a temporary path for the process.
  1059. public static var processTemporary: Path {
  1060. return Path.userTemporary + ProcessInfo.processInfo.globallyUniqueString
  1061. }
  1062. /// Returns a unique temporary path.
  1063. public static var uniqueTemporary: Path {
  1064. return Path.processTemporary + UUID().uuidString
  1065. }
  1066. /// Returns the path to the user's caches directory.
  1067. public static var userCaches: Path {
  1068. return _pathInUserDomain(.cachesDirectory)
  1069. }
  1070. /// Returns the path to the user's applications directory.
  1071. public static var userApplications: Path {
  1072. return _pathInUserDomain(.applicationDirectory)
  1073. }
  1074. /// Returns the path to the user's application support directory.
  1075. public static var userApplicationSupport: Path {
  1076. return _pathInUserDomain(.applicationSupportDirectory)
  1077. }
  1078. /// Returns the path to the user's desktop directory.
  1079. public static var userDesktop: Path {
  1080. return _pathInUserDomain(.desktopDirectory)
  1081. }
  1082. /// Returns the path to the user's documents directory.
  1083. public static var userDocuments: Path {
  1084. return _pathInUserDomain(.documentDirectory)
  1085. }
  1086. /// Returns the path to the user's autosaved documents directory.
  1087. public static var userAutosavedInformation: Path {
  1088. return _pathInUserDomain(.autosavedInformationDirectory)
  1089. }
  1090. /// Returns the path to the user's downloads directory.
  1091. public static var userDownloads: Path {
  1092. return _pathInUserDomain(.downloadsDirectory)
  1093. }
  1094. /// Returns the path to the user's library directory.
  1095. public static var userLibrary: Path {
  1096. return _pathInUserDomain(.libraryDirectory)
  1097. }
  1098. /// Returns the path to the user's movies directory.
  1099. public static var userMovies: Path {
  1100. return _pathInUserDomain(.moviesDirectory)
  1101. }
  1102. /// Returns the path to the user's music directory.
  1103. public static var userMusic: Path {
  1104. return _pathInUserDomain(.musicDirectory)
  1105. }
  1106. /// Returns the path to the user's pictures directory.
  1107. public static var userPictures: Path {
  1108. return _pathInUserDomain(.picturesDirectory)
  1109. }
  1110. /// Returns the path to the user's Public sharing directory.
  1111. public static var userSharedPublic: Path {
  1112. return _pathInUserDomain(.sharedPublicDirectory)
  1113. }
  1114. #if os(OSX)
  1115. /// Returns the path to the user scripts folder for the calling application
  1116. public static var userApplicationScripts: Path {
  1117. return _pathInUserDomain(.applicationScriptsDirectory)
  1118. }
  1119. /// Returns the path to the user's trash directory
  1120. public static var userTrash: Path {
  1121. return _pathInUserDomain(.trashDirectory)
  1122. }
  1123. #endif
  1124. /// Returns the path to the system's applications directory.
  1125. public static var systemApplications: Path {
  1126. return _pathInSystemDomain(.applicationDirectory)
  1127. }
  1128. /// Returns the path to the system's application support directory.
  1129. public static var systemApplicationSupport: Path {
  1130. return _pathInSystemDomain(.applicationSupportDirectory)
  1131. }
  1132. /// Returns the path to the system's library directory.
  1133. public static var systemLibrary: Path {
  1134. return _pathInSystemDomain(.libraryDirectory)
  1135. }
  1136. /// Returns the path to the system's core services directory.
  1137. public static var systemCoreServices: Path {
  1138. return _pathInSystemDomain(.coreServiceDirectory)
  1139. }
  1140. /// Returns the path to the system's PPDs directory.
  1141. public static var systemPrinterDescription: Path {
  1142. return _pathInSystemDomain(.printerDescriptionDirectory)
  1143. }
  1144. /// Returns the path to the system's PreferencePanes directory.
  1145. public static var systemPreferencePanes: Path {
  1146. return _pathInSystemDomain(.preferencePanesDirectory)
  1147. }
  1148. /// Returns the paths where resources can occur.
  1149. public static var allLibraries: [Path] {
  1150. return _pathsInDomains(.allLibrariesDirectory, .allDomainsMask)
  1151. }
  1152. /// Returns the paths where applications can occur
  1153. public static var allApplications: [Path] {
  1154. return _pathsInDomains(.allApplicationsDirectory, .allDomainsMask)
  1155. }
  1156. fileprivate static func _pathInUserDomain(_ directory: FileManager.SearchPathDirectory) -> Path {
  1157. return _pathsInDomains(directory, .userDomainMask)[0]
  1158. }
  1159. fileprivate static func _pathInSystemDomain(_ directory: FileManager.SearchPathDirectory) -> Path {
  1160. return _pathsInDomains(directory, .systemDomainMask)[0]
  1161. }
  1162. fileprivate static func _pathsInDomains(_ directory: FileManager.SearchPathDirectory,
  1163. _ domainMask: FileManager.SearchPathDomainMask) -> [Path] {
  1164. return NSSearchPathForDirectoriesInDomains(directory, domainMask, true)
  1165. .map({ Path($0).standardized })
  1166. }
  1167. }