1use std::borrow::Borrow;
2use std::borrow::Cow;
3use std::cmp::Ordering;
4use std::ffi::OsStr;
5use std::ffi::OsString;
6use std::fs::Metadata;
7use std::fs::ReadDir;
8use std::hash::Hash;
9use std::hash::Hasher;
10use std::io;
11use std::mem;
12use std::ops::Deref;
13use std::path::Component;
14use std::path::Components;
15use std::path::Path;
16use std::path::PathBuf;
17
18use super::error::MissingPrefixBufError;
19use super::error::MissingPrefixError;
20use super::error::ParentError;
21use super::imp;
22use super::PathExt;
23
24fn cow_path_into_base_path(path: Cow<'_, Path>) -> Cow<'_, BasePath> {
25 debug_assert!(imp::is_base(&path));
26
27 match path {
28 Cow::Borrowed(path) => {
29 Cow::Borrowed(BasePath::from_inner(path.as_os_str()))
30 }
31 Cow::Owned(path) => Cow::Owned(BasePathBuf(path)),
32 }
33}
34
35#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
49#[repr(transparent)]
50pub struct BasePath(pub(super) OsStr);
51
52impl BasePath {
53 pub(super) fn from_inner(path: &OsStr) -> &Self {
54 unsafe { mem::transmute(path) }
56 }
57
58 #[inline]
87 pub fn new<'a, P>(path: P) -> io::Result<Cow<'a, Self>>
88 where
89 P: Into<Cow<'a, Path>>,
90 {
91 let path = path.into();
92 match path {
93 Cow::Borrowed(path) => Self::try_new(path)
94 .map(Cow::Borrowed)
95 .or_else(|_| imp::to_base(path).map(Cow::Owned)),
96 Cow::Owned(path) => BasePathBuf::new(path).map(Cow::Owned),
97 }
98 }
99
100 #[inline]
126 pub fn try_new<P>(path: &P) -> Result<&Self, MissingPrefixError>
127 where
128 P: AsRef<Path> + ?Sized,
129 {
130 let path = path.as_ref();
131 if imp::is_base(path) {
132 Ok(Self::from_inner(path.as_os_str()))
133 } else {
134 Err(MissingPrefixError(()))
135 }
136 }
137
138 #[inline]
140 #[must_use]
141 pub fn as_os_str(&self) -> &OsStr {
142 &self.0
143 }
144
145 #[inline]
147 #[must_use]
148 pub fn as_path(&self) -> &Path {
149 Path::new(&self.0)
150 }
151
152 #[inline]
154 pub fn canonicalize(&self) -> io::Result<BasePathBuf> {
155 self.as_path().canonicalize().map(|base| {
156 debug_assert!(imp::is_base(&base));
157 BasePathBuf(base)
158 })
159 }
160
161 #[inline]
163 pub fn components(&self) -> Components<'_> {
164 self.as_path().components()
165 }
166
167 #[inline]
169 #[must_use]
170 pub fn ends_with<P>(&self, child: P) -> bool
171 where
172 P: AsRef<Path>,
173 {
174 self.as_path().ends_with(child)
175 }
176
177 #[inline]
179 #[must_use]
180 pub fn exists(&self) -> bool {
181 self.as_path().exists()
182 }
183
184 #[inline]
186 pub fn expand(&self) -> io::Result<Cow<'_, Self>> {
187 self.as_path().expand().map(cow_path_into_base_path)
188 }
189
190 #[inline]
192 #[must_use]
193 pub fn extension(&self) -> Option<&OsStr> {
194 self.as_path().extension()
195 }
196
197 #[inline]
199 #[must_use]
200 pub fn file_name(&self) -> Option<&OsStr> {
201 self.as_path().file_name()
202 }
203
204 #[inline]
206 #[must_use]
207 pub fn file_stem(&self) -> Option<&OsStr> {
208 self.as_path().file_stem()
209 }
210
211 #[inline]
213 #[must_use]
214 pub fn has_root(&self) -> bool {
215 self.as_path().has_root()
216 }
217
218 #[inline]
220 #[must_use]
221 pub fn is_absolute(&self) -> bool {
222 self.as_path().is_absolute()
223 }
224
225 #[inline]
227 #[must_use]
228 pub fn is_dir(&self) -> bool {
229 self.as_path().is_dir()
230 }
231
232 #[inline]
234 #[must_use]
235 pub fn is_file(&self) -> bool {
236 self.as_path().is_file()
237 }
238
239 #[inline]
241 #[must_use]
242 pub fn is_relative(&self) -> bool {
243 self.as_path().is_relative()
244 }
245
246 #[inline]
248 #[must_use]
249 pub fn is_symlink(&self) -> bool {
250 self.as_path().is_symlink()
251 }
252
253 #[inline]
283 pub fn join<P>(&self, path: P) -> BasePathBuf
284 where
285 P: AsRef<Path>,
286 {
287 let mut base = self.to_owned();
288 base.push(path);
289 base
290 }
291
292 #[cfg(feature = "localization")]
294 #[cfg_attr(normpath_docs_rs, doc(cfg(feature = "localization")))]
295 #[inline]
296 #[must_use]
297 pub fn localize_name(&self) -> Cow<'_, OsStr> {
298 self.as_path().localize_name()
299 }
300
301 #[inline]
303 pub fn metadata(&self) -> io::Result<Metadata> {
304 self.as_path().metadata()
305 }
306
307 #[inline]
309 pub fn normalize(&self) -> io::Result<BasePathBuf> {
310 self.as_path().normalize()
311 }
312
313 #[cfg(any(doc, windows))]
315 #[cfg_attr(normpath_docs_rs, doc(cfg(windows)))]
316 #[inline]
317 pub fn normalize_virtually(&self) -> io::Result<BasePathBuf> {
318 self.as_path().normalize_virtually()
319 }
320
321 fn check_parent(&self) -> Result<(), ParentError> {
322 self.components()
323 .next_back()
324 .filter(|x| matches!(x, Component::Normal(_) | Component::RootDir))
325 .map(|_| ())
326 .ok_or(ParentError(()))
327 }
328
329 #[inline]
363 pub fn parent(&self) -> Result<Option<&Self>, ParentError> {
364 self.check_parent().map(|()| self.parent_unchecked())
365 }
366
367 #[inline]
391 #[must_use]
392 pub fn parent_unchecked(&self) -> Option<&Self> {
393 self.as_path()
394 .parent()
395 .map(|x| Self::from_inner(x.as_os_str()))
396 }
397
398 #[inline]
400 pub fn read_dir(&self) -> io::Result<ReadDir> {
401 self.as_path().read_dir()
402 }
403
404 #[inline]
406 pub fn read_link(&self) -> io::Result<PathBuf> {
407 self.as_path().read_link()
408 }
409
410 #[inline]
412 pub fn shorten(&self) -> io::Result<Cow<'_, Self>> {
413 self.as_path().shorten().map(cow_path_into_base_path)
414 }
415
416 #[inline]
418 #[must_use]
419 pub fn starts_with<P>(&self, base: P) -> bool
420 where
421 P: AsRef<Path>,
422 {
423 self.as_path().starts_with(base)
424 }
425
426 #[inline]
428 pub fn symlink_metadata(&self) -> io::Result<Metadata> {
429 self.as_path().symlink_metadata()
430 }
431
432 #[inline]
434 pub fn try_exists(&self) -> io::Result<bool> {
435 self.as_path().try_exists()
436 }
437}
438
439impl AsRef<OsStr> for BasePath {
440 #[inline]
441 fn as_ref(&self) -> &OsStr {
442 &self.0
443 }
444}
445
446impl AsRef<Path> for BasePath {
447 #[inline]
448 fn as_ref(&self) -> &Path {
449 self.as_path()
450 }
451}
452
453impl AsRef<Self> for BasePath {
454 #[inline]
455 fn as_ref(&self) -> &Self {
456 self
457 }
458}
459
460impl<'a> From<&'a BasePath> for Cow<'a, BasePath> {
461 #[inline]
462 fn from(value: &'a BasePath) -> Self {
463 Cow::Borrowed(value)
464 }
465}
466
467impl PartialEq<Path> for BasePath {
468 #[inline]
469 fn eq(&self, other: &Path) -> bool {
470 &self.0 == other.as_os_str()
471 }
472}
473
474impl PartialEq<BasePath> for Path {
475 #[inline]
476 fn eq(&self, other: &BasePath) -> bool {
477 other == self
478 }
479}
480
481impl PartialOrd<Path> for BasePath {
482 #[inline]
483 fn partial_cmp(&self, other: &Path) -> Option<Ordering> {
484 self.0.partial_cmp(other.as_os_str())
485 }
486}
487
488impl PartialOrd<BasePath> for Path {
489 #[inline]
490 fn partial_cmp(&self, other: &BasePath) -> Option<Ordering> {
491 other.partial_cmp(self)
492 }
493}
494
495impl ToOwned for BasePath {
496 type Owned = BasePathBuf;
497
498 #[inline]
499 fn to_owned(&self) -> Self::Owned {
500 BasePathBuf(self.0.to_owned().into())
501 }
502}
503
504#[derive(Clone, Debug)]
510pub struct BasePathBuf(pub(super) PathBuf);
511
512impl BasePathBuf {
513 #[inline]
533 pub fn new<P>(path: P) -> io::Result<Self>
534 where
535 P: Into<PathBuf>,
536 {
537 Self::try_new(path).or_else(|x| imp::to_base(&x.0))
538 }
539
540 #[inline]
560 pub fn try_new<P>(path: P) -> Result<Self, MissingPrefixBufError>
561 where
562 P: Into<PathBuf>,
563 {
564 let path = path.into();
565 if imp::is_base(&path) {
566 Ok(Self(path))
567 } else {
568 Err(MissingPrefixBufError(path))
569 }
570 }
571
572 #[inline]
574 #[must_use]
575 pub fn into_os_string(self) -> OsString {
576 self.0.into_os_string()
577 }
578
579 #[inline]
581 #[must_use]
582 pub fn into_path_buf(self) -> PathBuf {
583 self.0
584 }
585
586 #[inline]
607 pub fn pop(&mut self) -> Result<bool, ParentError> {
608 self.check_parent().map(|()| self.pop_unchecked())
609 }
610
611 #[inline]
631 pub fn pop_unchecked(&mut self) -> bool {
632 self.0.pop()
633 }
634
635 #[inline]
651 pub fn push<P>(&mut self, path: P)
652 where
653 P: AsRef<Path>,
654 {
655 imp::push(self, path.as_ref());
656 }
657}
658
659impl AsRef<OsStr> for BasePathBuf {
660 #[inline]
661 fn as_ref(&self) -> &OsStr {
662 self.as_os_str()
663 }
664}
665
666impl AsRef<Path> for BasePathBuf {
667 #[inline]
668 fn as_ref(&self) -> &Path {
669 &self.0
670 }
671}
672
673impl AsRef<BasePath> for BasePathBuf {
674 #[inline]
675 fn as_ref(&self) -> &BasePath {
676 self
677 }
678}
679
680impl Borrow<BasePath> for BasePathBuf {
681 #[inline]
682 fn borrow(&self) -> &BasePath {
683 self
684 }
685}
686
687impl Deref for BasePathBuf {
688 type Target = BasePath;
689
690 #[inline]
691 fn deref(&self) -> &BasePath {
692 BasePath::from_inner(self.0.as_os_str())
693 }
694}
695
696impl Eq for BasePathBuf {}
697
698impl From<BasePathBuf> for Cow<'_, BasePath> {
699 #[inline]
700 fn from(value: BasePathBuf) -> Self {
701 Cow::Owned(value)
702 }
703}
704
705impl From<BasePathBuf> for OsString {
706 #[inline]
707 fn from(value: BasePathBuf) -> Self {
708 value.into_os_string()
709 }
710}
711
712impl From<BasePathBuf> for PathBuf {
713 #[inline]
714 fn from(value: BasePathBuf) -> Self {
715 value.0
716 }
717}
718
719impl Hash for BasePathBuf {
720 #[inline]
721 fn hash<H>(&self, state: &mut H)
722 where
723 H: Hasher,
724 {
725 (**self).hash(state);
726 }
727}
728
729impl Ord for BasePathBuf {
730 #[inline]
731 fn cmp(&self, other: &Self) -> Ordering {
732 (**self).cmp(&**other)
733 }
734}
735
736impl PartialEq for BasePathBuf {
737 #[inline]
738 fn eq(&self, other: &Self) -> bool {
739 **self == **other
740 }
741}
742
743#[expect(clippy::non_canonical_partial_ord_impl)]
744impl PartialOrd for BasePathBuf {
745 #[inline]
746 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
747 (**self).partial_cmp(&**other)
748 }
749}
750
751#[cfg(feature = "print_bytes")]
752#[cfg_attr(normpath_docs_rs, doc(cfg(feature = "print_bytes")))]
753mod print_bytes {
754 use print_bytes::ByteStr;
755 use print_bytes::ToBytes;
756 #[cfg(windows)]
757 use print_bytes::WideStr;
758
759 use super::BasePath;
760 use super::BasePathBuf;
761
762 impl ToBytes for BasePath {
763 #[inline]
764 fn to_bytes(&self) -> ByteStr<'_> {
765 self.0.to_bytes()
766 }
767
768 #[cfg(windows)]
769 #[inline]
770 fn to_wide(&self) -> Option<WideStr> {
771 self.0.to_wide()
772 }
773 }
774
775 impl ToBytes for BasePathBuf {
776 #[inline]
777 fn to_bytes(&self) -> ByteStr<'_> {
778 (**self).to_bytes()
779 }
780
781 #[cfg(windows)]
782 #[inline]
783 fn to_wide(&self) -> Option<WideStr> {
784 (**self).to_wide()
785 }
786 }
787}
788
789#[cfg(feature = "serde")]
790#[cfg_attr(normpath_docs_rs, doc(cfg(feature = "serde")))]
791mod serde {
792 use std::ffi::OsString;
793
794 use serde::Deserialize;
795 use serde::Deserializer;
796 use serde::Serialize;
797 use serde::Serializer;
798
799 use super::BasePath;
800 use super::BasePathBuf;
801
802 impl<'de> Deserialize<'de> for BasePathBuf {
803 #[inline]
804 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
805 where
806 D: Deserializer<'de>,
807 {
808 OsString::deserialize(deserializer).map(|x| Self(x.into()))
809 }
810 }
811
812 impl Serialize for BasePath {
813 #[inline]
814 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
815 where
816 S: Serializer,
817 {
818 serializer.serialize_newtype_struct("BasePath", &self.0)
819 }
820 }
821
822 impl Serialize for BasePathBuf {
823 #[inline]
824 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
825 where
826 S: Serializer,
827 {
828 serializer
829 .serialize_newtype_struct("BasePathBuf", self.as_os_str())
830 }
831 }
832}
833
834#[cfg(feature = "uniquote")]
835#[cfg_attr(normpath_docs_rs, doc(cfg(feature = "uniquote")))]
836mod uniquote {
837 use uniquote::Formatter;
838 use uniquote::Quote;
839 use uniquote::Result;
840
841 use super::BasePath;
842 use super::BasePathBuf;
843
844 impl Quote for BasePath {
845 #[inline]
846 fn escape(&self, f: &mut Formatter<'_>) -> Result {
847 self.0.escape(f)
848 }
849 }
850
851 impl Quote for BasePathBuf {
852 #[inline]
853 fn escape(&self, f: &mut Formatter<'_>) -> Result {
854 (**self).escape(f)
855 }
856 }
857}