Skip to main content

yazi_shared/loc/
buf.rs

1use std::{cmp, ffi::OsStr, fmt::{self, Debug, Formatter}, hash::{Hash, Hasher}, marker::PhantomData, mem, ops::Deref};
2
3use anyhow::Result;
4
5use crate::{loc::{Loc, LocAble, LocAbleImpl, LocBufAble, LocBufAbleImpl}, path::{AsPath, AsPathView, PathDyn, SetNameError}, scheme::SchemeKind, strand::AsStrandView};
6
7#[derive(Clone, Default, Eq, PartialEq)]
8pub struct LocBuf<P = std::path::PathBuf> {
9	pub(super) inner: P,
10	pub(super) uri:   usize,
11	pub(super) urn:   usize,
12}
13
14impl<P> Deref for LocBuf<P>
15where
16	P: LocBufAble,
17{
18	type Target = P;
19
20	fn deref(&self) -> &Self::Target { &self.inner }
21}
22
23// FIXME: remove
24impl AsRef<std::path::Path> for LocBuf<std::path::PathBuf> {
25	fn as_ref(&self) -> &std::path::Path { self.inner.as_ref() }
26}
27
28impl<T> AsPath for LocBuf<T>
29where
30	T: LocBufAble + AsPath,
31{
32	fn as_path(&self) -> PathDyn<'_> { self.inner.as_path() }
33}
34
35impl<T> AsPath for &LocBuf<T>
36where
37	T: LocBufAble + AsPath,
38{
39	fn as_path(&self) -> PathDyn<'_> { self.inner.as_path() }
40}
41
42impl<P> Ord for LocBuf<P>
43where
44	P: LocBufAble + Ord,
45{
46	fn cmp(&self, other: &Self) -> cmp::Ordering { self.inner.cmp(&other.inner) }
47}
48
49impl<P> PartialOrd for LocBuf<P>
50where
51	P: LocBufAble + PartialOrd,
52{
53	fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
54		self.inner.partial_cmp(&other.inner)
55	}
56}
57
58// --- Hash
59impl<P> Hash for LocBuf<P>
60where
61	P: LocBufAble + LocBufAbleImpl,
62	for<'a> &'a P: AsPathView<'a, P::Borrowed<'a>>,
63{
64	fn hash<H: Hasher>(&self, state: &mut H) { self.as_loc().hash(state) }
65}
66
67impl<P> Debug for LocBuf<P>
68where
69	P: LocBufAble + LocBufAbleImpl + Debug,
70	for<'a> &'a P: AsPathView<'a, P::Borrowed<'a>>,
71{
72	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
73		f.debug_struct("LocBuf")
74			.field("path", &self.inner)
75			.field("uri", &self.uri())
76			.field("urn", &self.urn())
77			.finish()
78	}
79}
80
81impl<P> From<P> for LocBuf<P>
82where
83	P: LocBufAble + LocBufAbleImpl,
84	for<'a> &'a P: AsPathView<'a, P::Borrowed<'a>>,
85{
86	fn from(path: P) -> Self {
87		let Loc { inner, uri, urn, _phantom } = Loc::bare(&path);
88		let len = inner.len();
89
90		let mut bytes = path.into_encoded_bytes();
91		bytes.truncate(len);
92		Self { inner: unsafe { P::from_encoded_bytes_unchecked(bytes) }, uri, urn }
93	}
94}
95
96impl<T: ?Sized + AsRef<OsStr>> From<&T> for LocBuf<std::path::PathBuf> {
97	fn from(value: &T) -> Self { Self::from(std::path::PathBuf::from(value)) }
98}
99
100impl<P> LocBuf<P>
101where
102	P: LocBufAble + LocBufAbleImpl,
103	for<'a> &'a P: AsPathView<'a, P::Borrowed<'a>>,
104{
105	pub fn new<'a, S>(path: P, base: S, trail: S) -> Self
106	where
107		S: for<'b> AsStrandView<'a, <P::Borrowed<'b> as LocAble<'b>>::Strand<'a>>,
108	{
109		let loc = Self::from(path);
110		let Loc { inner, uri, urn, _phantom } = Loc::new(&loc.inner, base, trail);
111
112		debug_assert!(inner.as_encoded_bytes() == loc.inner.as_encoded_bytes());
113		Self { inner: loc.inner, uri, urn }
114	}
115
116	pub fn with(path: P, uri: usize, urn: usize) -> Result<Self>
117	where
118		for<'a> P::Borrowed<'a>: LocAble<'a>,
119	{
120		let loc = Self::from(path);
121		let Loc { inner, uri, urn, _phantom } = Loc::with(&loc.inner, uri, urn)?;
122
123		debug_assert!(inner.as_encoded_bytes() == loc.inner.as_encoded_bytes());
124		Ok(Self { inner: loc.inner, uri, urn })
125	}
126
127	pub fn zeroed<T>(path: T) -> Self
128	where
129		T: Into<P>,
130	{
131		let loc = Self::from(path.into());
132		let Loc { inner, uri, urn, _phantom } = Loc::zeroed(&loc.inner);
133
134		debug_assert!(inner.as_encoded_bytes() == loc.inner.as_encoded_bytes());
135		Self { inner: loc.inner, uri, urn }
136	}
137
138	pub fn floated<'a, S>(path: P, base: S) -> Self
139	where
140		S: for<'b> AsStrandView<'a, <P::Borrowed<'b> as LocAble<'b>>::Strand<'a>>,
141	{
142		let loc = Self::from(path);
143		let Loc { inner, uri, urn, _phantom } = Loc::floated(&loc.inner, base);
144
145		debug_assert!(inner.as_encoded_bytes() == loc.inner.as_encoded_bytes());
146		Self { inner: loc.inner, uri, urn }
147	}
148
149	pub fn saturated(path: P, kind: SchemeKind) -> Self {
150		let loc = Self::from(path);
151		let Loc { inner, uri, urn, _phantom } = Loc::saturated(&loc.inner, kind);
152
153		debug_assert!(inner.as_encoded_bytes() == loc.inner.as_encoded_bytes());
154		Self { inner: loc.inner, uri, urn }
155	}
156
157	#[inline]
158	pub fn as_loc<'a>(&'a self) -> Loc<'a, P::Borrowed<'a>> {
159		Loc {
160			inner:    self.inner.as_path_view(),
161			uri:      self.uri,
162			urn:      self.urn,
163			_phantom: PhantomData,
164		}
165	}
166
167	#[inline]
168	pub fn into_inner(self) -> P { self.inner }
169
170	pub fn try_set_name<'a, T>(&mut self, name: T) -> Result<(), SetNameError>
171	where
172		T: AsStrandView<'a, P::Strand<'a>>,
173	{
174		let old = self.inner.len();
175		self.mutate(|path| path.set_file_name(name));
176
177		let new = self.inner.len();
178		if new == old {
179			return Ok(());
180		}
181
182		if self.uri != 0 {
183			if new > old {
184				self.uri += new - old;
185			} else {
186				self.uri -= old - new;
187			}
188		}
189		if self.urn != 0 {
190			if new > old {
191				self.urn += new - old;
192			} else {
193				self.urn -= old - new;
194			}
195		}
196		Ok(())
197	}
198
199	#[inline]
200	pub fn rebase<'a, 'b>(&'a self, base: P::Borrowed<'b>) -> Self
201	where
202		'a: 'b,
203		for<'c> <P::Borrowed<'c> as LocAble<'c>>::Owned: Into<Self>,
204	{
205		let mut loc: Self = base.join(self.uri()).into();
206		(loc.uri, loc.urn) = (self.uri, self.urn);
207		loc
208	}
209
210	#[inline]
211	pub fn parent(&self) -> Option<P::Borrowed<'_>> { self.as_loc().parent() }
212
213	#[inline]
214	fn mutate<T, F: FnOnce(&mut P) -> T>(&mut self, f: F) -> T {
215		let mut inner = mem::take(&mut self.inner);
216		let result = f(&mut inner);
217		self.inner = Self::from(inner).inner;
218		result
219	}
220}
221
222// FIXME: macro
223impl<P> LocBuf<P>
224where
225	P: LocBufAble + LocBufAbleImpl,
226	for<'a> &'a P: AsPathView<'a, P::Borrowed<'a>>,
227{
228	#[inline]
229	pub fn uri(&self) -> P::Borrowed<'_> { self.as_loc().uri() }
230
231	#[inline]
232	pub fn urn(&self) -> P::Borrowed<'_> { self.as_loc().urn() }
233
234	#[inline]
235	pub fn base(&self) -> P::Borrowed<'_> { self.as_loc().base() }
236
237	#[inline]
238	pub fn has_base(&self) -> bool { self.as_loc().has_base() }
239
240	#[inline]
241	pub fn trail(&self) -> P::Borrowed<'_> { self.as_loc().trail() }
242
243	#[inline]
244	pub fn has_trail(&self) -> bool { self.as_loc().has_trail() }
245}
246
247impl LocBuf<std::path::PathBuf> {
248	pub const fn empty() -> Self { Self { inner: std::path::PathBuf::new(), uri: 0, urn: 0 } }
249}
250
251#[cfg(test)]
252mod tests {
253	use std::path::{Path, PathBuf};
254
255	use super::*;
256	use crate::url::{UrlBuf, UrlLike};
257
258	#[test]
259	fn test_new() {
260		let loc: LocBuf = Path::new("/").into();
261		assert_eq!(loc.uri().as_os_str(), OsStr::new(""));
262		assert_eq!(loc.urn().as_os_str(), OsStr::new(""));
263		assert_eq!(loc.file_name(), None);
264		assert_eq!(loc.base().as_os_str(), OsStr::new("/"));
265		assert_eq!(loc.trail().as_os_str(), OsStr::new("/"));
266
267		let loc: LocBuf = Path::new("/root").into();
268		assert_eq!(loc.uri().as_os_str(), OsStr::new("root"));
269		assert_eq!(loc.urn().as_os_str(), OsStr::new("root"));
270		assert_eq!(loc.file_name().unwrap(), OsStr::new("root"));
271		assert_eq!(loc.base().as_os_str(), OsStr::new("/"));
272		assert_eq!(loc.trail().as_os_str(), OsStr::new("/"));
273
274		let loc: LocBuf = Path::new("/root/code/foo/").into();
275		assert_eq!(loc.uri().as_os_str(), OsStr::new("foo"));
276		assert_eq!(loc.urn().as_os_str(), OsStr::new("foo"));
277		assert_eq!(loc.file_name().unwrap(), OsStr::new("foo"));
278		assert_eq!(loc.base().as_os_str(), OsStr::new("/root/code/"));
279		assert_eq!(loc.trail().as_os_str(), OsStr::new("/root/code/"));
280	}
281
282	#[test]
283	fn test_with() -> Result<()> {
284		let loc = LocBuf::<PathBuf>::with("/".into(), 0, 0)?;
285		assert_eq!(loc.uri().as_os_str(), OsStr::new(""));
286		assert_eq!(loc.urn().as_os_str(), OsStr::new(""));
287		assert_eq!(loc.file_name(), None);
288		assert_eq!(loc.base().as_os_str(), OsStr::new("/"));
289		assert_eq!(loc.trail().as_os_str(), OsStr::new("/"));
290
291		let loc = LocBuf::<PathBuf>::with("/root/code/".into(), 1, 1)?;
292		assert_eq!(loc.uri().as_os_str(), OsStr::new("code"));
293		assert_eq!(loc.urn().as_os_str(), OsStr::new("code"));
294		assert_eq!(loc.file_name().unwrap(), OsStr::new("code"));
295		assert_eq!(loc.base().as_os_str(), OsStr::new("/root/"));
296		assert_eq!(loc.trail().as_os_str(), OsStr::new("/root/"));
297
298		let loc = LocBuf::<PathBuf>::with("/root/code/foo//".into(), 2, 1)?;
299		assert_eq!(loc.uri().as_os_str(), OsStr::new("code/foo"));
300		assert_eq!(loc.urn().as_os_str(), OsStr::new("foo"));
301		assert_eq!(loc.file_name().unwrap(), OsStr::new("foo"));
302		assert_eq!(loc.base().as_os_str(), OsStr::new("/root/"));
303		assert_eq!(loc.trail().as_os_str(), OsStr::new("/root/code/"));
304
305		let loc = LocBuf::<PathBuf>::with("/root/code/foo//".into(), 2, 2)?;
306		assert_eq!(loc.uri().as_os_str(), OsStr::new("code/foo"));
307		assert_eq!(loc.urn().as_os_str(), OsStr::new("code/foo"));
308		assert_eq!(loc.file_name().unwrap(), OsStr::new("foo"));
309		assert_eq!(loc.base().as_os_str(), OsStr::new("/root/"));
310		assert_eq!(loc.trail().as_os_str(), OsStr::new("/root/"));
311
312		let loc = LocBuf::<PathBuf>::with("/root/code/foo//bar/".into(), 2, 2)?;
313		assert_eq!(loc.uri().as_os_str(), OsStr::new("foo//bar"));
314		assert_eq!(loc.urn().as_os_str(), OsStr::new("foo//bar"));
315		assert_eq!(loc.file_name().unwrap(), OsStr::new("bar"));
316		assert_eq!(loc.base().as_os_str(), OsStr::new("/root/code/"));
317		assert_eq!(loc.trail().as_os_str(), OsStr::new("/root/code/"));
318
319		let loc = LocBuf::<PathBuf>::with("/root/code/foo//bar/".into(), 3, 2)?;
320		assert_eq!(loc.uri().as_os_str(), OsStr::new("code/foo//bar"));
321		assert_eq!(loc.urn().as_os_str(), OsStr::new("foo//bar"));
322		assert_eq!(loc.file_name().unwrap(), OsStr::new("bar"));
323		assert_eq!(loc.base().as_os_str(), OsStr::new("/root/"));
324		assert_eq!(loc.trail().as_os_str(), OsStr::new("/root/code/"));
325		Ok(())
326	}
327
328	#[test]
329	fn test_set_name() -> Result<()> {
330		crate::init_tests();
331		let cases = [
332			// Regular
333			("/", "a", "/a"),
334			("/a/b", "c", "/a/c"),
335			// Archive
336			("archive:////", "a.zip", "archive:////a.zip"),
337			("archive:////a.zip/b", "c", "archive:////a.zip/c"),
338			("archive://:2//a.zip/b", "c", "archive://:2//a.zip/c"),
339			("archive://:2:1//a.zip/b", "c", "archive://:2:1//a.zip/c"),
340			// Empty
341			("/a", "", "/"),
342			("archive:////a.zip", "", "archive:////"),
343			("archive:////a.zip/b", "", "archive:////a.zip"),
344			("archive://:1:1//a.zip", "", "archive:////"),
345			("archive://:2//a.zip/b", "", "archive://:1//a.zip"),
346			("archive://:2:2//a.zip/b", "", "archive://:1:1//a.zip"),
347		];
348
349		for (input, name, expected) in cases {
350			let mut a: UrlBuf = input.parse()?;
351			let b: UrlBuf = expected.parse()?;
352			a.try_set_name(name).unwrap();
353			assert_eq!(
354				(a.name(), format!("{a:?}").replace(r"\", "/")),
355				(b.name(), expected.replace(r"\", "/"))
356			);
357		}
358
359		Ok(())
360	}
361}