Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parity: FileManager: File display names. #2153

Merged
merged 2 commits into from
Apr 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 60 additions & 2 deletions Foundation/FileManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -679,17 +679,75 @@ open class FileManager : NSObject {
open func contentsEqual(atPath path1: String, andPath path2: String) -> Bool {
return _contentsEqual(atPath: path1, andPath: path2)
}

// For testing only: this facility pins the language used by displayName to the passed-in language.
private var _overriddenDisplayNameLanguages: [String]? = nil
internal func _overridingDisplayNameLanguages<T>(with languages: [String], within body: () throws -> T) rethrows -> T {
let old = _overriddenDisplayNameLanguages
defer { _overriddenDisplayNameLanguages = old }

_overriddenDisplayNameLanguages = languages
return try body()
}

private var _preferredLanguages: [String] {
return _overriddenDisplayNameLanguages ?? Locale.preferredLanguages
}

/* displayNameAtPath: returns an NSString suitable for presentation to the user. For directories which have localization information, this will return the appropriate localized string. This string is not suitable for passing to anything that must interact with the filesystem.
*/
open func displayName(atPath path: String) -> String {
NSUnimplemented()

let url = URL(fileURLWithPath: path)
let name = url.lastPathComponent
let nameWithoutExtension = url.deletingPathExtension().lastPathComponent

// https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemAdvancedPT/LocalizingtheNameofaDirectory/LocalizingtheNameofaDirectory.html
// This localizes a X.localized directory:

if url.pathExtension == "localized" {
let dotLocalized = url.appendingPathComponent(".localized")
var isDirectory: ObjCBool = false
if fileExists(atPath: dotLocalized.path, isDirectory: &isDirectory),
isDirectory.boolValue {
for language in _preferredLanguages {
let stringsFile = dotLocalized.appendingPathComponent(language).appendingPathExtension("strings")
if let data = try? Data(contentsOf: stringsFile),
let plist = (try? PropertyListSerialization.propertyList(from: data, format: nil)) as? NSDictionary {

let localizedName = (plist[nameWithoutExtension] as? NSString)?._swiftObject
return localizedName ?? nameWithoutExtension

}
}

// If we get here and we don't have a good name for this, still hide the extension:
return nameWithoutExtension
}
}

// We do not have the bundle resources to map the names of system directories with .localized files on Darwin, and system directories do not exist on other platforms, so just skip that on swift-corelibs-foundation:

// URL resource values are not yet implemented: https://bugs.swift.org/browse/SR-10365
// return (try? url.resourceValues(forKeys: [.hasHiddenExtensionKey]))?.hasHiddenExtension == true ? nameWithoutExtension : name

return name
}

/* componentsToDisplayForPath: returns an NSArray of display names for the path provided. Localization will occur as in displayNameAtPath: above. This array cannot and should not be reassembled into an usable filesystem path for any kind of access.
*/
open func componentsToDisplay(forPath path: String) -> [String]? {
NSUnimplemented()
var url = URL(fileURLWithPath: path)
var count = url.pathComponents.count

var result: [String] = []
while count > 0 {
result.insert(displayName(atPath: url.path), at: 0)
url = url.deletingLastPathComponent()
count -= 1
}

return result
}

/* enumeratorAtPath: returns an NSDirectoryEnumerator rooted at the provided path. If the enumerator cannot be created, this returns NULL. Because NSDirectoryEnumerator is a subclass of NSEnumerator, the returned object can be used in the for...in construct.
Expand Down
80 changes: 80 additions & 0 deletions TestFoundation/TestFileManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1432,6 +1432,85 @@ VIDEOS=StopgapVideos
XCTAssertEqual(relationship, .same)
}

func test_displayNames() throws {
/* a/
a/Test.localized (with a ./.localized/ subdirectory and strings file);
a/Test.localized/b
*/

let a = writableTestDirectoryURL.appendingPathComponent("a")
let a_Test = a.appendingPathComponent("Test.localized")
let a_Test_dotLocalized = a_Test.appendingPathComponent(".localized")
let a_Test_dotLocalized_enStrings = a_Test_dotLocalized.appendingPathComponent("en.strings")
let a_Test_dotLocalized_itStrings = a_Test_dotLocalized.appendingPathComponent("it.strings")
let a_Test_b = a_Test.appendingPathComponent("b")

let fm = FileManager.default
try fm.createDirectory(at: a, withIntermediateDirectories: true)
try fm.createDirectory(at: a_Test, withIntermediateDirectories: true)
try fm.createDirectory(at: a_Test_dotLocalized, withIntermediateDirectories: true)
try Data("\"Test\" = \"Test\";".utf8).write(to: a_Test_dotLocalized_enStrings)
try Data("\"Test\" = \"Prova\";".utf8).write(to: a_Test_dotLocalized_itStrings)
try fm.createDirectory(at: a_Test_b, withIntermediateDirectories: true)

XCTAssertEqual(fm.displayName(atPath: a.path), "a")

#if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
fm._overridingDisplayNameLanguages(with: ["en", "es", "it"]) {
XCTAssertEqual(fm.displayName(atPath: a_Test.path), "Test")
}
fm._overridingDisplayNameLanguages(with: ["it", "en", "es"]) {
XCTAssertEqual(fm.displayName(atPath: a_Test.path), "Prova")
}
fm._overridingDisplayNameLanguages(with: ["es", "it", "en"]) {
XCTAssertEqual(fm.displayName(atPath: a_Test.path), "Prova")
}
#endif

do {
let components = try fm.componentsToDisplay(forPath: a.path).unwrapped()
XCTAssertGreaterThanOrEqual(components.count, 2)
XCTAssertEqual(components.last, "a")
}

do {
#if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
let components = try fm._overridingDisplayNameLanguages(with: ["it"]) {
return try fm.componentsToDisplay(forPath: a_Test.path).unwrapped()
}
#else
let components = try fm.componentsToDisplay(forPath: a_Test.path).unwrapped()
#endif

XCTAssertGreaterThanOrEqual(components.count, 3)
XCTAssertEqual(components[components.count - 2], "a")

#if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
XCTAssertEqual(components.last, "Prova")
#endif
}

do {
#if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
let components = try fm._overridingDisplayNameLanguages(with: ["en"]) {
return try fm.componentsToDisplay(forPath: a_Test_b.path).unwrapped()
}
#else
let components = try fm.componentsToDisplay(forPath: a_Test_b.path).unwrapped()
#endif

XCTAssertGreaterThanOrEqual(components.count, 4)
XCTAssertEqual(components[components.count - 3], "a")

#if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
XCTAssertEqual(components[components.count - 2], "Test")
#endif

XCTAssertEqual(components.last, "b")
}

}

// -----

var writableTestDirectoryURL: URL!
Expand Down Expand Up @@ -1483,6 +1562,7 @@ VIDEOS=StopgapVideos
("test_copyItemsPermissions", test_copyItemsPermissions),
("test_emptyFilename", test_emptyFilename),
("test_getRelationship", test_getRelationship),
("test_displayNames", test_displayNames),
]

#if !DEPLOYMENT_RUNTIME_OBJC && NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
Expand Down