use std::{fs, path::PathBuf};
pub enum ExpansionFailed {
IoError((PathBuf, String)),
SymlinkNotFollowed(PathBuf),
FolderNotRecursed(PathBuf),
}
#[cfg(unix)]
fn get_ids(metadata: &fs::Metadata) -> (u64, u64) {
use std::os::unix::fs::MetadataExt;
(metadata.dev(), metadata.ino())
}
pub fn expand_to_files(
targets: &Vec<PathBuf>,
output_file: &Option<PathBuf>,
) -> (Vec<PathBuf>, Vec<ExpansionFailed>) {
let mut file_paths: Vec<PathBuf> = Vec::new();
let mut errors: Vec<ExpansionFailed> = Vec::new();
#[cfg(unix)]
let output_file_ids: Option<(u64, u64)> = output_file
.as_ref()
.and_then(|p| fs::metadata(p).ok())
.map(|m| get_ids(&m));
#[cfg(not(unix))]
let output_file_ids: Option<(u64, u64)> = None;
for target in targets {
collect_files(target, &output_file_ids, &mut file_paths, &mut errors);
}
(file_paths, errors)
}
fn collect_files(
target: &PathBuf,
output_file_ids: &Option<(u64, u64)>,
file_paths: &mut Vec<PathBuf>,
errors: &mut Vec<ExpansionFailed>,
) {
let metadata = match target.metadata() {
Ok(m) => m,
Err(e) => {
errors.push(ExpansionFailed::IoError((
target.clone(),
e.to_string(),
)));
return;
}
};
#[cfg(unix)]
if output_file_ids.is_some() {
let target_ids = get_ids(&metadata);
if target_ids == *output_file_ids.as_ref().unwrap() {
return;
}
}
if metadata.is_symlink() {
errors.push(ExpansionFailed::SymlinkNotFollowed(target.clone()));
} else if metadata.is_file() {
file_paths.push(target.clone());
} else if metadata.is_dir() {
let dir_entries = match fs::read_dir(target) {
Ok(ds) => ds,
Err(_) => {
errors.push(ExpansionFailed::FolderNotRecursed(target.clone()));
return;
}
};
for dir_entry in dir_entries {
let dir_entry = match dir_entry {
Ok(d) => d,
Err(e) => {
errors.push(ExpansionFailed::IoError((
target.clone(),
e.to_string(),
)));
return;
}
};
collect_files(
&dir_entry.path(),
output_file_ids,
file_paths,
errors,
);
}
}
}