anstyle_hyperlink/file.rs
1/// Create a URL from a given path
2pub fn path_to_url(path: &std::path::Path) -> Option<String> {
3 // Do a best-effort for getting the host in the URL
4 let hostname = if cfg!(windows) {
5 // Not supported correctly on windows
6 None
7 } else {
8 crate::hostname().ok().and_then(|os| os.into_string().ok())
9 };
10 if path.is_dir() {
11 dir_to_url(hostname.as_deref(), path)
12 } else {
13 file_to_url(hostname.as_deref(), path)
14 }
15}
16
17/// Create a URL from a given hostname and file path
18///
19/// For hyperlink escape codes, the hostname is used to avoid issues with opening a link scoped to
20/// the computer you've SSH'ed into
21/// ([reference](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda#file-uris-and-the-hostname))
22pub fn file_to_url(hostname: Option<&str>, path: &std::path::Path) -> Option<String> {
23 if !path.is_absolute() {
24 return None;
25 }
26
27 let mut url = "file://".to_owned();
28 if let Some(hostname) = hostname {
29 url.push_str(hostname);
30 }
31
32 // skip the root component
33 let mut is_path_empty = true;
34 for component in path.components().skip(1) {
35 is_path_empty = false;
36 url.push_str(URL_PATH_SEP);
37 let component = component.as_os_str().to_str()?;
38 url.extend(percent_encoding::percent_encode(
39 component.as_bytes(),
40 SPECIAL_PATH_SEGMENT,
41 ));
42 }
43 if is_path_empty {
44 // An URL's path must not be empty
45 url.push_str(URL_PATH_SEP);
46 }
47
48 Some(url)
49}
50
51/// Create a URL from a given hostname and directory path
52///
53/// For hyperlink escape codes, the hostname is used to avoid issues with opening a link scoped to
54/// the computer you've SSH'ed into
55/// ([reference](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda#file-uris-and-the-hostname))
56pub fn dir_to_url(hostname: Option<&str>, path: &std::path::Path) -> Option<String> {
57 let mut url = file_to_url(hostname, path)?;
58 if !url.ends_with(URL_PATH_SEP) {
59 url.push_str(URL_PATH_SEP);
60 }
61 Some(url)
62}
63
64const URL_PATH_SEP: &str = "/";
65
66/// <https://url.spec.whatwg.org/#fragment-percent-encode-set>
67const FRAGMENT: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
68 .add(b' ')
69 .add(b'"')
70 .add(b'<')
71 .add(b'>')
72 .add(b'`');
73
74/// <https://url.spec.whatwg.org/#path-percent-encode-set>
75const PATH: &percent_encoding::AsciiSet = &FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}');
76
77const PATH_SEGMENT: &percent_encoding::AsciiSet = &PATH.add(b'/').add(b'%');
78
79// The backslash (\) character is treated as a path separator in special URLs
80// so it needs to be additionally escaped in that case.
81const SPECIAL_PATH_SEGMENT: &percent_encoding::AsciiSet = &PATH_SEGMENT.add(b'\\');