1use crate::Action;
2use typed_path::{
3 PathType, TypedPath, TypedPathBuf, WindowsComponent, WindowsEncoding, WindowsPath,
4 WindowsPrefix,
5};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum Component {
11 Extension,
12 Stem,
13 Prefix,
14 Name,
15 Parent,
16 Disk,
17 Nth(isize),
18 }
27
28impl TryFrom<&str> for Component {
30 type Error = ();
31
32 fn try_from(s: &str) -> Result<Self, Self::Error> {
33 use Component::*;
34 let comp = match s {
35 "ext" => Extension,
36 "stem" => Stem,
37 "prefix" => Prefix,
38 "name" => Name,
39 "parent" => Parent,
40 "disk" => Disk,
41 _ => Nth(s.parse::<isize>().map_err(|_| ())?),
42 };
43 Ok(comp)
44 }
45}
46
47pub fn arg_into_component(s: &str) -> Result<Component, String> {
48 use Component::*;
49 if let Ok(n) = s.parse::<isize>() {
50 Ok(Nth(n))
51 } else {
52 let component = match s {
53 "ext" => Extension,
54 "stem" => Stem,
55 "prefix" => Prefix,
56 "name" => Name,
57 "parent" => Parent,
58 "disk" => Disk,
59 _ => Err("invalid component")?,
60 };
61 Ok(component)
62 }
63}
64
65trait FilePrefix {
80 fn file_prefix(&self) -> Option<&[u8]>;
84}
85
86impl FilePrefix for TypedPath<'_> {
87 fn file_prefix(&self) -> Option<&[u8]> {
90 self.file_name()
91 .map(split_file_at_dot)
92 .map(|(before, _after)| before)
93 }
94}
95
96fn split_file_at_dot(file: &[u8]) -> (&[u8], Option<&[u8]>) {
97 let slice = file;
100 if slice == b".." {
101 return (file, None);
102 }
103
104 let i = match slice[1..].iter().position(|b| *b == b'.') {
105 Some(i) => i + 1,
106 None => return (file, None),
107 };
108 let before = &slice[..i];
109 let after = &slice[i + 1..];
110 (before, Some(after))
111}
112
113impl Component {
114 pub fn action(self, action: &Action, path: &TypedPath) -> Vec<u8> {
115 match action {
116 Action::Get => self.get(path),
117 Action::Set(s) => self.set(path, s),
118 Action::Replace(s) => self.replace(path, s),
119 Action::Delete => self.delete(path),
120 }
121 }
122
123 pub fn get(self, path: &TypedPath) -> Vec<u8> {
124 use Component::*;
125 match self {
126 Extension => path.extension().unwrap_or_default().into(),
127 Stem => path.file_stem().unwrap_or_default().into(),
128 Prefix => path.file_prefix().unwrap_or_default().into(),
129 Name => path.file_name().unwrap_or_default().into(),
130 Parent => path
131 .parent()
132 .map(|p| p.as_bytes().to_vec())
133 .unwrap_or_default(),
134 Disk => match path {
135 TypedPath::Unix(_) => "".into(),
136 TypedPath::Windows(w) => match w.components().next() {
137 Some(WindowsComponent::Prefix(prefix)) => match prefix.kind() {
138 WindowsPrefix::Disk(disk) => [disk].into(),
139 _ => "".into(),
140 },
141 _ => "".into(),
142 },
143 },
144 Nth(n) => {
145 let num_components: usize = path.components().count();
146 let index: usize = if n >= 0 {
147 let positive: usize = n.try_into().unwrap();
148 positive
149 } else {
150 let positive: usize = (-n).try_into().unwrap();
151 if positive > num_components {
152 return Vec::new();
154 }
155 num_components - positive
156 };
157 path.components()
158 .nth(index)
159 .map(|c| c.as_bytes().to_vec())
160 .unwrap_or_default()
161 }
162 }
163 }
164
165 pub fn has(self, path: &TypedPath) -> bool {
166 !self.get(path).is_empty()
167 }
168
169 pub fn set(self, path: &TypedPath, value: &[u8]) -> Vec<u8> {
170 use Component::*;
171 match self {
172 Extension => path.with_extension(value).into_vec(),
173 Stem => {
174 if let Some(ext) = path.extension() {
175 let name = [value, b".", ext].concat();
176 path.with_file_name(name).into_vec()
177 } else {
178 path.with_file_name(value).into_vec()
179 }
180 }
181 Prefix => {
182 let after: &[u8] = path
183 .file_name()
184 .map(split_file_at_dot)
185 .and_then(|(_, after)| after)
186 .unwrap_or_default();
187
188 if let Some(parent) = path.parent() {
189 let name = if !after.is_empty() {
190 [value, b".", after].concat()
191 } else {
192 value.to_vec()
193 };
194 parent.join(name).into_vec()
195 } else {
196 let new_path = if path.is_unix() {
197 TypedPath::new(value, PathType::Unix)
198 } else {
199 TypedPath::new(value, PathType::Windows)
200 };
201 new_path.join(after).into_vec()
202 }
203 }
204 Name => path.with_file_name(value).into_vec(),
205 Parent => {
206 let new_parent = match path {
207 TypedPath::Unix(_) => TypedPath::new(value, PathType::Unix),
208 TypedPath::Windows(_) => TypedPath::new(value, PathType::Windows),
209 };
210 new_parent
211 .join(path.file_name().unwrap_or_default())
212 .into_vec()
213 }
214 Disk => match path {
215 TypedPath::Unix(_) => path.to_path_buf().into_vec(),
216 TypedPath::Windows(w) => {
217 let mut original = w.components();
218 let mut new = original.clone();
219 let has_prefix = match new.next() {
220 Some(WindowsComponent::Prefix(prefix)) => match prefix.kind() {
221 WindowsPrefix::Disk(_) => true,
222 _ => false,
223 },
224 _ => false,
225 };
226
227 let no_disk: &typed_path::Path<WindowsEncoding> = if has_prefix {
228 original.next(); original.as_path()
230 } else {
231 original.as_path()
232 };
233
234 if value.len() == 0 {
235 return original
236 .as_path::<WindowsEncoding>()
237 .to_path_buf()
238 .into_vec();
239 }
240
241 let disk_str = format!(r"{}:", String::from_utf8(vec![value[0]]).unwrap());
246 let disk_path = WindowsPath::new(&disk_str);
247 let mut new_path = disk_path.to_path_buf();
248 new_path.push(no_disk);
249
250 new_path.into()
251 }
252 },
253 Nth(n) => {
254 let num_components: usize = path.components().count();
258 let index: usize = if n >= 0 {
259 let positive: usize = n.try_into().unwrap();
260 positive
261 } else {
262 let positive: usize = (-n).try_into().unwrap();
263 if positive > num_components {
264 return Vec::new();
266 }
267 num_components - positive
268 };
269
270 let num_components = path.components().count();
272 if num_components == index {
273 return path.join(value).into_vec();
274 }
275
276 path.components()
280 .enumerate()
281 .map(|(i, c)| {
282 if i == index {
283 TypedPathBuf::from(value)
284 } else {
285 TypedPathBuf::from(c.as_bytes())
286 }
287 })
288 .reduce(|a, b| a.join(b))
289 .map(|p| p.into_vec())
290 .unwrap_or_default()
291 }
292 }
293 }
294
295 pub fn replace(self, path: &TypedPath, value: &[u8]) -> Vec<u8> {
296 if self.has(path) {
298 self.set(path, value)
299 } else {
300 path.to_path_buf().into_vec()
301 }
302 }
303
304 pub fn delete(&self, path: &TypedPath) -> Vec<u8> {
305 use Component::*;
306 match self {
307 Stem => {
308 if let Some(ext) = path.extension() {
309 path.with_file_name(ext).into_vec()
310 } else {
311 path.with_file_name("").into_vec()
312 }
313 }
314 Prefix => {
315 if path == &TypedPath::derive("/") {
317 return path.to_path_buf().into_vec();
318 }
319
320 let after: &[u8] = path
321 .file_name()
322 .map(split_file_at_dot)
323 .and_then(|(_, after)| after)
324 .unwrap_or_default();
325
326 if let Some(parent) = path.parent() {
327 parent.join(after).into_vec()
328 } else {
329 let new_path = if path.is_unix() {
330 TypedPath::new(after, PathType::Unix)
331 } else {
332 TypedPath::new(after, PathType::Windows)
333 };
334 new_path.to_path_buf().into_vec()
335 }
336 }
337 Name => path.with_file_name("").into_vec(),
338 _ => self.replace(path, b""),
339 }
340 }
341}