1use std::{hash::{Hash, Hasher}, marker::PhantomData, ops::Deref};
2
3use anyhow::{Result, bail};
4
5use super::LocAbleImpl;
6use crate::{loc::{LocAble, LocBuf, LocBufAble, StrandAbleImpl}, path::{AsPath, AsPathView, PathDyn}, scheme::SchemeKind, strand::AsStrandView};
7
8#[derive(Clone, Copy, Debug)]
9pub struct Loc<'p, P = &'p std::path::Path> {
10 pub(super) inner: P,
11 pub(super) uri: usize,
12 pub(super) urn: usize,
13 pub(super) _phantom: PhantomData<&'p ()>,
14}
15
16impl<'p, P> Default for Loc<'p, P>
17where
18 P: LocAble<'p> + LocAbleImpl<'p>,
19{
20 fn default() -> Self { Self { inner: P::empty(), uri: 0, urn: 0, _phantom: PhantomData } }
21}
22
23impl<'p, P> Deref for Loc<'p, P>
24where
25 P: LocAble<'p>,
26{
27 type Target = P;
28
29 fn deref(&self) -> &Self::Target { &self.inner }
30}
31
32impl<'p, P> AsPath for Loc<'p, P>
33where
34 P: LocAble<'p> + AsPath,
35{
36 fn as_path(&self) -> PathDyn<'_> { self.inner.as_path() }
37}
38
39impl AsRef<std::path::Path> for Loc<'_, &std::path::Path> {
41 fn as_ref(&self) -> &std::path::Path { self.inner }
42}
43
44impl<'p, P> Hash for Loc<'p, P>
46where
47 P: LocAble<'p> + Hash,
48{
49 fn hash<H: Hasher>(&self, state: &mut H) { self.inner.hash(state) }
50}
51
52impl<'p, P> From<Loc<'p, P>> for LocBuf<<P as LocAble<'p>>::Owned>
53where
54 P: LocAble<'p> + LocAbleImpl<'p>,
55 <P as LocAble<'p>>::Owned: LocBufAble,
56{
57 fn from(value: Loc<'p, P>) -> Self {
58 Self { inner: value.inner.to_path_buf(), uri: value.uri, urn: value.urn }
59 }
60}
61
62impl<'p, P> PartialEq for Loc<'p, P>
64where
65 P: LocAble<'p> + PartialEq,
66{
67 fn eq(&self, other: &Self) -> bool { self.inner == other.inner }
68}
69
70impl<'p, P> Eq for Loc<'p, P> where P: LocAble<'p> + Eq {}
71
72impl<'p, P> Loc<'p, P>
73where
74 P: LocAble<'p> + LocAbleImpl<'p>,
75{
76 #[inline]
77 pub fn as_inner(self) -> P { self.inner }
78
79 #[inline]
80 pub fn as_loc(self) -> Self { self }
81
82 pub fn bare<T>(path: T) -> Self
83 where
84 T: AsPathView<'p, P>,
85 {
86 let path = path.as_path_view();
87 let Some(name) = path.file_name() else {
88 let p = path.strip_prefix(P::empty()).unwrap();
89 return Self { inner: p, uri: 0, urn: 0, _phantom: PhantomData };
90 };
91
92 let name_len = name.len();
93 let prefix_len = unsafe {
94 name.as_encoded_bytes().as_ptr().offset_from_unsigned(path.as_encoded_bytes().as_ptr())
95 };
96
97 let bytes = &path.as_encoded_bytes()[..prefix_len + name_len];
98 Self {
99 inner: unsafe { P::from_encoded_bytes_unchecked(bytes) },
100 uri: name_len,
101 urn: name_len,
102 _phantom: PhantomData,
103 }
104 }
105
106 #[inline]
107 pub fn base(self) -> P {
108 unsafe {
109 P::from_encoded_bytes_unchecked(
110 self.inner.as_encoded_bytes().get_unchecked(..self.inner.len() - self.uri),
111 )
112 }
113 }
114
115 pub fn floated<'a, T, S>(path: T, base: S) -> Self
116 where
117 T: AsPathView<'p, P>,
118 S: AsStrandView<'a, P::Strand<'a>>,
119 {
120 let mut loc = Self::bare(path);
121 loc.uri = loc.inner.strip_prefix(base).expect("Loc must start with the given base").len();
122 loc
123 }
124
125 #[inline]
126 pub fn has_base(self) -> bool { self.inner.len() != self.uri }
127
128 #[inline]
129 pub fn has_trail(self) -> bool { self.inner.len() != self.urn }
130
131 #[inline]
132 pub fn is_empty(self) -> bool { self.inner.len() == 0 }
133
134 pub fn new<'a, T, S>(path: T, base: S, trail: S) -> Self
135 where
136 T: AsPathView<'p, P>,
137 S: AsStrandView<'a, P::Strand<'a>>,
138 {
139 let mut loc = Self::bare(path);
140 loc.uri = loc.inner.strip_prefix(base).expect("Loc must start with the given base").len();
141 loc.urn = loc.inner.strip_prefix(trail).expect("Loc must start with the given trail").len();
142 loc
143 }
144
145 #[inline]
146 pub fn parent(self) -> Option<P> {
147 self.inner.parent().filter(|p| !p.as_encoded_bytes().is_empty())
148 }
149
150 pub fn saturated<'a, T>(path: T, kind: SchemeKind) -> Self
151 where
152 T: AsPathView<'p, P>,
153 {
154 match kind {
155 SchemeKind::Regular => Self::bare(path),
156 SchemeKind::Search => Self::zeroed(path),
157 SchemeKind::Archive => Self::zeroed(path),
158 SchemeKind::Sftp => Self::bare(path),
159 }
160 }
161
162 #[inline]
163 pub fn trail(self) -> P {
164 unsafe {
165 P::from_encoded_bytes_unchecked(
166 self.inner.as_encoded_bytes().get_unchecked(..self.inner.len() - self.urn),
167 )
168 }
169 }
170
171 #[inline]
172 pub fn triple(self) -> (P, P, P) {
173 let len = self.inner.len();
174
175 let base = ..len - self.uri;
176 let rest = len - self.uri..len - self.urn;
177 let urn = len - self.urn..;
178
179 unsafe {
180 (
181 P::from_encoded_bytes_unchecked(self.inner.as_encoded_bytes().get_unchecked(base)),
182 P::from_encoded_bytes_unchecked(self.inner.as_encoded_bytes().get_unchecked(rest)),
183 P::from_encoded_bytes_unchecked(self.inner.as_encoded_bytes().get_unchecked(urn)),
184 )
185 }
186 }
187
188 #[inline]
189 pub fn uri(self) -> P {
190 unsafe {
191 P::from_encoded_bytes_unchecked(
192 self.inner.as_encoded_bytes().get_unchecked(self.inner.len() - self.uri..),
193 )
194 }
195 }
196
197 #[inline]
198 pub fn urn(self) -> P {
199 unsafe {
200 P::from_encoded_bytes_unchecked(
201 self.inner.as_encoded_bytes().get_unchecked(self.inner.len() - self.urn..),
202 )
203 }
204 }
205
206 pub fn with<T>(path: T, uri: usize, urn: usize) -> Result<Self>
207 where
208 T: AsPathView<'p, P>,
209 {
210 if urn > uri {
211 bail!("URN cannot be longer than URI");
212 }
213
214 let mut loc = Self::bare(path);
215 if uri == 0 {
216 (loc.uri, loc.urn) = (0, 0);
217 return Ok(loc);
218 } else if urn == 0 {
219 loc.urn = 0;
220 }
221
222 let mut it = loc.inner.components();
223 for i in 1..=uri {
224 if it.next_back().is_none() {
225 bail!("URI exceeds the entire URL");
226 }
227 if i == urn {
228 loc.urn = loc.strip_prefix(it.clone()).unwrap().len();
229 }
230 if i == uri {
231 loc.uri = loc.strip_prefix(it).unwrap().len();
232 break;
233 }
234 }
235 Ok(loc)
236 }
237
238 pub fn zeroed<T>(path: T) -> Self
239 where
240 T: AsPathView<'p, P>,
241 {
242 let mut loc = Self::bare(path);
243 (loc.uri, loc.urn) = (0, 0);
244 loc
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_with() -> Result<()> {
254 let cases = [
255 ("tmp/test.zip/foo/bar", 3, 2, "test.zip/foo/bar", "foo/bar"),
257 ("tmp/test.zip/foo/bar/", 3, 2, "test.zip/foo/bar", "foo/bar"),
258 ("/tmp/test.zip/foo/bar", 3, 2, "test.zip/foo/bar", "foo/bar"),
260 ("/tmp/test.zip/foo/bar/", 3, 2, "test.zip/foo/bar", "foo/bar"),
261 ("tmp/test.zip/foo/bar/../..", 5, 4, "test.zip/foo/bar/../..", "foo/bar/../.."),
263 ("tmp/test.zip/foo/bar/../../", 5, 4, "test.zip/foo/bar/../..", "foo/bar/../.."),
264 ("/tmp/test.zip/foo/bar/../..", 5, 4, "test.zip/foo/bar/../..", "foo/bar/../.."),
266 ("/tmp/test.zip/foo/bar/../../", 5, 4, "test.zip/foo/bar/../..", "foo/bar/../.."),
267 ];
268
269 for (path, uri, urn, expect_uri, expect_urn) in cases {
270 let loc = Loc::with(std::path::Path::new(path), uri, urn)?;
271 assert_eq!(loc.uri().to_str().unwrap(), expect_uri);
272 assert_eq!(loc.urn().to_str().unwrap(), expect_urn);
273
274 let loc = Loc::with(typed_path::UnixPath::new(path), uri, urn)?;
275 assert_eq!(loc.uri().to_str().unwrap(), expect_uri);
276 assert_eq!(loc.urn().to_str().unwrap(), expect_urn);
277 }
278
279 Ok(())
280 }
281}