virtual_filesystem/
util.rs1use crate::FileSystem;
2use normalize_path::NormalizePath;
3use path_slash::PathBufExt;
4use std::io;
5use std::io::ErrorKind;
6use std::iter::once;
7use std::path::{Component, Path, PathBuf};
8
9pub fn component_iter(path: &Path) -> impl DoubleEndedIterator<Item = &str> {
25 path.components().filter_map(|component| {
26 if let Component::Normal(component) = component {
27 component.to_str()
28 } else {
29 None
30 }
31 })
32}
33
34pub fn create_dir_all<FS: FileSystem + ?Sized>(fs: &FS, path: &str) -> crate::Result<()> {
41 let normalized = normalize_path(make_relative(path));
42
43 for path in parent_iter(&normalized).chain(once(normalized.as_ref())) {
44 if let Err(err) = fs.create_dir(path.to_str().unwrap()) {
46 if err.kind() != ErrorKind::AlreadyExists {
47 return Err(err);
48 }
49 }
50 }
51
52 Ok(())
53}
54
55pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
70 Path::new(path.as_ref().normalize().to_slash_lossy().as_ref()).to_owned()
71}
72
73pub fn parent_iter(path: &Path) -> impl DoubleEndedIterator<Item = &Path> {
104 path.ancestors()
106 .filter(|path| !path.as_os_str().is_empty())
107 .skip(1)
108 .collect::<Vec<_>>()
109 .into_iter()
110}
111
112pub(crate) fn make_relative<P: AsRef<Path>>(path: P) -> PathBuf {
114 let path = path.as_ref().to_str().unwrap_or("");
115 path.trim_start_matches('/').trim_start_matches('\\').into()
116}
117
118pub(crate) fn already_exists() -> io::Error {
120 io::Error::new(ErrorKind::AlreadyExists, "Already exists")
121}
122
123pub(crate) fn invalid_input(error: &str) -> io::Error {
125 io::Error::new(ErrorKind::InvalidInput, error)
126}
127
128pub(crate) fn invalid_path() -> io::Error {
130 io::Error::new(ErrorKind::InvalidInput, "Invalid path")
131}
132
133pub(crate) fn not_found() -> io::Error {
135 io::Error::new(ErrorKind::NotFound, "File not found")
136}
137
138pub(crate) fn not_supported() -> io::Error {
140 io::Error::new(ErrorKind::Unsupported, "Not supported")
141}
142
143#[cfg(test)]
144pub mod test {
145 use crate::file::Metadata;
146 use crate::util::{component_iter, create_dir_all, normalize_path, parent_iter};
147 use crate::{FileSystem, MockFileSystem};
148 use std::collections::BTreeMap;
149 use std::io;
150 use std::io::ErrorKind;
151 use std::path::Path;
152
153 pub(crate) fn read_directory<F: FileSystem>(fs: &F, dir: &str) -> BTreeMap<String, Metadata> {
155 fs.read_dir(dir)
156 .unwrap()
157 .map(|entry| {
158 let entry = entry.unwrap();
159 (entry.path.to_str().unwrap().to_owned(), entry.metadata)
160 })
161 .collect()
162 }
163
164 #[test]
165 fn components() {
166 itertools::assert_equal(
167 component_iter(Path::new("../many/files/and/directories/")),
168 vec!["many", "files", "and", "directories"],
169 );
170 }
171
172 const TARGET_DIR: &str = "/some/directory/somewhere/";
173
174 #[test]
175 fn create_all_happy_case() {
176 let mut mock_fs = MockFileSystem::new();
177
178 let mut i = 0;
179 mock_fs.expect_create_dir().times(3).returning(move |_| {
180 i += 1;
181
182 if i == 1 {
183 Err(io::Error::new(ErrorKind::AlreadyExists, ""))
184 } else {
185 Ok(())
186 }
187 });
188
189 assert!(create_dir_all(&mock_fs, TARGET_DIR).is_ok())
190 }
191
192 #[test]
193 fn create_all_error() {
194 let mut mock_fs = MockFileSystem::new();
195
196 mock_fs
197 .expect_create_dir()
198 .returning(|_| Err(io::Error::new(ErrorKind::Unsupported, "")));
199
200 assert!(create_dir_all(&mock_fs, TARGET_DIR).is_err())
201 }
202
203 #[test]
204 fn normalize() {
205 assert_eq!(normalize_path("///////"), Path::new("/"));
206 assert_eq!(normalize_path("./test/something/../"), Path::new("test"));
207 assert_eq!(normalize_path("../test"), Path::new("test"));
208 }
209
210 #[test]
211 fn parent() {
212 itertools::assert_equal(
213 parent_iter(Path::new("/many/files/and/directories")),
214 vec![
215 Path::new("/many/files/and"),
216 Path::new("/many/files"),
217 Path::new("/many"),
218 Path::new("/"),
219 ],
220 );
221
222 itertools::assert_equal(
223 parent_iter(Path::new("../many/files/and/directories")),
224 vec![
225 Path::new("../many/files/and"),
226 Path::new("../many/files"),
227 Path::new("../many"),
228 Path::new(".."),
229 ],
230 );
231 }
232}