1use bstr::ByteSlice;
2use logix_vfs::LogixVfs;
3
4use crate::{
5 error::{ParseError, PathError, Result, SourceSpan, Wanted},
6 parser::LogixParser,
7 token::{Literal, StrLit, Token},
8 type_trait::{LogixTypeDescriptor, LogixValueDescriptor, Value},
9 LogixType,
10};
11use std::{
12 borrow::Cow,
13 ffi::OsStr,
14 fmt,
15 path::{Path, PathBuf},
16};
17
18macro_rules! impl_path_type_traits {
19 ($name:ident, $doc:literal) => {
20 impl $name {
21 pub fn from_lit(lit: StrLit, span: &SourceSpan) -> Result<Self> {
22 lit.decode_str(span)?
23 .try_into()
24 .map_err(|error| ParseError::PathError {
25 span: span.clone(),
26 error,
27 })
28 }
29 }
30
31 impl<'a> TryFrom<&'a str> for $name {
32 type Error = PathError;
33
34 fn try_from(v: &'a str) -> Result<Self, PathError> {
35 PathBuf::from(v).try_into()
36 }
37 }
38
39 impl<'a> TryFrom<Cow<'a, str>> for $name {
40 type Error = PathError;
41
42 fn try_from(v: Cow<'a, str>) -> Result<Self, PathError> {
43 v.into_owned().try_into()
44 }
45 }
46
47 impl TryFrom<String> for $name {
48 type Error = PathError;
49
50 fn try_from(v: String) -> Result<Self, PathError> {
51 PathBuf::from(v).try_into()
52 }
53 }
54
55 impl<'a> TryFrom<&'a Path> for $name {
56 type Error = PathError;
57
58 fn try_from(v: &'a Path) -> Result<Self, PathError> {
59 v.to_path_buf().try_into()
60 }
61 }
62
63 impl<'a> TryFrom<Cow<'a, Path>> for $name {
64 type Error = PathError;
65
66 fn try_from(v: Cow<'a, Path>) -> Result<Self, PathError> {
67 v.into_owned().try_into()
68 }
69 }
70
71 impl std::ops::Deref for $name {
72 type Target = Path;
73
74 fn deref(&self) -> &Self::Target {
75 self.as_path()
76 }
77 }
78
79 impl AsRef<Path> for $name {
80 fn as_ref(&self) -> &Path {
81 self.as_path()
82 }
83 }
84
85 impl AsRef<::std::ffi::OsStr> for $name {
86 fn as_ref(&self) -> &std::ffi::OsStr {
87 self.as_path().as_ref()
88 }
89 }
90
91 impl PartialEq<PathBuf> for $name {
92 fn eq(&self, rhs: &PathBuf) -> bool {
93 self.as_path() == rhs
94 }
95 }
96
97 impl<'a> PartialEq<&'a Path> for $name {
98 fn eq(&self, rhs: &&'a Path) -> bool {
99 self.as_path() == *rhs
100 }
101 }
102
103 impl fmt::Debug for $name {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 fmt::Debug::fmt(&**self, f)
106 }
107 }
108
109 impl LogixType for $name {
110 fn descriptor() -> &'static LogixTypeDescriptor {
111 static DESC: LogixTypeDescriptor = LogixTypeDescriptor {
112 name: stringify!($name),
113 doc: $doc,
114 value: LogixValueDescriptor::Native,
115 };
116 &DESC
117 }
118
119 fn default_value() -> Option<Self> {
120 None
121 }
122
123 fn logix_parse<FS: LogixVfs>(p: &mut LogixParser<FS>) -> Result<Value<Self>> {
124 match p.next_token()? {
125 (span, Token::Literal(Literal::Str(value))) => Ok(Value {
126 value: Self::from_lit(value, &span)?,
127 span,
128 }),
129 (span, token) => Err(ParseError::UnexpectedToken {
130 span,
131 while_parsing: Self::descriptor().name,
132 wanted: Wanted::$name,
133 got_token: token.token_type_name(),
134 }),
135 }
136 }
137 }
138 };
139
140 ($name:ident, $doc:literal, ($($variant:ident),+), $error:ident) => {
141 impl_path_type_traits!($name, $doc);
142
143 impl $name {
144 pub fn as_path(&self) -> &Path {
145 &self.path
146 }
147
148 pub fn join(&self, path: impl AsRef<Path>) -> Result<Self, PathError> {
149 let path = path.as_ref();
150 if path.is_absolute() {
151 Err(PathError::JoinAbsolute)
152 } else {
153 Ok(Self { path: self.path.join(path) })
154 }
155 }
156
157 pub fn with_file_name(&self, name: impl AsRef<OsStr>) -> Self {
158 Self { path: self.path.with_file_name(name) }
159 }
160
161 pub fn with_extension(&self, ext: impl AsRef<OsStr>) -> Self {
162 Self { path: self.path.with_extension(ext) }
163 }
164 }
165
166 impl From<$name> for PathBuf {
167 fn from(v: $name) -> Self {
168 v.path
169 }
170 }
171
172 impl TryFrom<PathBuf> for $name {
173 type Error = PathError;
174
175 fn try_from(path: PathBuf) -> Result<Self, PathError> {
176 match ValidPath::try_from(path)? {
177 $(ValidPath::$variant(ret) => Ok(Self { path: PathBuf::from(ret) }),)+
178 _ => Err(PathError::$error),
179 }
180 }
181 }
182 };
183}
184
185#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
187pub enum ValidPath {
188 Full(FullPath),
189 Rel(RelPath),
190 Name(NameOnlyPath),
191}
192
193impl ValidPath {
194 pub fn as_path(&self) -> &Path {
195 let (ValidPath::Full(FullPath { path })
196 | ValidPath::Rel(RelPath { path })
197 | ValidPath::Name(NameOnlyPath { path })) = self;
198 path
199 }
200
201 pub fn join(&self, path: impl AsRef<Path>) -> Result<Self, PathError> {
202 match self {
203 Self::Full(v) => v.join(path).map(Self::Full),
204 Self::Rel(v) => v.join(path).map(Self::Rel),
205 Self::Name(v) => v.join(path).map(Self::Name),
206 }
207 }
208
209 pub fn with_file_name(&self, name: impl AsRef<OsStr>) -> Self {
210 match self {
211 Self::Full(v) => Self::Full(v.with_file_name(name)),
212 Self::Rel(v) => Self::Rel(v.with_file_name(name)),
213 Self::Name(v) => Self::Name(v.with_file_name(name)),
214 }
215 }
216
217 pub fn with_extension(&self, ext: impl AsRef<OsStr>) -> Self {
218 match self {
219 Self::Full(v) => Self::Full(v.with_extension(ext)),
220 Self::Rel(v) => Self::Rel(v.with_extension(ext)),
221 Self::Name(v) => Self::Name(v.with_extension(ext)),
222 }
223 }
224}
225
226impl From<ValidPath> for PathBuf {
227 fn from(v: ValidPath) -> Self {
228 let (ValidPath::Full(FullPath { path })
229 | ValidPath::Rel(RelPath { path })
230 | ValidPath::Name(NameOnlyPath { path })) = v;
231 path
232 }
233}
234
235impl TryFrom<PathBuf> for ValidPath {
236 type Error = PathError;
237
238 fn try_from(path: PathBuf) -> Result<Self, PathError> {
239 let raw_bytes = path.as_os_str().as_encoded_bytes();
240 if let Some(i) = raw_bytes.find_byteset(b"\n|\"'") {
242 Err(PathError::InvalidChar(char::from(raw_bytes[i])))
243 } else if path == PathBuf::new() {
244 Err(PathError::EmptyPath)
245 } else if path.is_absolute() {
246 Ok(Self::Full(FullPath { path }))
247 } else {
248 let mut it = path.components();
249 if matches!(it.next(), Some(std::path::Component::Normal(_))) && it.next().is_none() {
250 Ok(Self::Name(NameOnlyPath { path }))
251 } else {
252 Ok(Self::Rel(RelPath { path }))
253 }
254 }
255 }
256}
257
258impl_path_type_traits!(ValidPath, "A valid path");
259
260#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
262pub struct FullPath {
263 path: PathBuf,
264}
265
266impl_path_type_traits!(FullPath, "A full path", (Full), NotAbsolute);
267
268#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
270pub struct RelPath {
271 path: PathBuf,
272}
273
274impl_path_type_traits!(RelPath, "A relative path", (Rel, Name), NotRelative);
275
276#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
278pub struct NameOnlyPath {
279 path: PathBuf,
280}
281
282impl_path_type_traits!(NameOnlyPath, "A file or directory name", (Name), NotName);
283
284impl LogixType for PathBuf {
285 fn descriptor() -> &'static LogixTypeDescriptor {
286 static RET: LogixTypeDescriptor = LogixTypeDescriptor {
287 name: "path",
288 doc: "a valid path",
289 value: LogixValueDescriptor::Native,
290 };
291 &RET
292 }
293
294 fn default_value() -> Option<Self> {
295 None
296 }
297
298 fn logix_parse<FS: LogixVfs>(p: &mut LogixParser<FS>) -> Result<Value<Self>> {
299 Ok(ValidPath::logix_parse(p)?.map(|v| v.into()))
300 }
301}