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