• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #38002

06 Feb 2025 06:14AM UTC coverage: 20.322% (-2.4%) from 22.722%
#38002

push

local

web-flow
bpart: Fully switch to partitioned semantics (#57253)

This is the final PR in the binding partitions series (modulo bugs and
tweaks), i.e. it closes #54654 and thus closes #40399, which was the
original design sketch.

This thus activates the full designed semantics for binding partitions,
in particular allowing safe replacement of const bindings. It in
particular allows struct redefinitions. This thus closes
timholy/Revise.jl#18 and also closes #38584.

The biggest semantic change here is probably that this gets rid of the
notion of "resolvedness" of a binding. Previously, a lot of the behavior
of our implementation depended on when bindings were "resolved", which
could happen at basically an arbitrary point (in the compiler, in REPL
completion, in a different thread), making a lot of the semantics around
bindings ill- or at least implementation-defined. There are several
related issues in the bugtracker, so this closes #14055 closes #44604
closes #46354 closes #30277

It is also the last step to close #24569.
It also supports bindings for undef->defined transitions and thus closes
#53958 closes #54733 - however, this is not activated yet for
performance reasons and may need some further optimization.

Since resolvedness no longer exists, we need to replace it with some
hopefully more well-defined semantics. I will describe the semantics
below, but before I do I will make two notes:

1. There are a number of cases where these semantics will behave
slightly differently than the old semantics absent some other task going
around resolving random bindings.
2. The new behavior (except for the replacement stuff) was generally
permissible under the old semantics if the bindings happened to be
resolved at the right time.

With all that said, there are essentially three "strengths" of bindings:

1. Implicit Bindings: Anything implicitly obtained from `using Mod`, "no
binding", plus slightly more exotic corner cases around conflicts

2. Weakly declared bindin... (continued)

11 of 111 new or added lines in 7 files covered. (9.91%)

1273 existing lines in 68 files now uncovered.

9908 of 48755 relevant lines covered (20.32%)

105126.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

31.08
/base/path.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
export
4
    abspath,
5
    basename,
6
    dirname,
7
    expanduser,
8
    contractuser,
9
    homedir,
10
    isabspath,
11
    isdirpath,
12
    joinpath,
13
    normpath,
14
    realpath,
15
    relpath,
16
    splitdir,
17
    splitdrive,
18
    splitext,
19
    splitpath
20

21
if Sys.isunix()
22
    const path_separator    = "/"
23
    const path_separator_re = r"/+"sa
24
    const path_directory_re = r"(?:^|/)\.{0,2}$"sa
25
    const path_dir_splitter = r"^(.*?)(/+)([^/]*)$"sa
26
    const path_ext_splitter = r"^((?:.*/)?(?:\.|[^/\.])[^/]*?)(\.[^/\.]*|)$"sa
27

28
    splitdrive(path::String) = ("",path)
×
29
elseif Sys.iswindows()
30
    const path_separator    = "\\"
31
    const path_separator_re = r"[/\\]+"sa
32
    const path_absolute_re  = r"^(?:[A-Za-z]+:)?[/\\]"sa
33
    const path_directory_re = r"(?:^|[/\\])\.{0,2}$"sa
34
    const path_dir_splitter = r"^(.*?)([/\\]+)([^/\\]*)$"sa
35
    const path_ext_splitter = r"^((?:.*[/\\])?(?:\.|[^/\\\.])[^/\\]*?)(\.[^/\\\.]*|)$"sa
36

37
    const splitdrive_re = let
38
        # Slash in either direction.
39
        S = raw"[\\/]"
40
        # Not a slash in either direction.
41
        N = raw"[^\\/]"
42
        # Drive letter, e.g. `C:`
43
        drive = "$(N)+:"
44
        # UNC path, e.g. `\\server\share`
45
        unc = "$(S)$(S)$(N)+$(S)$(N)+"
46
        # Long drive letter, e.g. `\\?\C:`
47
        long_drive = "$(S)$(S)\\?$(S)$(drive)"
48
        # Long UNC path, e.g. `\\?\UNC\server\share`
49
        long_unc = "$(S)$(S)\\?$(S)UNC$(S)$(N)+$(S)$(N)+"
50
        # Need to match the long patterns first so they get priority.
51
        Regex("^($long_unc|$long_drive|$unc|$drive|)(.*)\$", "sa")
52
    end
53

54
    function splitdrive(path::String)
×
55
        m = match(splitdrive_re, path)::AbstractMatch
×
56
        String(something(m.captures[1])), String(something(m.captures[2]))
×
57
    end
58
else
59
    error("path primitives for this OS need to be defined")
60
end
61

62

63
"""
64
    splitdrive(path::AbstractString) -> (AbstractString, AbstractString)
65

66
On Windows, split a path into the drive letter part and the path part. On Unix systems, the
67
first component is always the empty string.
68
"""
69
splitdrive(path::AbstractString)
70

71
"""
72
    homedir() -> String
73

74
Return the current user's home directory.
75

76
!!! note
77
    `homedir` determines the home directory via `libuv`'s `uv_os_homedir`. For details
78
    (for example on how to specify the home directory via environment variables), see the
79
    [`uv_os_homedir` documentation](http://docs.libuv.org/en/v1.x/misc.html#c.uv_os_homedir).
80

81
See also [`Sys.username`](@ref).
82
"""
83
function homedir()
8✔
84
    buf = Base.StringVector(AVG_PATH - 1) # space for null-terminator implied by StringVector
8✔
85
    sz = RefValue{Csize_t}(length(buf) + 1) # total buffer size including null
8✔
86
    while true
×
87
        rc = ccall(:uv_os_homedir, Cint, (Ptr{UInt8}, Ptr{Csize_t}), buf, sz)
8✔
88
        if rc == 0
8✔
89
            resize!(buf, sz[])
8✔
90
            return String(buf)
8✔
91
        elseif rc == Base.UV_ENOBUFS
×
92
            resize!(buf, sz[] - 1) # space for null-terminator implied by StringVector
×
93
        else
94
            uv_error("homedir()", rc)
×
95
        end
96
    end
×
97
end
98

99

100
if Sys.iswindows()
101
    isabspath(path::AbstractString) = occursin(path_absolute_re, path)
×
102
else
103
    isabspath(path::AbstractString) = startswith(path, '/')
26,776✔
104
end
105

106
"""
107
    isabspath(path::AbstractString) -> Bool
108

109
Determine whether a path is absolute (begins at the root directory).
110

111
# Examples
112
```jldoctest
113
julia> isabspath("/home")
114
true
115

116
julia> isabspath("home")
117
false
118
```
119
"""
120
isabspath(path::AbstractString)
121

122
"""
123
    isdirpath(path::AbstractString) -> Bool
124

125
Determine whether a path refers to a directory (for example, ends with a path separator).
126

127
# Examples
128
```jldoctest
129
julia> isdirpath("/home")
130
false
131

132
julia> isdirpath("/home/")
133
true
134
```
135
"""
136
isdirpath(path::String) = occursin(path_directory_re, splitdrive(path)[2])
29✔
137

138
"""
139
    splitdir(path::AbstractString) -> (AbstractString, AbstractString)
140

141
Split a path into a tuple of the directory name and file name.
142

143
# Examples
144
```jldoctest
145
julia> splitdir("/home/myuser")
146
("/home", "myuser")
147
```
148
"""
149
function splitdir(path::String)
150
    a, b = splitdrive(path)
×
151
    _splitdir_nodrive(a,b)
12,298✔
152
end
153

154
# Common splitdir functionality without splitdrive, needed for splitpath.
155
_splitdir_nodrive(path::String) = _splitdir_nodrive("", path)
×
156
function _splitdir_nodrive(a::String, b::String)
19✔
157
    m = match(path_dir_splitter,b)
19✔
158
    m === nothing && return (a,b)
19✔
159
    cs = m.captures
17✔
160
    getcapture(cs, i) = cs[i]::AbstractString
17✔
161
    c1, c2, c3 = getcapture(cs, 1), getcapture(cs, 2), getcapture(cs, 3)
17✔
162
    a = string(a, isempty(c1) ? c2[1] : c1)
34✔
163
    a, String(c3)
17✔
164
end
165

166
"""
167
    dirname(path::AbstractString) -> String
168

169
Get the directory part of a path. Trailing characters ('/' or '\\') in the path are
170
counted as part of the path.
171

172
# Examples
173
```jldoctest
174
julia> dirname("/home/myuser")
175
"/home"
176

177
julia> dirname("/home/myuser/")
178
"/home/myuser"
179
```
180

181
See also [`basename`](@ref).
182
"""
183
dirname(path::AbstractString) = splitdir(path)[1]
4,167✔
184

185
"""
186
    basename(path::AbstractString) -> String
187

188
Get the file name part of a path.
189

190
!!! note
191
    This function differs slightly from the Unix `basename` program, where trailing slashes are ignored,
192
    i.e. `\$ basename /foo/bar/` returns `bar`, whereas `basename` in Julia returns an empty string `""`.
193

194
# Examples
195
```jldoctest
196
julia> basename("/home/myuser/example.jl")
197
"example.jl"
198

199
julia> basename("/home/myuser/")
200
""
201
```
202

203
See also [`dirname`](@ref).
204
"""
205
basename(path::AbstractString) = splitdir(path)[2]
8,130✔
206

207
"""
208
    splitext(path::AbstractString) -> (String, String)
209

210
If the last component of a path contains one or more dots, split the path into everything before the
211
last dot and everything including and after the dot. Otherwise, return a tuple of the argument
212
unmodified and the empty string. "splitext" is short for "split extension".
213

214
# Examples
215
```jldoctest
216
julia> splitext("/home/myuser/example.jl")
217
("/home/myuser/example", ".jl")
218

219
julia> splitext("/home/myuser/example.tar.gz")
220
("/home/myuser/example.tar", ".gz")
221

222
julia> splitext("/home/my.user/example")
223
("/home/my.user/example", "")
224
```
225
"""
226
function splitext(path::String)
×
227
    a, b = splitdrive(path)
×
228
    m = match(path_ext_splitter, b)
×
229
    m === nothing && return (path,"")
×
230
    (a*something(m.captures[1])), String(something(m.captures[2]))
×
231
end
232

233
# NOTE: deprecated in 1.4
234
pathsep() = path_separator
×
235

236
"""
237
    splitpath(path::AbstractString) -> Vector{String}
238

239
Split a file path into all its path components. This is the opposite of
240
`joinpath`. Returns an array of substrings, one for each directory or file in
241
the path, including the root directory if present.
242

243
!!! compat "Julia 1.1"
244
    This function requires at least Julia 1.1.
245

246
# Examples
247
```jldoctest
248
julia> splitpath("/home/myuser/example.jl")
249
4-element Vector{String}:
250
 "/"
251
 "home"
252
 "myuser"
253
 "example.jl"
254
```
255
"""
256
splitpath(p::AbstractString) = splitpath(String(p))
×
257

258
function splitpath(p::String)
×
259
    drive, p = splitdrive(p)
×
260
    out = String[]
×
261
    isempty(p) && (pushfirst!(out,p))  # "" means the current directory.
×
262
    while !isempty(p)
×
263
        dir, base = _splitdir_nodrive(p)
×
264
        dir == p && (pushfirst!(out, dir); break)  # Reached root node.
×
265
        if !isempty(base)  # Skip trailing '/' in basename
×
266
            pushfirst!(out, base)
×
267
        end
268
        p = dir
×
269
    end
×
270
    if !isempty(drive)  # Tack the drive back on to the first element.
×
271
        out[1] = drive*out[1]  # Note that length(out) is always >= 1.
×
272
    end
273
    return out
×
274
end
275

276
if Sys.iswindows()
277

278
function joinpath(paths::Union{Tuple, AbstractVector})::String
×
279
    assertstring(x) = x isa AbstractString || throw(ArgumentError("path component is not a string: $(repr(x))"))
×
280

281
    isempty(paths) && throw(ArgumentError("collection of path components must be non-empty"))
×
282
    assertstring(paths[1])
×
283
    result_drive, result_path = splitdrive(paths[1])
×
284

285
    p_path = ""
×
286
    for i in firstindex(paths)+1:lastindex(paths)
×
287
        assertstring(paths[i])
×
288
        p_drive, p_path = splitdrive(paths[i])
×
289

290
        if startswith(p_path, ('\\', '/'))
×
291
            # second path is absolute
292
            if !isempty(p_drive) || !isempty(result_drive)
×
293
                result_drive = p_drive
×
294
            end
295
            result_path = p_path
×
296
            continue
×
297
        elseif !isempty(p_drive) && p_drive != result_drive
×
298
            if lowercase(p_drive) != lowercase(result_drive)
×
299
                # different drives, ignore the first path entirely
300
                result_drive = p_drive
×
301
                result_path = p_path
×
302
                continue
×
303
            end
304
        end
305

306
        # second path is relative to the first
307
        if !isempty(result_path) && result_path[end] ∉ ('\\', '/')
×
308
            result_path *= "\\"
×
309
        end
310

311
        result_path = result_path * p_path
×
312
    end
×
313

314
    # add separator between UNC and non-absolute path
315
    if !isempty(p_path) && result_path[1] ∉ ('\\', '/') && !isempty(result_drive) && result_drive[end] != ':'
×
316
        return result_drive * "\\" * result_path
×
317
    end
318

319
    return result_drive * result_path
×
320
end
321

322
else
323

324
function joinpath(paths::Union{Tuple, AbstractVector})::String
9,328✔
325
    assertstring(x) = x isa AbstractString || throw(ArgumentError("path component is not a string: $(repr(x))"))
9,313✔
326

327
    isempty(paths) && throw(ArgumentError("collection of path components must be non-empty"))
9,313✔
328
    assertstring(paths[1])
9,313✔
329
    path = paths[1]
9,328✔
330
    for i in firstindex(paths)+1:lastindex(paths)
9,344✔
331
        p = paths[i]
9,348✔
332
        assertstring(p)
9,313✔
333
        if isabspath(p)
18,696✔
334
            path = p
×
335
        elseif isempty(path) || path[end] == '/'
28,044✔
336
            path *= p
×
337
        else
338
            path *= "/" * p
9,348✔
339
        end
340
    end
9,368✔
341
    return path
9,328✔
342
end
343

344
end # os-test
345

346
joinpath(paths::AbstractString...)::String = joinpath(paths)
25,027✔
347

348
"""
349
    joinpath(parts::AbstractString...) -> String
350
    joinpath(parts::Vector{AbstractString}) -> String
351
    joinpath(parts::Tuple{AbstractString}) -> String
352

353
Join path components into a full path. If some argument is an absolute path or
354
(on Windows) has a drive specification that doesn't match the drive computed for
355
the join of the preceding paths, then prior components are dropped.
356

357
Note on Windows since there is a current directory for each drive, `joinpath("c:", "foo")`
358
represents a path relative to the current directory on drive "c:" so this is equal to "c:foo",
359
not "c:\\foo". Furthermore, `joinpath` treats this as a non-absolute path and ignores the drive
360
letter casing, hence `joinpath("C:\\A","c:b") = "C:\\A\\b"`.
361

362
# Examples
363
```jldoctest
364
julia> joinpath("/home/myuser", "example.jl")
365
"/home/myuser/example.jl"
366
```
367

368
```jldoctest
369
julia> joinpath(["/home/myuser", "example.jl"])
370
"/home/myuser/example.jl"
371
```
372
"""
373
joinpath
374

375
"""
376
    normpath(path::AbstractString) -> String
377

378
Normalize a path, removing "." and ".." entries and changing "/" to the canonical path separator
379
for the system.
380

381
# Examples
382
```jldoctest
383
julia> normpath("/home/myuser/../example.jl")
384
"/home/example.jl"
385

386
julia> normpath("Documents/Julia") == joinpath("Documents", "Julia")
387
true
388
```
389
"""
390
function normpath(path::String)
29✔
391
    isabs = isabspath(path)
58✔
392
    isdir = isdirpath(path)
29✔
393
    drive, path = splitdrive(path)
×
394
    parts = split(path, path_separator_re; keepempty=false)
29✔
395
    filter!(!=("."), parts)
29✔
396
    while true
×
397
        clean = true
×
398
        for j = 1:length(parts)-1
44✔
399
            if parts[j] != ".." && parts[j+1] == ".."
290✔
400
                deleteat!(parts, j:j+1)
30✔
401
                clean = false
×
402
                break
15✔
403
            end
404
        end
275✔
405
        clean && break
44✔
406
    end
15✔
407
    if isabs
29✔
408
        while !isempty(parts) && parts[1] == ".."
22✔
409
            popfirst!(parts)
×
410
        end
×
411
    elseif isempty(parts)
7✔
412
        push!(parts, ".")
×
413
    end
414
    path = join(parts, path_separator)
29✔
415
    if isabs
29✔
416
        path = path_separator*path
22✔
417
    end
418
    if isdir && !isdirpath(path)
29✔
UNCOV
419
        path *= path_separator
×
420
    end
421
    string(drive,path)
29✔
422
end
423

424
"""
425
    normpath(path::AbstractString, paths::AbstractString...) -> String
426

427
Convert a set of paths to a normalized path by joining them together and removing
428
"." and ".." entries. Equivalent to `normpath(joinpath(path, paths...))`.
429
"""
430
normpath(a::AbstractString, b::AbstractString...) = normpath(joinpath(a,b...))
4,007✔
431

432
"""
433
    abspath(path::AbstractString) -> String
434

435
Convert a path to an absolute path by adding the current directory if necessary.
436
Also normalizes the path as in [`normpath`](@ref).
437

438
# Examples
439

440
If you are in a directory called `JuliaExample` and the data you are using is two levels up relative to the `JuliaExample` directory, you could write:
441

442
    abspath("../../data")
443

444
Which gives a path like `"/home/JuliaUser/data/"`.
445

446
See also [`joinpath`](@ref), [`pwd`](@ref), [`expanduser`](@ref).
447
"""
448
function abspath(a::String)::String
7✔
449
    if !isabspath(a)
14✔
450
        cwd = pwd()
×
451
        a_drive, a_nodrive = splitdrive(a)
×
452
        if a_drive != "" && lowercase(splitdrive(cwd)[1]) != lowercase(a_drive)
×
453
            cwd = a_drive * path_separator
×
454
            a = joinpath(cwd, a_nodrive)
×
455
        else
456
            a = joinpath(cwd, a)
×
457
        end
458
    end
459
    return normpath(a)
7✔
460
end
461

462
"""
463
    abspath(path::AbstractString, paths::AbstractString...) -> String
464

465
Convert a set of paths to an absolute path by joining them together and adding the
466
current directory if necessary. Equivalent to `abspath(joinpath(path, paths...))`.
467
"""
468
abspath(a::AbstractString, b::AbstractString...) = abspath(joinpath(a,b...))
517✔
469

470
if Sys.iswindows()
471

472
function longpath(path::AbstractString)
×
473
    p = cwstring(path)
×
474
    buf = zeros(UInt16, length(p))
×
475
    while true
×
476
        n = ccall((:GetLongPathNameW, "kernel32"), stdcall,
×
477
            UInt32, (Ptr{UInt16}, Ptr{UInt16}, UInt32),
478
            p, buf, length(buf))
479
        windowserror(:longpath, n == 0)
×
480
        x = n < length(buf) # is the buffer big enough?
×
481
        resize!(buf, n) # shrink if x, grow if !x
×
482
        x && return transcode(String, buf)
×
483
    end
×
484
end
485

486
end # os-test
487

488

489
"""
490
    realpath(path::AbstractString) -> String
491

492
Canonicalize a path by expanding symbolic links and removing "." and ".." entries.
493
On case-insensitive case-preserving filesystems (typically Mac and Windows), the
494
filesystem's stored case for the path is returned.
495

496
(This function throws an exception if `path` does not exist in the filesystem.)
497
"""
498
function realpath(path::AbstractString)
×
499
    req = Libc.malloc(_sizeof_uv_fs)
×
500
    try
×
501
        ret = ccall(:uv_fs_realpath, Cint,
×
502
                    (Ptr{Cvoid}, Ptr{Cvoid}, Cstring, Ptr{Cvoid}),
503
                    C_NULL, req, path, C_NULL)
504
        if ret < 0
×
505
            uv_fs_req_cleanup(req)
×
506
            uv_error("realpath($(repr(path)))", ret)
×
507
        end
508
        path = unsafe_string(ccall(:jl_uv_fs_t_ptr, Cstring, (Ptr{Cvoid},), req))
×
509
        uv_fs_req_cleanup(req)
×
510
        return path
×
511
    finally
512
        Libc.free(req)
×
513
    end
514
end
515

516
if Sys.iswindows()
517
# on windows, ~ means "temporary file"
518
expanduser(path::AbstractString) = path
×
519
contractuser(path::AbstractString) = path
×
520
else
521
function expanduser(path::AbstractString)
3✔
522
    y = iterate(path)
6✔
523
    y === nothing && return path
3✔
524
    c, i = y::Tuple{eltype(path),Int}
×
525
    c != '~' && return path
3✔
526
    y = iterate(path, i)
×
527
    y === nothing && return homedir()
×
528
    y[1]::eltype(path) == '/' && return homedir() * path[i:end]
×
529
    throw(ArgumentError("~user tilde expansion not yet implemented"))
×
530
end
531
function contractuser(path::AbstractString)
532
    home = homedir()
71✔
533
    if path == home
71✔
534
        return "~"
×
535
    elseif startswith(path, home)
71✔
536
        return joinpath("~", relpath(path, home))
3✔
537
    else
538
        return path
68✔
539
    end
540
end
541
end
542

543

544
"""
545
    expanduser(path::AbstractString) -> AbstractString
546

547
On Unix systems, replace a tilde character at the start of a path with the current user's home directory.
548

549
See also: [`contractuser`](@ref).
550
"""
551
expanduser(path::AbstractString)
552

553
"""
554
    contractuser(path::AbstractString) -> AbstractString
555

556
On Unix systems, if the path starts with `homedir()`, replace it with a tilde character.
557

558
See also: [`expanduser`](@ref).
559
"""
560
contractuser(path::AbstractString)
561

562

563
"""
564
    relpath(path::AbstractString, startpath::AbstractString = ".") -> String
565

566
Return a relative filepath to `path` either from the current directory or from an optional
567
start directory. This is a path computation: the filesystem is not accessed to confirm the
568
existence or nature of `path` or `startpath`.
569

570
On Windows, case sensitivity is applied to every part of the path except drive letters. If
571
`path` and `startpath` refer to different drives, the absolute path of `path` is returned.
572
"""
573
function relpath(path::String, startpath::String = ".")
×
574
    isempty(path) && throw(ArgumentError("`path` must be non-empty"))
×
575
    isempty(startpath) && throw(ArgumentError("`startpath` must be non-empty"))
×
576
    curdir = "."
×
577
    pardir = ".."
×
578
    path == startpath && return curdir
×
579
    if Sys.iswindows()
×
580
        path_drive, path_without_drive = splitdrive(path)
×
581
        startpath_drive, startpath_without_drive = splitdrive(startpath)
×
582
        isempty(startpath_drive) && (startpath_drive = path_drive) # by default assume same as path drive
×
583
        uppercase(path_drive) == uppercase(startpath_drive) || return abspath(path) # if drives differ return first path
×
584
        path_arr  = split(abspath(path_drive * path_without_drive),      path_separator_re)
×
585
        start_arr = split(abspath(path_drive * startpath_without_drive), path_separator_re)
×
586
    else
587
        path_arr  = split(abspath(path),      path_separator_re)
×
588
        start_arr = split(abspath(startpath), path_separator_re)
×
589
    end
590
    i = 0
×
591
    while i < min(length(path_arr), length(start_arr))
×
592
        i += 1
×
593
        if path_arr[i] != start_arr[i]
×
594
            i -= 1
×
595
            break
×
596
        end
597
    end
×
598
    pathpart = join(path_arr[i+1:something(findlast(x -> !isempty(x), path_arr), 0)], path_separator)
×
599
    prefix_num = something(findlast(x -> !isempty(x), start_arr), 0) - i - 1
×
600
    if prefix_num >= 0
×
601
        prefix = pardir * path_separator
×
602
        relpath_ = isempty(pathpart)     ?
×
603
            (prefix^prefix_num) * pardir :
604
            (prefix^prefix_num) * pardir * path_separator * pathpart
605
    else
606
        relpath_ = pathpart
×
607
    end
608
    return isempty(relpath_) ? curdir :  relpath_
×
609
end
610
relpath(path::AbstractString, startpath::AbstractString) =
×
611
    relpath(String(path), String(startpath))
612

613
for f in (:isdirpath, :splitdir, :splitdrive, :splitext, :normpath, :abspath)
614
    @eval $f(path::AbstractString) = $f(String(path))
1✔
615
end
616

617
# RFC3986 Section 2.1
618
percent_escape(s) = '%' * join(map(b -> uppercase(string(b, base=16)), codeunits(s)), '%')
×
619
# RFC3986 Section 2.3
620
encode_uri_component(s) = replace(s, r"[^A-Za-z0-9\-_.~/]+" => percent_escape)
×
621

622
"""
623
    uripath(path::AbstractString)
624

625
Encode `path` as a URI as per [RFC8089: The "file" URI
626
Scheme](https://www.rfc-editor.org/rfc/rfc8089), [RFC3986: Uniform Resource
627
Identifier (URI): Generic Syntax](https://www.rfc-editor.org/rfc/rfc3986), and
628
the [Freedesktop File URI spec](https://www.freedesktop.org/wiki/Specifications/file-uri-spec/).
629

630
## Examples
631

632
```julia-repl
633
julia> uripath("/home/user/example file.jl") # On a unix machine
634
"file://<hostname>/home/user/example%20file.jl"
635

636
juila> uripath("C:\\Users\\user\\example file.jl") # On a windows machine
637
"file:///C:/Users/user/example%20file.jl"
638
```
639
"""
640
function uripath end
641

642
@static if Sys.iswindows()
643
    function uripath(path::String)
×
644
        path = abspath(path)
×
645
        if startswith(path, "\\\\") # UNC path, RFC8089 Appendix E.3
×
646
            unixpath = join(eachsplit(path, path_separator_re, keepempty=false), '/')
×
647
            string("file://", encode_uri_component(unixpath)) # RFC8089 Section 2
×
648
        else
649
            drive, localpath = splitdrive(path) # Assuming that non-UNC absolute paths on Windows always have a drive component
×
650
            unixpath = join(eachsplit(localpath, path_separator_re, keepempty=false), '/')
×
651
            encdrive = replace(encode_uri_component(drive), "%3A" => ':', "%7C" => '|') # RFC8089 Appendices D.2, E.2.1, and E.2.2
×
652
            string("file:///", encdrive, '/', encode_uri_component(unixpath)) # RFC8089 Section 2
×
653
        end
654
    end
655
else
656
    function uripath(path::String)
×
657
        localpath = join(eachsplit(abspath(path), path_separator_re, keepempty=false), '/')
×
658
        host = if ispath("/proc/sys/fs/binfmt_misc/WSLInterop") # WSL sigil
×
659
            distro = get(ENV, "WSL_DISTRO_NAME", "") # See <https://patrickwu.space/wslconf/>
×
660
            "wsl\$/$distro" # See <https://github.com/microsoft/terminal/pull/14993> and <https://learn.microsoft.com/en-us/windows/wsl/filesystems>
×
661
        else
662
            gethostname() # Freedesktop File URI Spec, Hostnames section
×
663
        end
664
        string("file://", encode_uri_component(host), '/', encode_uri_component(localpath)) # RFC8089 Section 2
×
665
    end
666
end
667

668
uripath(path::AbstractString) = uripath(String(path))
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc