jsona_util/util/
url.rs

1use once_cell::sync::Lazy;
2use regex::Regex;
3use std::borrow::Cow;
4use url::Url;
5
6use super::path as path_utils;
7
8const FILE_PROTOCOL: &str = "file://";
9static RE_URI_PROTOCOL: Lazy<Regex> = Lazy::new(|| Regex::new(r"^([A-Za-z_-]+://)").unwrap());
10
11/// Judge string is a url
12pub fn is_url(path: &str) -> bool {
13    RE_URI_PROTOCOL
14        .captures(path)
15        .and_then(|v| v.get(0))
16        .is_some()
17}
18
19/// Convert path to uri
20pub fn to_url(path: &str, base: &Option<Url>) -> Option<Url> {
21    if is_url(path) {
22        return path.parse().ok();
23    }
24    let url = if path_utils::is_window(path) {
25        format!("{}/{}", FILE_PROTOCOL, path_utils::encode_url(path))
26    } else if path_utils::is_absolute(path) {
27        if let Some(base) = base.as_ref() {
28            let mut base: Url = base.clone();
29            base.set_path("");
30            format!("{}{}", base, path_utils::to_unix(path))
31        } else {
32            format!("{}{}", FILE_PROTOCOL, path_utils::encode_url(path))
33        }
34    } else {
35        let base = base.as_ref()?.as_str();
36        let slash = if base.ends_with('/') { "" } else { "/" };
37        format!("{}{}{}", base, slash, path_utils::to_unix(path))
38    };
39    url.parse().ok()
40}
41
42/// Convert url to file path
43pub fn to_file_path(url: &Url) -> Option<String> {
44    let path = url.path();
45    let mut chars = path.chars();
46    let mut driver = None;
47    let check_driver = |v: char, driver: &mut Option<char>| {
48        if v.is_ascii_alphabetic() {
49            *driver = Some(v);
50            true
51        } else {
52            false
53        }
54    };
55    if matches!(
56        (
57            chars.next(),
58            chars.next().map(|v| check_driver(v, &mut driver)),
59            chars.next(),
60            chars.next(),
61            chars.next()
62        ),
63        (Some('/'), Some(true), Some('%'), Some('3'), Some('A'))
64    ) {
65        // windows driver `/c%3A` => `/C:`
66        let (_, path) = path.split_at(5);
67        let path = path
68            .split('/')
69            .map(|v| urlencoding::decode(v).ok())
70            .collect::<Option<Vec<Cow<str>>>>()?
71            .join("\\");
72        Some(format!("{}:{}", driver.unwrap().to_ascii_uppercase(), path))
73    } else {
74        Some(path.to_string())
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    macro_rules! assert_to_url {
82        ($p:expr, $o:expr) => {
83            assert_eq!(to_url($p, &None).unwrap().to_string(), $o);
84        };
85        ($p:expr, $b:expr, $o:expr) => {
86            let b = $b.parse().ok();
87            assert_eq!(to_url($p, &b).unwrap().to_string(), $o);
88        };
89    }
90    macro_rules! assert_to_file_path {
91        ($i:expr, $o:expr) => {
92            assert_eq!(to_file_path(&$i.parse().unwrap()).unwrap(), $o.to_string());
93        };
94    }
95
96    #[test]
97    fn test_to_url() {
98        assert_to_url!("a\\b", "file:///c%3A/dir1", "file:///c%3A/dir1/a/b");
99        assert_to_url!("D:\\c", "file:///c%3A/dir1", "file:///d%3A/c");
100        assert_to_url!("a/b", "file:///dir1", "file:///dir1/a/b");
101        assert_to_url!("/a/b", "file:///dir1", "file:///a/b");
102        assert_to_url!("/a/b", "file:///dir1/", "file:///a/b");
103        assert_to_url!(
104            "/a/b",
105            "vscode-test-web://mount",
106            "vscode-test-web://mount/a/b"
107        );
108        assert_to_url!(
109            "/a/b",
110            "vscode-test-web://mount/",
111            "vscode-test-web://mount/a/b"
112        );
113        assert_to_url!(
114            "a/b",
115            "vscode-test-web://mount",
116            "vscode-test-web://mount/a/b"
117        );
118        assert_to_url!(
119            "a/b",
120            "vscode-test-web://mount/",
121            "vscode-test-web://mount/a/b"
122        );
123        assert_to_url!("a/b", "vscode-vfs:///dir1", "vscode-vfs:///dir1/a/b");
124        assert_to_url!("http://example.com/a/b", "http://example.com/a/b");
125    }
126
127    #[test]
128    fn test_to_file_path() {
129        assert_to_file_path!("file:///c%3A/a/b", "C:\\a\\b");
130        assert_to_file_path!("file:///C%3A/a/b", "C:\\a\\b");
131        assert_to_file_path!("file:///dir1/a/b", "/dir1/a/b");
132        assert_to_file_path!(
133            "vscode-vfs://github/jsona/schemastore",
134            "/jsona/schemastore"
135        );
136    }
137
138    #[test]
139    fn test_is_url() {
140        assert!(is_url("file:///c%3A/a/b"));
141        assert!(is_url("vscode-test-web://mount"));
142        assert!(is_url("vscode-vfs://github/jsona/jsona"));
143        assert!(!is_url("/a/b"));
144        assert!(!is_url("a/b"));
145        assert!(!is_url("C:\\a\\b"));
146    }
147}