@@ -1,4 +1,11 @@
import Foundation
#if ! os ( Linux )
import func Darwin . realpath
let _realpath = Darwin . realpath
#else
import func Glibc . realpath
let _realpath = Glibc . realpath
#endif
/* *
A ` P a t h ` r e p r e s e n t s a n a b s o l u t e p a t h o n a f i l e s y s t e m .
@@ -32,19 +39,85 @@ import Foundation
public struct Path : Equatable , Hashable , Comparable {
init ( string : String ) {
assert ( string . first = = " / " )
assert ( string . last != " / " || string = = " / " )
assert ( string . split ( separator : " / " ) . contains ( " .. " ) = = false )
self . string = string
}
// / R e t u r n s ` n i l ` u n l e s s f e d a n a b s o l u t e p a t h .
/* *
C r e a t e s a n e w a b s o l u t e , s t a n d a r d i z e d p a t h .
- N o t e : R e s o l v e s a n y . . o r . c o m p o n e n t s .
- N o t e : R e m o v e s m u l t i p l e s u b s e q u e n t a n d t r a i l i n g o c c u r e n c e s o f ` / ` .
- N o t e : D o e s * n o t * r e s o l v e a n y s y m l i n k s .
- N o t e : O n m a c O S , r e m o v e s a n i n i t i a l c o m p o n e n t o f “ / p r i v a t e / v a r / a u t o m o u n t ” , “ / v a r / a u t o m o u n t ” , o r “ / p r i v a t e ” f r o m t h e p a t h , i f t h e r e s u l t s t i l l i n d i c a t e s a n e x i s t i n g f i l e o r d i r e c t o r y ( c h e c k e d b y c o n s u l t i n g t h e f i l e s y s t e m ) .
- R e t u r n s : T h e p a t h o r ` n i l ` i f f e d a r e l a t i v e p a t h o r a ` ~ f o o ` s t r i n g w h e r e t h e r e i s n o u s e r ` f o o ` .
*/
public init ? ( _ description : String ) {
gu ard description . starts ( with : " / " ) || description . starts ( with : " ~ /" ) else { return nil }
self . init ( string : ( description as NSString ) . standardizingPath )
v ar pathComponents = description . split ( separator : " / " )
switch description . first {
case " / " :
break
case " ~ " :
if description = = " ~ " {
self = Path . home
return
}
let tilded : String
if description . hasPrefix ( " ~/ " ) {
tilded = Path . home . string
} else {
let username = String ( pathComponents [ 0 ] . dropFirst ( ) )
if #available ( OSX 10.12 , * ) {
guard let url = FileManager . default . homeDirectory ( forUser : username ) else { return nil }
assert ( url . scheme = = " file " )
tilded = url . path
} else {
guard let dir = NSHomeDirectoryForUser ( username ) else { return nil }
tilded = dir
}
}
pathComponents . remove ( at : 0 )
pathComponents . insert ( contentsOf : tilded . split ( separator : " / " ) , at : 0 )
default :
return nil
}
#if os ( macOS )
func ifExists ( withPrefix prefix : String , removeFirst n : Int ) {
assert ( prefix . split ( separator : " / " ) . count = = n )
if description . hasPrefix ( prefix ) , FileManager . default . fileExists ( atPath : description ) {
pathComponents . removeFirst ( n )
}
}
ifExists ( withPrefix : " /private/var/automount " , removeFirst : 3 )
ifExists ( withPrefix : " /var/automount " , removeFirst : 2 )
ifExists ( withPrefix : " /private " , removeFirst : 1 )
#endif
self . string = join_ ( prefix : " / " , pathComponents : pathComponents )
}
public init ? ( _ url : URL ) {
guard url . scheme = = " file " else { return nil }
self . init ( string : url . path )
// N O T E : U R L c a n n o t b e a f i l e - r e f e r e n c e u r l , u n l i k e N S U R L , s o t h i s a l w a y s w o r k s
}
public init ? ( _ url : NSURL ) {
guard url . scheme = = " file " , let path = url . path else { return nil }
self . init ( string : path )
// ^ ^ w o r k s e v e n i f t h e u r l i s a f i l e - r e f e r e n c e u r l
}
// / : n o d o c :
public subscript ( dynamicMember pathComponent : String ) -> Path {
let str = ( string as NSString ) . appendingPathComponent ( pathComponent )
return Path ( string : str )
public subscript ( dynamicMember addendum : String ) -> Path {
// N O T E i t ’ s p o s s i b l e f o r t h e s t r i n g t o b e a n y t h i n g i f w e a r e i n v o k e d v i a
// e x p l i c i t s u b s c r i p t t h u s w e u s e o u r f u l l y s a n i t i z e d ` j o i n ` f u n c t i o n
return Path ( string : join_ ( prefix : string , appending : addendum ) )
}
// MARK: P r o p e r t i e s
@@ -57,6 +130,21 @@ public struct Path: Equatable, Hashable, Comparable {
return URL ( fileURLWithPath : string )
}
/* *
R e t u r n s a f i l e - r e f e r e n c e U R L .
- N o t e : O n l y N S U R L c a n b e a f i l e - r e f e r e n c e - U R L , h e n c e w e r e t u r n N S U R L .
- S e e A l s o : h t t p s : / / d e v e l o p e r . a p p l e . c o m / d o c u m e n t a t i o n / f o u n d a t i o n / n s u r l / 1 4 0 8 6 3 1 - f i l e r e f e r e n c e u r l
- I m p o r t a n t : O n L i n u x r e t u r n s a n f i l e s c h e m e N S U R L f o r t h i s p a t h s t r i n g .
*/
public var fileReferenceURL : NSURL ? {
#if ! os ( Linux )
// h t t p s : / / b u g s . s w i f t . o r g / b r o w s e / S R - 2 7 2 8
return ( url as NSURL ) . perform ( #selector ( NSURL . fileReferenceURL ) ) ? . takeUnretainedValue ( ) as ? NSURL
#else
return NSURL ( fileURLWithPath : string )
#endif
}
/* *
R e t u r n s t h e p a r e n t d i r e c t o r y f o r t h i s p a t h .
@@ -66,20 +154,37 @@ public struct Path: Equatable, Hashable, Comparable {
- N o t e : a l w a y s r e t u r n s a v a l i d p a t h , ` P a t h . r o o t . p a r e n t ` * i s * ` P a t h . r o o t ` .
*/
public var parent : Path {
return Path ( string : ( string as NSString ) . deletingLastPathComponent )
let index = string . lastIndex ( of : " / " ) !
let substr = string [ string . indices . startIndex . . < index ]
return Path ( string : String ( substr ) )
}
/* *
R e t u r n s t h e f i l e n a m e e x t e n s i o n o f t h i s p a t h .
- R e m a r k : I m p l e m e n t e d v i a ` N S S t r i n g . p a t h E x t e n s i o n ` .
- R e m a r k : I f t h e r e i s n o e x t e n s i o n r e t u r n s " " .
- R e m a r k : I f t h e f i l e n a m e e n d s w i t h a n y n u m b e r o f " . " , r e t u r n s " " .
- N o t e : W e s p e c i a l c a s e e g . ` f o o . t a r . g z ` .
*/
@ inlinable
public var ` extension ` : String {
if string . hasSuffix ( " .tar.gz " ) {
// F I X M E e f f i c i e n c y
switch true {
case string . hasSuffix ( " .tar.gz " ) :
return " tar.gz "
} else {
return ( string as NSString ) . pathExtension
case string . hasSuffix ( " .tar.bz " ) :
return " tar.bz "
case string . hasSuffix ( " .tar.bz2 " ) :
return " tar.bz2 "
case string . hasSuffix ( " .tar.xz " ) :
return " tar.xz "
default :
let slash = string . lastIndex ( of : " / " ) !
if let dot = string . lastIndex ( of : " . " ) , slash < dot {
let foo = string . index ( after : dot )
return String ( string [ foo . . . ] )
} else {
return " "
}
}
}
@@ -93,14 +198,15 @@ public struct Path: Equatable, Hashable, Comparable {
P a t h . r o o t . j o i n ( " a " ) . j o i n ( " b " ) / / = > / a / b
P a t h . r o o t . j o i n ( " a " ) . j o i n ( " / b " ) / / = > / a / b
- N o t e : ` . . ` a n d ` . ` c o m p o n e n t s a r e i n t e r p r e t e d .
- N o t e : p a t h C o m p o n e n t * m a y * b e m u l t i p l e c o m p o n e n t s .
- N o t e : s y m l i n k s a r e * n o t * r e s o l v e d .
- P a r a m e t e r p a t h C o m p o n e n t : T h e s t r i n g t o j o i n w i t h t h i s p a t h .
- R e t u r n s : A n e w j o i n e d p a t h .
- S e e A l s o : ` P a t h . / ( _ : _ : ) `
*/
public func join < S > ( _ pathComponent : S ) -> Path where S : StringProtocol {
// T O D O s t a n d a r d i z i n g P a t h d o e s m o r e t h a n w e w a n t r e a l l y ( e g t i l d e e x p a n s i o n )
let str = ( string as NSString ) . appendingPathComponent ( String ( pathComponent ) )
return Path ( string : ( str as NSString ) . standardizingPath )
public func join < S > ( _ addendum : S ) -> Path where S : StringProtocol {
return Path ( string : join_ ( prefix : string , appending : addendum ) )
}
/* *
@@ -111,6 +217,9 @@ public struct Path: Equatable, Hashable, Comparable {
P a t h . r o o t / " a " / " b " / / = > / a / b
P a t h . r o o t / " a " / " / b " / / = > / a / b
- N o t e : ` . . ` a n d ` . ` c o m p o n e n t s a r e i n t e r p r e t e d .
- N o t e : p a t h C o m p o n e n t * m a y * b e m u l t i p l e c o m p o n e n t s .
- N o t e : s y m l i n k s a r e * n o t * r e s o l v e d .
- P a r a m e t e r l h s : T h e b a s e p a t h t o j o i n w i t h ` r h s ` .
- P a r a m e t e r r h s : T h e s t r i n g t o j o i n w i t h t h i s ` l h s ` .
- R e t u r n s : A n e w j o i n e d p a t h .
@@ -168,17 +277,71 @@ public struct Path: Equatable, Hashable, Comparable {
- P a r a m e t e r d r o p E x t e n s i o n : I f ` t r u e ` r e t u r n s t h e b a s e n a m e w i t h o u t i t s f i l e e x t e n s i o n .
*/
public func basename ( dropExtension : Bool = false ) -> String {
let str = string as NSString
if ! dropExtension {
return str . lastPathComponent
} else {
let ext = str . pathExtension
if ! ext . isEmpty {
return String ( str . lastPathComponent . dropLast ( ext . count + 1 ) )
var lastPathComponent : Substring {
let slash = string . lastIndex ( of : " / " ) !
let index = string . index ( after : slash )
return string [ index . . . ]
}
var go : Substring {
if ! dropExtension {
return lastPathComponent
} else {
return str . lastPathComponent
let ext = self . extension
if ! ext . isEmpty {
return lastPathComponent . dropLast ( ext . count + 1 )
} else {
return lastPathComponent
}
}
}
return String ( go )
}
/* *
I f t h e p a t h r e p r e s e n t s a n a c t u a l e n t r y t h a t i s a s y m l i n k , r e t u r n s t h e s y m l i n k ’ s
a b s o l u t e d e s t i n a t i o n .
- I m p o r t a n t : T h i s i s n o t e x h a u s t i v e , t h e r e s u l t i n g p a t h m a y s t i l l c o n t a i n
s y m l i n k .
- I m p o r t a n t : T h e p a t h w i l l o n l y b e d i f f e r e n t i f t h e l a s t p a t h c o m p o n e n t i s a
s y m l i n k , a n y s y m l i n k s i n p r i o r c o m p o n e n t s a r e n o t r e s o l v e d .
- N o t e : I f f i l e e x i s t s b u t i s n ’ t a s y m l i n k , r e t u r n s ` s e l f ` .
- N o t e : I f s y m l i n k d e s t i n a t i o n d o e s n o t e x i s t , i s * * n o t * * a n e r r o r .
*/
public func readlink ( ) throws -> Path {
do {
let rv = try FileManager . default . destinationOfSymbolicLink ( atPath : string )
return Path ( rv ) ? ? parent / rv
} catch CocoaError . fileReadUnknown {
// f i l e i s n o t s y m l i n k , r e t u r n ` s e l f `
assert ( exists )
return self
} catch {
#if os ( Linux )
// u g h : S w i f t o n L i n u x
let nsError = error as NSError
if nsError . domain = = NSCocoaErrorDomain , nsError . code = = CocoaError . fileReadUnknown . rawValue , exists {
return self
}
#endif
throw error
}
}
// / R e c u r s i v e l y r e s o l v e s s y m l i n k s i n t h i s p a t h .
public func realpath ( ) throws -> Path {
guard let rv = _realpath ( string , nil ) else { throw CocoaError . error ( . fileNoSuchFile ) }
defer { free ( rv ) }
guard let rvv = String ( validatingUTF8 : rv ) else { throw CocoaError . error ( . fileReadUnknownStringEncoding ) }
// “ R e m o v i n g a n i n i t i a l c o m p o n e n t o f “ / p r i v a t e / v a r / a u t o m o u n t ” , “ / v a r / a u t o m o u n t ” ,
// o r “ / p r i v a t e ” f r o m t h e p a t h , i f t h e r e s u l t s t i l l i n d i c a t e s a n e x i s t i n g f i l e o r
// d i r e c t o r y ( c h e c k e d b y c o n s u l t i n g t h e f i l e s y s t e m ) . ”
// ^ ^ w e d o t h i s t o n o t c o n f l i c t w i t h t h e r e s u l t s t h a t o t h e r A p p l e A P I s g i v e
// w h i c h i s n e c e s s a r y i f w e a r e t o h a v e e q u a l i t y c h e c k s w o r k r e l i a b l y
let rvvv = ( rvv as NSString ) . standardizingPath
return Path ( string : rvvv )
}
// / R e t u r n s t h e l o c a l e - a w a r e s o r t o r d e r f o r t h e t w o p a t h s .
@@ -188,3 +351,38 @@ public struct Path: Equatable, Hashable, Comparable {
return lhs . string . compare ( rhs . string , locale : . current ) = = . orderedAscending
}
}
@ inline ( __always )
private func join_ < S > ( prefix : String , appending : S ) -> String where S : StringProtocol {
return join_ ( prefix : prefix , pathComponents : appending . split ( separator : " / " ) )
}
private func join_ < S > ( prefix : String , pathComponents : S ) -> String where S : Sequence , S . Element : StringProtocol {
assert ( prefix . first = = " / " )
var rv = prefix
for component in pathComponents {
assert ( ! component . contains ( " / " ) )
switch component {
case " .. " :
let start = rv . indices . startIndex
let index = rv . lastIndex ( of : " / " ) !
if start = = index {
rv = " / "
} else {
rv = String ( rv [ start . . < index ] )
}
case " . " :
break
default :
if rv = = " / " {
rv = " / \( component ) "
} else {
rv = " \( rv ) / \( component ) "
}
}
}
return rv
}