blob: 4590787a789529ae02376e11db7681de935b1fa6 [file] [log] [blame]
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
import CoreFoundation
#if os(OSX) || os(iOS)
import Darwin
#elseif os(Linux) || CYGWIN
import Glibc
#endif
public func NSTemporaryDirectory() -> String {
#if os(OSX) || os(iOS)
var buf = [Int8](repeating: 0, count: 100)
let r = confstr(_CS_DARWIN_USER_TEMP_DIR, &buf, buf.count)
if r != 0 && r < buf.count {
return String(cString: buf, encoding: .utf8)!
}
#endif
if let tmpdir = ProcessInfo.processInfo.environment["TMPDIR"] {
if !tmpdir.hasSuffix("/") {
return tmpdir + "/"
} else {
return tmpdir
}
}
return "/tmp/"
}
internal extension String {
internal var _startOfLastPathComponent : String.CharacterView.Index {
precondition(!hasSuffix("/") && length > 1)
let characterView = characters
let startPos = characterView.startIndex
let endPos = characterView.endIndex
var curPos = endPos
// Find the beginning of the component
while curPos > startPos {
let prevPos = characterView.index(before: curPos)
if characterView[prevPos] == "/" {
break
}
curPos = prevPos
}
return curPos
}
internal var _startOfPathExtension : String.CharacterView.Index? {
precondition(!hasSuffix("/"))
let characterView = self.characters
let endPos = characterView.endIndex
var curPos = endPos
let lastCompStartPos = _startOfLastPathComponent
// Find the beginning of the extension
while curPos > lastCompStartPos {
let prevPos = characterView.index(before: curPos)
let char = characterView[prevPos]
if char == "/" {
return nil
} else if char == "." {
if lastCompStartPos == prevPos {
return nil
} else {
return curPos
}
}
curPos = prevPos
}
return nil
}
internal var absolutePath: Bool {
return hasPrefix("~") || hasPrefix("/")
}
internal func _stringByAppendingPathComponent(_ str: String, doneAppending : Bool = true) -> String {
if str.length == 0 {
return self
}
if self == "" {
return "/" + str
}
if self == "/" {
return self + str
}
return self + "/" + str
}
internal func _stringByFixingSlashes(compress : Bool = true, stripTrailing: Bool = true) -> String {
var result = self
if compress {
result.withMutableCharacters { characterView in
let startPos = characterView.startIndex
var endPos = characterView.endIndex
var curPos = startPos
while curPos < endPos {
if characterView[curPos] == "/" {
var afterLastSlashPos = curPos
while afterLastSlashPos < endPos && characterView[afterLastSlashPos] == "/" {
afterLastSlashPos = characterView.index(after: afterLastSlashPos)
}
if afterLastSlashPos != characterView.index(after: curPos) {
characterView.replaceSubrange(curPos ..< afterLastSlashPos, with: ["/"])
endPos = characterView.endIndex
}
curPos = afterLastSlashPos
} else {
curPos = characterView.index(after: curPos)
}
}
}
}
if stripTrailing && result.length > 1 && result.hasSuffix("/") {
result.remove(at: result.characters.index(before: result.characters.endIndex))
}
return result
}
internal func _stringByRemovingPrefix(_ prefix: String) -> String {
guard hasPrefix(prefix) else {
return self
}
var temp = self
temp.removeSubrange(startIndex..<prefix.endIndex)
return temp
}
internal func _tryToRemovePathPrefix(_ prefix: String) -> String? {
guard self != prefix else {
return nil
}
let temp = _stringByRemovingPrefix(prefix)
if FileManager.default.fileExists(atPath: temp) {
return temp
}
return nil
}
}
public extension NSString {
public var isAbsolutePath: Bool {
return hasPrefix("~") || hasPrefix("/")
}
public static func pathWithComponents(_ components: [String]) -> String {
var result = ""
for comp in components.prefix(components.count - 1) {
result = result._stringByAppendingPathComponent(comp._stringByFixingSlashes(), doneAppending: false)
}
if let last = components.last {
result = result._stringByAppendingPathComponent(last._stringByFixingSlashes(), doneAppending: true)
}
return result
}
public var pathComponents : [String] {
return _pathComponents(self._swiftObject)!
}
public var lastPathComponent : String {
let fixedSelf = _stringByFixingSlashes()
if fixedSelf.length <= 1 {
return fixedSelf
}
return String(fixedSelf.characters.suffix(from: fixedSelf._startOfLastPathComponent))
}
public var deletingLastPathComponent : String {
let fixedSelf = _stringByFixingSlashes()
if fixedSelf == "/" {
return fixedSelf
}
switch fixedSelf._startOfLastPathComponent {
// relative path, single component
case fixedSelf.startIndex:
return ""
// absolute path, single component
case fixedSelf.index(after: fixedSelf.startIndex):
return "/"
// all common cases
case let startOfLast:
return String(fixedSelf.characters.prefix(upTo: fixedSelf.index(before: startOfLast)))
}
}
internal func _stringByFixingSlashes(compress : Bool = true, stripTrailing: Bool = true) -> String {
if _swiftObject == "/" {
return _swiftObject
}
var result = _swiftObject
if compress {
result.withMutableCharacters { characterView in
let startPos = characterView.startIndex
var endPos = characterView.endIndex
var curPos = startPos
while curPos < endPos {
if characterView[curPos] == "/" {
var afterLastSlashPos = curPos
while afterLastSlashPos < endPos && characterView[afterLastSlashPos] == "/" {
afterLastSlashPos = characterView.index(after: afterLastSlashPos)
}
if afterLastSlashPos != characterView.index(after: curPos) {
characterView.replaceSubrange(curPos ..< afterLastSlashPos, with: ["/"])
endPos = characterView.endIndex
}
curPos = afterLastSlashPos
} else {
curPos = characterView.index(after: curPos)
}
}
}
}
if stripTrailing && result.hasSuffix("/") {
result.remove(at: result.characters.index(before: result.characters.endIndex))
}
return result
}
internal func _stringByAppendingPathComponent(_ str: String, doneAppending : Bool = true) -> String {
if str.length == 0 {
return _swiftObject
}
if self == "" {
return "/" + str
}
if self == "/" {
return _swiftObject + str
}
return _swiftObject + "/" + str
}
public func appendingPathComponent(_ str: String) -> String {
return _stringByAppendingPathComponent(str)
}
public var pathExtension : String {
let fixedSelf = _stringByFixingSlashes()
if fixedSelf.length <= 1 {
return ""
}
if let extensionPos = fixedSelf._startOfPathExtension {
return String(fixedSelf.characters.suffix(from: extensionPos))
} else {
return ""
}
}
public var deletingPathExtension: String {
let fixedSelf = _stringByFixingSlashes()
if fixedSelf.length <= 1 {
return fixedSelf
}
if let extensionPos = (fixedSelf._startOfPathExtension) {
return String(fixedSelf.characters.prefix(upTo: fixedSelf.characters.index(before: extensionPos)))
} else {
return fixedSelf
}
}
public func appendingPathExtension(_ str: String) -> String? {
if str.hasPrefix("/") || self == "" || self == "/" {
print("Cannot append extension \(str) to path \(self)")
return nil
}
let result = _swiftObject._stringByFixingSlashes(compress: false, stripTrailing: true) + "." + str
return result._stringByFixingSlashes()
}
public var expandingTildeInPath: String {
guard hasPrefix("~") else {
return _swiftObject
}
let endOfUserName = _swiftObject.characters.index(of: "/") ?? _swiftObject.endIndex
let startOfUserName = _swiftObject.characters.index(after: _swiftObject.characters.startIndex)
let userName = String(_swiftObject.characters[startOfUserName..<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.replaceSubrange(_swiftObject.startIndex..<endOfUserName, with: homeDir)
result = result._stringByFixingSlashes(compress: false, stripTrailing: true)
return result
}
public var standardizingPath: String {
let expanded = expandingTildeInPath
var resolved = expanded._bridgeToObjectiveC().resolvingSymlinksInPath
let automount = "/var/automount"
resolved = resolved._tryToRemovePathPrefix(automount) ?? resolved
return resolved
}
public var resolvingSymlinksInPath: String {
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._bridgeToObjectiveC().deletingLastPathComponent
default:
resolvedPath = resolvedPath._bridgeToObjectiveC().appendingPathComponent(component)
if let destination = FileManager.default._tryToResolveTrailingSymlinkInPath(resolvedPath) {
resolvedPath = destination
}
}
}
let privatePrefix = "/private"
resolvedPath = resolvedPath._tryToRemovePathPrefix(privatePrefix) ?? resolvedPath
return resolvedPath
}
public func stringsByAppendingPaths(_ paths: [String]) -> [String] {
if self == "" {
return paths
}
return paths.map(appendingPathComponent)
}
/// - Experiment: This is a draft API currently under consideration for official import into Foundation
/// - Note: Since this API is under consideration it may be either removed or revised in the near future
public func completePath(into outputName: inout String?, caseSensitive flag: Bool, matchesInto outputArray: inout [String], filterTypes: [String]?) -> Int {
let path = _swiftObject
guard !path.isEmpty else {
return 0
}
let url = URL(fileURLWithPath: path)
let searchAllFilesInDirectory = _stringIsPathToDirectory(path)
let namePrefix = searchAllFilesInDirectory ? "" : url.lastPathComponent
let checkFileName = _getFileNamePredicate(namePrefix, caseSensitive: flag)
let checkExtension = _getExtensionPredicate(filterTypes, caseSensitive: flag)
let resolvedURL: URL = url.resolvingSymlinksInPath()
let urlWhereToSearch: URL = searchAllFilesInDirectory ? resolvedURL : resolvedURL.deletingLastPathComponent()
var matches = _getNamesAtURL(urlWhereToSearch, prependWith: "", namePredicate: checkFileName, typePredicate: checkExtension)
if matches.count == 1 {
let theOnlyFoundItem = URL(fileURLWithPath: matches[0], relativeTo: urlWhereToSearch)
if theOnlyFoundItem.hasDirectoryPath {
matches = _getNamesAtURL(theOnlyFoundItem, prependWith: matches[0], namePredicate: { _ in true }, typePredicate: checkExtension)
}
}
let commonPath = searchAllFilesInDirectory ? path : _ensureLastPathSeparator(deletingLastPathComponent)
if searchAllFilesInDirectory {
outputName = "/"
} else {
if let lcp = _longestCommonPrefix(matches, caseSensitive: flag) {
outputName = (commonPath + lcp)
}
}
outputArray = matches.map({ (commonPath + $0) })
return matches.count
}
internal func _stringIsPathToDirectory(_ path: String) -> Bool {
if !path.hasSuffix("/") {
return false
}
var isDirectory = false
let exists = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory)
return exists && isDirectory
}
internal typealias _FileNamePredicate = (String?) -> Bool
internal func _getNamesAtURL(_ filePathURL: URL, prependWith: String, namePredicate: _FileNamePredicate, typePredicate: _FileNamePredicate) -> [String] {
var result: [String] = []
if let enumerator = FileManager.default.enumerator(at: filePathURL, includingPropertiesForKeys: nil, options: .skipsSubdirectoryDescendants, errorHandler: nil) {
for item in enumerator.lazy.map({ $0 as! URL }) {
let itemName = item.lastPathComponent
let matchByName = namePredicate(itemName)
let matchByExtension = typePredicate(item.pathExtension)
if matchByName && matchByExtension {
if prependWith.isEmpty {
result.append(itemName)
} else {
result.append(prependWith._bridgeToObjectiveC().appendingPathComponent(itemName))
}
}
}
}
return result
}
fileprivate func _getExtensionPredicate(_ extensions: [String]?, caseSensitive: Bool) -> _FileNamePredicate {
guard let exts = extensions else {
return { _ in true }
}
if caseSensitive {
let set = Set(exts)
return { $0 != nil && set.contains($0!) }
} else {
let set = Set(exts.map { $0.lowercased() })
return { $0 != nil && set.contains($0!.lowercased()) }
}
}
fileprivate func _getFileNamePredicate(_ prefix: String, caseSensitive: Bool) -> _FileNamePredicate {
guard !prefix.isEmpty else {
return { _ in true }
}
if caseSensitive {
return { $0 != nil && $0!.hasPrefix(prefix) }
} else {
return { $0 != nil && $0!._bridgeToObjectiveC().range(of: prefix, options: .caseInsensitive).location == 0 }
}
}
internal func _longestCommonPrefix(_ strings: [String], caseSensitive: Bool) -> String? {
guard !strings.isEmpty else {
return nil
}
guard strings.count > 1 else {
return strings.first
}
var sequences = strings.map({ $0.characters.makeIterator() })
var prefix: [Character] = []
loop: while true {
var char: Character? = nil
for (idx, s) in sequences.enumerated() {
var seq = s
guard let c = seq.next() else {
break loop
}
if char != nil {
let lhs = caseSensitive ? char : String(char!).lowercased().characters.first!
let rhs = caseSensitive ? c : String(c).lowercased().characters.first!
if lhs != rhs {
break loop
}
} else {
char = c
}
sequences[idx] = seq
}
prefix.append(char!)
}
return String(prefix)
}
internal func _ensureLastPathSeparator(_ path: String) -> String {
if path.hasSuffix("/") || path.isEmpty {
return path
}
return path + "/"
}
public var fileSystemRepresentation : UnsafePointer<Int8> {
NSUnimplemented()
}
public func getFileSystemRepresentation(_ cname: UnsafeMutablePointer<Int8>, maxLength max: Int) -> Bool {
guard self.length > 0 else {
return false
}
return CFStringGetFileSystemRepresentation(self._cfObject, cname, max)
}
}
extension FileManager {
public enum SearchPathDirectory: UInt {
case applicationDirectory // supported applications (Applications)
case demoApplicationDirectory // unsupported applications, demonstration versions (Demos)
case developerApplicationDirectory // developer applications (Developer/Applications). DEPRECATED - there is no one single Developer directory.
case adminApplicationDirectory // system and network administration applications (Administration)
case libraryDirectory // various documentation, support, and configuration files, resources (Library)
case developerDirectory // developer resources (Developer) DEPRECATED - there is no one single Developer directory.
case userDirectory // user home directories (Users)
case documentationDirectory // documentation (Documentation)
case documentDirectory // documents (Documents)
case coreServiceDirectory // location of CoreServices directory (System/Library/CoreServices)
case autosavedInformationDirectory // location of autosaved documents (Documents/Autosaved)
case desktopDirectory // location of user's desktop
case cachesDirectory // location of discardable cache files (Library/Caches)
case applicationSupportDirectory // location of application support files (plug-ins, etc) (Library/Application Support)
case downloadsDirectory // location of the user's "Downloads" directory
case inputMethodsDirectory // input methods (Library/Input Methods)
case moviesDirectory // location of user's Movies directory (~/Movies)
case musicDirectory // location of user's Music directory (~/Music)
case picturesDirectory // location of user's Pictures directory (~/Pictures)
case printerDescriptionDirectory // location of system's PPDs directory (Library/Printers/PPDs)
case sharedPublicDirectory // location of user's Public sharing directory (~/Public)
case preferencePanesDirectory // location of the PreferencePanes directory for use with System Preferences (Library/PreferencePanes)
case applicationScriptsDirectory // location of the user scripts folder for the calling application (~/Library/Application Scripts/code-signing-id)
case itemReplacementDirectory // For use with NSFileManager's URLForDirectory:inDomain:appropriateForURL:create:error:
case allApplicationsDirectory // all directories where applications can occur
case allLibrariesDirectory // all directories where resources can occur
case trashDirectory // location of Trash directory
}
public struct SearchPathDomainMask: OptionSet {
public let rawValue : UInt
public init(rawValue: UInt) { self.rawValue = rawValue }
public static let userDomainMask = SearchPathDomainMask(rawValue: 1) // user's home directory --- place to install user's personal items (~)
public static let localDomainMask = SearchPathDomainMask(rawValue: 2) // local to the current machine --- place to install items available to everyone on this machine (/Library)
public static let networkDomainMask = SearchPathDomainMask(rawValue: 4) // publically available location in the local area network --- place to install items available on the network (/Network)
public static let systemDomainMask = SearchPathDomainMask(rawValue: 8) // provided by Apple, unmodifiable (/System)
public static let allDomainsMask = SearchPathDomainMask(rawValue: 0x0ffff) // all domains: all of the above and future items
}
}
public func NSSearchPathForDirectoriesInDomains(_ directory: FileManager.SearchPathDirectory, _ domainMask: FileManager.SearchPathDomainMask, _ expandTilde: Bool) -> [String] {
NSUnimplemented()
}
public func NSHomeDirectory() -> String {
return NSHomeDirectoryForUser(nil)!
}
public func NSHomeDirectoryForUser(_ user: String?) -> String? {
let userName = user?._cfObject
guard let homeDir = CFCopyHomeDirectoryURLForUser(userName)?.takeRetainedValue() else {
return nil
}
let url: URL = homeDir._swiftObject
return url.path
}
public func NSUserName() -> String {
let userName = CFCopyUserName().takeRetainedValue()
return userName._swiftObject
}
internal func _NSCreateTemporaryFile(_ filePath: String) throws -> (Int32, String) {
let template = "." + filePath + ".tmp.XXXXXX"
let maxLength = Int(PATH_MAX) + 1
var buf = [Int8](repeating: 0, count: maxLength)
let _ = template._nsObject.getFileSystemRepresentation(&buf, maxLength: maxLength)
let fd = mkstemp(&buf)
if fd == -1 {
throw _NSErrorWithErrno(errno, reading: false, path: filePath)
}
let pathResult = FileManager.default.string(withFileSystemRepresentation: buf, length: Int(strlen(buf)))
return (fd, pathResult)
}
internal func _NSCleanupTemporaryFile(_ auxFilePath: String, _ filePath: String) throws {
if rename(auxFilePath, filePath) != 0 {
do {
try FileManager.default.removeItem(atPath: auxFilePath)
} catch _ {
}
throw _NSErrorWithErrno(errno, reading: false, path: filePath)
}
}