Search code examples
swiftcocoansurl

How do I see if some folder is a child of another using NSURL?


I have an [URL] which represent a set of special parent directories. I am given another [URL] which represents files scattered around the system. I want to know if any of these files are in any of my special directories, or any of their subdirectories. Is there a simple/intended way to do this, without manually parsing/traversing an absolute URL's path?


Solution

  • A slightly improved, Swift 5 version combining nice features of both Umair and rmaddy's existing excellent answers.

    Like Umair's answer, this correctly handles the case where a folder in the path is a partial match (that is, it will not say that /foo/bar-baz/bang has /foo/bar/ as an ancestor).

    It also performs the canonicalization (standardizing paths to eliminate things like . or ../, and resolving symlinks) like rmaddy's answer.

    func pathHasAncestor(maybeChild: URL, maybeAncestor: URL) -> Bool {
        let ancestorComponents: [String] = canonicalize(maybeAncestor).pathComponents
        let childComponents: [String] = canonicalize(maybeChild).pathComponents
    
        return ancestorComponents.count < childComponents.count
            && !zip(ancestorComponents, childComponents).contains(where: !=)
    }
    
    func canonicalize(_ url: URL) -> URL {
        url.standardizedFileURL.resolvingSymlinksInPath()
    }
    

    Here's a very simple unit test to verify the basic functionality:

    import XCTest
    
    class FileUtilsTests: XCTestCase {
        func testPathHasAncestor() throws {
            let leaf = URL(fileURLWithPath: "/foo/bar/baz")
            let parent = leaf.deletingLastPathComponent()
            XCTAssertTrue(pathHasAncestor(maybeChild: leaf, maybeAncestor: parent))
            XCTAssertFalse(pathHasAncestor(maybeChild: URL(fileURLWithPath: "/foo/bar 1/baz"), maybeAncestor: parent))
            XCTAssertFalse(pathHasAncestor(maybeChild: leaf, maybeAncestor: leaf))
        }
    }