better_path/
lib.rs

1pub trait PathFlavour: Sized {
2    type ParseError: std::fmt::Display;
3
4    fn new_path<S: AsRef<std::ffi::OsStr> + ?Sized>(
5        string: &S,
6    ) -> Result<&Path<Self>, Self::ParseError>;
7}
8
9pub trait PathInternals {
10    fn inner(&self) -> &std::path::Path;
11}
12
13pub trait Join<OperandFlavour: crate::PathFlavour>: PathInternals {
14    type ResultFlavour: crate::PathFlavour;
15    fn join_internal(&self, operand: &Path<OperandFlavour>) -> PathBuf<Self::ResultFlavour> {
16        PathBuf::wrap(self.inner().join(operand.inner()))
17    }
18}
19
20pub trait StartsWith<OperandFlavour: crate::PathFlavour>: PathInternals {
21    fn starts_with_internal(&self, operand: &Path<OperandFlavour>) -> bool {
22        self.inner().starts_with(operand.inner())
23    }
24}
25
26pub trait EndsWith<OperandFlavour: crate::PathFlavour>: PathInternals {
27    fn ends_with_internal(&self, operand: &Path<OperandFlavour>) -> bool {
28        self.inner().ends_with(operand.inner())
29    }
30}
31
32mod flavours {
33    use std::ffi::OsStr;
34
35    pub struct Absolute;
36    #[derive(Debug, thiserror::Error)]
37    #[error("path is relative")]
38    pub struct PathIsRelative;
39    impl super::PathFlavour for Absolute {
40        type ParseError = PathIsRelative;
41
42        fn new_path<S: AsRef<OsStr> + ?Sized>(
43            string: &S,
44        ) -> Result<&super::Path<Self>, Self::ParseError> {
45            let path = std::path::Path::new(string);
46            if path.is_absolute() {
47                Ok(super::Path::wrap(path))
48            } else {
49                Err(PathIsRelative)
50            }
51        }
52    }
53
54    pub struct Relative;
55    #[derive(Debug, thiserror::Error)]
56    #[error("path is absolute")]
57    pub struct PathIsAbsolute;
58    impl super::PathFlavour for Relative {
59        type ParseError = PathIsAbsolute;
60
61        fn new_path<S: AsRef<OsStr> + ?Sized>(
62            string: &S,
63        ) -> Result<&super::Path<Self>, Self::ParseError> {
64            let path = std::path::Path::new(string);
65            if path.is_relative() {
66                Ok(super::Path::wrap(path))
67            } else {
68                Err(PathIsAbsolute)
69            }
70        }
71    }
72
73    pub struct Unknown;
74    #[derive(Debug, thiserror::Error)]
75    #[error("impossible")]
76    pub enum Impossible {}
77    impl super::PathFlavour for Unknown {
78        type ParseError = Impossible;
79
80        fn new_path<S: AsRef<OsStr> + ?Sized>(
81            string: &S,
82        ) -> Result<&super::Path<Self>, Impossible> {
83            Ok(super::Path::wrap(std::path::Path::new(string)))
84        }
85    }
86}
87
88use std::ffi::OsStr;
89
90pub use flavours::*;
91
92mod absolute;
93mod ends_with;
94mod join;
95mod relative;
96mod starts_with;
97mod unknown;
98
99#[cfg(feature = "serde")]
100mod serde;
101
102#[cfg(test)]
103mod tests;
104
105#[derive(ref_cast::RefCast)]
106#[repr(transparent)]
107pub struct Path<PF: PathFlavour = Unknown>(std::marker::PhantomData<PF>, std::path::Path);
108
109impl<PF: PathFlavour> PathInternals for Path<PF> {
110    fn inner<'a>(&'a self) -> &'a std::path::Path {
111        &self.1
112    }
113}
114
115#[derive(Copy, Clone, Debug)]
116#[must_use = "iterators are lazy and do nothing unless consumed"]
117pub struct Ancestors<'a, PF: PathFlavour> {
118    next: Option<&'a std::path::Path>,
119    data: std::marker::PhantomData<PF>,
120}
121
122impl<'a, PF: PathFlavour + 'a> Iterator for Ancestors<'a, PF> {
123    type Item = &'a Path<PF>;
124
125    #[inline]
126    fn next(&mut self) -> Option<Self::Item> {
127        let next = self.next;
128        self.next = next.and_then(|path| path.parent());
129        next.map(Path::<PF>::wrap)
130    }
131}
132
133impl<PF: PathFlavour> Path<PF> {
134    fn wrap<'a>(path: &'a std::path::Path) -> &'a Path<PF> {
135        use ref_cast::RefCast;
136        Self::ref_cast(path)
137    }
138
139    pub fn new<S: AsRef<std::ffi::OsStr> + ?Sized>(string: &S) -> Result<&Self, PF::ParseError> {
140        PF::new_path(string)
141    }
142
143    pub fn ancestors(&self) -> Ancestors<'_, PF> {
144        Ancestors {
145            next: Some(self.inner()),
146            data: Default::default(),
147        }
148    }
149
150    pub fn components(&self) -> std::path::Components<'_> {
151        self.1.components()
152    }
153
154    pub fn display(&self) -> std::path::Display<'_> {
155        self.1.display()
156    }
157
158    pub fn extension(&self) -> Option<&OsStr> {
159        self.1.extension()
160    }
161
162    pub fn file_name(&self) -> Option<&OsStr> {
163        self.1.file_name()
164    }
165
166    pub fn file_stem(&self) -> Option<&OsStr> {
167        self.1.file_stem()
168    }
169
170    pub fn with_extension<S: AsRef<OsStr>>(&self, extension: S) -> PathBuf<PF> {
171        let mut buf = self.1.to_path_buf();
172        buf.set_extension(extension);
173        PathBuf::wrap(buf)
174    }
175
176    pub fn with_file_name<S: AsRef<OsStr>>(&self, file_name: S) -> PathBuf<PF> {
177        let mut buf = self.1.to_path_buf();
178        buf.set_file_name(file_name);
179        PathBuf::wrap(buf)
180    }
181
182    pub fn as_std(&self) -> &std::path::Path {
183        &self.1
184    }
185}
186
187impl<PF: PathFlavour> std::borrow::Borrow<Path<PF>> for PathBuf<PF> {
188    fn borrow(&self) -> &Path<PF> {
189        Path::wrap(self.path_buf.as_path())
190    }
191}
192
193impl<PF: PathFlavour> std::borrow::ToOwned for Path<PF> {
194    type Owned = PathBuf<PF>;
195
196    fn to_owned(&self) -> Self::Owned {
197        PathBuf::wrap(self.1.to_owned())
198    }
199}
200
201pub struct PathBuf<PF: PathFlavour> {
202    path_buf: std::path::PathBuf,
203    state: std::marker::PhantomData<PF>,
204}
205
206impl<PF: PathFlavour> PathBuf<PF> {
207    fn wrap(path_buf: std::path::PathBuf) -> PathBuf<PF> {
208        Self {
209            path_buf,
210            state: std::marker::PhantomData,
211        }
212    }
213}