1use std::borrow::Cow;
4use std::path::Component;
5use std::path::Path;
6use std::path::PathBuf;
7
8use url::Url;
9
10#[derive(Debug, Clone)]
11pub enum UrlOrPath {
12 Url(Url),
13 Path(PathBuf),
14}
15
16impl UrlOrPath {
17 pub fn is_file(&self) -> bool {
18 match self {
19 UrlOrPath::Url(url) => url.scheme() == "file",
20 UrlOrPath::Path(_) => true,
21 }
22 }
23
24 pub fn is_node_url(&self) -> bool {
25 match self {
26 UrlOrPath::Url(url) => url.scheme() == "node",
27 UrlOrPath::Path(_) => false,
28 }
29 }
30
31 pub fn into_path(
32 self,
33 ) -> Result<PathBuf, deno_path_util::UrlToFilePathError> {
34 match self {
35 UrlOrPath::Url(url) => deno_path_util::url_to_file_path(&url),
36 UrlOrPath::Path(path) => Ok(path),
37 }
38 }
39
40 pub fn into_url(self) -> Result<Url, deno_path_util::PathToUrlError> {
41 match self {
42 UrlOrPath::Url(url) => Ok(url),
43 UrlOrPath::Path(path) => deno_path_util::url_from_file_path(&path),
44 }
45 }
46
47 pub fn to_string_lossy(&self) -> Cow<'_, str> {
48 match self {
49 UrlOrPath::Url(url) => Cow::Borrowed(url.as_str()),
50 UrlOrPath::Path(path) => path.to_string_lossy(),
51 }
52 }
53}
54
55impl std::fmt::Display for UrlOrPath {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 match self {
58 UrlOrPath::Url(url) => url.fmt(f),
59 UrlOrPath::Path(path) => {
60 match deno_path_util::url_from_file_path(path) {
62 Ok(url) => url.fmt(f),
63 Err(_) => {
64 write!(f, "{}", path.display())
65 }
66 }
67 }
68 }
69 }
70}
71
72pub struct UrlOrPathRef<'a> {
73 url: once_cell::unsync::OnceCell<Cow<'a, Url>>,
74 path: once_cell::unsync::OnceCell<Cow<'a, Path>>,
75}
76
77impl<'a> UrlOrPathRef<'a> {
78 pub fn from_path(path: &'a Path) -> Self {
79 Self {
80 url: Default::default(),
81 path: once_cell::unsync::OnceCell::with_value(Cow::Borrowed(path)),
82 }
83 }
84
85 pub fn from_url(url: &'a Url) -> Self {
86 Self {
87 path: Default::default(),
88 url: once_cell::unsync::OnceCell::with_value(Cow::Borrowed(url)),
89 }
90 }
91
92 pub fn url(&self) -> Result<&Url, deno_path_util::PathToUrlError> {
93 self
94 .url
95 .get_or_try_init(|| {
96 deno_path_util::url_from_file_path(self.path.get().unwrap())
97 .map(Cow::Owned)
98 })
99 .map(|v| v.as_ref())
100 }
101
102 pub fn path(&self) -> Result<&Path, deno_path_util::UrlToFilePathError> {
103 self
104 .path
105 .get_or_try_init(|| {
106 deno_path_util::url_to_file_path(self.url.get().unwrap())
107 .map(Cow::Owned)
108 })
109 .map(|v| v.as_ref())
110 }
111
112 pub fn display(&self) -> UrlOrPath {
113 if let Ok(url) = self.url() {
115 UrlOrPath::Url(url.clone())
116 } else {
117 UrlOrPath::Path(self.path.get().unwrap().to_path_buf())
119 }
120 }
121}
122
123pub trait PathClean<T> {
125 fn clean(&self) -> T;
126}
127
128impl PathClean<PathBuf> for PathBuf {
129 fn clean(&self) -> PathBuf {
130 fn is_clean_path(path: &Path) -> bool {
131 let path = path.to_string_lossy();
132 let mut current_index = 0;
133 while let Some(index) = path[current_index..].find("\\.") {
134 let trailing_index = index + current_index + 2;
135 let mut trailing_chars = path[trailing_index..].chars();
136 match trailing_chars.next() {
137 Some('.') => match trailing_chars.next() {
138 Some('/') | Some('\\') | None => {
139 return false;
140 }
141 _ => {}
142 },
143 Some('/') | Some('\\') => {
144 return false;
145 }
146 _ => {}
147 }
148 current_index = trailing_index;
149 }
150 true
151 }
152
153 let path = path_clean::PathClean::clean(self);
154 if cfg!(windows) && !is_clean_path(&path) {
155 let mut components = Vec::new();
158
159 for component in path.components() {
160 match component {
161 Component::CurDir => {
162 }
164 Component::ParentDir => {
165 let maybe_last_component = components.pop();
166 if !matches!(maybe_last_component, Some(Component::Normal(_))) {
167 panic!("Error normalizing: {}", path.display());
168 }
169 }
170 Component::Normal(_) | Component::RootDir | Component::Prefix(_) => {
171 components.push(component);
172 }
173 }
174 }
175 components.into_iter().collect::<PathBuf>()
176 } else {
177 path
178 }
179 }
180}
181
182#[cfg(test)]
183mod test {
184 #[cfg(windows)]
185 #[test]
186 fn test_path_clean() {
187 use super::*;
188
189 run_test("C:\\test\\./file.txt", "C:\\test\\file.txt");
190 run_test("C:\\test\\../other/file.txt", "C:\\other\\file.txt");
191 run_test("C:\\test\\../other\\file.txt", "C:\\other\\file.txt");
192
193 fn run_test(input: &str, expected: &str) {
194 assert_eq!(PathBuf::from(input).clean(), PathBuf::from(expected));
195 }
196 }
197}