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}