Skip to content

Commit 4bae02c

Browse files
committed
Add followSymlink to children()
Closes #225.
1 parent 487caad commit 4bae02c

File tree

6 files changed

+76
-10
lines changed

6 files changed

+76
-10
lines changed

Sources/Pathos/children.swift

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@ import Darwin
1010
/// - path: the path whose children will be returned.
1111
/// - recursive: set to `true` to include results from child directories in addition to that from `path`.
1212
/// Defaults to `false`.
13+
/// - followSymlink: include content of a directroy, if it's pointed at by a symlink in the result.
1314
///
1415
/// - Returns: path to directories and files of all types in `path`, and their types, in pairs.
1516
/// - Throws: A `SystemError` if path cannot be opened as a directory or there's not enough memory to hold all
1617
/// data for the results.
1718
/// - SeeAlso: To work with `Path` or `PathRepresentable`, use `PathRepresentable.children(recursive:)`.
18-
public func children(inPath path: String, recursive: Bool = false) throws -> [(String, FileType)] {
19+
public func children(inPath path: String, recursive: Bool = false, followSymlink: Bool = false) throws
20+
-> [(String, FileType)]
21+
{
1922
var result = [(String, FileType)]()
2023
guard let streamPtr = opendir(path) else {
2124
throw SystemError(posixErrorCode: errno)
@@ -40,7 +43,13 @@ public func children(inPath path: String, recursive: Bool = false) throws -> [(S
4043
let fullName = join(paths: path, name)
4144
result.append((fullName, pathType))
4245

43-
if recursive && pathType == .directory {
46+
if pathType == .symlink,
47+
followSymlink,
48+
let realName = try? realPath(ofPath: fullName),
49+
(try? isA(.directory, atPath: realName)) == true
50+
{
51+
result += try children(inPath: fullName, recursive: true)
52+
} else if recursive && pathType == .directory {
4453
result += try children(inPath: fullName, recursive: true)
4554
}
4655
}
@@ -72,13 +81,15 @@ extension PathRepresentable {
7281
/// Result will be empty if this path cannot be opened or there's not enough memory to hold all data for
7382
/// the results.
7483
///
75-
/// - Parameter recursive: set to `true` to include results from child directories in addition to that
76-
/// in this path. Defaults to `false`.
84+
/// - Parameters:
85+
/// - recursive: set to `true` to include results from child directories in addition to that
86+
/// in this path. Defaults to `false`.
87+
/// - followSymlink: include content of a directroy, if it's pointed at by a symlink in the result.
7788
///
7889
/// - Returns: paths to directories and files of all types in `path`, and their types, in pairs.
7990
/// - SeeAlso: `children(inPath:recursive:)`.
80-
public func children(recursive: Bool = false) -> [(Self, FileType)] {
81-
return ((try? children(inPath:recursive:)(self.pathString, recursive)) ?? [])
91+
public func children(recursive: Bool = false, followSymlink: Bool = false) -> [(Self, FileType)] {
92+
return ((try? children(inPath:recursive:followSymlink:)(self.pathString, recursive, followSymlink)) ?? [])
8293
.map { (.init($0), $1) }
8394
}
8495

@@ -87,15 +98,18 @@ extension PathRepresentable {
8798
/// Result will be empty if this path cannot be opened or there's not enough memory to hold all data for
8899
/// the results.
89100
///
90-
/// - Parameters
101+
/// - Parameters:
91102
/// - type: The file type in question.
92103
/// - recursive: set to `true` to include results from child directories in addition to that
93104
/// in this path. Defaults to `false`.
105+
/// - followSymlink: include content of a directroy, if it's pointed at by a symlink in the result.
94106
///
95107
/// - Returns: paths to directories and files of all types in `path`, and their types, in pairs.
96108
/// - SeeAlso: `children(inPath:recursive:)`.
97-
public func children(ofType type: FileType, recursive: Bool = false) -> [Self] {
98-
return ((try? children(inPath:recursive:)(self.pathString, recursive)) ?? [])
109+
public func children(ofType type: FileType, recursive: Bool = false, followSymlink: Bool = false)
110+
-> [Self]
111+
{
112+
return ((try? children(inPath:recursive:followSymlink:)(self.pathString, recursive, followSymlink)) ?? [])
99113
.filter { $1 == type }
100114
.map { .init($0.0) }
101115
}

Tests/PathosTests/ChildrenTests.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,23 @@ final class ChildrenTests: XCTestCase {
8484
)
8585
}
8686

87+
func testNotFollowingSymlinks() throws {
88+
XCTAssertEqual(
89+
Set(try children(inPath: self.fixture(.secondDirectoryThatExists)).map { $0.0 }),
90+
[self.fixture(.secondSymbolInDirectory)]
91+
)
92+
}
93+
94+
func testFollowingSymlinks() throws {
95+
XCTAssertEqual(
96+
Set(try children(inPath: self.fixture(.secondDirectoryThatExists), followSymlink: true).map { $0.0 }),
97+
[
98+
self.fixture(.secondSymbolInDirectory),
99+
self.fixture(.secondFileViaSymlink),
100+
]
101+
)
102+
}
103+
87104
func testPathRepresentableChildrenInPath() {
88105
XCTAssertEqual(
89106
Set(Path(self.fixtureRoot).children().map { $0.0.pathString }),
@@ -261,4 +278,21 @@ final class ChildrenTests: XCTestCase {
261278
[]
262279
)
263280
}
281+
282+
func testPathRepresentableNotFollowingSymlinks() throws {
283+
XCTAssertEqual(
284+
Set(self.fixturePath(.secondDirectoryThatExists).children().map { $0.0 }),
285+
[self.fixturePath(.secondSymbolInDirectory)]
286+
)
287+
}
288+
289+
func testPathRepresentableFollowingSymlinks() throws {
290+
XCTAssertEqual(
291+
Set(self.fixturePath(.secondDirectoryThatExists).children(followSymlink: true).map { $0.0 }),
292+
[
293+
self.fixturePath(.secondSymbolInDirectory),
294+
self.fixturePath(.secondFileViaSymlink),
295+
]
296+
)
297+
}
264298
}

Tests/PathosTests/FixtureSupport.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@ enum FixturePath: String {
55
case fileThatExists = "hello"
66
case noneExistence = "hello_not"
77
case directoryThatExists = "world"
8+
case secondDirectoryThatExists = "earth"
9+
case thirdDirectoryThatExists = "another_world"
810
case goodFileSymbol = "hello_symbol"
911
case goodDirectorySymbol = "world_symbol"
1012
case badSymbol = "broken_symbol"
1113
case fileInDirectory = "world/world_hello"
14+
case secondFileInDirectory = "another_world/me"
15+
case secondFileViaSymlink = "earth/portal/me"
1216
case symbolInDirectory = "world/world_symbol"
17+
case secondSymbolInDirectory = "earth/portal"
1318
case directoryInDirectory = "world/world_world"
1419
case fileInNestedDirectory = "world/world_world/hello"
1520
case fileInNestedDirectoryViaDirectorySymbol = "world_symbol/world_world/hello"
@@ -58,7 +63,11 @@ extension XCTestCase {
5863
}
5964

6065
var childDirectoryFixture: Set<String> {
61-
return [self.fixture(.directoryThatExists)]
66+
return [
67+
self.fixture(.directoryThatExists),
68+
self.fixture(.secondDirectoryThatExists),
69+
self.fixture(.thirdDirectoryThatExists),
70+
]
6271
}
6372

6473
var childSymlinkFixture: Set<String> {
@@ -74,13 +83,16 @@ extension XCTestCase {
7483
self.fixture(.fileThatExists),
7584
self.fixture(.fileInDirectory),
7685
self.fixture(.fileInNestedDirectory),
86+
self.fixture(.secondFileInDirectory),
7787
]
7888
}
7989

8090
var childDirectoryRecursiveFixture: Set<String> {
8191
return [
8292
self.fixture(.directoryThatExists),
8393
self.fixture(.directoryInDirectory),
94+
self.fixture(.secondDirectoryThatExists),
95+
self.fixture(.thirdDirectoryThatExists)
8496
]
8597
}
8698

@@ -90,6 +102,7 @@ extension XCTestCase {
90102
self.fixture(.goodDirectorySymbol),
91103
self.fixture(.badSymbol),
92104
self.fixture(.symbolInDirectory),
105+
self.fixture(.secondSymbolInDirectory),
93106
]
94107
}
95108
}

Tests/PathosTests/Fixtures/another_world/me

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../another_world

Tests/PathosTests/XCTestManifests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ extension ChildrenTests {
1616
("testDirectoriesRecursiveInPath", testDirectoriesRecursiveInPath),
1717
("testFilesInPath", testFilesInPath),
1818
("testFilesRecursiveInPath", testFilesRecursiveInPath),
19+
("testFollowingSymlinks", testFollowingSymlinks),
20+
("testNotFollowingSymlinks", testNotFollowingSymlinks),
1921
("testPathRepresentableBlockDevicesInPath", testPathRepresentableBlockDevicesInPath),
2022
("testPathRepresentableBlockDevicesRecursiveInPath", testPathRepresentableBlockDevicesRecursiveInPath),
2123
("testPathRepresentableCharacterDevicesInPath", testPathRepresentableCharacterDevicesInPath),
@@ -26,6 +28,8 @@ extension ChildrenTests {
2628
("testPathRepresentableDirectoriesRecursiveInPath", testPathRepresentableDirectoriesRecursiveInPath),
2729
("testPathRepresentableFilesInPath", testPathRepresentableFilesInPath),
2830
("testPathRepresentableFilesRecursiveInPath", testPathRepresentableFilesRecursiveInPath),
31+
("testPathRepresentableFollowingSymlinks", testPathRepresentableFollowingSymlinks),
32+
("testPathRepresentableNotFollowingSymlinks", testPathRepresentableNotFollowingSymlinks),
2933
("testPathRepresentablePipesInPath", testPathRepresentablePipesInPath),
3034
("testPathRepresentablePipesRecursiveInPath", testPathRepresentablePipesRecursiveInPath),
3135
("testPathRepresentableSocketsInPath", testPathRepresentableSocketsInPath),

0 commit comments

Comments
 (0)