use std::fmt;
use std::fs;
use std::io;
use std::path;
use crate::reflection;
use crate::Predicate;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum FileType {
File,
Dir,
Symlink,
}
impl FileType {
fn from_path(path: &path::Path, follow: bool) -> io::Result<FileType> {
let file_type = if follow {
path.metadata()
} else {
path.symlink_metadata()
}?
.file_type();
if file_type.is_dir() {
return Ok(FileType::Dir);
}
if file_type.is_file() {
return Ok(FileType::File);
}
Ok(FileType::Symlink)
}
fn eval(self, ft: fs::FileType) -> bool {
match self {
FileType::File => ft.is_file(),
FileType::Dir => ft.is_dir(),
FileType::Symlink => ft.is_symlink(),
}
}
}
impl fmt::Display for FileType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let t = match *self {
FileType::File => "file",
FileType::Dir => "dir",
FileType::Symlink => "symlink",
};
write!(f, "{t}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FileTypePredicate {
ft: FileType,
follow: bool,
}
impl FileTypePredicate {
pub fn follow_links(mut self, yes: bool) -> Self {
self.follow = yes;
self
}
pub fn from_path(path: &path::Path) -> io::Result<FileTypePredicate> {
Ok(FileTypePredicate {
ft: FileType::from_path(path, true)?,
follow: true,
})
}
}
impl Predicate<path::Path> for FileTypePredicate {
fn eval(&self, path: &path::Path) -> bool {
let metadata = if self.follow {
path.metadata()
} else {
path.symlink_metadata()
};
metadata
.map(|m| self.ft.eval(m.file_type()))
.unwrap_or(false)
}
fn find_case<'a>(
&'a self,
expected: bool,
variable: &path::Path,
) -> Option<reflection::Case<'a>> {
let actual_type = FileType::from_path(variable, self.follow);
match (expected, actual_type) {
(_, Ok(actual_type)) => {
let result = self.ft == actual_type;
if result == expected {
Some(
reflection::Case::new(Some(self), result)
.add_product(reflection::Product::new("actual filetype", actual_type)),
)
} else {
None
}
}
(true, Err(_)) => None,
(false, Err(err)) => Some(
reflection::Case::new(Some(self), false)
.add_product(reflection::Product::new("error", err)),
),
}
}
}
impl reflection::PredicateReflection for FileTypePredicate {
fn parameters<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Parameter<'a>> + 'a> {
let params = vec![reflection::Parameter::new("follow", &self.follow)];
Box::new(params.into_iter())
}
}
impl fmt::Display for FileTypePredicate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let palette = crate::Palette::new(f.alternate());
write!(
f,
"{} {} {}",
palette.var("var"),
palette.description("is"),
palette.expected(self.ft)
)
}
}
pub fn is_file() -> FileTypePredicate {
FileTypePredicate {
ft: FileType::File,
follow: false,
}
}
pub fn is_dir() -> FileTypePredicate {
FileTypePredicate {
ft: FileType::Dir,
follow: false,
}
}
pub fn is_symlink() -> FileTypePredicate {
FileTypePredicate {
ft: FileType::Symlink,
follow: false,
}
}