FilePathString.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. /*
  2. This source file is part of the Swift System open source project
  3. Copyright (c) 2020 Apple Inc. and the Swift System project authors
  4. Licensed under Apache License v2.0 with Runtime Library Exception
  5. See https://swift.org/LICENSE.txt for license information
  6. */
  7. // MARK: - Platform string
  8. @available(System 0.0.2, *)
  9. extension FilePath {
  10. /// Creates a file path by copying bytes from a null-terminated platform
  11. /// string.
  12. ///
  13. /// - Parameter platformString: A pointer to a null-terminated platform
  14. /// string.
  15. public init(platformString: UnsafePointer<CInterop.PlatformChar>) {
  16. self.init(_platformString: platformString)
  17. }
  18. /// Creates a file path by copying bytes from a null-terminated platform
  19. /// string.
  20. ///
  21. /// - Note It is a precondition that `platformString` must be null-terminated.
  22. /// The absence of a null byte will trigger a runtime error.
  23. ///
  24. /// - Parameter platformString: A null-terminated platform string.
  25. @inlinable
  26. @_alwaysEmitIntoClient
  27. public init(platformString: [CInterop.PlatformChar]) {
  28. guard let _ = platformString.firstIndex(of: 0) else {
  29. fatalError(
  30. "input of FilePath.init(platformString:) must be null-terminated"
  31. )
  32. }
  33. self = platformString.withUnsafeBufferPointer {
  34. FilePath(platformString: $0.baseAddress!)
  35. }
  36. }
  37. @inlinable
  38. @_alwaysEmitIntoClient
  39. @available(*, deprecated, message: "Use FilePath.init(_ scalar: Unicode.Scalar)")
  40. public init(platformString: inout CInterop.PlatformChar) {
  41. guard platformString == 0 else {
  42. fatalError(
  43. "input of FilePath.init(platformString:) must be null-terminated"
  44. )
  45. }
  46. self = FilePath()
  47. }
  48. @inlinable
  49. @_alwaysEmitIntoClient
  50. @available(*, deprecated, message: "Use FilePath(_: String) to create a path from a String")
  51. public init(platformString: String) {
  52. if let nullLoc = platformString.firstIndex(of: "\0") {
  53. self = FilePath(String(platformString[..<nullLoc]))
  54. } else {
  55. self = FilePath(platformString)
  56. }
  57. }
  58. #if !os(Windows)
  59. // Note: This function should have been opaque, but it shipped as
  60. // `@_alwaysEmitIntoClient` in macOS 12/iOS 15, and now it is stuck
  61. // this way forever. (Or until the language provides a way for us
  62. // to declare separate availability for a function's exported symbol
  63. // and its inlinable body.)
  64. /// Calls the given closure with a pointer to the contents of the file path,
  65. /// represented as a null-terminated platform string.
  66. ///
  67. /// - Parameter body: A closure with a pointer parameter
  68. /// that points to a null-terminated platform string.
  69. /// If `body` has a return value,
  70. /// that value is also used as the return value for this method.
  71. /// - Returns: The return value, if any, of the `body` closure parameter.
  72. ///
  73. /// The pointer passed as an argument to `body` is valid
  74. /// only during the execution of this method.
  75. /// Don't try to store the pointer for later use.
  76. @_alwaysEmitIntoClient
  77. public func withPlatformString<Result>(
  78. _ body: (UnsafePointer<CInterop.PlatformChar>) throws -> Result
  79. ) rethrows -> Result {
  80. return try withCString(body)
  81. }
  82. #else
  83. /// Calls the given closure with a pointer to the contents of the file path,
  84. /// represented as a null-terminated platform string.
  85. ///
  86. /// - Parameter body: A closure with a pointer parameter
  87. /// that points to a null-terminated platform string.
  88. /// If `body` has a return value,
  89. /// that value is also used as the return value for this method.
  90. /// - Returns: The return value, if any, of the `body` closure parameter.
  91. ///
  92. /// The pointer passed as an argument to `body` is valid
  93. /// only during the execution of this method.
  94. /// Don't try to store the pointer for later use.
  95. public func withPlatformString<Result>(
  96. _ body: (UnsafePointer<CInterop.PlatformChar>) throws -> Result
  97. ) rethrows -> Result {
  98. return try _withPlatformString(body)
  99. }
  100. #endif
  101. }
  102. @available(System 0.0.2, *)
  103. extension FilePath.Component {
  104. /// Creates a file path component by copying bytes from a null-terminated
  105. /// platform string.
  106. ///
  107. /// Returns `nil` if `platformString` is empty, is a root, or has more than
  108. /// one component in it.
  109. ///
  110. /// - Parameter platformString: A pointer to a null-terminated platform
  111. /// string.
  112. public init?(platformString: UnsafePointer<CInterop.PlatformChar>) {
  113. self.init(_platformString: platformString)
  114. }
  115. /// Creates a file path component by copying bytes from a null-terminated
  116. /// platform string. It is a precondition that a null byte indicates the end of
  117. /// the string. The absence of a null byte will trigger a runtime error.
  118. ///
  119. /// Returns `nil` if `platformString` is empty, is a root, or has more than
  120. /// one component in it.
  121. ///
  122. /// - Note It is a precondition that `platformString` must be null-terminated.
  123. /// The absence of a null byte will trigger a runtime error.
  124. ///
  125. /// - Parameter platformString: A null-terminated platform string.
  126. @inlinable
  127. @_alwaysEmitIntoClient
  128. public init?(platformString: [CInterop.PlatformChar]) {
  129. guard let _ = platformString.firstIndex(of: 0) else {
  130. fatalError(
  131. "input of FilePath.Component.init?(platformString:) must be null-terminated"
  132. )
  133. }
  134. guard let component = platformString.withUnsafeBufferPointer({
  135. FilePath.Component(platformString: $0.baseAddress!)
  136. }) else {
  137. return nil
  138. }
  139. self = component
  140. }
  141. @inlinable
  142. @_alwaysEmitIntoClient
  143. @available(*, deprecated, message: "Use FilePath.Component.init(_ scalar: Unicode.Scalar)")
  144. public init?(platformString: inout CInterop.PlatformChar) {
  145. guard platformString == 0 else {
  146. fatalError(
  147. "input of FilePath.Component.init?(platformString:) must be null-terminated"
  148. )
  149. }
  150. return nil
  151. }
  152. @inlinable
  153. @_alwaysEmitIntoClient
  154. @available(*, deprecated, message: "Use FilePath.Component.init(_: String)")
  155. public init?(platformString: String) {
  156. let string: String
  157. if let nullLoc = platformString.firstIndex(of: "\0") {
  158. string = String(platformString[..<nullLoc])
  159. } else {
  160. string = platformString
  161. }
  162. guard let component = FilePath.Component(string) else { return nil }
  163. self = component
  164. }
  165. /// Calls the given closure with a pointer to the contents of the file path
  166. /// component, represented as a null-terminated platform string.
  167. ///
  168. /// If this is not the last component of a path, an allocation will occur in
  169. /// order to add the null terminator.
  170. ///
  171. /// - Parameter body: A closure with a pointer parameter
  172. /// that points to a null-terminated platform string.
  173. /// If `body` has a return value,
  174. /// that value is also used as the return value for this method.
  175. /// - Returns: The return value, if any, of the `body` closure parameter.
  176. ///
  177. /// The pointer passed as an argument to `body` is valid
  178. /// only during the execution of this method.
  179. /// Don't try to store the pointer for later use.
  180. public func withPlatformString<Result>(
  181. _ body: (UnsafePointer<CInterop.PlatformChar>) throws -> Result
  182. ) rethrows -> Result {
  183. try _withPlatformString(body)
  184. }
  185. }
  186. @available(System 0.0.2, *)
  187. extension FilePath.Root {
  188. /// Creates a file path root by copying bytes from a null-terminated platform
  189. /// string.
  190. ///
  191. /// Returns `nil` if `platformString` is empty or is not a root.
  192. ///
  193. /// - Parameter platformString: A pointer to a null-terminated platform
  194. /// string.
  195. public init?(platformString: UnsafePointer<CInterop.PlatformChar>) {
  196. self.init(_platformString: platformString)
  197. }
  198. /// Creates a file path root by copying bytes from a null-terminated platform
  199. /// string. It is a precondition that a null byte indicates the end of
  200. /// the string. The absence of a null byte will trigger a runtime error.
  201. ///
  202. /// Returns `nil` if `platformString` is empty or is not a root.
  203. ///
  204. /// - Note It is a precondition that `platformString` must be null-terminated.
  205. /// The absence of a null byte will trigger a runtime error.
  206. ///
  207. /// - Parameter platformString: A null-terminated platform string.
  208. @inlinable
  209. @_alwaysEmitIntoClient
  210. public init?(platformString: [CInterop.PlatformChar]) {
  211. guard let _ = platformString.firstIndex(of: 0) else {
  212. fatalError(
  213. "input of FilePath.Root.init?(platformString:) must be null-terminated"
  214. )
  215. }
  216. guard let component = platformString.withUnsafeBufferPointer({
  217. FilePath.Root(platformString: $0.baseAddress!)
  218. }) else {
  219. return nil
  220. }
  221. self = component
  222. }
  223. @inlinable
  224. @_alwaysEmitIntoClient
  225. @available(*, deprecated, message: "Use FilePath.Root.init(_ scalar: Unicode.Scalar)")
  226. public init?(platformString: inout CInterop.PlatformChar) {
  227. guard platformString == 0 else {
  228. fatalError(
  229. "input of FilePath.Root.init?(platformString:) must be null-terminated"
  230. )
  231. }
  232. return nil
  233. }
  234. @inlinable
  235. @_alwaysEmitIntoClient
  236. @available(*, deprecated, message: "Use FilePath.Root.init(_: String)")
  237. public init?(platformString: String) {
  238. let string: String
  239. if let nullLoc = platformString.firstIndex(of: "\0") {
  240. string = String(platformString[..<nullLoc])
  241. } else {
  242. string = platformString
  243. }
  244. guard let root = FilePath.Root(string) else { return nil }
  245. self = root
  246. }
  247. /// Calls the given closure with a pointer to the contents of the file path
  248. /// root, represented as a null-terminated platform string.
  249. ///
  250. /// If the path has a relative portion, an allocation will occur in order to
  251. /// add the null terminator.
  252. ///
  253. /// - Parameter body: A closure with a pointer parameter
  254. /// that points to a null-terminated platform string.
  255. /// If `body` has a return value,
  256. /// that value is also used as the return value for this method.
  257. /// - Returns: The return value, if any, of the `body` closure parameter.
  258. ///
  259. /// The pointer passed as an argument to `body` is valid
  260. /// only during the execution of this method.
  261. /// Don't try to store the pointer for later use.
  262. public func withPlatformString<Result>(
  263. _ body: (UnsafePointer<CInterop.PlatformChar>) throws -> Result
  264. ) rethrows -> Result {
  265. try _withPlatformString(body)
  266. }
  267. }
  268. // MARK: - String literals
  269. @available(System 0.0.1, *)
  270. extension FilePath: ExpressibleByStringLiteral {
  271. /// Creates a file path from a string literal.
  272. ///
  273. /// - Parameter stringLiteral: A string literal
  274. /// whose Unicode encoded contents to use as the contents of the path.
  275. public init(stringLiteral: String) {
  276. self.init(stringLiteral)
  277. }
  278. /// Creates a file path from a string.
  279. ///
  280. /// - Parameter string: A string
  281. /// whose Unicode encoded contents to use as the contents of the path.
  282. public init(_ string: String) {
  283. self.init(SystemString(string))
  284. }
  285. }
  286. @available(System 0.0.2, *)
  287. extension FilePath.Component: ExpressibleByStringLiteral {
  288. /// Create a file path component from a string literal.
  289. ///
  290. /// Precondition: `stringLiteral` is non-empty, is not a root,
  291. /// and has only one component in it.
  292. public init(stringLiteral: String) {
  293. guard let s = FilePath.Component(stringLiteral) else {
  294. // TODO: static assert
  295. fatalError("""
  296. FilePath.Component must be created from exactly one non-root component
  297. """)
  298. }
  299. self = s
  300. }
  301. /// Create a file path component from a string.
  302. ///
  303. /// Returns `nil` if `string` is empty, a root, or has more than one component
  304. /// in it.
  305. public init?(_ string: String) {
  306. self.init(SystemString(string))
  307. }
  308. }
  309. @available(System 0.0.2, *)
  310. extension FilePath.Root: ExpressibleByStringLiteral {
  311. /// Create a file path root from a string literal.
  312. ///
  313. /// Precondition: `stringLiteral` is non-empty and is a root.
  314. public init(stringLiteral: String) {
  315. guard let s = FilePath.Root(stringLiteral) else {
  316. // TODO: static assert
  317. fatalError("""
  318. FilePath.Root must be created from a root
  319. """)
  320. }
  321. self = s
  322. }
  323. /// Create a file path root from a string.
  324. ///
  325. /// Returns `nil` if `string` is empty or is not a root.
  326. public init?(_ string: String) {
  327. self.init(SystemString(string))
  328. }
  329. }
  330. // MARK: - Printing and dumping
  331. @available(System 0.0.1, *)
  332. extension FilePath: CustomStringConvertible, CustomDebugStringConvertible {
  333. /// A textual representation of the file path.
  334. ///
  335. /// If the content of the path isn't a well-formed Unicode string,
  336. /// this replaces invalid bytes with U+FFFD. See `String.init(decoding:)`
  337. @inline(never)
  338. public var description: String { String(decoding: self) }
  339. /// A textual representation of the file path, suitable for debugging.
  340. ///
  341. /// If the content of the path isn't a well-formed Unicode string,
  342. /// this replaces invalid bytes with U+FFFD. See `String.init(decoding:)`
  343. public var debugDescription: String { description.debugDescription }
  344. }
  345. @available(System 0.0.2, *)
  346. extension FilePath.Component: CustomStringConvertible, CustomDebugStringConvertible {
  347. /// A textual representation of the path component.
  348. ///
  349. /// If the content of the path component isn't a well-formed Unicode string,
  350. /// this replaces invalid bytes with U+FFFD. See `String.init(decoding:)`.
  351. @inline(never)
  352. public var description: String { String(decoding: self) }
  353. /// A textual representation of the path component, suitable for debugging.
  354. ///
  355. /// If the content of the path component isn't a well-formed Unicode string,
  356. /// this replaces invalid bytes with U+FFFD. See `String.init(decoding:)`.
  357. public var debugDescription: String { description.debugDescription }
  358. }
  359. @available(System 0.0.2, *)
  360. extension FilePath.Root: CustomStringConvertible, CustomDebugStringConvertible {
  361. /// A textual representation of the path root.
  362. ///
  363. /// If the content of the path root isn't a well-formed Unicode string,
  364. /// this replaces invalid bytes with U+FFFD. See `String.init(decoding:)`.
  365. @inline(never)
  366. public var description: String { String(decoding: self) }
  367. /// A textual representation of the path root, suitable for debugging.
  368. ///
  369. /// If the content of the path root isn't a well-formed Unicode string,
  370. /// this replaces invalid bytes with U+FFFD. See `String.init(decoding:)`.
  371. public var debugDescription: String { description.debugDescription }
  372. }
  373. // MARK: - Convenience helpers
  374. // Convenience helpers
  375. @available(System 0.0.2, *)
  376. extension FilePath {
  377. /// Creates a string by interpreting the path’s content as UTF-8 on Unix
  378. /// and UTF-16 on Windows.
  379. ///
  380. /// This property is equivalent to calling `String(decoding: path)`
  381. public var string: String {
  382. String(decoding: self)
  383. }
  384. }
  385. @available(System 0.0.2, *)
  386. extension FilePath.Component {
  387. /// Creates a string by interpreting the component’s content as UTF-8 on Unix
  388. /// and UTF-16 on Windows.
  389. ///
  390. /// This property is equivalent to calling `String(decoding: component)`.
  391. public var string: String {
  392. String(decoding: self)
  393. }
  394. }
  395. @available(System 0.0.2, *)
  396. extension FilePath.Root {
  397. /// On Unix, this returns `"/"`.
  398. ///
  399. /// On Windows, interprets the root's content as UTF-16 on Windows.
  400. ///
  401. /// This property is equivalent to calling `String(decoding: root)`.
  402. public var string: String {
  403. String(decoding: self)
  404. }
  405. }
  406. // MARK: - Decoding and validating
  407. @available(System 0.0.1, *)
  408. extension String {
  409. /// Creates a string by interpreting the file path's content as UTF-8 on Unix
  410. /// and UTF-16 on Windows.
  411. ///
  412. /// - Parameter path: The file path to be interpreted as
  413. /// `CInterop.PlatformUnicodeEncoding`.
  414. ///
  415. /// If the content of the file path isn't a well-formed Unicode string,
  416. /// this initializer replaces invalid bytes with U+FFFD.
  417. /// This means that, depending on the semantics of the specific file system,
  418. /// conversion to a string and back to a path
  419. /// might result in a value that's different from the original path.
  420. public init(decoding path: FilePath) {
  421. self.init(_decoding: path)
  422. }
  423. }
  424. @available(System 0.0.2, *)
  425. extension String {
  426. /// Creates a string from a file path, validating its contents as UTF-8 on
  427. /// Unix and UTF-16 on Windows.
  428. ///
  429. /// - Parameter path: The file path to be interpreted as
  430. /// `CInterop.PlatformUnicodeEncoding`.
  431. ///
  432. /// If the contents of the file path isn't a well-formed Unicode string,
  433. /// this initializer returns `nil`.
  434. @available(System 0.0.2, *)
  435. public init?(validating path: FilePath) {
  436. self.init(_validating: path)
  437. }
  438. }
  439. @available(System 0.0.2, *)
  440. extension String {
  441. /// Creates a string by interpreting the path component's content as UTF-8 on
  442. /// Unix and UTF-16 on Windows.
  443. ///
  444. /// - Parameter component: The path component to be interpreted as
  445. /// `CInterop.PlatformUnicodeEncoding`.
  446. ///
  447. /// If the content of the path component isn't a well-formed Unicode string,
  448. /// this initializer replaces invalid bytes with U+FFFD.
  449. /// This means that, depending on the semantics of the specific file system,
  450. /// conversion to a string and back to a path component
  451. /// might result in a value that's different from the original path component.
  452. public init(decoding component: FilePath.Component) {
  453. self.init(_decoding: component)
  454. }
  455. /// Creates a string from a path component, validating its contents as UTF-8
  456. /// on Unix and UTF-16 on Windows.
  457. ///
  458. /// - Parameter component: The path component to be interpreted as
  459. /// `CInterop.PlatformUnicodeEncoding`.
  460. ///
  461. /// If the contents of the path component isn't a well-formed Unicode string,
  462. /// this initializer returns `nil`.
  463. public init?(validating component: FilePath.Component) {
  464. self.init(_validating: component)
  465. }
  466. }
  467. @available(System 0.0.2, *)
  468. extension String {
  469. /// On Unix, creates the string `"/"`
  470. ///
  471. /// On Windows, creates a string by interpreting the path root's content as
  472. /// UTF-16.
  473. ///
  474. /// - Parameter root: The path root to be interpreted as
  475. /// `CInterop.PlatformUnicodeEncoding`.
  476. ///
  477. /// If the content of the path root isn't a well-formed Unicode string,
  478. /// this initializer replaces invalid bytes with U+FFFD.
  479. /// This means that on Windows,
  480. /// conversion to a string and back to a path root
  481. /// might result in a value that's different from the original path root.
  482. public init(decoding root: FilePath.Root) {
  483. self.init(_decoding: root)
  484. }
  485. /// On Unix, creates the string `"/"`
  486. ///
  487. /// On Windows, creates a string from a path root, validating its contents as
  488. /// UTF-16 on Windows.
  489. ///
  490. /// - Parameter root: The path root to be interpreted as
  491. /// `CInterop.PlatformUnicodeEncoding`.
  492. ///
  493. /// On Windows, if the contents of the path root isn't a well-formed Unicode
  494. /// string, this initializer returns `nil`.
  495. public init?(validating root: FilePath.Root) {
  496. self.init(_validating: root)
  497. }
  498. }
  499. // MARK: - Internal helpers
  500. extension String {
  501. fileprivate init<PS: _PlatformStringable>(_decoding ps: PS) {
  502. self = ps._withPlatformString { String(platformString: $0) }
  503. }
  504. fileprivate init?<PS: _PlatformStringable>(_validating ps: PS) {
  505. guard let str = ps._withPlatformString(
  506. String.init(validatingPlatformString:)
  507. ) else {
  508. return nil
  509. }
  510. self = str
  511. }
  512. }
  513. // MARK: - Deprecations
  514. @available(System 0.0.1, *)
  515. extension String {
  516. @available(*, deprecated, renamed: "init(decoding:)")
  517. public init(_ path: FilePath) { self.init(decoding: path) }
  518. @available(*, deprecated, renamed: "init(validating:)")
  519. public init?(validatingUTF8 path: FilePath) { self.init(validating: path) }
  520. }
  521. #if !os(Windows)
  522. @available(System 0.0.1, *)
  523. extension FilePath {
  524. /// For backwards compatibility only. This initializer is equivalent to
  525. /// the preferred `FilePath(platformString:)`.
  526. @available(*, deprecated, renamed: "init(platformString:)")
  527. public init(cString: UnsafePointer<CChar>) {
  528. self.init(platformString: cString)
  529. }
  530. @available(*, deprecated, renamed: "init(platformString:)")
  531. public init(cString: [CChar]) {
  532. self.init(platformString: cString)
  533. }
  534. @available(*, deprecated, renamed: "init(platformString:)")
  535. public init(cString: inout CChar) {
  536. self.init(platformString: &cString)
  537. }
  538. @available(*, deprecated, renamed: "init(platformString:)")
  539. public init(cString: String) {
  540. self.init(platformString: cString)
  541. }
  542. /// For backwards compatibility only. This function is equivalent to
  543. /// the preferred `withPlatformString`.
  544. public func withCString<Result>(
  545. _ body: (UnsafePointer<CChar>) throws -> Result
  546. ) rethrows -> Result {
  547. return try _withPlatformString(body)
  548. }
  549. }
  550. #endif