use std::error::Error;
use std::fmt;
use std::io::{Write, stdout};
use regex::Regex;
use crate::color_templates::WARN_TEMPLATE_NO_BG_COLOR;
use crate::os_specifics;
use anyhow::Result;
#[derive(Debug)]
pub enum FilenameError {
InvalidOnWindows(String),
InvalidOnUnix(String),
ReservedFilenameOnWindows,
EndsWithADot,
}
impl Error for FilenameError {}
impl fmt::Display for FilenameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FilenameError::InvalidOnWindows(invalid_chars) => {
write!(f, "Following chars are NOT allowed: {invalid_chars}")
}
FilenameError::InvalidOnUnix(invalid_chars) => {
write!(f, "Following chars are NOT allowed: {invalid_chars}")
}
FilenameError::ReservedFilenameOnWindows => {
write!(
f,
"This is a reserved file name on Windows and cannot be used",
)
}
FilenameError::EndsWithADot => {
write!(f, "File names on Windows must not end with a dot",)
}
}
}
}
pub fn is_filename_valid_on_unix(filename: &str) -> bool {
let regex_invalid_chars = Regex::new(r"^[^:/\\]+$").unwrap();
regex_invalid_chars.is_match(filename)
}
pub fn is_filename_valid_on_windows(filename: &str) -> bool {
let regex_invalid_chars = Regex::new(r#"^[^<>:"/\\|?*]+$"#).unwrap();
regex_invalid_chars.is_match(filename)
}
pub fn is_reserved_filename_on_windows(filename: &str) -> bool {
let regex_reserved_filenames =
Regex::new(r"^(?i:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\..+)?$").unwrap();
regex_reserved_filenames.is_match(filename)
}
pub fn validate_filename(os_type: &os_specifics::OS, filename: &str) -> Result<()> {
if filename.trim().is_empty() {
return Err(anyhow::anyhow!("Filename can not be empty"));
}
match os_type {
os_specifics::OS::Linux | os_specifics::OS::MacOs => {
if !is_filename_valid_on_unix(filename) {
let file_name_err = FilenameError::InvalidOnUnix(
os_specifics::UNIX_INVALID_FILE_NAME_CHARS.to_string(),
);
return Err(file_name_err.into());
}
}
os_specifics::OS::Windows => {
if filename.ends_with('.') {
return Err(FilenameError::EndsWithADot.into());
} else {
if is_reserved_filename_on_windows(filename) {
return Err(FilenameError::ReservedFilenameOnWindows.into());
} else if !is_filename_valid_on_windows(filename) {
let file_name_err = FilenameError::InvalidOnWindows(
os_specifics::WINDOWS_INVALID_FILE_NAME_CHARS.to_string(),
);
return Err(file_name_err.into());
}
}
}
}
Ok(())
}
pub fn enter_and_verify_file_name(os_type: &os_specifics::OS) -> Result<String> {
let mut file_name = String::new();
loop {
print!("\t--->: ");
stdout().flush()?;
match std::io::stdin().read_line(&mut file_name) {
Ok(_) => {
let file_name_trim = file_name.trim().trim_end_matches(&['\r', '\n'][..]);
match validate_filename(os_type, file_name_trim) {
Ok(_) => {
break Ok(file_name_trim.to_string());
}
Err(filename_err) => {
println!(
"{} - {}",
WARN_TEMPLATE_NO_BG_COLOR.output("Invalid file name"),
filename_err
);
file_name.clear();
}
}
}
Err(err) => {
let err_msg = format!("{err:?}");
break Err(anyhow::anyhow!(err_msg));
}
}
}
}