use std::fs;
use std::io::{Error, ErrorKind, Result};
use std::path::{Component, Path, PathBuf};
pub struct VirtualFileSystem {
pub root: PathBuf,
}
impl VirtualFileSystem {
pub fn try_new<P: AsRef<Path>>(root: P) -> Result<Self> {
Path::new(root.as_ref())
.canonicalize()
.map(|path| Self { root: path })
}
pub fn absolute<P: AsRef<Path>>(&self, path: P) -> Option<PathBuf> {
if path.as_ref().is_relative() {
Some(Self::normalize(self.root.join(path.as_ref())))
} else {
let path_norm = Self::normalize(path.as_ref());
if path_norm.starts_with(&self.root) {
Some(path_norm)
} else {
None
}
}
}
pub fn relative<P: AsRef<Path>>(&self, path: P) -> Option<PathBuf> {
let path = self.absolute(path)?;
Some(Self::normalize(path.strip_prefix(&self.root).unwrap()))
}
pub fn exists<P: AsRef<Path>>(&self, path: P) -> bool {
if let Some(path) = self.absolute(path) {
path.exists()
} else {
false
}
}
pub fn chroot<P: AsRef<Path>>(&mut self, new_root: P) -> bool {
match self.absolute(new_root) {
Some(path) => {
self.root = path;
true
}
None => false,
}
}
pub fn create_dir<P: AsRef<Path>>(&self, path: P) -> Result<()> {
match self.absolute(path) {
Some(path) => {
fs::create_dir(path)?;
Ok(())
}
None => Err(Error::from(ErrorKind::NotFound)),
}
}
pub fn create_dir_all<P: AsRef<Path>>(&self, path: P) -> Result<()> {
match self.absolute(path) {
Some(path) => {
fs::create_dir_all(path)?;
Ok(())
}
None => Err(Error::from(ErrorKind::NotFound)),
}
}
pub fn remove_dir<P: AsRef<Path>>(&self, path: P) -> Result<()> {
match self.absolute(path) {
Some(path) => {
fs::remove_dir(path)?;
Ok(())
}
None => Err(Error::from(ErrorKind::NotFound)),
}
}
pub fn remove_dir_all<P: AsRef<Path>>(&self, path: P) -> Result<()> {
match self.absolute(path) {
Some(path) => {
fs::remove_dir_all(path)?;
Ok(())
}
None => Err(Error::from(ErrorKind::NotFound)),
}
}
pub fn normalize<P: AsRef<Path>>(path: P) -> PathBuf {
match path.as_ref().components().count() {
0 => PathBuf::from("."),
1 => PathBuf::from(path.as_ref()),
_ => {
let mut normalized = PathBuf::new();
for component in path.as_ref().components() {
match component {
Component::CurDir => {}
Component::ParentDir => {
if normalized.components().count() == 0 {
normalized.push(component);
} else {
if normalized.components().last().unwrap() == Component::ParentDir {
normalized.push(component);
} else if normalized.components().last().unwrap()
!= Component::RootDir
{
normalized.pop();
}
}
}
_ => normalized.push(component),
}
}
normalized
}
}
}
}
#[cfg(test)]
mod tests {
use std::path::{Path, PathBuf};
use super::VirtualFileSystem;
const ROOT: &str = "tests/root";
fn new_vfs() -> VirtualFileSystem {
VirtualFileSystem::try_new(ROOT).unwrap()
}
fn cur_dir() -> PathBuf {
Path::new(ROOT).canonicalize().unwrap()
}
#[test]
fn normalize_ok() {
assert_eq!(VirtualFileSystem::normalize(""), PathBuf::from("."));
assert_eq!(VirtualFileSystem::normalize("."), PathBuf::from("."));
assert_eq!(VirtualFileSystem::normalize(".."), PathBuf::from(".."));
assert_eq!(VirtualFileSystem::normalize("../."), PathBuf::from(".."));
assert_eq!(
VirtualFileSystem::normalize("../.."),
PathBuf::from("../..")
);
assert_eq!(
VirtualFileSystem::normalize("../../.."),
PathBuf::from("../../..")
);
assert_eq!(
VirtualFileSystem::normalize(".././.."),
PathBuf::from("../..")
);
assert_eq!(VirtualFileSystem::normalize("./dir"), PathBuf::from("dir"));
assert_eq!(
VirtualFileSystem::normalize("../dir"),
PathBuf::from("../dir")
);
assert_eq!(
VirtualFileSystem::normalize("../dir/.."),
PathBuf::from("..")
);
assert_eq!(
VirtualFileSystem::normalize("./first/second/.."),
PathBuf::from("first")
);
assert_eq!(
VirtualFileSystem::normalize("first/./second"),
PathBuf::from("first/second")
);
#[cfg(windows)]
{
assert_eq!(
VirtualFileSystem::normalize(r"\\?\C:\"),
PathBuf::from(r"\\?\C:\")
);
assert_eq!(
VirtualFileSystem::normalize(r"\\?\C:\."),
PathBuf::from(r"\\?\C:\")
);
assert_eq!(
VirtualFileSystem::normalize(cur_dir().join(r"more\..")),
PathBuf::from(cur_dir())
);
}
}
#[test]
fn root_ok() {
let vfs = new_vfs();
assert_eq!(vfs.root, cur_dir());
}
#[test]
fn absolute_ok() {
let vfs = new_vfs();
assert_eq!(vfs.absolute(".").unwrap(), cur_dir());
assert_eq!(vfs.absolute("more").unwrap(), cur_dir().join("more"));
assert_eq!(
vfs.absolute(cur_dir().join("more")).unwrap(),
cur_dir().join("more")
);
#[cfg(unix)]
assert_eq!(vfs.absolute(PathBuf::from("/other/absolute")), None);
#[cfg(windows)]
assert_eq!(vfs.absolute(PathBuf::from(r"F:\other\absolute")), None);
}
#[test]
fn relative_ok() {
let vfs = new_vfs();
assert_eq!(
vfs.relative("./relative").unwrap(),
PathBuf::from("relative")
);
assert_eq!(vfs.relative(cur_dir()).unwrap(), PathBuf::from("."));
assert_eq!(
vfs.relative(cur_dir().join("more")).unwrap(),
PathBuf::from("more")
);
#[cfg(unix)]
assert_eq!(vfs.relative(PathBuf::from("/other/absolute")), None);
#[cfg(windows)]
assert_eq!(vfs.relative(PathBuf::from(r"F:\other\absolute")), None);
}
#[test]
fn exists_ok() {
let vfs = new_vfs();
#[cfg(unix)]
assert!(vfs.exists("more/example.txt"));
#[cfg(windows)]
assert!(vfs.exists(r"more\example.txt"));
assert!(!vfs.exists("foo"));
}
#[test]
fn chroot_ok() {
let mut vfs = new_vfs();
assert!(vfs.chroot("."));
assert_eq!(vfs.root, cur_dir());
assert!(vfs.chroot("more"));
assert_eq!(vfs.root, cur_dir().join("more"));
#[cfg(unix)]
assert!(vfs.chroot("../.."));
#[cfg(windows)]
assert!(vfs.chroot(r"..\.."));
assert_eq!(vfs.root, cur_dir().parent().unwrap());
}
#[test]
fn create_dir_ok() {
let vfs = new_vfs();
vfs.create_dir("new_dir").unwrap();
assert!(vfs.exists("new_dir"));
vfs.remove_dir("new_dir").unwrap();
}
#[test]
fn remove_dir_ok() {
let vfs = new_vfs();
vfs.create_dir("new_dir").unwrap();
assert!(vfs.exists("new_dir"));
vfs.remove_dir("new_dir").unwrap();
assert!(!vfs.exists("new_dir"));
}
#[test]
fn create_dir_all_ok() {
let vfs = new_vfs();
#[cfg(unix)]
{
vfs.create_dir_all("new1/new2").unwrap();
assert!(vfs.exists("new1/new2"));
vfs.remove_dir_all("new1/new2").unwrap();
}
#[cfg(windows)]
{
vfs.create_dir_all(r"new1\new2").unwrap();
assert!(vfs.exists(r"new1\new2"));
vfs.remove_dir_all(r"new1\new2").unwrap();
}
}
#[test]
fn remove_dir_all_ok() {
let vfs = new_vfs();
#[cfg(unix)]
{
vfs.create_dir_all("new1/new2").unwrap();
assert!(vfs.exists("new1/new2"));
vfs.remove_dir_all("new1/new2").unwrap();
vfs.remove_dir_all("new1").unwrap();
assert!(!vfs.exists("new1/new2"));
assert!(!vfs.exists("new1"));
}
#[cfg(windows)]
{
vfs.create_dir_all(r"new1\new2").unwrap();
assert!(vfs.exists(r"new1\new2"));
vfs.remove_dir_all(r"new1\new2").unwrap();
vfs.remove_dir_all("new1").unwrap();
assert!(!vfs.exists(r"new1\new2"));
assert!(!vfs.exists("new1"));
}
}
}