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 let path = path.replace('\\', "/");
116 path.trim_start_matches('/').into()
117}
118
119pub(crate) fn already_exists() -> io::Error {
121 io::Error::new(ErrorKind::AlreadyExists, "Already exists")
122}
123
124pub(crate) fn invalid_input(error: &str) -> io::Error {
126 io::Error::new(ErrorKind::InvalidInput, error)
127}
128
129pub(crate) fn invalid_path() -> io::Error {
131 io::Error::new(ErrorKind::InvalidInput, "Invalid path")
132}
133
134pub(crate) fn not_found() -> io::Error {
136 io::Error::new(ErrorKind::NotFound, "File not found")
137}
138
139pub(crate) fn not_supported() -> io::Error {
141 io::Error::new(ErrorKind::Unsupported, "Not supported")
142}
143
144#[cfg(test)]
145pub mod test {
146 use crate::file::Metadata;
147 use crate::util::{component_iter, create_dir_all, normalize_path, parent_iter};
148 use crate::{FileSystem, MockFileSystem};
149 use std::collections::BTreeMap;
150 use std::io;
151 use std::io::ErrorKind;
152 use std::path::Path;
153
154 pub(crate) fn read_directory<F: FileSystem>(fs: &F, dir: &str) -> BTreeMap<String, Metadata> {
156 fs.read_dir(dir)
157 .unwrap()
158 .map(|entry| {
159 let entry = entry.unwrap();
160 (entry.path.to_str().unwrap().to_owned(), entry.metadata)
161 })
162 .collect()
163 }
164
165 #[test]
166 fn components() {
167 itertools::assert_equal(
168 component_iter(Path::new("../many/files/and/directories/")),
169 vec!["many", "files", "and", "directories"],
170 );
171 }
172
173 const TARGET_DIR: &str = "/some/directory/somewhere/";
174
175 #[test]
176 fn create_all_happy_case() {
177 let mut mock_fs = MockFileSystem::new();
178
179 let mut i = 0;
180 mock_fs.expect_create_dir().times(3).returning(move |_| {
181 i += 1;
182
183 if i == 1 {
184 Err(io::Error::new(ErrorKind::AlreadyExists, ""))
185 } else {
186 Ok(())
187 }
188 });
189
190 assert!(create_dir_all(&mock_fs, TARGET_DIR).is_ok())
191 }
192
193 #[test]
194 fn create_all_error() {
195 let mut mock_fs = MockFileSystem::new();
196
197 mock_fs
198 .expect_create_dir()
199 .returning(|_| Err(io::Error::new(ErrorKind::Unsupported, "")));
200
201 assert!(create_dir_all(&mock_fs, TARGET_DIR).is_err())
202 }
203
204 #[test]
205 fn normalize() {
206 assert_eq!(normalize_path("///////"), Path::new("/"));
207 assert_eq!(normalize_path("./test/something/../"), Path::new("test"));
208 assert_eq!(normalize_path("../test"), Path::new("test"));
209 }
210
211 #[test]
212 fn parent() {
213 itertools::assert_equal(
214 parent_iter(Path::new("/many/files/and/directories")),
215 vec![
216 Path::new("/many/files/and"),
217 Path::new("/many/files"),
218 Path::new("/many"),
219 Path::new("/"),
220 ],
221 );
222
223 itertools::assert_equal(
224 parent_iter(Path::new("../many/files/and/directories")),
225 vec![
226 Path::new("../many/files/and"),
227 Path::new("../many/files"),
228 Path::new("../many"),
229 Path::new(".."),
230 ],
231 );
232 }
233}