Skip to content

Commit 176e8c5

Browse files
committed
Merge pull request #134 from euantorano/feature/NSFileManager_contentsOfDirectoryAtPath
Implement contentsOfDirectoryAtPath
2 parents 32cc070 + 12df0aa commit 176e8c5

File tree

2 files changed

+197
-8
lines changed

2 files changed

+197
-8
lines changed

Foundation/NSFileManager.swift

Lines changed: 103 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -191,20 +191,115 @@ public class NSFileManager : NSObject {
191191
}
192192
}
193193

194-
/* contentsOfDirectoryAtPath:error: returns an NSArray of NSStrings representing the filenames of the items in the directory. If this method returns 'nil', an NSError will be returned by reference in the 'error' parameter. If the directory contains no items, this method will return the empty array.
194+
/**
195+
Performs a shallow search of the specified directory and returns the paths of any contained items.
195196

196-
This method replaces directoryContentsAtPath:
197+
This method performs a shallow search of the directory and therefore does not traverse symbolic links or return the contents of any subdirectories. This method also does not return URLs for the current directory (“.”), parent directory (“..”) but it does return other hidden files (files that begin with a period character).
198+
199+
The order of the files in the returned array is undefined.
200+
201+
- Parameter path: The path to the directory whose contents you want to enumerate.
202+
203+
- Throws: `NSError` if the directory does not exist, this error is thrown with the associated error code.
204+
205+
- Returns: An array of String each of which identifies a file, directory, or symbolic link contained in `path`. The order of the files returned is undefined.
197206
*/
198207
public func contentsOfDirectoryAtPath(path: String) throws -> [String] {
199-
NSUnimplemented()
208+
var contents : [String] = [String]()
209+
210+
let dir = opendir(path)
211+
212+
if dir == nil {
213+
throw NSError(domain: NSCocoaErrorDomain, code: NSCocoaError.FileReadNoSuchFileError.rawValue, userInfo: [NSFilePathErrorKey: path])
214+
}
215+
216+
defer {
217+
closedir(dir)
218+
}
219+
220+
var entry: UnsafeMutablePointer<dirent> = readdir(dir)
221+
222+
while entry != nil {
223+
if let entryName = withUnsafePointer(&entry.memory.d_name, { (ptr) -> String? in
224+
let int8Ptr = unsafeBitCast(ptr, UnsafePointer<Int8>.self)
225+
return String.fromCString(int8Ptr)
226+
}) {
227+
// TODO: `entryName` should be limited in length to `entry.memory.d_namlen`.
228+
if entryName != "." && entryName != ".." {
229+
contents.append(entryName)
230+
}
231+
}
232+
233+
entry = readdir(dir)
234+
}
235+
236+
return contents
200237
}
201238

202-
/* subpathsOfDirectoryAtPath:error: returns an NSArray of NSStrings representing the filenames of the items in the specified directory and all its subdirectories recursively. If this method returns 'nil', an NSError will be returned by reference in the 'error' parameter. If the directory contains no items, this method will return the empty array.
203-
204-
This method replaces subpathsAtPath:
205-
*/
239+
/**
240+
Performs a deep enumeration of the specified directory and returns the paths of all of the contained subdirectories.
241+
242+
This method recurses the specified directory and its subdirectories. The method skips the “.” and “..” directories at each level of the recursion.
243+
244+
Because this method recurses the directory’s contents, you might not want to use it in performance-critical code. Instead, consider using the enumeratorAtURL:includingPropertiesForKeys:options:errorHandler: or enumeratorAtPath: method to enumerate the directory contents yourself. Doing so gives you more control over the retrieval of items and more opportunities to abort the enumeration or perform other tasks at the same time.
245+
246+
- Parameter path: The path of the directory to list.
247+
248+
- Throws: `NSError` if the directory does not exist, this error is thrown with the associated error code.
249+
250+
- Returns: An array of NSString objects, each of which contains the path of an item in the directory specified by path. If path is a symbolic link, this method traverses the link. This method returns nil if it cannot retrieve the device of the linked-to file.
251+
*/
206252
public func subpathsOfDirectoryAtPath(path: String) throws -> [String] {
207-
NSUnimplemented()
253+
var contents : [String] = [String]()
254+
255+
let dir = opendir(path)
256+
257+
if dir == nil {
258+
throw NSError(domain: NSCocoaErrorDomain, code: NSCocoaError.FileReadNoSuchFileError.rawValue, userInfo: [NSFilePathErrorKey: path])
259+
}
260+
261+
defer {
262+
closedir(dir)
263+
}
264+
265+
var entry = readdir(dir)
266+
267+
while entry != nil {
268+
if let entryName = withUnsafePointer(&entry.memory.d_name, { (ptr) -> String? in
269+
let int8Ptr = unsafeBitCast(ptr, UnsafePointer<Int8>.self)
270+
return String.fromCString(int8Ptr)
271+
}) {
272+
// TODO: `entryName` should be limited in length to `entry.memory.d_namlen`.
273+
if entryName != "." && entryName != ".." {
274+
contents.append(entryName)
275+
276+
if let entryType = withUnsafePointer(&entry.memory.d_type, { (ptr) -> Int32? in
277+
let int32Ptr = unsafeBitCast(ptr, UnsafePointer<UInt8>.self)
278+
return Int32(int32Ptr.memory)
279+
}) {
280+
#if os(OSX) || os(iOS)
281+
if entryType == DT_DIR {
282+
let subPath: String = path + "/" + entryName
283+
284+
let entries = try subpathsOfDirectoryAtPath(subPath)
285+
contents.appendContentsOf(entries.map({file in "\(entryName)/\(file)"}))
286+
}
287+
#elseif os(Linux)
288+
if Int(entryType) == DT_DIR {
289+
let subPath: String = path + "/" + entryName
290+
291+
let entries = try subpathsOfDirectoryAtPath(subPath)
292+
contents.appendContentsOf(entries.map({file in "\(entryName)/\(file)"}))
293+
}
294+
#endif
295+
}
296+
}
297+
}
298+
299+
entry = readdir(dir)
300+
}
301+
302+
return contents
208303
}
209304

