#![allow(dead_code)]
use crate::error;
use crate::{dir::Dir, file::File};
use std::ffi::OsString;
use std::path::{Path, PathBuf};
#[derive(PartialEq)]
#[allow(missing_docs)]
pub enum ShapeItemStatic<'a> {
File(&'a str, &'a str),
DirectoryPattern(&'a str, &'a str, &'a str),
DirectorySchema(&'a str, &'a str, &'a ShapeSchemaStatic<'a>),
}
impl ShapeItemStatic<'_> {
fn identifier(&self) -> &&str {
match self {
ShapeItemStatic::File(identifier, _)
| ShapeItemStatic::DirectoryPattern(identifier, _, _)
| ShapeItemStatic::DirectorySchema(identifier, _, _) => identifier,
}
}
}
#[allow(missing_docs)]
pub type ShapeSchemaStatic<'a> = [ShapeItemStatic<'a>];
#[derive(Debug)]
#[allow(missing_docs)]
pub enum ShapeInstItem {
File(File),
Directory(Dir),
ShapedDirectory(ShapeInst),
}
#[allow(missing_docs)]
pub type ShapeInst = Vec<ShapeInstItem>;
#[allow(missing_docs)]
pub trait ShapeDescribe {
fn shape_describe() -> &'static ShapeSchemaStatic<'static>;
fn shape_new(inst: ShapeInst) -> Self;
}
#[allow(missing_docs)]
pub trait ShapeDescribeStatic {
fn shape_describe() -> &'static ShapeSchemaStatic<'static>;
fn shape_new(inst: ShapeInst) -> Self;
}
pub struct Shape<T: ShapeDescribe> {
#[allow(dead_code)]
ignore_me: Option<T>,
}
impl<T: ShapeDescribe> Shape<T> {
pub fn new() -> Self {
Self { ignore_me: None }
}
pub fn create_at<'a, P: 'a + AsRef<Path>>(&self, path: P) -> Result<T, error::Error> {
let target = T::shape_describe();
let mut path_buf = PathBuf::new();
path_buf.push(path);
let res = create_shape_inst(path_buf, target, true, None)?;
Ok(T::shape_new(res))
}
pub fn create_at_hook<'a, P: 'a + AsRef<Path>>(
&self,
path: P,
hook: &'a dyn Fn(PathBuf, bool),
) -> Result<T, error::Error> {
let target = T::shape_describe();
let mut path_buf = PathBuf::new();
path_buf.push(path);
let res = create_shape_inst(path_buf, target, true, Some(hook))?;
Ok(T::shape_new(res))
}
pub fn validate<'a, P: 'a + AsRef<Path>>(&self, path: P) -> Result<(), Errors> {
let mut errors: Errors = vec![];
let dir = Dir::new(path);
if let Err(e) = dir {
errors.push(e);
return Err(errors);
}
let dir = dir.unwrap();
let target = T::shape_describe();
let errors = validate_dir(dir, target);
if errors.len() > 0 {
return Err(errors);
}
Ok(())
}
}
fn check_pattern(pattern: &&str, path: OsString) -> bool {
let pattern = pattern.replace(".", "\\.").replace("*", ".*");
let regex = regex::Regex::new(pattern.as_str()).unwrap();
if !regex.is_match(path.to_string_lossy().as_ref()) {
return false;
}
true
}
use error::{Error, ErrorKind};
type Errors = Vec<Error>;
fn validate_dir(dir: Dir, target: &'static ShapeSchemaStatic<'static>) -> Errors {
let mut errors: Errors = vec![];
if !dir.exists() {
errors.push(Error::new_from_kind(ErrorKind::NotFound).set_path(dir.path.clone()));
return errors;
}
for item in target {
match item {
ShapeItemStatic::File(_, name) => {
if !dir.entry_exists(name) {
errors.push(Error::new_from_kind(ErrorKind::NotFound).set_path(dir.path.join(name)));
}
}
ShapeItemStatic::DirectoryPattern(_, name, pattern) => {
if !dir.entry_exists(name) {
errors.push(Error::new_from_kind(ErrorKind::NotFound).set_path(dir.path.join(name)));
continue;
}
let sub_dir = dir.get_dir(name);
if let Err(e) = sub_dir {
errors.push(e);
continue;
}
let sub_dir = sub_dir.unwrap();
let sub_dir_read = sub_dir.read();
if let Err(e) = sub_dir_read {
errors.push(e);
continue;
}
let sub_dir_read = sub_dir_read.unwrap();
for entry in sub_dir_read {
if !entry.path().is_file() {
errors.push(Error::new_from_kind(ErrorKind::InvalidFolder).set_path(entry.path()));
continue;
}
if !check_pattern(pattern, entry.file_name()) {
errors.push(
Error::new2(
ErrorKind::InvalidFile,
format!("file doesn't match pattern \"{}\"", pattern),
)
.set_path(entry.path()),
);
continue;
}
}
}
ShapeItemStatic::DirectorySchema(_, name, schema) => {
if !dir.entry_exists(name) {
errors.push(Error::new_from_kind(ErrorKind::NotFound).set_path(dir.path.join(name)));
continue;
}
let dir_get_dir = dir.get_dir(name);
if let Err(e) = dir_get_dir {
errors.push(e);
continue;
}
let dir_get_dir = dir_get_dir.unwrap();
errors.append(&mut validate_dir(dir_get_dir, schema));
}
}
}
errors
}
fn create_shape_inst(
path_buf: PathBuf,
target_shape: &[ShapeItemStatic<'_>],
create: bool,
hook: Option<&dyn Fn(PathBuf, bool)>,
) -> Result<ShapeInst, error::Error> {
let mut res: ShapeInst = vec![];
for shape_item in target_shape {
match shape_item {
ShapeItemStatic::File(_, name) => {
let file = File::new(path_buf.join(name))?;
if create {
file.create()?;
if let Some(hook_fn) = hook {
hook_fn(path_buf.join(name), true);
}
}
res.push(ShapeInstItem::File(file));
}
ShapeItemStatic::DirectoryPattern(_, name, _) => {
let dir = Dir::new(path_buf.join(name))?;
if create {
dir.create()?;
if let Some(hook_fn) = hook {
hook_fn(path_buf.join(name), true);
}
}
res.push(ShapeInstItem::Directory(dir));
}
ShapeItemStatic::DirectorySchema(_, name, schema) => {
let dir = Dir::new(path_buf.join(name))?;
if create {
dir.create()?;
if let Some(hook_fn) = hook {
hook_fn(path_buf.join(name), true);
}
}
let child = create_shape_inst(path_buf.join(name), schema, create, hook)?;
res.push(ShapeInstItem::ShapedDirectory(child));
}
}
}
Ok(res)
}
impl std::fmt::Debug for ShapeItemStatic<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ShapeItemStatic::File(_, name) => write!(f, "File('{}')", name),
ShapeItemStatic::DirectoryPattern(_, name, pattern) => {
write!(f, "Dir('{}', FilePattern({}))", name, pattern)
}
ShapeItemStatic::DirectorySchema(_, name, inner_schema) => {
write!(f, "Dir('{}',", name)?;
if inner_schema.len() > 0 {
let mut fo = f.debug_struct("");
for schema in *inner_schema {
fo.field(schema.identifier(), schema);
}
fo.finish()?;
} else {
write!(f, " {{}}")?;
}
write!(f, ")")
}
}
}
}
impl<T: ShapeDescribe> std::fmt::Debug for Shape<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut formatter = f.debug_struct("Shape");
for schema in T::shape_describe() {
formatter.field(schema.identifier(), &schema);
}
formatter.finish()
}
}