Skip to content

Commit d913cd1

Browse files
committed
Fix #31368: joinpath works on collections of paths
1 parent d33e7e0 commit d913cd1

File tree

2 files changed

+44
-10
lines changed

2 files changed

+44
-10
lines changed

base/path.jl

+35-10
Original file line numberDiff line numberDiff line change
@@ -248,16 +248,15 @@ function splitpath(p::String)
248248
return out
249249
end
250250

251-
joinpath(path::AbstractString)::String = path
252-
253251
if Sys.iswindows()
254252

255-
function joinpath(path::AbstractString, paths::AbstractString...)::String
256-
result_drive, result_path = splitdrive(path)
253+
function joinpath(paths::Union{NTuple{n,<:AbstractString} where n, AbstractVector{<:AbstractString}})::String
254+
isempty(paths) && return path_separator
255+
result_drive, result_path = splitdrive(paths[1])
257256

258-
local p_drive, p_path
259-
for p in paths
260-
p_drive, p_path = splitdrive(p)
257+
p_path = ""
258+
for i in firstindex(paths)+1:lastindex(paths)
259+
p_drive, p_path = splitdrive(paths[i])
261260

262261
if startswith(p_path, ('\\', '/'))
263262
# second path is absolute
@@ -293,8 +292,11 @@ end
293292

294293
else
295294

296-
function joinpath(path::AbstractString, paths::AbstractString...)::String
297-
for p in paths
295+
function joinpath(paths::Union{NTuple{n,<:AbstractString} where n, AbstractVector{<:AbstractString}})::String
296+
isempty(paths) && return path_separator
297+
path = paths[1]
298+
for i in firstindex(paths)+1:lastindex(paths)
299+
p = paths[i]
298300
if isabspath(p)
299301
path = p
300302
elseif isempty(path) || path[end] == '/'
@@ -308,6 +310,28 @@ end
308310

309311
end # os-test
310312

313+
"""
314+
joinpath(parts) -> String
315+
316+
Join collection of path components into a full path. If some argument is an absolute path or
317+
(on Windows) has a drive specification that doesn't match the drive computed for
318+
the join of the preceding paths, then prior components are dropped.
319+
320+
Note on Windows since there is a current directory for each drive, `joinpath("c:", "foo")`
321+
represents a path relative to the current directory on drive "c:" so this is equal to "c:foo",
322+
not "c:\\foo". Furthermore, `joinpath` treats this as a non-absolute path and ignores the drive
323+
letter casing, hence `joinpath("C:\\A","c:b") = "C:\\A\\b"`.
324+
325+
# Examples
326+
```jldoctest
327+
julia> joinpath(["/home/myuser", "example.jl"])
328+
"/home/myuser/example.jl"
329+
julia> joinpath(())
330+
"$(path_separator)"
331+
```
332+
"""
333+
joinpath
334+
311335
"""
312336
joinpath(parts::AbstractString...) -> String
313337
@@ -326,7 +350,8 @@ julia> joinpath("/home/myuser", "example.jl")
326350
"/home/myuser/example.jl"
327351
```
328352
"""
329-
joinpath
353+
joinpath(path::AbstractString)::String = path
354+
joinpath(path::AbstractString, paths::AbstractString...)::String = joinpath((path, paths...))
330355

331356
"""
332357
normpath(path::AbstractString) -> String

test/path.jl

+9
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@
4242
@test joinpath(S("foo"), S(homedir())) == homedir()
4343
@test joinpath(S(abspath("foo")), S(homedir())) == homedir()
4444

45+
@test joinpath(()) == sep
46+
for str in map(S, [sep, "a$(sep)b", "a$(sep)b$(sep)c", "a$(sep)b$(sep)c$(sep)d"])
47+
@test str == joinpath(splitpath(str))
48+
end
49+
4550
if Sys.iswindows()
4651
@test joinpath(S("foo"),S("bar:baz")) == "bar:baz"
4752
@test joinpath(S("C:"),S("foo"),S("D:"),S("bar")) == "D:bar"
@@ -58,6 +63,10 @@
5863
@test joinpath(S("\\\\server\\share"),S("a")) == "\\\\server\\share\\a"
5964
@test joinpath(S("\\\\server\\share\\"), S("a")) == "\\\\server\\share\\a"
6065

66+
for str in map(S, ["c:\\", "c:\\a", "c:\\a\\b", "c:\\a\\b\\c", "c:\\a\\b\\c\\d"])
67+
@test str == joinpath(splitpath(str))
68+
end
69+
6170
elseif Sys.isunix()
6271
@test joinpath(S("foo"),S("bar:baz")) == "foo$(sep)bar:baz"
6372
@test joinpath(S("C:"),S("foo"),S("D:"),S("bar")) == "C:$(sep)foo$(sep)D:$(sep)bar"

0 commit comments

Comments
 (0)