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
23impl 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
58impl<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
222impl<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 ("/", "a", "/a"),
334 ("/a/b", "c", "/a/c"),
335 ("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 ("/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}