1use std::fs;
2use std::path::PathBuf;
3use walkdir::WalkDir;
4
5#[derive(Debug, Clone)]
6pub struct FlattenDirectory {
7 root: PathBuf,
8}
9
10impl FlattenDirectory {
11 pub fn new(root: PathBuf) -> Self {
12 Self { root }
13 }
14
15 pub fn execute(&self) -> Result<(), std::io::Error> {
16 self.flatten()?;
17 self.remove_directories(&self.root)?;
18
19 Ok(())
20 }
21
22 fn flatten(&self) -> Result<(), std::io::Error> {
23 let files = WalkDir::new(&self.root)
24 .into_iter()
25 .filter_map(|e| e.ok())
26 .filter(|e| e.file_type().is_file());
27
28 for f in files {
29 let mut dest = self.root.join(f.file_name());
30 if dest.exists() {
32 let mut c = 0u128;
33 let p = f.path();
34 let filename = p.file_stem().unwrap_or_default().to_string_lossy();
35 let ext = p.extension().unwrap_or_default().to_string_lossy();
36 let dot = if ext == "" { "" } else { "." };
37 loop {
38 dest = self.root.join(format!("{filename}_{c}{dot}{ext}"));
39 if !dest.exists() {
40 break;
41 }
42 c += 1;
43 }
44 }
45
46 fs::rename(f.path(), dest)?;
47 }
48
49 Ok(())
50 }
51
52 fn remove_directories(&self, root: &PathBuf) -> Result<(), std::io::Error> {
53 for path in fs::read_dir(root)?
54 .into_iter()
55 .filter_map(|f| f.ok())
56 .filter(|f| f.path().is_dir())
57 .map(|f: fs::DirEntry| f.path())
58 {
59 self.remove_directories(&path)?;
60 fs::remove_dir(path)?;
61 }
62
63 Ok(())
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use fake::{Fake, Faker};
71
72 #[test]
73 fn test_default() {
74 let path = Faker.fake::<PathBuf>();
75 let fd = FlattenDirectory::new(path.clone());
76 assert_eq!(fd.root, path);
77 }
78}