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