From 52e65c8605ac3ee7fc7a72ff8506738eb369f23d Mon Sep 17 00:00:00 2001 From: TEC Date: Sun, 11 Aug 2024 12:54:00 +0800 Subject: [PATCH] Add filesystem func to transform a path to a URI 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. --- base/path.jl | 27 +++++++++++++++++++++++++++ test/path.jl | 12 ++++++++++++ 2 files changed, 39 insertions(+) diff --git a/base/path.jl b/base/path.jl index 3b8124f34f174a..218184b339735a 100644 --- a/base/path.jl +++ b/base/path.jl @@ -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:///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) + string("file://", gethostname(), '/', + join(map(encode_uri_component, + split(abspath(path), path_separator_re, keepempty=false)), + '/')) +end + +uripath(path::AbstractString) = uripath(String(path)) diff --git a/test/path.jl b/test/path.jl index 2f4f2d0983a58e..a455c651df6aa0 100644 --- a/test/path.jl +++ b/test/path.jl @@ -311,6 +311,18 @@ test_relpath() end + @testset "uripath" begin + host = gethostname() + drive, absdrive = if Sys.iswindows() "C:$sep", "C:$sep" else "", "$sep" 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" + end + if Sys.iswindows() @testset "issue #23646" begin @test lowercase(relpath("E:\\a\\b", "C:\\c")) == "e:\\a\\b"