Skip to content

Commit

Permalink
Add filesystem func to transform a path to a URI
Browse files Browse the repository at this point in the history
In a few places across Base and the stdlib, we emit paths that we like
people to be able to click on in their terminal and editor. Up to this
point, we have relied on auto-filepath detection, but this does not
allow for alternative link text, such as contracted paths.

Doing so (via OSC 8 terminal links for example) requires filepath URI
encoding.

This functionality was previously part of a PR modifying stacktrace
printing, but after that became held up for unrelated reasons and
another PR appeared that would benefit from this utility, I've split out
this functionality so it can be used before the stacktrace printing PR
is resolved.
  • Loading branch information
tecosaur committed Aug 12, 2024
1 parent 7e809b0 commit 220782d
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 0 deletions.
27 changes: 27 additions & 0 deletions base/path.jl
Original file line number Diff line number Diff line change
Expand Up @@ -613,3 +613,30 @@ relpath(path::AbstractString, startpath::AbstractString) =
for f in (:isdirpath, :splitdir, :splitdrive, :splitext, :normpath, :abspath)
@eval $f(path::AbstractString) = $f(String(path))
end

"""
uripath(path::AbstractString)
Encode `path` as a URI as per [RFC1738](https://www.rfc-editor.org/rfc/rfc1738),
[RFC3986](https://www.rfc-editor.org/rfc/rfc3986), and the [Freedesktop File URI
spec](https://www.freedesktop.org/wiki/Specifications/file-uri-spec/).
## Examples
```julia-repl
julia> uripath("/home/user/example file.jl")
"file://<hostname>/home/user/example%20file.jl"
```
"""
function uripath(path::String)
percent_escape(s) =
'%' * join(map(b -> string(b, base=16), codeunits(s)), '%')
encode_uri_component(s) =
replace(s, r"[^A-Za-z0-9\-_.~]+" => percent_escape)
localpath = join(Iterators.map(encode_uri_component,
eachsplit(abspath(path), path_separator_re, keepempty=false)),
'/')
string("file://", gethostname(), '/', localpath)
end

uripath(path::AbstractString) = uripath(String(path))
13 changes: 13 additions & 0 deletions test/path.jl
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,19 @@
test_relpath()
end

@testset "uripath" begin
host = gethostname()
drive, absdrive = if Sys.iswindows() "C:$sep", "C%3a/" else "", "/" end
@test Base.Filesystem.uripath("$(absdrive)some$(sep)file.txt") == "file://$host/$(drive)some/file.txt"
@test Base.Filesystem.uripath("$(absdrive)another$(sep)$(sep)folder$(sep)file.md") == "file://$host/$(drive)another/folder/file.md"
@test Base.Filesystem.uripath("$(absdrive)some file with ^odd% chars") == "file://$host/$(drive)some%20file%20with%20%5eodd%25%20chars"
@test Base.Filesystem.uripath("$(absdrive)weird chars like @#&()[]{}") == "file://$host/$(drive)weird%20chars%20like%20%40%23%26%28%29%5b%5d%7b%7d"
@test Base.Filesystem.uripath("$absdrive") == "file://$host/$drive"
@test Base.Filesystem.uripath(".") == Base.Filesystem.uripath(pwd())
@test Base.Filesystem.uripath("$(absdrive)unicode$(sep)Δεδομένα") == "file://$host/$(drive)unicode/%ce%94%ce%b5%ce%b4%ce%bf%ce%bc%ce%ad%ce%bd%ce%b1"
@test Base.Filesystem.uripath("$(absdrive)unicode$(sep)🧮🐛🔨") == "file://$host/$(drive)unicode/%f0%9f%a7%ae%f0%9f%90%9b%f0%9f%94%a8"
end

if Sys.iswindows()
@testset "issue #23646" begin
@test lowercase(relpath("E:\\a\\b", "C:\\c")) == "e:\\a\\b"
Expand Down

0 comments on commit 220782d

Please sign in to comment.