Skip to content

Commit

Permalink
Merge pull request #185 from HeMet/symlinks-and-standardizing
Browse files Browse the repository at this point in the history
Symlinks and standardizing
  • Loading branch information
phausler committed Jan 2, 2016
2 parents bb5fffd + 13f3ab1 commit de8559c
Show file tree
Hide file tree
Showing 7 changed files with 412 additions and 32 deletions.
4 changes: 4 additions & 0 deletions Foundation.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@
7A7D6FBB1C16439400957E2E /* TestNSURLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7D6FBA1C16439400957E2E /* TestNSURLResponse.swift */; };
83712C8E1C1684900049AD49 /* TestNSURLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83712C8D1C1684900049AD49 /* TestNSURLRequest.swift */; };
844DC3331C17584F005611F9 /* TestNSScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844DC3321C17584F005611F9 /* TestNSScanner.swift */; };
8460AACC1C3307D800B01413 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8460AACB1C3307D700B01413 /* TestUtils.swift */; };
848A30581C137B3500C83206 /* TestNSHTTPCookie.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848A30571C137B3500C83206 /* TestNSHTTPCookie.swift */; };
84BA558E1C16F90900F48C54 /* TestNSTimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BA558D1C16F90900F48C54 /* TestNSTimeZone.swift */; };
88D28DE71C13AE9000494606 /* TestNSGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D28DE61C13AE9000494606 /* TestNSGeometry.swift */; };
Expand Down Expand Up @@ -567,6 +568,7 @@
7A7D6FBA1C16439400957E2E /* TestNSURLResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSURLResponse.swift; sourceTree = "<group>"; };
83712C8D1C1684900049AD49 /* TestNSURLRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSURLRequest.swift; sourceTree = "<group>"; };
844DC3321C17584F005611F9 /* TestNSScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSScanner.swift; sourceTree = "<group>"; };
8460AACB1C3307D700B01413 /* TestUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = "<group>"; };
848A30571C137B3500C83206 /* TestNSHTTPCookie.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TestNSHTTPCookie.swift; path = TestFoundation/TestNSHTTPCookie.swift; sourceTree = SOURCE_ROOT; };
84BA558D1C16F90900F48C54 /* TestNSTimeZone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSTimeZone.swift; sourceTree = "<group>"; };
88D28DE61C13AE9000494606 /* TestNSGeometry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSGeometry.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1107,6 +1109,7 @@
612952F81C1B235900BE0FD9 /* TestNSNull.swift */,
AA664D4E1C1B03CA00C22186 /* TestNSNumberFormatter.swift */,
45BBD92A1C30E9C200540DDB /* TestNSTask.swift */,
8460AACB1C3307D700B01413 /* TestUtils.swift */,
);
name = Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -1813,6 +1816,7 @@
5B915F0F1C1A320E00BE40C5 /* TestNSJSONSerialization.swift in Sources */,
EA66F6501BF1619600136161 /* TestNSNumber.swift in Sources */,
844DC3331C17584F005611F9 /* TestNSScanner.swift in Sources */,
8460AACC1C3307D800B01413 /* TestUtils.swift in Sources */,
E876A73E1C1180E000F279EC /* TestNSRange.swift in Sources */,
5E5835F41C20C9B500C81317 /* TestNSThread.swift in Sources */,
C2A9D75C1C15C08B00993803 /* TestNSUUID.swift in Sources */,
Expand Down
32 changes: 32 additions & 0 deletions Foundation/NSFileManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,38 @@ public class NSFileManager : NSObject {
public func replaceItemAtURL(originalItemURL: NSURL, withItemAtURL newItemURL: NSURL, backupItemName: String?, options: NSFileManagerItemReplacementOptions) throws -> NSURL {
NSUnimplemented()
}

internal func _tryToResolveTrailingSymlinkInPath(path: String) -> String? {
guard _pathIsSymbolicLink(path) else {
return nil
}

guard let destination = try? NSFileManager.defaultManager().destinationOfSymbolicLinkAtPath(path) else {
return nil
}

return _appendSymlinkDestination(destination, toPath: path)
}

internal func _appendSymlinkDestination(dest: String, toPath: String) -> String {
if dest.hasPrefix("/") {
return dest
} else {
let temp = toPath.bridge().stringByDeletingLastPathComponent
return temp.bridge().stringByAppendingPathComponent(dest)
}
}

internal func _pathIsSymbolicLink(path: String) -> Bool {
guard let
attrs = try? attributesOfItemAtPath(path),
fileType = attrs[NSFileType] as? String
else {
return false
}

return fileType == NSFileTypeSymbolicLink
}
}

extension NSFileManagerDelegate {
Expand Down
85 changes: 83 additions & 2 deletions Foundation/NSPathUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,29 @@ internal extension String {
}
return result
}

internal func _stringByRemovingPrefix(prefix: String) -> String {
guard hasPrefix(prefix) else {
return self
}

var temp = self
temp.removeRange(startIndex..<prefix.endIndex)
return temp
}

