libherokubuildpack/
fs.rs

1use std::fs;
2use std::path::Path;
3
4/// Moves all contents of a directory into another directory, leaving `src_dir` empty.
5///
6/// # Examples:
7/// ```no_run
8/// use libherokubuildpack::fs::move_directory_contents;
9/// use std::path::PathBuf;
10///
11/// move_directory_contents(PathBuf::from("foo"), PathBuf::from("bar")).unwrap();
12/// ```
13/// # Errors:
14/// This function will return an error in the following situations, but is not
15/// limited to just these cases:
16///
17/// * `src_dir` does not exist.
18/// * `dst_dir` does not exist.
19/// * The user lacks the permission to move any of the files in `src_dir`
20/// * The user lacks the permission to write files to `dst_dir`
21///
22/// # Atomicity:
23/// This functions makes no atomicity guarantees. It is possible that this function errors after
24/// some files already have been moved, leaving `src_dir` and `dst_dir` in an inconsistent state.
25pub fn move_directory_contents(
26    src_dir: impl AsRef<Path>,
27    dst_dir: impl AsRef<Path>,
28) -> Result<(), std::io::Error> {
29    for dir_entry in fs::read_dir(src_dir.as_ref())? {
30        let dir_entry = dir_entry?;
31        let relative_path = pathdiff::diff_paths(dir_entry.path(), src_dir.as_ref())
32            .ok_or_else(|| std::io::Error::other("std::fs::read_dir unexpectedly returned an entry that is not in the directory that was read."))?;
33
34        fs::rename(dir_entry.path(), dst_dir.as_ref().join(relative_path))?;
35    }
36
37    Ok(())
38}
39
40#[cfg(test)]
41mod test {
42    use std::collections::HashMap;
43    use std::path::PathBuf;
44
45    use tempfile::tempdir;
46
47    use super::*;
48
49    #[test]
50    fn test() {
51        let src_dir = tempdir().unwrap();
52        let dst_dir = tempdir().unwrap();
53
54        let mut test_data = HashMap::new();
55        test_data.insert(PathBuf::from("foo"), String::from("foo"));
56        test_data.insert(PathBuf::from("bar"), String::from("bar"));
57        test_data.insert(PathBuf::from("baz"), String::from("baz"));
58        test_data.insert(
59            PathBuf::from("subdir").join("foo"),
60            String::from("foosubdir"),
61        );
62        test_data.insert(
63            PathBuf::from("subdir")
64                .join("more")
65                .join("more")
66                .join("file.txt"),
67            String::from("Hello World!"),
68        );
69
70        for (path, contents) in &test_data {
71            if let Some(parent_dir) = path.parent() {
72                fs::create_dir_all(src_dir.path().join(parent_dir)).unwrap();
73            }
74
75            fs::write(src_dir.path().join(path), contents).unwrap();
76        }
77
78        move_directory_contents(&src_dir, &dst_dir).unwrap();
79
80        for (path, expected_contents) in &test_data {
81            let actual_contents = fs::read_to_string(dst_dir.path().join(path)).unwrap();
82            assert_eq!(expected_contents, &actual_contents);
83        }
84
85        assert!(src_dir.path().exists());
86
87        assert!(fs::read_dir(src_dir.path()).unwrap().next().is_none());
88    }
89}