use std::{error::Error, fs, path::Path};
#[derive(Debug, PartialEq)]
pub struct FilePath {
pub input_path: String,
pub output_path: String,
}
#[derive(PartialEq)]
enum PathType {
File,
Directory,
}
fn get_pathtype(input_path: &str) -> Result<PathType, Box<dyn Error>> {
let input_metadata = fs::metadata(input_path)?;
if input_metadata.is_file() {
return Ok(PathType::File);
} else if input_metadata.is_dir() || input_metadata.is_symlink() {
return Ok(PathType::Directory);
}
Err("input path is not an existing file or directory".into())
}
pub fn extend_dir_path<'a>(dir_path: &'a str, file_path: &'a str) -> String {
format!("{}/{}", dir_path, file_path)
}
pub fn get_filepaths(
input_path: String,
output_path: String,
) -> Result<Vec<FilePath>, Box<dyn Error>> {
let mut filepaths = Vec::<FilePath>::new();
let pathtype = get_pathtype(&input_path)?;
if pathtype == PathType::File {
let path = FilePath {
input_path,
output_path,
};
filepaths.push(path);
return Ok(filepaths);
}
let dirpaths = fs::read_dir(&input_path)?;
for entry in dirpaths {
let filename = match entry?.file_name().into_string() {
Ok(filename) => filename,
Err(_) => return Err("could not parse file".into()),
};
let ext_input_path = extend_dir_path(&input_path, &filename);
let ext_output_path = extend_dir_path(&output_path, &filename);
let mut paths = get_filepaths(ext_input_path, ext_output_path)?;
filepaths.append(&mut paths);
}
Ok(filepaths)
}
pub fn read_file(filepath: &str) -> Result<Vec<u8>, Box<dyn Error>> {
let contents = fs::read(filepath)?;
Ok(contents)
}
pub fn enable_write_to_file(filepath: &str) -> Result<(), Box<dyn Error>> {
let mut permissions = fs::metadata(filepath)?.permissions();
permissions.set_readonly(false);
fs::set_permissions(filepath, permissions)?;
Ok(())
}
pub fn write_file(filepath: &str, contents: Vec<u8>) -> Result<(), Box<dyn Error>> {
let path = Path::new(filepath);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let _ = enable_write_to_file(filepath);
println!("Writing contents to {filepath}");
fs::write(path, contents)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_read_file_contents() {
let contents = read_file("./tests/sample/test_file.txt").unwrap();
let exp_contents = b"\
my text
second line\n"
.to_vec();
assert_eq!(contents, exp_contents);
}
#[test]
fn test_write_file_only() {
let contents = b"foo bar baz".to_vec();
let path = "./tests/sample/written_file.txt";
write_file(path, contents).unwrap();
fs::remove_file(path).unwrap();
}
#[test]
fn test_write_readonly_file() {
let original_contents = b"foo bar baz".to_vec();
let path = "./tests/sample/written_file.txt";
write_file(path, original_contents).unwrap();
let updated_contents = b"baz bar foo".to_vec();
let mut permissions = fs::metadata(path).unwrap().permissions();
permissions.set_readonly(true);
fs::set_permissions(path, permissions).unwrap();
write_file(path, updated_contents).unwrap();
fs::remove_file(path).unwrap();
}
#[test]
fn test_extend_dir_path() {
let dir_path = "./tests/directory";
let file_path = extend_dir_path(&dir_path, "foo/bar.txt");
assert_eq!(file_path, "./tests/directory/foo/bar.txt");
}
#[test]
fn test_write_file_with_dirs() {
let contents = b"file contents".to_vec();
let dir_path = "./tests/new_dir";
let file_path = extend_dir_path(&dir_path, "new_file.txt");
write_file(&file_path, contents.clone()).unwrap();
let read_contents = read_file(&file_path).unwrap();
assert_eq!(read_contents, contents);
fs::remove_dir_all(&dir_path).unwrap();
}
#[test]
fn test_get_filepaths() {
let contents = b"".to_vec();
let dir_path = String::from("./tests/filepaths");
let input_path = extend_dir_path(&dir_path, "input");
let output_path = extend_dir_path(&dir_path, "output");
let inner_file_path = extend_dir_path(&input_path, "dir1/dir2/inner_file.txt");
write_file(&inner_file_path, contents.clone()).unwrap();
let base_file_path = extend_dir_path(&input_path, "base.txt");
write_file(&base_file_path, contents.clone()).unwrap();
let filepaths = get_filepaths(input_path, output_path).unwrap();
assert_eq!(
filepaths[0],
FilePath {
input_path: String::from("./tests/filepaths/input/dir1/dir2/inner_file.txt"),
output_path: String::from("./tests/filepaths/output/dir1/dir2/inner_file.txt"),
}
);
assert_eq!(
filepaths[1],
FilePath {
input_path: String::from("./tests/filepaths/input/base.txt"),
output_path: String::from("./tests/filepaths/output/base.txt"),
}
);
fs::remove_dir_all(&dir_path).unwrap();
}
#[test]
#[should_panic]
fn test_nonexistent_input() {
let input_path = String::from("./tests/non/existent/file.txt");
let output_path = String::from("./tests");
get_filepaths(input_path, output_path).unwrap();
}
}