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