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