use std::path::PathBuf;
use std::sync::OnceLock;
use crate::search::output::style::PathDisplay;
#[derive(Debug, Clone)]
pub struct Candidate {
rel_path: PathBuf,
abs_path: PathBuf,
rel_str: OnceLock<String>,
cached_size: Option<u64>,
cached_depth: Option<usize>,
}
impl Candidate {
#[must_use]
pub const fn new(rel_path: PathBuf, abs_path: PathBuf) -> Self {
Self {
rel_path,
abs_path,
rel_str: OnceLock::new(),
cached_size: None,
cached_depth: None,
}
}
#[must_use]
pub const fn with_metadata(
rel_path: PathBuf,
abs_path: PathBuf,
size: Option<u64>,
depth: Option<usize>,
) -> Self {
Self {
rel_path,
abs_path,
rel_str: OnceLock::new(),
cached_size: size,
cached_depth: depth,
}
}
#[must_use]
pub fn rel_path(&self) -> &std::path::Path {
&self.rel_path
}
#[must_use]
pub fn abs_path(&self) -> &std::path::Path {
&self.abs_path
}
#[must_use]
pub fn rel_str(&self) -> &str {
self.rel_str
.get_or_init(|| self.rel_path.to_string_lossy().replace('\\', "/"))
}
#[must_use]
pub fn display_path(&self, display: PathDisplay, path_separator: Option<u8>) -> String {
let raw = match display {
PathDisplay::Absolute => self.abs_path().display().to_string(),
PathDisplay::Relative => self.rel_path().display().to_string(),
};
if let Some(sep) = path_separator {
let mut buf = [0u8; 4];
let sep_str = (sep as char).encode_utf8(&mut buf);
raw.replace(std::path::MAIN_SEPARATOR, sep_str)
} else {
raw
}
}
#[must_use]
pub fn within_depth(&self, max_depth: Option<usize>) -> bool {
max_depth.is_none_or(|d| {
self.cached_depth
.unwrap_or_else(|| self.rel_path.components().count().saturating_sub(1))
<= d
})
}
#[must_use]
pub const fn cached_size(&self) -> Option<u64> {
self.cached_size
}
#[must_use]
pub fn within_filesize(&self, max_filesize: Option<u64>) -> bool {
max_filesize.is_none_or(|limit| {
self.cached_size.map_or_else(
|| std::fs::metadata(&self.abs_path).map_or(true, |m| m.len() <= limit),
|size| size <= limit,
)
})
}
#[must_use]
pub fn total_file_bytes(candidates: &[Self]) -> u64 {
candidates.iter().fold(0u64, |acc, c| {
acc + c
.cached_size
.unwrap_or_else(|| std::fs::metadata(c.abs_path()).map_or(0, |m| m.len()))
})
}
#[must_use]
pub fn matches(&self, filter: &crate::search::filter::CandidateFilter) -> bool {
self.within_depth(filter.max_depth())
&& self.within_filesize(filter.max_filesize())
&& filter.matches_candidate(self)
}
}
impl PartialEq for Candidate {
fn eq(&self, other: &Self) -> bool {
self.rel_path == other.rel_path && self.abs_path == other.abs_path
}
}
impl Eq for Candidate {}