From 8b36796e2471088781ccb576dbbdd1a6add1c171 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 | 20 ++++++++++++++++++++ test/path.jl | 12 ++++++++++++ 2 files changed, 32 insertions(+) diff --git a/base/path.jl b/base/path.jl index 3b8124f34f174a..482cd31de11ac8 100644 --- a/base/path.jl +++ b/base/path.jl @@ -613,3 +613,23 @@ 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/). +""" +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..85053cc77219dc 100644 --- a/test/path.jl +++ b/test/path.jl @@ -311,6 +311,18 @@ test_relpath() end + @testset "uripath" begin + host = gethostname() + absprefix = if Sys.iswindows() "C:$sep" else "$sep" end + @test Base.Filesystem.uripath("$(absprefix)some$(sep)file.txt") == "file://$host/some/file.txt" + @test Base.Filesystem.uripath("$(absprefix)another$(sep)$(sep)folder$(sep)file.md") == "file://$host/another/folder/file.md" + @test Base.Filesystem.uripath("$(absprefix)some file with ^odd% chars") == "file://$host/some%20file%20with%20%5eodd%25%20chars" + @test Base.Filesystem.uripath("$(absprefix)weird chars like @#&()[]{}") == "file://$host/weird%20chars%20like%20%40%23%26%28%29%5b%5d%7b%7d" + @test Base.Filesystem.uripath("$absprefix") == "file://$host/" + @test Base.Filesystem.uripath(".") == Base.Filesystem.uripath(pwd()) + @test Base.Filesystem.uripath("$(absprefix)unicode$(sep)Δεδομένα") == "file://$host/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"