use crate::FileSystem;
use normalize_path::NormalizePath;
use path_slash::PathBufExt;
use std::io;
use std::io::ErrorKind;
use std::iter::once;
use std::path::{Component, Path, PathBuf};
pub fn component_iter(path: &Path) -> impl DoubleEndedIterator<Item = &str> {
path.components().filter_map(|component| {
if let Component::Normal(component) = component {
component.to_str()
} else {
None
}
})
}
pub fn create_dir_all<FS: FileSystem + ?Sized>(fs: &FS, path: &str) -> crate::Result<()> {
let normalized = normalize_path(make_relative(path));
for path in parent_iter(&normalized).chain(once(normalized.as_ref())) {
if let Err(err) = fs.create_dir(path.to_str().unwrap()) {
if err.kind() != ErrorKind::AlreadyExists {
return Err(err);
}
}
}
Ok(())
}
pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
Path::new(path.as_ref().normalize().to_slash_lossy().as_ref()).to_owned()
}
pub fn parent_iter(path: &Path) -> impl DoubleEndedIterator<Item = &Path> {
path.ancestors()
.filter(|path| !path.as_os_str().is_empty())
.skip(1)
.collect::<Vec<_>>()
.into_iter()
}
pub(crate) fn make_relative<P: AsRef<Path>>(path: P) -> PathBuf {
let path = path.as_ref().to_str().unwrap_or("");
let path = path.replace('\\', "/");
path.trim_start_matches('/').into()
}
pub(crate) fn already_exists() -> io::Error {
io::Error::new(ErrorKind::AlreadyExists, "Already exists")
}
pub(crate) fn invalid_input(error: &str) -> io::Error {
io::Error::new(ErrorKind::InvalidInput, error)
}
pub(crate) fn invalid_path() -> io::Error {
io::Error::new(ErrorKind::InvalidInput, "Invalid path")
}
pub(crate) fn not_found() -> io::Error {
io::Error::new(ErrorKind::NotFound, "File not found")
}
pub(crate) fn not_supported() -> io::Error {
io::Error::new(ErrorKind::Unsupported, "Not supported")
}
#[cfg(test)]
pub mod test {
use crate::file::Metadata;
use crate::util::{component_iter, create_dir_all, normalize_path, parent_iter};
use crate::{FileSystem, MockFileSystem};
use std::collections::BTreeMap;
use std::io;
use std::io::ErrorKind;
use std::path::Path;
pub(crate) fn read_directory<F: FileSystem>(fs: &F, dir: &str) -> BTreeMap<String, Metadata> {
fs.read_dir(dir)
.unwrap()
.map(|entry| {
let entry = entry.unwrap();
(entry.path.to_str().unwrap().to_owned(), entry.metadata)
})
.collect()
}
#[test]
fn components() {
itertools::assert_equal(
component_iter(Path::new("../many/files/and/directories/")),
vec!["many", "files", "and", "directories"],
);
}
const TARGET_DIR: &str = "/some/directory/somewhere/";
#[test]
fn create_all_happy_case() {
let mut mock_fs = MockFileSystem::new();
let mut i = 0;
mock_fs.expect_create_dir().times(3).returning(move |_| {
i += 1;
if i == 1 {
Err(io::Error::new(ErrorKind::AlreadyExists, ""))
} else {
Ok(())
}
});
assert!(create_dir_all(&mock_fs, TARGET_DIR).is_ok())
}
#[test]
fn create_all_error() {
let mut mock_fs = MockFileSystem::new();
mock_fs
.expect_create_dir()
.returning(|_| Err(io::Error::new(ErrorKind::Unsupported, "")));
assert!(create_dir_all(&mock_fs, TARGET_DIR).is_err())
}
#[test]
fn normalize() {
assert_eq!(normalize_path("///////"), Path::new("/"));
assert_eq!(normalize_path("./test/something/../"), Path::new("test"));
assert_eq!(normalize_path("../test"), Path::new("test"));
}
#[test]
fn parent() {
itertools::assert_equal(
parent_iter(Path::new("/many/files/and/directories")),
vec![
Path::new("/many/files/and"),
Path::new("/many/files"),
Path::new("/many"),
Path::new("/"),
],
);
itertools::assert_equal(
parent_iter(Path::new("../many/files/and/directories")),
vec![
Path::new("../many/files/and"),
Path::new("../many/files"),
Path::new("../many"),
Path::new(".."),
],
);
}
}