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}