1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//! Command-line arguments.
#![deny(missing_docs)]
use std::path::PathBuf;
use clap::Parser;
use regex::Regex;
use time::Duration;
use crate::build_tools::BuildToolKind;
/// Options
#[derive(Parser, Debug)]
#[clap(version, about, long_about=None)]
pub struct Cli {
/// JSON output (default when stdout is piped)
///
/// When present, either `--dry-run` or `--yes` is required.
#[clap(long)]
pub json: bool,
/// Lists projects only.
#[clap(short, long)]
pub list: bool,
/// Projects that were modified more recently than this are ignored.
/// With `--list`, this defaults to 0, otherwise to one month.
/// Examples: 1d = a day, 2w = two weeks, 1m = a month, 1y = a year.
#[clap(value_name(r"DURATION"), short, long, parse(try_from_str=parse_duration))]
pub min_stale: Option<Duration>,
/// Only consider projects that use the given build tool.
/// Use more than once for multiple project types.
/// By default, all known project types are considered.
///
/// For example, to consider Cargo and NPM projects:
///
/// makeclean -t cargo -t npm
#[clap(short = 't', long = "type", arg_enum)]
pub types: Vec<BuildToolKind>,
/// Dry run - prints what would happen but doesn't actually remove/change anything.
#[clap(short = 'n', long)]
pub dry_run: bool,
/// Automatically execute without asking, i.e., skipping the prompt.
#[clap(long)]
pub yes: bool,
/// Additionally compress cleaned projects.
///
/// After cleaning a project, its contents are moved into a tar.xz file. To
/// restore the project, use `tar` (which is probably already installed on
/// your system):
///
/// cd path/to/project && tar -xaf project-name.tar.xz && rm project-name.tar.xz
#[clap(short = 'z', long)]
pub archive: bool,
/// Recursively searches for project in these directories
#[clap(default_value = ".")]
pub directories: Vec<PathBuf>,
}
fn parse_duration(s: &str) -> anyhow::Result<Duration> {
let captures = Regex::new(r"^(?P<n>\d+)(?P<unit>[dDwWmMyY])?$")
.unwrap()
.captures(s)
.ok_or_else(|| {
anyhow::format_err!(
r#"Cannot parse {:?}. Try "1d" for a day, "1w" for a week, "1m" for a month or "1y" for a year."#,
s
)
})?;
let n = captures
.name("n")
.and_then(|n| n.as_str().parse::<i64>().ok())
.unwrap();
assert!(n >= 0, "Duration cannot be negative");
let unit = captures.name("unit").map(|u| u.as_str()).unwrap_or("d");
let duration = match unit {
"d" | "D" => Duration::days(n),
"w" | "W" => Duration::weeks(n),
"m" | "M" => Duration::days(7 * n * 52 / 12),
"y" | "Y" => Duration::weeks(n * 52),
_ => unreachable!("the regex should make sure of that"),
};
Ok(duration)
}