sweet_potator/
util.rs

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
54/// # Panics
55///
56/// Will panic if `Path::file_name` returns `None`
57pub 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}