use crate::Ctx;
use crate::scan::Matcher;
use serde::Serialize;
use std::path::{Path, PathBuf};
#[derive(Serialize, Clone, Copy, Debug, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Regen {
Free,
Slow,
Network,
}
pub enum Root {
Home(&'static str),
Tmp,
}
pub struct Rule {
pub id: &'static str,
pub root: Root,
pub matcher: Matcher,
pub min_age_days: u64,
pub regen: Regen,
pub opt_in: bool,
}
impl Rule {
pub fn busy_scope<'a>(&self, path: &'a Path) -> &'a Path {
match self.matcher {
Matcher::IdleArtifacts => path.parent().unwrap_or(path),
_ => path,
}
}
pub fn root_path(&self, ctx: &Ctx) -> PathBuf {
match self.root {
Root::Home(rel) => ctx.home.join(rel),
Root::Tmp => ctx.tmp.clone(),
}
}
}
pub static RULES: &[Rule] = &[
Rule {
id: "xdg-cache",
root: Root::Home(".cache"),
matcher: Matcher::Children {
exclude: &["pip", "uv"],
},
min_age_days: 7,
regen: Regen::Network,
opt_in: false,
},
Rule {
id: "pip",
root: Root::Home(".cache/pip"),
matcher: Matcher::Whole,
min_age_days: 1,
regen: Regen::Network,
opt_in: false,
},
Rule {
id: "uv",
root: Root::Home(".cache/uv"),
matcher: Matcher::Whole,
min_age_days: 1,
regen: Regen::Network,
opt_in: false,
},
Rule {
id: "npm",
root: Root::Home(".npm/_cacache"),
matcher: Matcher::Whole,
min_age_days: 1,
regen: Regen::Network,
opt_in: false,
},
Rule {
id: "cargo",
root: Root::Home(".cargo/registry/cache"),
matcher: Matcher::Whole,
min_age_days: 1,
regen: Regen::Network,
opt_in: false,
},
Rule {
id: "pycache",
root: Root::Home(""),
matcher: Matcher::NamedDirs("__pycache__"),
min_age_days: 0,
regen: Regen::Free,
opt_in: true,
},
Rule {
id: "artifacts",
root: Root::Home(""),
matcher: Matcher::IdleArtifacts,
min_age_days: 30,
regen: Regen::Slow,
opt_in: true,
},
Rule {
id: "tmp",
root: Root::Tmp,
matcher: Matcher::Children { exclude: &[] },
min_age_days: 7,
regen: Regen::Free,
opt_in: false,
},
];
pub fn select(ids: &[String]) -> Result<Vec<&'static Rule>, String> {
if ids.is_empty() {
return Ok(RULES.iter().filter(|r| !r.opt_in).collect());
}
ids.iter()
.map(|id| {
RULES.iter().find(|r| r.id == id).ok_or_else(|| {
let valid: Vec<_> = RULES.iter().map(|r| r.id).collect();
format!("unknown category '{id}'; valid: {}", valid.join(", "))
})
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn select_default_skips_opt_in() {
let ids: Vec<_> = select(&[]).unwrap().iter().map(|r| r.id).collect();
assert_eq!(ids, ["xdg-cache", "pip", "uv", "npm", "cargo", "tmp"]);
}
#[test]
fn select_opt_in_by_name() {
let ids: Vec<_> = select(&["pycache".into()])
.unwrap()
.iter()
.map(|r| r.id)
.collect();
assert_eq!(ids, ["pycache"]);
}
#[test]
fn select_unknown_errs() {
assert_eq!(
select(&["nope".into()]).err(),
Some(
"unknown category 'nope'; valid: xdg-cache, pip, uv, npm, cargo, pycache, artifacts, tmp"
.into()
)
);
}
}