internal func _tryToRemovePathPrefix(prefix: String) -> String? {
guard self != prefix else {
return nil
}

let temp = _stringByRemovingPrefix(prefix)
if NSFileManager.defaultManager().fileExistsAtPath(temp) {
return temp
}

return nil
}
}

public extension NSString {
Expand Down Expand Up @@ -347,12 +370,70 @@ public extension NSString {
return result._stringByFixingSlashes()
}

public var stringByExpandingTildeInPath: String {
guard hasPrefix("~") else {
return _swiftObject
}

let endOfUserName = _swiftObject.characters.indexOf("/") ?? _swiftObject.endIndex
let userName = String(_swiftObject.characters[_swiftObject.startIndex.successor()..<endOfUserName])
let optUserName: String? = userName.isEmpty ? nil : userName

guard let homeDir = NSHomeDirectoryForUser(optUserName) else {
return _swiftObject._stringByFixingSlashes(compress: false, stripTrailing: true)
}

var result = _swiftObject
result.replaceRange(_swiftObject.startIndex..<endOfUserName, with: homeDir)
result = result._stringByFixingSlashes(compress: false, stripTrailing: true)

return result
}

public var stringByStandardizingPath: String {
NSUnimplemented()
let expanded = stringByExpandingTildeInPath
var resolved = expanded.bridge().stringByResolvingSymlinksInPath

let automount = "/var/automount"
resolved = resolved._tryToRemovePathPrefix(automount) ?? resolved
return resolved
}

public var stringByResolvingSymlinksInPath: String {
NSUnimplemented()
var components = pathComponents
guard !components.isEmpty else {
return _swiftObject
}

// TODO: pathComponents keeps final path separator if any. Check that logic.
if components.last == "/" {
components.removeLast()
}

let isAbsolutePath = components.first == "/"

var resolvedPath = components.removeFirst()
for component in components {
switch component {

case "", ".":
break

case ".." where isAbsolutePath:
resolvedPath = resolvedPath.bridge().stringByDeletingLastPathComponent

default:
resolvedPath = resolvedPath.bridge().stringByAppendingPathComponent(component)
if let destination = NSFileManager.defaultManager()._tryToResolveTrailingSymlinkInPath(resolvedPath) {
resolvedPath = destination
}
}
}

let privatePrefix = "/private"
resolvedPath = resolvedPath._tryToRemovePathPrefix(privatePrefix) ?? resolvedPath

return resolvedPath
}

public func stringsByAppendingPaths(paths: [String]) -> [String] {
Expand Down
63 changes: 61 additions & 2 deletions Foundation/NSURL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -477,11 +477,70 @@ extension NSURL {
/* The following methods work only on `file:` scheme URLs; for non-`file:` scheme URLs, these methods return the URL unchanged.
*/
public var URLByStandardizingPath: NSURL? {
NSUnimplemented()
// Documentation says it should expand initial tilde, but it does't do this on OS X.
// In remaining cases it works just like URLByResolvingSymlinksInPath.
return URLByResolvingSymlinksInPath
}

public var URLByResolvingSymlinksInPath: NSURL? {
NSUnimplemented()
guard fileURL else {
return NSURL(string: absoluteString!)
}

guard let selfPath = path else {
return NSURL(string: absoluteString!)
}

let absolutePath: String
if selfPath.hasPrefix("/") {
absolutePath = selfPath
} else {
let workingDir = NSFileManager.defaultManager().currentDirectoryPath
absolutePath = workingDir.bridge().stringByAppendingPathComponent(selfPath)
}

var components = absolutePath.pathComponents
guard !components.isEmpty else {
return NSURL(string: absoluteString!)
}

var resolvedPath = components.removeFirst()
for component in components {
switch component {

case "", ".":
break

case "..":
resolvedPath = resolvedPath.bridge().stringByDeletingLastPathComponent

default:
resolvedPath = resolvedPath.bridge().stringByAppendingPathComponent(component)
if let destination = NSFileManager.defaultManager()._tryToResolveTrailingSymlinkInPath(resolvedPath) {
resolvedPath = destination
}
}
}

// It might be a responsibility of NSURL(fileURLWithPath:). Check it.
var isExistingDirectory = false
NSFileManager.defaultManager().fileExistsAtPath(resolvedPath, isDirectory: &isExistingDirectory)

let privatePrefix = "/private"

if resolvedPath.hasPrefix(privatePrefix) && resolvedPath != privatePrefix {
var temp = resolvedPath
temp.removeRange(resolvedPath.startIndex..<privatePrefix.endIndex)
if NSFileManager.defaultManager().fileExistsAtPath(temp) {
resolvedPath = temp
}
}

if isExistingDirectory && !resolvedPath.hasSuffix("/") {
resolvedPath += "/"
}

return NSURL(fileURLWithPath: resolvedPath)
}
}

Expand Down

0 comments on commit de8559c

Please sign in to comment.