diff --git a/base/exports.jl b/base/exports.jl index f9e6444d6e347..e7cdc8808100b 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -832,6 +832,7 @@ export splitdir, splitdrive, splitext, + splitpath, # filesystem operations cd, diff --git a/base/path.jl b/base/path.jl index 3a3fa8c4a300c..21d5085bff95a 100644 --- a/base/path.jl +++ b/base/path.jl @@ -15,7 +15,8 @@ export relpath, splitdir, splitdrive, - splitext + splitext, + splitpath if Sys.isunix() const path_separator = "/" @@ -129,6 +130,12 @@ julia> splitdir("/home/myuser") """ function splitdir(path::String) a, b = splitdrive(path) + _splitdir_nodrive(a,b) +end + +# Common splitdir functionality without splitdrive, needed for splitpath. +_splitdir_nodrive(path::String) = _splitdir_nodrive("", path) +function _splitdir_nodrive(a::String, b::String) m = match(path_dir_splitter,b) m === nothing && return (a,b) a = string(a, isempty(m.captures[1]) ? m.captures[2][1] : m.captures[1]) @@ -196,6 +203,41 @@ function pathsep(paths::AbstractString...) return path_separator end +""" + splitpath(path::AbstractString) -> Vector{String} + +Split a file path into all its path components. This is the opposite of +`joinpath`. Returns an array of substrings, one for each directory or file in +the path, including the root directory if present. + +# Examples +```jldoctest +julia> splitpath("/home/myuser/example.jl") +4-element Array{String,1}: + "/" + "home" + "myuser" + "example.jl" +``` +""" +function splitpath(p::String) + drive, p = splitdrive(p) + out = String[] + isempty(p) && (pushfirst!(out,p)) # "" means the current directory. + while !isempty(p) + dir, base = _splitdir_nodrive(p) + dir == p && (pushfirst!(out, dir); break) # Reached root node. + if !isempty(base) # Skip trailing '/' in basename + pushfirst!(out, base) + end + p = dir + end + if !isempty(drive) # Tack the drive back on to the first element. + out[1] = drive*out[1] # Note that length(out) is always >= 1. + end + return out +end + joinpath(a::AbstractString) = a """ diff --git a/doc/src/base/file.md b/doc/src/base/file.md index d2f57e2462486..9e0ac19ddd739 100644 --- a/doc/src/base/file.md +++ b/doc/src/base/file.md @@ -62,4 +62,5 @@ Base.Filesystem.expanduser Base.Filesystem.splitdir Base.Filesystem.splitdrive Base.Filesystem.splitext +Base.Filesystem.splitpath ``` diff --git a/test/path.jl b/test/path.jl index 6cea909288f6d..2dccf12ed78b5 100644 --- a/test/path.jl +++ b/test/path.jl @@ -85,6 +85,42 @@ end @test relpath(S(joinpath("foo","bar")), S("foo")) == "bar" + @testset "splitpath" begin + @test splitpath(joinpath("a","b","c")) == ["a", "b", "c"] + @test splitpath("") == [""] + + @test splitpath(joinpath("cats are", "gr8t")) == ["cats are", "gr8t"] + @test splitpath(joinpath(" ", " ")) == [" ", " "] + + # Unix-style paths are understood by all systems. + @test splitpath("/a/b") == ["/", "a", "b"] + @test splitpath("/") == ["/"] + @test splitpath("a/") == ["a"] + @test splitpath("a/b/") == ["a", "b"] + @test splitpath("a.dir/b.txt") == ["a.dir", "b.txt"] + @test splitpath("///") == ["/"] + @test splitpath("///a///b///") == ["/", "a", "b"] + + if Sys.iswindows() + @test splitpath("C:\\\\a\\b\\c") == ["C:\\", "a", "b", "c"] + @test splitpath("C:\\\\") == ["C:\\"] + @test splitpath("J:\\") == ["J:\\"] + @test splitpath("C:") == ["C:"] + @test splitpath("C:a") == ["C:a"] + @test splitpath("C:a\\b") == ["C:a", "b"] + + @test splitpath("a\\") == ["a"] + @test splitpath("a\\\\b\\\\") == ["a","b"] + @test splitpath("a.dir\\b.txt") == ["a.dir", "b.txt"] + @test splitpath("\\a\\b\\") == ["\\", "a","b"] + @test splitpath("\\\\a\\b") == ["\\\\a\\b"] # This is actually a valid drive name in windows. + + @test splitpath("/a/b\\c/d\\\\e") == ["/", "a", "b", "c", "d", "e"] + @test splitpath("/\\/\\") == ["/"] + @test splitpath("\\/\\a/\\//b") == ["\\","a","b"] + end + end + @testset "splitting" begin @test joinpath(splitdir(S(homedir()))...) == homedir() @test string(splitdrive(S(homedir()))...) == homedir()