use {
std::{
fs::{self, File},
io::{Error, ErrorKind},
path::{Path, PathBuf},
},
crate::{Args, Result},
};
#[derive(Debug, Eq, PartialEq, Hash)]
pub enum PathKind {
Directory,
File,
}
#[derive(Debug, Eq, PartialEq, Hash)]
pub enum TakeOption {
MustExist,
DenyExisting,
Take {
make: bool,
},
}
pub fn get_path(args: &Args, keys: &[&str], kind: PathKind, option: TakeOption) -> Result<Option<PathBuf>> {
match args.get::<PathBuf>(keys)? {
Some(path) => handle_path(path, kind, option).map(|p| Some(p)),
None => Ok(None),
}
}
pub fn take_path(args: &mut Args, keys: &[&str], kind: PathKind, option: TakeOption) -> Result<Option<PathBuf>> {
match args.take::<PathBuf>(keys)? {
Some(path) => handle_path(path, kind, option).map(|p| Some(p)),
None => Ok(None),
}
}
pub fn handle_path<P>(path: P, kind: PathKind, option: TakeOption) -> Result<P> where P: AsRef<Path> {
{
let path = path.as_ref();
if match option {
TakeOption::MustExist => true,
TakeOption::Take { .. } if path.exists() => true,
_ => false,
} {
match kind {
PathKind::Directory => if path.is_dir() == false {
return Err(Error::new(ErrorKind::InvalidInput, format!("Not a directory: {:?}", path)));
},
PathKind::File => if path.is_file() == false {
return Err(Error::new(ErrorKind::InvalidInput, format!("Not a file: {:?}", path)));
},
};
}
match option {
TakeOption::MustExist => if path.exists() == false {
return Err(Error::new(ErrorKind::NotFound, format!("Not found: {:?}", path)));
},
TakeOption::DenyExisting => if path.exists() {
return Err(Error::new(ErrorKind::AlreadyExists, format!("Already exists: {:?}", path)));
},
TakeOption::Take { make } => if make && path.exists() == false {
match kind {
PathKind::Directory => fs::create_dir_all(&path)?,
PathKind::File => drop(File::create(&path)?),
};
},
};
}
Ok(path)
}
#[test]
fn test_take_path() {
const KEYS: &[&str] = &["--path"];
let mut args = crate::parse_strings([&KEYS[0], file!()].iter()).unwrap();
assert!(take_path(&mut args, KEYS, PathKind::File, TakeOption::MustExist).unwrap().is_some());
assert!(args.is_empty());
let mut args = crate::parse_strings([&KEYS[0], file!()].iter()).unwrap();
assert_eq!(get_path(&mut args, KEYS, PathKind::File, TakeOption::DenyExisting).unwrap_err().kind(), ErrorKind::AlreadyExists);
assert!(args.is_empty() == false);
let mut args = crate::parse_strings([&KEYS[0], file!()].iter()).unwrap();
assert_eq!(take_path(&mut args, KEYS, PathKind::Directory, TakeOption::Take { make: false }).unwrap_err().kind(), ErrorKind::InvalidInput);
assert!(args.is_empty());
}
#[cfg(unix)]
const INVALID_FILE_NAME_CHARS: &[char] = &[];
#[cfg(not(unix))]
const INVALID_FILE_NAME_CHARS: &[char] = &['^', '?', '%', '*', ':', '|', '"', '<', '>'];
pub fn verify_ne_file_name<S>(name: S, max_len: Option<usize>) -> Result<S> where S: AsRef<str> {
{
let name = name.as_ref();
if name.len() > max_len.unwrap_or(1024) {
return Err(Error::new(ErrorKind::InvalidInput, "File name is too long"));
}
if name.is_empty() {
return Err(Error::new(ErrorKind::InvalidInput, "File name is empty"));
}
if name.trim().len() != name.len() {
return Err(Error::new(ErrorKind::InvalidInput, "File name contains leading or trailing white space(s)"));
}
if name.chars().any(|c| if c.is_ascii() {
match c as u8 {
0..=31 | 127 | b'\\' | b'/' => true,
_ => INVALID_FILE_NAME_CHARS.contains(&c),
}
} else {
false
}) {
return Err(Error::new(ErrorKind::InvalidInput, "File name contains invalid character(s)"));
}
}
Ok(name)
}