use std::path::PathBuf;
use std::time::Duration;
use anyhow::Result;
use clap::{Args, Parser, Subcommand, ValueEnum};
use crate::config::Config;
use crate::fmt;
use crate::humantime;
use crate::project::{Filter, Sort};
use crate::scan::ScanOptions;
#[derive(Debug, Parser)]
#[command(
name = "rusty-broom",
version,
about = "Reclaim disk space from build artifacts of projects you stopped working on",
long_about = "rusty-broom finds projects under one or more roots, works out how long \
ago each was touched, and removes the build artifacts and dependency \
directories of the stale ones.\n\n\
Only paths that git itself ignores are ever deleted, so tracked files \
are never at risk. Run without a subcommand to open the interactive UI.",
after_help = "Examples:\n \
rusty-broom ~/dev interactive UI over ~/dev\n \
rusty-broom scan ~/dev -o 6mo list projects idle for half a year\n \
rusty-broom clean ~/dev -o 1y -m 500M delete the big, year-old ones\n \
rusty-broom types show the project catalogue"
)]
pub struct Cli {
#[arg(short, long, global = true, value_name = "FILE")]
pub config: Option<PathBuf>,
#[command(subcommand)]
pub command: Option<Command>,
#[arg(value_name = "ROOT")]
pub roots: Vec<PathBuf>,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Scan(ScanArgs),
Clean(CleanArgs),
Tui(TuiArgs),
Types,
Config {
#[command(subcommand)]
action: ConfigAction,
},
}
#[derive(Debug, Subcommand)]
pub enum ConfigAction {
Init {
#[arg(long)]
force: bool,
},
Path,
Show,
}
#[derive(Debug, Args, Clone)]
pub struct Selection {
#[arg(value_name = "ROOT")]
pub roots: Vec<PathBuf>,
#[arg(short = 'o', long, value_name = "DURATION")]
pub older_than: Option<String>,
#[arg(short = 'm', long, value_name = "SIZE")]
pub min_size: Option<String>,
#[arg(short = 't', long = "type", value_name = "NAME")]
pub types: Vec<String>,
#[arg(short = 'q', long, value_name = "TEXT")]
pub query: Option<String>,
#[arg(long, value_name = "N")]
pub max_depth: Option<usize>,
#[arg(short = 'a', long)]
pub all: bool,
#[arg(long)]
pub nested: bool,
#[arg(long)]
pub no_git_check: bool,
}
#[derive(Debug, Args)]
pub struct ScanArgs {
#[command(flatten)]
pub selection: Selection,
#[arg(short = 's', long, value_enum, default_value_t = SortArg::Size)]
pub sort: SortArg,
#[arg(short = 'n', long, value_name = "N")]
pub limit: Option<usize>,
#[arg(short = 'l', long)]
pub long: bool,
#[arg(long)]
pub json: bool,
}
#[derive(Debug, Args)]
pub struct CleanArgs {
#[command(flatten)]
pub selection: Selection,
#[arg(short = 'y', long)]
pub yes: bool,
#[arg(long)]
pub dry_run: bool,
}
#[derive(Debug, Args)]
pub struct TuiArgs {
#[command(flatten)]
pub selection: Selection,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum SortArg {
Size,
Age,
Path,
Type,
}
impl From<SortArg> for Sort {
fn from(value: SortArg) -> Self {
match value {
SortArg::Size => Sort::Size,
SortArg::Age => Sort::Age,
SortArg::Path => Sort::Path,
SortArg::Type => Sort::Type,
}
}
}
impl Selection {
pub fn with_roots(roots: Vec<PathBuf>) -> Self {
Self {
roots,
older_than: None,
min_size: None,
types: Vec::new(),
query: None,
max_depth: None,
all: false,
nested: false,
no_git_check: false,
}
}
pub fn roots_or_cwd(&self) -> Result<Vec<PathBuf>> {
if self.roots.is_empty() {
Ok(vec![std::env::current_dir()?])
} else {
Ok(self.roots.clone())
}
}
pub fn older_than(&self, config: &Config) -> Result<Duration> {
if self.all {
return Ok(Duration::ZERO);
}
match &self.older_than {
Some(text) => humantime::parse_duration(text),
None => config.default_older_than(),
}
}
pub fn filter(&self, config: &Config) -> Result<Filter> {
Ok(Filter {
older_than: self.older_than(config)?,
min_size: match &self.min_size {
Some(text) => fmt::parse_bytes(text)?,
None => 0,
},
query: self.query.clone().unwrap_or_default(),
with_artifacts_only: true,
})
}
pub fn scan_options(&self, config: &Config) -> Result<ScanOptions> {
config.validate_type_names(&self.types)?;
let mut options =
ScanOptions::from_config(config, self.roots_or_cwd()?, &self.types, self.max_depth);
if self.nested {
options.descend_into_projects = true;
}
Ok(options)
}
}