1
2use std::fmt::Display;
3use std::io::Write;
4use std::path::Path;
5use std::fs::{File, create_dir};
6
7use proc_macro2::TokenStream;
8
9#[derive(Debug, Eq, PartialEq)]
11pub struct FileStructure {
12 path: String,
13 kind: FileType,
14}
15
16#[derive(Debug, Eq, PartialEq)]
21pub enum FileType {
22 String(String),
24 Blob(Box<[u8]>),
26 Dir(Vec<FileStructure>)
28}
29
30impl Default for FileStructure {
31 fn default() -> Self {
32 Self::new(String::new(), FileType::Dir(vec![]))
33 }
34}
35
36impl FileStructure {
37 #[must_use]
39 pub const fn new(path: String, kind: FileType) -> Self {
40 Self { path, kind }
41 }
42
43 #[must_use]
45 pub const fn new_dir(name: String) -> Self {
46 Self { path: name, kind: FileType::Dir(Vec::new()) }
47 }
48
49 fn split_path(path: &str) -> (Vec<String>, bool) {
63 let mut iter = path.split('/');
64 let mut ret = Vec::new();
65 let is_root = iter.next().map_or(true, |s|
66 if s.is_empty() {
67 true
68 } else {
69 ret.push(s.to_string());
70 false
71 });
72 for sub in iter {
73 if !sub.is_empty() {
74 ret.push(sub.to_string());
75 }
76 }
77 (ret, is_root)
78 }
79
80 fn _from_path(mut components: Vec<String>, is_root: bool) -> Self {
81 components.pop().map_or_else(|| Self::new(String::new(), FileType::Dir(vec![])), |leaf_path| {
82 let mut curr = Self::new(leaf_path, FileType::Dir(vec![]));
83 while let Some(sub_dir) = components.pop() {
84 let new = Self::new(sub_dir, FileType::Dir(vec![curr]));
85 curr = new;
86 }
87 if is_root {
88 Self::new(String::new(), FileType::Dir(vec![curr]))
89 } else {
90 curr
91 }
92 })
93 }
94
95 #[must_use]
117 pub fn from_path(path: &str) -> Self {
118 let (components, is_root) = Self::split_path(path);
119 Self::_from_path(components, is_root)
120 }
121
122 pub fn write_to_disk(&self, root: &Path) -> std::io::Result<()> {
132 let path = root.join(&self.path);
133 match &self.kind {
134 FileType::String(s) => File::create(path)?.write_all(s.as_bytes())?,
135 FileType::Blob(b) => File::create(path)?.write_all(b)?,
136 FileType::Dir(fs) => {
137 if !path.exists() {
138 create_dir(path.clone())?;
139 }
140 for f in fs {
141 f.write_to_disk(&path)?;
142 }
143 },
144 }
145 Ok(())
146 }
147
148 #[must_use]
152 pub fn get(&self, path: &str) -> Option<&Self> {
153 let (components, is_root) = Self::split_path(path);
154 let stop = components.len() - 1;
155 let mut iter = components.iter().enumerate();
156 if let Some((_, component)) = iter.next() { if &self.path != component {
158 return None
159 }
160 } else if self.path.is_empty() && is_root {
161 return Some(self)
162 } else {
163 return None
164 };
165 let mut curr = self;
166 for (i, component) in iter {
167 if let FileType::Dir(files) = &curr.kind {
168 if let Some(next) = files.iter().find(|file| &file.path == component) {
169 curr = next;
170 } else {
171 return None;
172 }
173 } else if i < stop { return None;
175 }
176 }
177 Some(curr)
178 }
179
180 #[must_use]
184 pub fn get_mut(&mut self, path: &str) -> Option<&mut Self> {
185 let (components, is_root) = Self::split_path(path);
186 let stop = components.len() - 1;
187 let mut iter = components.iter().enumerate();
188 if let Some((_, component)) = iter.next() { if &self.path != component {
190 return None
191 }
192 } else if self.path.is_empty() && is_root {
193 return Some(self)
194 } else {
195 return None
196 };
197 let mut curr = self;
198 for (i, component) in iter {
199 if let FileType::Dir(ref mut files) = curr.kind {
200 if let Some(next) = files.iter_mut().find(|file| &file.path == component) {
201 curr = next;
202 } else {
203 return None;
204 }
205 } else if i < stop { return None;
207 } else {
208 break; }
210 }
211 Some(curr)
212 }
213
214 fn _insert_dir(&mut self, components: Vec<String>) -> Option<&mut Self> {
215 let mut iter = components.into_iter();
216 let mut curr = if let Some(component) = iter.next() {
217 if self.path.is_empty() {
218 if let FileType::Dir(ref mut files) = self.kind {
219 if let Some(index) = files.iter().position(|file| file.path == component) { &mut files[index]
221 } else {
222 let subdir = Self::new_dir(component);
223 files.push(subdir);
224 files.last_mut()?
225 }
226 } else { return None;
228 }
229 } else if self.path != component {
230 if let FileType::Dir(ref mut files) = self.kind { let subdir = Self::new_dir(component);
232 files.push(subdir);
233 files.last_mut()?
234 } else {
235 return None;
236 }
237 } else {
238 self
239 }
240 } else {
241 return Some(self)
242 };
243 for component in iter {
244 if let FileType::Dir(ref mut files) = curr.kind {
245 if let Some(index) = files.iter().position(|file| file.path == component) { curr = &mut files[index];
247 } else {
248 let subdir = Self::new_dir(component); files.push(subdir);
250 curr = files.last_mut()?;
251 }
252 } else { return None;
254 }
255 }
256 Some(curr)
257 }
258
259 fn insert_data(&mut self, path: &str, data: FileType) -> Option<&mut Self> {
260 let (mut components, _) = Self::split_path(path);
261 let filename = components.pop()?;
262 let dir = self._insert_dir(components)?;
263 dir.insert(Self::new(filename, data))
264 }
265
266 pub fn insert_dir(&mut self, path: &str) -> Option<&mut Self> {
270 let (components, _) = Self::split_path(path);
271 self._insert_dir(components)
272 }
273
274 pub fn insert_blob(&mut self, path: &str, blob: Box<[u8]>) -> Option<&mut Self> {
278 self.insert_data(path, FileType::Blob(blob))
279 }
280
281 pub fn insert_string(&mut self, path: &str, data: String) -> Option<&mut Self> {
285 self.insert_data(path, FileType::String(data))
286 }
287
288 pub fn insert_tokenstream(&mut self, path: &str, data: TokenStream, pretty: bool) -> syn::parse::Result<Option<&mut Self>> {
297 let data = if pretty {
298 let ast: syn::File = syn::parse2(data)?;
299 FileType::String(prettyplease::unparse(&ast))
300 } else {
301 FileType::String(data.to_string())
302 };
303 self.insert_data(path, data).map_or(Ok(None), |dir| Ok(Some(dir)))
304 }
305
306 pub fn insert(&mut self, child: Self) -> Option<&mut Self> {
310 if let FileType::Dir(ref mut files) = self.kind {
311 files.push(child);
312 Some(files.last_mut()?)
313 } else {
314 None
315 }
316 }
317
318 #[must_use]
320 pub fn get_path(&self) -> &str {
321 &self.path
322 }
323
324 #[must_use]
328 pub fn len(&self) -> usize {
329 match &self.kind {
330 FileType::Dir(files) => {
331 let mut sum = 0;
332 for file in files {
333 sum += file.len();
334 }
335 sum
336 },
337 _ => 1
338 }
339 }
340
341 #[must_use]
343 pub fn is_empty(&self) -> bool {
344 self.len() == 0
345 }
346
347 #[must_use]
351 pub fn iter(&self) -> FileIterator {
352 FileIterator { stack: vec![self] }
353 }
354
355}
356
357pub struct FileIterator<'a> {
358 stack: Vec<&'a FileStructure>
359}
360
361pub struct OwnedFileIterator {
362 stack: Vec<FileStructure>
363}
364
365impl<'a> Iterator for FileIterator<'a> {
366 type Item = &'a FileStructure;
367
368 fn next(&mut self) -> Option<Self::Item> {
369 let cont = self.stack.pop()?;
370 if let FileType::Dir(subdirs) = &cont.kind {
371 self.stack.extend(subdirs.iter());
372 }
373 Some(cont)
374 }
375}
376
377impl Iterator for OwnedFileIterator {
378 type Item = FileStructure;
379
380 fn next(&mut self) -> Option<Self::Item> {
381 let mut cont = self.stack.pop()?;
382 if let FileType::Dir(ref mut subdirs) = cont.kind {
383 self.stack.extend(std::mem::take(subdirs));
384 }
385 Some(cont)
386 }
387}
388
389impl IntoIterator for FileStructure {
390 type Item = Self;
391
392 type IntoIter = OwnedFileIterator;
393
394 fn into_iter(self) -> Self::IntoIter {
395 OwnedFileIterator { stack: vec![self] }
396 }
397}
398
399impl<'a> IntoIterator for &'a FileStructure {
400 type Item = Self;
401
402 type IntoIter = FileIterator<'a>;
403
404 fn into_iter(self) -> Self::IntoIter {
405 self.iter()
406 }
407}
408
409impl Display for FileStructure {
410 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
411 match &self.kind {
412 FileType::Dir(files) => {
413 writeln!(f, "{}: [", self.path)?;
414 for file in files {
415 writeln!(f, "{file},")?;
416 }
417 writeln!(f, "]")
418 },
419 _ => write!(f, "{}", self.path)
420 }
421 }
422}
423
424#[cfg(test)]
425mod test {
426 use super::{FileStructure, FileType};
427
428 #[test]
429 fn test_get() {
430 let structure = FileStructure {
431 path: "some".to_string(),
432 kind: FileType::Dir(vec![
433 FileStructure {
434 path: "dir".to_string(),
435 kind: FileType::Dir(vec![
436 FileStructure {
437 path: "file.txt".to_string(),
438 kind: FileType::Blob([].into()),
439 },
440 FileStructure {
441 path: "other.txt".to_string(),
442 kind: FileType::Blob([].into()),
443 }
444 ]),
445 },
446 FileStructure {
447 path: "other".to_string(),
448 kind: crate::FileType::Dir(vec![
449 FileStructure {
450 path: "file.txt".to_string(),
451 kind: FileType::Blob([].into()),
452 }
453 ]),
454 }
455 ]),
456 };
457 let target = FileStructure {
458 path: "file.txt".to_string(),
459 kind: FileType::Blob([].into()),
460 };
461 assert_eq!(structure.get("some/dir/file.txt"), Some(&target));
462 assert_eq!(structure.get("some/dir/none.txt"), None);
463 assert_eq!(structure.get("nothing"), None);
464 }
465
466 #[test]
467 fn test_insert() {
468 let mut structure = FileStructure::from_path("some");
469 structure.insert_dir("some/dir/");
470 structure.insert_dir("some/other");
471 structure.insert_blob("some/dir/file.txt", [].into()).unwrap();
472 structure.insert_blob("some/dir/other.txt", [].into()).unwrap();
473 structure.insert_blob("some/other/file.txt", [].into()).unwrap();
474 let expected = FileStructure {
475 path: "some".to_string(),
476 kind: FileType::Dir(vec![
477 FileStructure {
478 path: "dir".to_string(),
479 kind: FileType::Dir(vec![
480 FileStructure {
481 path: "file.txt".to_string(),
482 kind: FileType::Blob([].into()),
483 },
484 FileStructure {
485 path: "other.txt".to_string(),
486 kind: FileType::Blob([].into()),
487 }
488 ]),
489 },
490 FileStructure {
491 path: "other".to_string(),
492 kind: crate::FileType::Dir(vec![
493 FileStructure {
494 path: "file.txt".to_string(),
495 kind: FileType::Blob([].into()),
496 }
497 ]),
498 }
499 ]),
500 };
501 assert_eq!(structure, expected);
502 }
503
504 #[test]
505 fn test_insert_empty() {
506 let mut structure = FileStructure::default();
507 structure.insert_dir("some/dir/");
508 structure.insert_dir("some/other");
509 structure.insert_blob("some/dir/file.txt", [].into()).unwrap();
510 structure.insert_blob("some/dir/other.txt", [].into()).unwrap();
511 structure.insert_blob("some/other/file.txt", [].into()).unwrap();
512 let expected = FileStructure {
513 path: String::new(),
514 kind: FileType::Dir(vec![
515 FileStructure {
516 path: "some".to_string(),
517 kind: FileType::Dir(vec![
518 FileStructure {
519 path: "dir".to_string(),
520 kind: FileType::Dir(vec![
521 FileStructure {
522 path: "file.txt".to_string(),
523 kind: FileType::Blob([].into()),
524 },
525 FileStructure {
526 path: "other.txt".to_string(),
527 kind: FileType::Blob([].into()),
528 }
529 ]),
530 },
531 FileStructure {
532 path: "other".to_string(),
533 kind: crate::FileType::Dir(vec![
534 FileStructure {
535 path: "file.txt".to_string(),
536 kind: FileType::Blob([].into()),
537 }
538 ]),
539 }
540 ]),
541 }
542 ])
543 };
544 assert_eq!(structure, expected);
545 }
546}