1use serde::Serialize;
2use std::fs::{self, metadata};
3use std::path::{Path, PathBuf};
4use tempfile::{self, tempdir};
5
6#[derive(Debug, Serialize)]
11pub struct DiskEntry {
12 pub path: PathBuf,
14 pub name: Option<String>,
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub children: Option<Vec<DiskEntry>>,
19}
20
21pub fn is_dir<P: AsRef<Path>>(path: P) -> crate::Result<bool> {
23 metadata(path).map(|md| md.is_dir()).map_err(|e| e.into())
24}
25
26pub fn read_dir<P: AsRef<Path>>(path: P, recursive: bool) -> crate::Result<Vec<DiskEntry>> {
28 let mut files_and_dirs: Vec<DiskEntry> = vec![];
29 for entry in fs::read_dir(path)? {
30 let path = entry?.path();
31 let path_as_string = path.display().to_string();
32
33 if let Ok(flag) = is_dir(&path_as_string) {
34 files_and_dirs.push(DiskEntry {
35 path: path.clone(),
36 children: if flag {
37 Some(if recursive {
38 read_dir(&path_as_string, true)?
39 } else {
40 vec![]
41 })
42 } else {
43 None
44 },
45 name: path
46 .file_name()
47 .map(|name| name.to_string_lossy())
48 .map(|name| name.to_string()),
49 });
50 }
51 }
52 Result::Ok(files_and_dirs)
53}
54
55pub fn with_temp_dir<F: FnOnce(&tempfile::TempDir)>(callback: F) -> crate::Result<()> {
57 let dir = tempdir()?;
58 callback(&dir);
59 dir.close()?;
60 Ok(())
61}
62
63#[cfg(test)]
64mod test {
65 use super::*;
66 use quickcheck_macros::quickcheck;
67 use std::ffi::OsStr;
68 use std::path::PathBuf;
69
70 #[quickcheck]
72 fn qc_is_dir(f: String) -> bool {
73 if is_dir(f.clone()).is_ok() {
75 PathBuf::from(f).is_dir()
76 } else {
77 true
78 }
79 }
80
81 fn name_from_path(path: PathBuf) -> Option<String> {
82 path
83 .file_name()
84 .map(|name| name.to_string_lossy())
85 .map(|name| name.to_string())
86 }
87
88 #[test]
89 fn check_read_dir_recursively() {
91 let dir = PathBuf::from("test/");
93 let mut file_one = dir.clone();
95 file_one.push("test.txt");
96 let mut file_two = dir.clone();
97 file_two.push("test_binary");
98
99 let res = read_dir(dir, true);
101
102 assert!(res.is_ok());
104
105 if let Ok(vec) = res {
107 assert_eq!(vec.len(), 2);
109
110 let first = &vec[0];
112 let second = &vec[1];
114
115 if first.path.extension() == Some(OsStr::new("txt")) {
116 assert_eq!(first.path, file_one);
118 assert_eq!(first.children.is_some(), false);
119 assert_eq!(first.name, name_from_path(file_one));
120
121 assert_eq!(second.path, file_two);
123 assert_eq!(second.children.is_some(), false);
124 assert_eq!(second.name, name_from_path(file_two));
125 } else {
126 assert_eq!(first.path, file_two);
128 assert_eq!(first.children.is_some(), false);
129 assert_eq!(first.name, name_from_path(file_two));
130
131 assert_eq!(second.path, file_one);
133 assert_eq!(second.children.is_some(), false);
134 assert_eq!(second.name, name_from_path(file_one));
135 }
136 }
137 }
138
139 #[test]
140 fn check_read_dir() {
142 let dir = PathBuf::from("test/");
144
145 let res = read_dir(dir, false);
147
148 assert!(res.is_ok());
150
151 if let Ok(vec) = res {
153 assert_eq!(vec.len(), 2);
155
156 let first = &vec[0];
158 let second = &vec[1];
159
160 if first.path.extension() == Some(OsStr::new("txt")) {
161 assert_eq!(first.path, PathBuf::from("test/test.txt"));
163 assert_eq!(first.children.is_some(), false);
164 assert_eq!(first.name, Some("test.txt".to_string()));
165
166 assert_eq!(second.path, PathBuf::from("test/test_binary"));
168 assert_eq!(second.children.is_some(), false);
169 assert_eq!(second.name, Some("test_binary".to_string()));
170 } else {
171 assert_eq!(second.path, PathBuf::from("test/test.txt"));
173 assert_eq!(second.children.is_some(), false);
174 assert_eq!(second.name, Some("test.txt".to_string()));
175
176 assert_eq!(first.path, PathBuf::from("test/test_binary"));
178 assert_eq!(first.children.is_some(), false);
179 assert_eq!(first.name, Some("test_binary".to_string()));
180 }
181 }
182 }
183
184 #[test]
185 fn check_test_dir() {
187 let callback = |td: &tempfile::TempDir| {
189 println!("{:?}", td);
190 };
191
192 let res = with_temp_dir(callback);
194
195 assert!(res.is_ok());
197 }
198}