210305
/* attributesOfItemAtPath:error: returns an NSDictionary of key/value pairs containing the attributes of the item (file, directory, symlink, etc.) at the path in question. If this method returns 'nil', an NSError will be returned by reference in the 'error' parameter. This method does not traverse a terminal symlink.

TestFoundation/TestNSFileManager.swift

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class TestNSFileManger : XCTestCase {
2424
("test_fileSystemRepresentation", test_fileSystemRepresentation),
2525
("test_fileAttributes", test_fileAttributes),
2626
("test_directoryEnumerator", test_directoryEnumerator),
27+
("test_contentsOfDirectoryAtPath", test_contentsOfDirectoryAtPath),
28+
("test_subpathsOfDirectoryAtPath", test_subpathsOfDirectoryAtPath)
2729
]
2830
}
2931

@@ -239,4 +241,96 @@ class TestNSFileManger : XCTestCase {
239241
}
240242
}
241243

244+
func test_contentsOfDirectoryAtPath() {
245+
let fm = NSFileManager.defaultManager()
246+
let path = "/tmp/testdir"
247+
let itemPath1 = "/tmp/testdir/item"
248+
let itemPath2 = "/tmp/testdir/item2"
249+
250+
ignoreError { try fm.removeItemAtPath(path) }
251+
252+
do {
253+
try fm.createDirectoryAtPath(path, withIntermediateDirectories: false, attributes: nil)
254+
fm.createFileAtPath(itemPath1, contents: NSData(), attributes: nil)
255+
fm.createFileAtPath(itemPath2, contents: NSData(), attributes: nil)
256+
} catch _ {
257+
XCTFail()
258+
}
259+
260+
do {
261+
let entries = try fm.contentsOfDirectoryAtPath(path)
262+
263+
XCTAssertEqual(2, entries.count)
264+
XCTAssertTrue(entries.contains("item"))
265+
XCTAssertTrue(entries.contains("item2"))
266+
}
267+
catch _ {
268+
XCTFail()
269+
}
270+
271+
do {
272+
try fm.contentsOfDirectoryAtPath("")
273+
274+
XCTFail()
275+
}
276+
catch _ {
277+
// Invalid directories should fail.
278+
}
279+
280+
do {
281+
try fm.removeItemAtPath(path)
282+
} catch {
283+
XCTFail("Failed to clean up files")
284+
}
285+
}
286+
287+
func test_subpathsOfDirectoryAtPath() {
288+
let fm = NSFileManager.defaultManager()
289+
let path = "/tmp/testdir"
290+
let path2 = "/tmp/testdir/sub"
291+
let itemPath1 = "/tmp/testdir/item"
292+
let itemPath2 = "/tmp/testdir/item2"
293+
let itemPath3 = "/tmp/testdir/sub/item3"
294+
295+
ignoreError { try fm.removeItemAtPath(path) }
296+
297+
do {
298+
try fm.createDirectoryAtPath(path, withIntermediateDirectories: false, attributes: nil)
299+
fm.createFileAtPath(itemPath1, contents: NSData(), attributes: nil)
300+
fm.createFileAtPath(itemPath2, contents: NSData(), attributes: nil)
301+
302+
try fm.createDirectoryAtPath(path2, withIntermediateDirectories: false, attributes: nil)
303+
fm.createFileAtPath(itemPath3, contents: NSData(), attributes: nil)
304+
} catch _ {
305+
XCTFail()
306+
}
307+
308+
do {
309+
let entries = try fm.subpathsOfDirectoryAtPath(path)
310+
311+
XCTAssertEqual(4, entries.count)
312+
XCTAssertTrue(entries.contains("item"))
313+
XCTAssertTrue(entries.contains("item2"))
314+
XCTAssertTrue(entries.contains("sub"))
315+
XCTAssertTrue(entries.contains("sub/item3"))
316+
}
317+
catch _ {
318+
XCTFail()
319+
}
320+
321+
do {
322+
try fm.subpathsOfDirectoryAtPath("")
323+
324+
XCTFail()
325+
}
326+
catch _ {
327+
// Invalid directories should fail.
328+
}
329+
330+
do {
331+
try fm.removeItemAtPath(path)
332+
} catch {
333+
XCTFail("Failed to clean up files")
334+
}
335+
}
242336
}

0 commit comments

Comments
 (0)