Line data Source code
1 : /// Extensions to `std::fs` types.
2 : use std::{fs, io, path::Path};
3 :
4 : use anyhow::Context;
5 :
6 : mod rename_noreplace;
7 : pub use rename_noreplace::rename_noreplace;
8 :
9 : pub trait PathExt {
10 : /// Returns an error if `self` is not a directory.
11 : fn is_empty_dir(&self) -> io::Result<bool>;
12 : }
13 :
14 : impl<P> PathExt for P
15 : where
16 : P: AsRef<Path>,
17 : {
18 18 : fn is_empty_dir(&self) -> io::Result<bool> {
19 18 : Ok(fs::read_dir(self)?.next().is_none())
20 18 : }
21 : }
22 :
23 18 : pub async fn is_directory_empty(path: impl AsRef<Path>) -> anyhow::Result<bool> {
24 18 : let mut dir = tokio::fs::read_dir(&path)
25 18 : .await
26 18 : .context(format!("read_dir({})", path.as_ref().display()))?;
27 6 : Ok(dir.next_entry().await?.is_none())
28 18 : }
29 :
30 18 : pub async fn list_dir(path: impl AsRef<Path>) -> anyhow::Result<Vec<String>> {
31 18 : let mut dir = tokio::fs::read_dir(&path)
32 18 : .await
33 18 : .context(format!("read_dir({})", path.as_ref().display()))?;
34 :
35 18 : let mut content = vec![];
36 36 : while let Some(next) = dir.next_entry().await? {
37 18 : let file_name = next.file_name();
38 18 : content.push(file_name.to_string_lossy().to_string());
39 18 : }
40 :
41 18 : Ok(content)
42 18 : }
43 :
44 84 : pub fn ignore_not_found(e: io::Error) -> io::Result<()> {
45 84 : if e.kind() == io::ErrorKind::NotFound {
46 84 : Ok(())
47 : } else {
48 0 : Err(e)
49 : }
50 84 : }
51 :
52 30 : pub fn ignore_absent_files<F>(fs_operation: F) -> io::Result<()>
53 30 : where
54 30 : F: Fn() -> io::Result<()>,
55 30 : {
56 30 : fs_operation().or_else(ignore_not_found)
57 30 : }
58 :
59 : #[cfg(test)]
60 : mod test {
61 : use crate::fs_ext::{is_directory_empty, list_dir};
62 :
63 : use super::ignore_absent_files;
64 :
65 : #[test]
66 6 : fn is_empty_dir() {
67 6 : use super::PathExt;
68 6 :
69 6 : let dir = camino_tempfile::tempdir().unwrap();
70 6 : let dir_path = dir.path();
71 6 :
72 6 : // test positive case
73 6 : assert!(
74 6 : dir_path.is_empty_dir().expect("test failure"),
75 0 : "new tempdir should be empty"
76 : );
77 :
78 : // invoke on a file to ensure it returns an error
79 6 : let file_path = dir_path.join("testfile");
80 6 : let f = std::fs::File::create(&file_path).unwrap();
81 6 : drop(f);
82 6 : assert!(file_path.is_empty_dir().is_err());
83 :
84 : // do it again on a path, we know to be nonexistent
85 6 : std::fs::remove_file(&file_path).unwrap();
86 6 : assert!(file_path.is_empty_dir().is_err());
87 6 : }
88 :
89 : #[tokio::test]
90 6 : async fn is_empty_dir_async() {
91 6 : let dir = camino_tempfile::tempdir().unwrap();
92 6 : let dir_path = dir.path();
93 6 :
94 6 : // test positive case
95 6 : assert!(
96 6 : is_directory_empty(dir_path).await.expect("test failure"),
97 6 : "new tempdir should be empty"
98 6 : );
99 6 :
100 6 : // invoke on a file to ensure it returns an error
101 6 : let file_path = dir_path.join("testfile");
102 6 : let f = std::fs::File::create(&file_path).unwrap();
103 6 : drop(f);
104 6 : assert!(is_directory_empty(&file_path).await.is_err());
105 6 :
106 6 : // do it again on a path, we know to be nonexistent
107 6 : std::fs::remove_file(&file_path).unwrap();
108 6 : assert!(is_directory_empty(file_path).await.is_err());
109 6 : }
110 :
111 : #[test]
112 6 : fn ignore_absent_files_works() {
113 6 : let dir = camino_tempfile::tempdir().unwrap();
114 6 :
115 6 : let file_path = dir.path().join("testfile");
116 6 :
117 6 : ignore_absent_files(|| std::fs::remove_file(&file_path)).expect("should execute normally");
118 6 :
119 6 : let f = std::fs::File::create(&file_path).unwrap();
120 6 : drop(f);
121 6 :
122 6 : ignore_absent_files(|| std::fs::remove_file(&file_path)).expect("should execute normally");
123 6 :
124 6 : assert!(!file_path.exists());
125 6 : }
126 :
127 : #[tokio::test]
128 6 : async fn list_dir_works() {
129 6 : let dir = camino_tempfile::tempdir().unwrap();
130 6 : let dir_path = dir.path();
131 6 :
132 6 : assert!(list_dir(dir_path).await.unwrap().is_empty());
133 6 :
134 6 : let file_path = dir_path.join("testfile");
135 6 : let _ = std::fs::File::create(&file_path).unwrap();
136 6 :
137 6 : assert_eq!(&list_dir(dir_path).await.unwrap(), &["testfile"]);
138 6 :
139 6 : let another_dir_path = dir_path.join("testdir");
140 6 : std::fs::create_dir(another_dir_path).unwrap();
141 6 :
142 6 : let expected = &["testdir", "testfile"];
143 6 : let mut actual = list_dir(dir_path).await.unwrap();
144 6 : actual.sort();
145 6 : assert_eq!(actual, expected);
146 6 : }
147 : }
|