Skip to main content

yazi_shared/path/
path.rs

1use std::{borrow::Cow, ffi::OsStr};
2
3use anyhow::Result;
4use hashbrown::Equivalent;
5
6use super::{RsplitOnceError, StartsWithError};
7use crate::{BytesExt, Utf8BytePredictor, path::{AsPath, Components, Display, EndsWithError, JoinError, PathBufDyn, PathDynError, PathKind, StripPrefixError, StripSuffixError}, strand::{AsStrand, Strand, StrandError}};
8
9#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
10pub enum PathDyn<'p> {
11	Os(&'p std::path::Path),
12	Unix(&'p typed_path::UnixPath),
13}
14
15impl<'a> From<&'a std::path::Path> for PathDyn<'a> {
16	fn from(value: &'a std::path::Path) -> Self { Self::Os(value) }
17}
18
19impl<'a> From<&'a typed_path::UnixPath> for PathDyn<'a> {
20	fn from(value: &'a typed_path::UnixPath) -> Self { Self::Unix(value) }
21}
22
23impl<'a> From<&'a PathBufDyn> for PathDyn<'a> {
24	fn from(value: &'a PathBufDyn) -> Self { value.as_path() }
25}
26
27impl PartialEq<PathBufDyn> for PathDyn<'_> {
28	fn eq(&self, other: &PathBufDyn) -> bool { *self == other.as_path() }
29}
30
31impl PartialEq<PathDyn<'_>> for &std::path::Path {
32	fn eq(&self, other: &PathDyn<'_>) -> bool { matches!(*other, PathDyn::Os(p) if p == *self) }
33}
34
35impl PartialEq<&std::path::Path> for PathDyn<'_> {
36	fn eq(&self, other: &&std::path::Path) -> bool { matches!(*self, PathDyn::Os(p) if p == *other) }
37}
38
39impl PartialEq<&str> for PathDyn<'_> {
40	fn eq(&self, other: &&str) -> bool {
41		match *self {
42			PathDyn::Os(p) => p == *other,
43			PathDyn::Unix(p) => p == typed_path::UnixPath::new(other),
44		}
45	}
46}
47
48impl Equivalent<PathBufDyn> for PathDyn<'_> {
49	fn equivalent(&self, key: &PathBufDyn) -> bool { *self == key.as_path() }
50}
51
52impl<'p> PathDyn<'p> {
53	#[inline]
54	pub fn as_os(self) -> Result<&'p std::path::Path, PathDynError> {
55		match self {
56			Self::Os(p) => Ok(p),
57			Self::Unix(_) => Err(PathDynError::AsOs),
58		}
59	}
60
61	#[inline]
62	pub fn as_unix(self) -> Result<&'p typed_path::UnixPath, PathDynError> {
63		match self {
64			Self::Os(_) => Err(PathDynError::AsUnix),
65			Self::Unix(p) => Ok(p),
66		}
67	}
68
69	pub fn components(self) -> Components<'p> {
70		match self {
71			Self::Os(p) => Components::Os(p.components()),
72			Self::Unix(p) => Components::Unix(p.components()),
73		}
74	}
75
76	pub fn display(self) -> Display<'p> { Display(self) }
77
78	pub fn encoded_bytes(self) -> &'p [u8] {
79		match self {
80			Self::Os(p) => p.as_os_str().as_encoded_bytes(),
81			Self::Unix(p) => p.as_bytes(),
82		}
83	}
84
85	pub fn ext(self) -> Option<Strand<'p>> {
86		Some(match self {
87			Self::Os(p) => p.extension()?.into(),
88			Self::Unix(p) => p.extension()?.into(),
89		})
90	}
91
92	#[inline]
93	pub unsafe fn from_encoded_bytes<K>(kind: K, bytes: &'p [u8]) -> Self
94	where
95		K: Into<PathKind>,
96	{
97		match kind.into() {
98			PathKind::Os => Self::Os(unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }.as_ref()),
99			PathKind::Unix => Self::Unix(typed_path::UnixPath::new(bytes)),
100		}
101	}
102
103	pub fn has_root(self) -> bool {
104		match self {
105			Self::Os(p) => p.has_root(),
106			Self::Unix(p) => p.has_root(),
107		}
108	}
109
110	pub fn is_absolute(self) -> bool {
111		match self {
112			Self::Os(p) => p.is_absolute(),
113			Self::Unix(p) => p.is_absolute(),
114		}
115	}
116
117	pub fn is_empty(self) -> bool { self.encoded_bytes().is_empty() }
118
119	#[cfg(unix)]
120	pub fn is_hidden(self) -> bool {
121		self.name().is_some_and(|n| n.encoded_bytes().first() == Some(&b'.'))
122	}
123
124	pub fn kind(self) -> PathKind {
125		match self {
126			Self::Os(_) => PathKind::Os,
127			Self::Unix(_) => PathKind::Unix,
128		}
129	}
130
131	pub fn len(self) -> usize { self.encoded_bytes().len() }
132
133	pub fn name(self) -> Option<Strand<'p>> {
134		Some(match self {
135			Self::Os(p) => p.file_name()?.into(),
136			Self::Unix(p) => p.file_name()?.into(),
137		})
138	}
139
140	pub fn parent(self) -> Option<Self> {
141		Some(match self {
142			Self::Os(p) => Self::Os(p.parent().filter(|p| !p.as_os_str().is_empty())?),
143			Self::Unix(p) => Self::Unix(p.parent().filter(|p| !p.as_bytes().is_empty())?),
144		})
145	}
146
147	pub fn rsplit_pred<T>(self, pred: T) -> Option<(Self, Self)>
148	where
149		T: Utf8BytePredictor,
150	{
151		let (a, b) = self.encoded_bytes().rsplit_pred_once(pred)?;
152		Some(unsafe {
153			(Self::from_encoded_bytes(self.kind(), a), Self::from_encoded_bytes(self.kind(), b))
154		})
155	}
156
157	pub fn stem(self) -> Option<Strand<'p>> {
158		Some(match self {
159			Self::Os(p) => p.file_stem()?.into(),
160			Self::Unix(p) => p.file_stem()?.into(),
161		})
162	}
163
164	#[inline]
165	pub fn to_os_owned(self) -> Result<std::path::PathBuf, PathDynError> {
166		match self {
167			Self::Os(p) => Ok(p.to_owned()),
168			Self::Unix(_) => Err(PathDynError::AsOs),
169		}
170	}
171
172	pub fn to_owned(self) -> PathBufDyn {
173		match self {
174			Self::Os(p) => PathBufDyn::Os(p.to_owned()),
175			Self::Unix(p) => PathBufDyn::Unix(p.to_owned()),
176		}
177	}
178
179	pub fn to_str(self) -> Result<&'p str, std::str::Utf8Error> {
180		str::from_utf8(self.encoded_bytes())
181	}
182
183	pub fn to_string_lossy(self) -> Cow<'p, str> { String::from_utf8_lossy(self.encoded_bytes()) }
184
185	pub fn to_unix_owned(self) -> Result<typed_path::UnixPathBuf, PathDynError> {
186		match self {
187			Self::Os(_) => Err(PathDynError::AsUnix),
188			Self::Unix(p) => Ok(p.to_owned()),
189		}
190	}
191
192	pub fn try_ends_with<T>(self, child: T) -> Result<bool, EndsWithError>
193	where
194		T: AsStrand,
195	{
196		let s = child.as_strand();
197		Ok(match self {
198			Self::Os(p) => p.ends_with(s.as_os()?),
199			Self::Unix(p) => p.ends_with(s.encoded_bytes()),
200		})
201	}
202
203	pub fn try_join<T>(self, path: T) -> Result<PathBufDyn, JoinError>
204	where
205		T: AsStrand,
206	{
207		let s = path.as_strand();
208		Ok(match self {
209			Self::Os(p) => PathBufDyn::Os(p.join(s.as_os()?)),
210			Self::Unix(p) => PathBufDyn::Unix(p.join(s.encoded_bytes())),
211		})
212	}
213
214	pub fn try_rsplit_seq<T>(self, pat: T) -> Result<(Self, Self), RsplitOnceError>
215	where
216		T: AsStrand,
217	{
218		let pat = pat.as_strand();
219
220		let (a, b) = match self {
221			PathDyn::Os(p) => {
222				p.as_os_str().as_encoded_bytes().rsplit_seq_once(pat.as_os()?.as_encoded_bytes())
223			}
224			PathDyn::Unix(p) => p.as_bytes().rsplit_seq_once(pat.encoded_bytes()),
225		}
226		.ok_or(RsplitOnceError::NotFound)?;
227
228		Ok(unsafe {
229			(Self::from_encoded_bytes(self.kind(), a), Self::from_encoded_bytes(self.kind(), b))
230		})
231	}
232
233	pub fn try_starts_with<T>(self, base: T) -> Result<bool, StartsWithError>
234	where
235		T: AsStrand,
236	{
237		let s = base.as_strand();
238		Ok(match self {
239			Self::Os(p) => p.starts_with(s.as_os()?),
240			Self::Unix(p) => p.starts_with(s.encoded_bytes()),
241		})
242	}
243
244	pub fn try_strip_prefix<T>(self, base: T) -> Result<Self, StripPrefixError>
245	where
246		T: AsStrand,
247	{
248		let s = base.as_strand();
249		Ok(match self {
250			Self::Os(p) => Self::Os(p.strip_prefix(s.as_os()?)?),
251			Self::Unix(p) => Self::Unix(p.strip_prefix(s.encoded_bytes())?),
252		})
253	}
254
255	pub fn try_strip_suffix<T>(self, suffix: T) -> Result<Self, StripSuffixError>
256	where
257		T: AsStrand,
258	{
259		let s = suffix.as_strand();
260		let mut me_comps = self.components();
261		let mut suf_comps = match self.kind() {
262			PathKind::Os => Components::Os(s.as_os_path()?.components()),
263			PathKind::Unix => Components::Unix(s.as_unix_path().components()),
264		};
265
266		while let Some(next) = suf_comps.next_back() {
267			if me_comps.next_back() != Some(next) {
268				return Err(StripSuffixError::NotSuffix);
269			}
270		}
271
272		Ok(me_comps.path())
273	}
274
275	pub fn with<K, S>(kind: K, strand: &'p S) -> Result<Self, StrandError>
276	where
277		K: Into<PathKind>,
278		S: ?Sized + AsStrand,
279	{
280		let s = strand.as_strand();
281		Ok(match kind.into() {
282			PathKind::Os => Self::Os(s.as_os_path()?),
283			PathKind::Unix => Self::Unix(s.as_unix_path()),
284		})
285	}
286
287	pub fn with_str<K, S>(kind: K, s: &'p S) -> Self
288	where
289		K: Into<PathKind>,
290		S: ?Sized + AsRef<str>,
291	{
292		let s = s.as_ref();
293		match kind.into() {
294			PathKind::Os => Self::Os(s.as_ref()),
295			PathKind::Unix => Self::Unix(s.as_ref()),
296		}
297	}
298}