use std::{env,
fs,
fs::File,
io::{ErrorKind, Write},
path::{Path, PathBuf}};
use miette::Diagnostic;
use thiserror::Error;
use crate::ok;
#[macro_export]
macro_rules! fs_paths {
(with_root: $path:expr=> $($x:expr)=>*) => {{
let mut it: std::path::PathBuf = $path.to_path_buf();
$(
it = it.join($x);
)*
it
}};
(with_empty_root=> $($x:expr)=>*) => {{
use std::path::{PathBuf};
let mut it = PathBuf::new();
$(
it = it.join($x);
)*
it
}}
}
#[macro_export]
macro_rules! fs_paths_exist {
($($x:expr),*) => {'block: {
$(
if !std::fs::metadata($x).is_ok() {
break 'block false;
};
)*
true
}};
}
#[derive(Debug, Error, Diagnostic)]
pub enum FsOpError {
#[error("File does not exist: {0}")]
FileDoesNotExist(String),
#[error("Directory does not exist: {0}")]
DirectoryDoesNotExist(String),
#[error("File already exists: {0}")]
FileAlreadyExists(String),
#[error("Directory already exists: {0}")]
DirectoryAlreadyExists(String),
#[error("Insufficient permissions: {0}")]
PermissionDenied(String),
#[error("Invalid name: {0}")]
InvalidName(String),
#[error("Failed to perform fs operation directory: {0}")]
IoError(#[from] std::io::Error),
}
pub type FsOpResult<T> = miette::Result<T, FsOpError>;
pub fn directory_exists(directory: impl AsRef<Path>) -> bool {
fs::metadata(directory).is_ok_and(|metadata| metadata.is_dir())
}
pub fn file_exists(file: impl AsRef<Path>) -> bool {
fs::metadata(file).is_ok_and(|metadata| metadata.is_file())
}
pub fn try_directory_exists(directory_path: impl AsRef<Path>) -> FsOpResult<bool> {
match fs::metadata(directory_path) {
Ok(metadata) => {
ok!(metadata.is_dir())
}
Err(err) => match err.kind() {
ErrorKind::NotFound => {
FsOpResult::Err(FsOpError::DirectoryDoesNotExist(err.to_string()))
}
ErrorKind::InvalidInput => {
FsOpResult::Err(FsOpError::InvalidName(err.to_string()))
}
_ => FsOpResult::Err(FsOpError::IoError(err)),
},
}
}
pub fn try_file_exists(file_path: impl AsRef<Path>) -> FsOpResult<bool> {
match fs::metadata(file_path) {
Ok(metadata) => ok!(metadata.is_file()),
Err(err) => match err.kind() {
ErrorKind::NotFound => {
FsOpResult::Err(FsOpError::FileDoesNotExist(err.to_string()))
}
ErrorKind::InvalidInput => {
FsOpResult::Err(FsOpError::InvalidName(err.to_string()))
}
_ => FsOpResult::Err(FsOpError::IoError(err)),
},
}
}
pub fn try_pwd() -> FsOpResult<PathBuf> {
match env::current_dir() {
Ok(pwd) => FsOpResult::Ok(pwd),
Err(err) => match err.kind() {
ErrorKind::NotFound => {
FsOpResult::Err(FsOpError::DirectoryDoesNotExist(err.to_string()))
}
_ => FsOpResult::Err(FsOpError::IoError(err)),
},
}
}
pub fn path_as_string(path: &Path) -> String { path.display().to_string() }
pub fn try_write_file(
folder: impl AsRef<Path>,
file_name: impl AsRef<str>,
content: impl AsRef<str>,
) -> FsOpResult<()> {
let file_path = folder.as_ref().join(file_name.as_ref());
match File::create(file_path) {
Ok(mut file) => {
if let Err(err) = file.write_all(content.as_ref().as_bytes()) {
return FsOpResult::Err(FsOpError::IoError(err));
}
ok!()
}
Err(err) => match err.kind() {
ErrorKind::NotFound => {
FsOpResult::Err(FsOpError::DirectoryDoesNotExist(err.to_string()))
}
ErrorKind::PermissionDenied => {
FsOpResult::Err(FsOpError::PermissionDenied(err.to_string()))
}
ErrorKind::InvalidInput => {
FsOpResult::Err(FsOpError::InvalidName(err.to_string()))
}
_ => FsOpResult::Err(FsOpError::IoError(err)),
},
}
}
#[cfg(test)]
mod tests {
use std::os::unix::fs::PermissionsExt as _;
use fs_path::try_pwd;
use super::*;
use crate::{fs_path, serial_preserve_pwd_test, try_create_temp_dir};
serial_preserve_pwd_test!(test_try_write, {
let root = try_create_temp_dir().unwrap();
let content = "Hello, world!";
let file_name = "test_file.txt";
try_write_file(&root, file_name, content).unwrap();
let file_path = root.join(file_name);
assert!(file_path.exists());
assert!(file_path.is_file());
let read_content = fs::read_to_string(&file_path).unwrap();
assert_eq!(read_content, content);
});
serial_preserve_pwd_test!(test_try_pwd, {
let root = try_create_temp_dir().unwrap();
let new_dir = root.join("test_pwd");
fs::create_dir_all(&new_dir).unwrap();
env::set_current_dir(&new_dir).unwrap();
let pwd = try_pwd().unwrap();
assert!(pwd.exists());
assert_eq!(pwd, new_dir);
});
serial_preserve_pwd_test!(test_try_pwd_errors, {
let root = try_create_temp_dir().unwrap();
let no_permissions_dir = root.join("no_permissions_dir");
fs::create_dir_all(&no_permissions_dir).unwrap();
env::set_current_dir(&no_permissions_dir).unwrap();
let mut permissions = fs::metadata(&no_permissions_dir).unwrap().permissions();
permissions.set_mode(0o000);
fs::set_permissions(&no_permissions_dir, permissions).unwrap();
assert!(no_permissions_dir.exists());
let result = try_pwd();
assert!(result.is_ok());
let mut permissions = fs::metadata(&no_permissions_dir).unwrap().permissions();
permissions.set_mode(0o777);
fs::set_permissions(&no_permissions_dir, permissions).unwrap();
fs::remove_dir_all(&no_permissions_dir).unwrap();
let result = try_pwd();
assert!(result.is_err());
assert!(matches!(result, Err(FsOpError::DirectoryDoesNotExist(_))));
});
serial_preserve_pwd_test!(test_fq_path_relative_to_try_pwd, {
let root = try_create_temp_dir().unwrap();
let sub_path = "test_fq_path_relative_to_pwd";
let new_dir = root.join(sub_path);
fs::create_dir_all(&new_dir).unwrap();
env::set_current_dir(&root).unwrap();
println!("Current directory set to: {root}");
println!("Current directory is : {}", try_pwd().unwrap().display());
let fq_path = fs_paths!(with_root: try_pwd().unwrap() => sub_path);
println!("Sub directory created at: {}", fq_path.display());
println!("Sub directory exists : {}", fq_path.exists());
assert!(fq_path.exists());
});
serial_preserve_pwd_test!(test_path_as_string, {
let root = try_create_temp_dir().unwrap();
env::set_current_dir(&root).unwrap();
let fq_path = fs_paths!(with_root: try_pwd().unwrap() => "some_dir");
let fq_path_str = fs_path::path_as_string(&fq_path);
assert_eq!(fq_path_str, fq_path.display().to_string());
});
serial_preserve_pwd_test!(test_try_file_exists, {
let root = try_create_temp_dir().unwrap();
let new_dir = root.join("test_file_exists_dir");
fs::create_dir_all(&new_dir).unwrap();
let new_file = new_dir.join("test_file_exists_file.txt");
fs::write(&new_file, "test").unwrap();
assert!(fs_path::try_file_exists(&new_file).unwrap());
assert!(!fs_path::try_file_exists(&new_dir).unwrap());
fs::remove_dir_all(&new_dir).unwrap();
assert!(fs_path::try_file_exists(&new_file).is_err()); assert!(fs_path::try_file_exists(&new_dir).is_err()); });
serial_preserve_pwd_test!(test_try_file_exists_not_found_error, {
let root = try_create_temp_dir().unwrap();
let new_dir = root.join("test_file_exists_not_found_error");
let result = fs_path::try_file_exists(&new_dir);
assert!(result.is_err());
assert!(matches!(result, Err(FsOpError::FileDoesNotExist(_))));
});
serial_preserve_pwd_test!(test_try_file_exists_invalid_name_error, {
let root = try_create_temp_dir().unwrap();
let new_dir = root.join("test_file_exists_invalid_name_error\0");
let result = fs_path::try_file_exists(&new_dir);
assert!(result.is_err());
assert!(matches!(result, Err(FsOpError::InvalidName(_))));
});
serial_preserve_pwd_test!(test_try_file_exists_permissions_errors, {
let root = try_create_temp_dir().unwrap();
let no_permissions_dir = root.join("no_permissions_dir");
fs::create_dir_all(&no_permissions_dir).unwrap();
let mut permissions = fs::metadata(&no_permissions_dir).unwrap().permissions();
permissions.set_mode(0o000);
fs::set_permissions(&no_permissions_dir, permissions).unwrap();
assert!(no_permissions_dir.exists());
let result = fs_path::try_file_exists(&no_permissions_dir);
assert!(result.is_ok());
let mut permissions = fs::metadata(&no_permissions_dir).unwrap().permissions();
permissions.set_mode(0o777);
fs::set_permissions(&no_permissions_dir, permissions).unwrap();
});
serial_preserve_pwd_test!(test_try_directory_exists, {
let root = try_create_temp_dir().unwrap();
let new_dir = root.join("test_dir_exists_dir");
fs::create_dir_all(&new_dir).unwrap();
let new_file = new_dir.join("test_dir_exists_file.txt");
fs::write(&new_file, "test").unwrap();
assert!(fs_path::try_directory_exists(&new_dir).unwrap());
assert!(!fs_path::try_directory_exists(&new_file).unwrap());
});
serial_preserve_pwd_test!(test_try_directory_exists_not_found_error, {
let root = try_create_temp_dir().unwrap();
let new_dir = root.join("test_dir_exists_not_found_error");
let result = fs_path::try_directory_exists(&new_dir);
assert!(result.is_err());
assert!(matches!(result, Err(FsOpError::DirectoryDoesNotExist(_))));
});
serial_preserve_pwd_test!(test_try_directory_exists_invalid_name_error, {
let root = try_create_temp_dir().unwrap();
let new_dir = root.join("test_dir_exists_invalid_name_error\0");
let result = fs_path::try_directory_exists(&new_dir);
assert!(result.is_err());
assert!(matches!(result, Err(FsOpError::InvalidName(_))));
});
serial_preserve_pwd_test!(test_try_directory_exists_permissions_errors, {
let root = try_create_temp_dir().unwrap();
let no_permissions_dir = root.join("no_permissions_dir");
fs::create_dir_all(&no_permissions_dir).unwrap();
let mut permissions = fs::metadata(&no_permissions_dir).unwrap().permissions();
permissions.set_mode(0o000);
fs::set_permissions(&no_permissions_dir, permissions).unwrap();
assert!(no_permissions_dir.exists());
let result = fs_path::try_directory_exists(&no_permissions_dir);
assert!(result.is_ok());
let mut permissions = fs::metadata(&no_permissions_dir).unwrap().permissions();
permissions.set_mode(0o777);
fs::set_permissions(&no_permissions_dir, permissions).unwrap();
});
}