use std::path::{Path, PathBuf};
use super::Entry;
pub(super) fn within_root(path: &Path, root: Option<&Path>) -> bool {
root.is_none_or(|root| path.starts_with(root))
}
pub(super) fn confined_selection(
path: &Path,
root: Option<&Path>,
) -> Option<PathBuf> {
let Some(root) = root else {
return Some(path.to_path_buf());
};
let canonical = path
.canonicalize()
.inspect_err(|error| {
log::warn!(
"could not canonicalize {}: {error}; refusing the selection",
path.display()
);
})
.ok()?;
within_root(&canonical, Some(root)).then_some(canonical)
}
pub(super) fn confine(dir: PathBuf, root: Option<&Path>) -> PathBuf {
let Some(root) = root else {
return dir;
};
let canonical = match dir.canonicalize() {
Ok(canonical) => canonical,
Err(error) => {
log::warn!(
"could not canonicalize {}: {error}; checking the path as given",
dir.display()
);
dir
}
};
if within_root(&canonical, Some(root)) {
canonical
} else {
root.to_path_buf()
}
}
pub(super) fn first_existing(start: &Path) -> PathBuf {
let mut candidate = Some(start);
while let Some(path) = candidate {
if path.is_dir() {
return path.to_path_buf();
}
candidate = path.parent();
}
PathBuf::from(".")
}
pub(super) fn read_entries(
dir: &Path,
allow_files: bool,
show_hidden: bool,
) -> Vec<Entry> {
let read = match std::fs::read_dir(dir) {
Ok(read) => read,
Err(error) => {
log::warn!("could not read directory {}: {error}", dir.display());
return Vec::new();
}
};
let mut entries: Vec<Entry> = read
.flatten()
.filter_map(|item| {
let path = item.path();
let is_dir = path.is_dir();
if !is_dir && !allow_files {
return None;
}
let name = item.file_name().to_string_lossy().into_owned();
if !show_hidden && is_hidden(&name) {
return None;
}
Some(Entry { name, path, is_dir })
})
.collect();
entries.sort_by(|a, b| {
b.is_dir
.cmp(&a.is_dir)
.then_with(|| a.name.to_lowercase().cmp(&b.name.to_lowercase()))
});
entries
}
pub(super) fn is_hidden(name: &str) -> bool {
name.starts_with('.')
}