diff --git a/LICENSE.md b/LICENSE.md index 3d315105d038e..0d5d98311766c 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -35,7 +35,7 @@ Julia includes code from the following projects, which have their own licenses: - [MUSL](https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT) (for getopt implementation on Windows) [MIT] - [MINGW](https://sourceforge.net/p/mingw/mingw-org-wsl/ci/legacy/tree/mingwrt/mingwex/dirname.c) (for dirname implementation on Windows) [MIT] - [NetBSD](https://www.netbsd.org/about/redistribution.html) (for setjmp, longjmp, and strptime implementations on Windows) [BSD-3] -- [Python](https://docs.python.org/2/license.html) (for strtod implementation on Windows) [BSD-3, effectively] +- [Python](https://docs.python.org/2/license.html) (for strtod and joinpath implementation on Windows) [BSD-3, effectively] The following components included in Julia `Base` have their own separate licenses: diff --git a/base/path.jl b/base/path.jl index 234519818075a..8fcc25cb1e5b7 100644 --- a/base/path.jl +++ b/base/path.jl @@ -198,14 +198,6 @@ function splitext(path::String) a*m.captures[1], String(m.captures[2]) end -function pathsep(paths::AbstractString...) - for path in paths - m = match(path_separator_re, String(path)) - m !== nothing && return m.match[1:1] - end - return path_separator -end - """ splitpath(path::AbstractString) -> Vector{String} @@ -246,8 +238,69 @@ function splitpath(p::String) return out end +joinpath(path::AbstractString) = String(path) + +if Sys.iswindows() + +function joinpath(path::AbstractString, paths::AbstractString...) + result_drive, result_path = splitdrive(path) + + local p_drive, p_path + for p in paths + p_drive, p_path = splitdrive(p) + + if startswith(p_path, ('\\', '/')) + # second path is absolute + if !isempty(p_drive) || !isempty(result_drive) + result_drive = p_drive + end + result_path = p_path + continue + elseif !isempty(p_drive) && p_drive != result_drive + if lowercase(p_drive) != lowercase(result_drive) + # different drives, ignore the first path entirely + result_drive = p_drive + result_path = p_path + continue + end + result_drive = p_drive # same drive in different case + end + + # second path is relative to the first + if !isempty(result_path) && result_path[end] ∉ ('\\', '/') + result_path *= "\\" + end + + result_path = result_path * p_path + end + + # add separator between UNC and non-absolute path + if !isempty(p_path) && result_path[1] ∉ ('\\', '/') && !isempty(result_drive) && result_drive[end] != ':' + return result_drive * "\\" * result_path + end + + return result_drive * result_path +end + +else + +function joinpath(path::AbstractString, paths::AbstractString...) + for p in paths + if isabspath(p) + path = p + elseif isempty(path) || path[end] == '/' + path *= p + else + path *= "/" * p + end + end + return path +end + +end # os-test + """ - joinpath(parts...) -> AbstractString + joinpath(parts::AbstractString...) -> String Join path components into a full path. If some argument is an absolute path or (on Windows) has a drive specification that doesn't match the drive computed for @@ -259,20 +312,7 @@ julia> joinpath("/home/myuser", "example.jl") "/home/myuser/example.jl" ``` """ -joinpath(a::AbstractString, b::AbstractString, c::AbstractString...) = joinpath(joinpath(a,b), c...) -joinpath(a::AbstractString) = a - -function joinpath(a::String, b::String) - isabspath(b) && return b - A, a = splitdrive(a) - B, b = splitdrive(b) - !isempty(B) && A != B && return string(B,b) - C = isempty(B) ? A : B - isempty(a) ? string(C,b) : - occursin(path_separator_re, a[end:end]) ? string(C,a,b) : - string(C,a,pathsep(a,b),b) -end -joinpath(a::AbstractString, b::AbstractString) = joinpath(String(a), String(b)) +joinpath """ normpath(path::AbstractString) -> AbstractString diff --git a/test/path.jl b/test/path.jl index b61535a4031a9..0ac50e926e918 100644 --- a/test/path.jl +++ b/test/path.jl @@ -31,8 +31,14 @@ @test isdirpath(S("..")) end @testset "joinpath" begin + @test joinpath(S("")) == "" @test joinpath(S("foo")) == "foo" @test joinpath(S("foo"), S("bar")) == "foo$(sep)bar" + @test joinpath(S("foo"), S("bar"), S("baz")) == "foo$(sep)bar$(sep)baz" + @test joinpath(S("foo"), S(""), S("baz")) == "foo$(sep)baz" + @test joinpath(S("foo"), S(""), S("")) == "foo$(sep)" + @test joinpath(S("foo"), S(""), S(""), S("bar")) == "foo$(sep)bar" + @test joinpath(S("foo"), S(homedir())) == homedir() @test joinpath(S(abspath("foo")), S(homedir())) == homedir() @@ -40,6 +46,18 @@ @test joinpath(S("foo"),S("bar:baz")) == "bar:baz" @test joinpath(S("C:"),S("foo"),S("D:"),S("bar")) == "D:bar" @test joinpath(S("C:"),S("foo"),S("D:bar"),S("baz")) == "D:bar$(sep)baz" + + # relative folders and case-insensitive drive letters + @test joinpath(S("C:\\a\\b"), S("c:c\\e")) == "c:\\a\\b\\c\\e" + + # UNC paths + @test joinpath(S("\\\\server"), S("share")) == "\\\\server\\share" + @test joinpath(S("\\\\server"), S("share"), S("a")) == "\\\\server\\share\\a" + @test joinpath(S("\\\\server\\"), S("share"), S("a")) == "\\\\server\\share\\a" + @test joinpath(S("\\\\server"), S("share"), S("a"), S("b")) == "\\\\server\\share\\a\\b" + @test joinpath(S("\\\\server\\share"),S("a")) == "\\\\server\\share\\a" + @test joinpath(S("\\\\server\\share\\"), S("a")) == "\\\\server\\share\\a" + elseif Sys.isunix() @test joinpath(S("foo"),S("bar:baz")) == "foo$(sep)bar:baz" @test joinpath(S("C:"),S("foo"),S("D:"),S("bar")) == "C:$(sep)foo$(sep)D:$(sep)bar"