1use std::{
2 collections::HashSet,
3 ffi::{OsStr, OsString},
4 fs, io,
5 path::{Path, PathBuf},
6};
7
8pub(crate) struct UniqueNameFinder {
9 names: HashSet<String>,
10 number_prefix: String,
11 number_suffix: String,
12}
13
14impl UniqueNameFinder {
15 pub fn new<P, S>(number_prefix: P, number_suffix: S) -> Self
16 where
17 P: Into<String>,
18 S: Into<String>,
19 {
20 Self {
21 names: HashSet::new(),
22 number_prefix: number_prefix.into(),
23 number_suffix: number_suffix.into(),
24 }
25 }
26
27 pub fn find<S: Into<String>>(&mut self, name: S) -> String {
28 let name = name.into();
29 if self.names.insert(name.clone()) {
30 return name;
31 }
32 let mut i = 2;
33 loop {
34 let name = format!("{}{}{}{}", name, self.number_prefix, i, self.number_suffix);
35 if self.names.insert(name.clone()) {
36 return name;
37 }
38 i += 1;
39 }
40 }
41}
42
43pub(crate) fn append_os_file_ext<P, E>(path: P, file_ext: E) -> OsString
44where
45 P: AsRef<OsStr>,
46 E: AsRef<OsStr>,
47{
48 let mut s = path.as_ref().to_os_string();
49 s.push(".");
50 s.push(file_ext);
51 s
52}
53
54pub fn copy_dir<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
58 let mut stack = vec![from.as_ref().to_owned()];
59 let output_root = to.as_ref().to_owned();
60 let input_root = from.as_ref().components().count();
61 while let Some(working_path) = stack.pop() {
62 let src: PathBuf = working_path.components().skip(input_root).collect();
63 let dest = output_root.join(&src);
64 fs::create_dir_all(&dest)?;
65 for entry in fs::read_dir(working_path)? {
66 let path = entry?.path();
67 if path.is_dir() {
68 stack.push(path);
69 } else {
70 let file_name = path.file_name().expect("invalid file name");
71 let dest_path = dest.join(file_name);
72 fs::copy(&path, &dest_path)?;
73 }
74 }
75 }
76 Ok(())
77}
78
79pub fn sanitize_file_name(file_name: &str) -> String {
80 use sanitize_filename::{sanitize_with_options, Options};
81
82 sanitize_with_options(
83 file_name,
84 Options {
85 replacement: "_",
86 truncate: false,
87 ..Options::default()
88 },
89 )
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn unique_name_finder_test() {
98 let mut finder = UniqueNameFinder::new(" (", ")");
99 assert_eq!(finder.find("foo"), "foo");
100 assert_eq!(finder.find("foo"), "foo (2)");
101 assert_eq!(finder.find("foo (2)"), "foo (2) (2)");
102 assert_eq!(finder.find("bar (2)"), "bar (2)");
103 assert_eq!(finder.find("bar"), "bar");
104 assert_eq!(finder.find("bar"), "bar (3)");
105 }
106}