From 220782d6d6bbdbbfdb647f9868d31068faea82db 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 | 13 +++++++++++++ 2 files changed, 40 insertions(+) diff --git a/base/path.jl b/base/path.jl index 3b8124f34f174a..ed77373e734e78 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) + 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)) diff --git a/test/path.jl b/test/path.jl index 2f4f2d0983a58e..2ca8e0f88221a4 100644 --- a/test/path.jl +++ b/test/path.jl @@ -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"