diff --git a/Cargo.lock b/Cargo.lock index 4b918e9..9ce5288 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,15 +11,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi", -] - [[package]] name = "anyhow" version = "1.0.43" @@ -46,16 +37,6 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "atty" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "0.1.7" @@ -122,6 +103,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "cc" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" + [[package]] name = "cfg-if" version = "0.1.10" @@ -159,17 +146,39 @@ dependencies = [ [[package]] name = "clap" -version = "2.33.0" +version = "4.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" dependencies = [ - "ansi_term", - "atty", "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", "strsim", - "textwrap", - "unicode-width", - "vec_map", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", ] [[package]] @@ -241,6 +250,27 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "fastrand" version = "1.8.0" @@ -282,6 +312,21 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "human-size" version = "0.4.2" @@ -297,6 +342,28 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "io-lifetimes" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +dependencies = [ + "libc", + "windows-sys 0.42.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys 0.42.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -324,6 +391,12 @@ version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lock_api" version = "0.4.9" @@ -358,7 +431,7 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.36.1", ] [[package]] @@ -401,6 +474,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + [[package]] name = "output_vt100" version = "0.1.3" @@ -430,7 +509,7 @@ dependencies = [ "libc", "redox_syscall 0.2.16", "smallvec", - "windows-sys", + "windows-sys 0.36.1", ] [[package]] @@ -475,6 +554,30 @@ dependencies = [ "yansi", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.46" @@ -583,6 +686,20 @@ version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2089e4031214d129e201f8c3c8c2fe97cd7322478a0d1cdf78e7029b0042efdb" +[[package]] +name = "rustix" +version = "0.36.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.42.0", +] + [[package]] name = "ryu" version = "1.0.2" @@ -686,9 +803,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "strsim" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" @@ -716,19 +833,19 @@ dependencies = [ ] [[package]] -name = "termtree" -version = "0.2.4" +name = "termcolor" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] [[package]] -name = "textwrap" -version = "0.11.0" +name = "termtree" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] +checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "time" @@ -747,12 +864,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" -[[package]] -name = "unicode-width" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" - [[package]] name = "unindent" version = "0.1.11" @@ -760,10 +871,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" [[package]] -name = "vec_map" -version = "0.8.1" +name = "version_check" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wait-timeout" @@ -820,9 +931,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] @@ -851,43 +962,100 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + [[package]] name = "windows_i686_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + [[package]] name = "windows_i686_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index b09c7ea..d18afe2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/holmgr/cargo-sweep" readme = "README.md" [dependencies] -clap = "2.32.0" +clap = { version = "4.0.32", features = ["derive"] } crossterm = "0.25.0" walkdir = "2" anyhow = "1.0.43" diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..236cdf5 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,163 @@ +use clap::{ArgGroup, Parser}; +use std::path::PathBuf; + +pub fn parse() -> Args { + SweepArgs::parse().into_args() +} + +#[derive(Parser)] +#[command(version, about)] +pub struct SweepArgs { + #[command(subcommand)] + sweep: SweepCommand, +} + +impl SweepArgs { + fn into_args(self) -> Args { + let SweepCommand::Sweep(args) = self.sweep; + args + } +} + +#[derive(clap::Subcommand)] +pub enum SweepCommand { + Sweep(Args), +} + +#[derive(Parser, Debug)] +#[cfg_attr(test, derive(Default, PartialEq))] +#[command( + about, + version, + group( + ArgGroup::new("criterion") + .required(true) + .args(["stamp", "file", "time", "installed", "toolchains", "maxsize"]) + ) +)] +pub struct Args { + /// Path to check + pub path: Option, + + /// Dry run which will not delete any files + #[arg(short, long)] + pub dry_run: bool, + + /// Load timestamp file in the given path, cleaning everything older + #[arg(short, long)] + file: bool, + + #[arg( + long, + help = "The `recursive` flag defaults to ignoring directories \ + that start with a `.`, `.git` for example is unlikely to include a \ + Cargo project, this flag changes it to look in them" + )] + pub hidden: bool, + + /// Keep only artifacts made by Toolchains currently installed by rustup + #[arg(short, long)] + installed: bool, + + /// Remove oldest artifacts until the target directory is below the specified size in MB + /// + /// TODO: consider parsing units like GB, KB + /// https://github.com/holmgr/cargo-sweep/issues/82 + #[arg(short, long, value_name = "MAXSIZE_MB")] + maxsize: Option, + + /// Apply on all projects below the given path + #[arg(short, long)] + pub recursive: bool, + + /// Store timestamp file at the given path, is used by file option + #[arg(short, long)] + stamp: bool, + + /// Number of days backwards to keep + #[arg(short, long)] + time: Option, + + /// Toolchains currently installed by rustup that should have their artifacts kept + #[arg(long, value_delimiter = ',')] + toolchains: Vec, + + /// Turn verbose information on + #[arg(short, long)] + pub verbose: bool, +} + +impl Args { + pub fn criterion(&self) -> Criterion { + match &self { + _ if self.stamp => Criterion::Stamp, + _ if self.file => Criterion::File, + _ if self.installed => Criterion::Installed, + _ if !self.toolchains.is_empty() => Criterion::Toolchains(self.toolchains.clone()), + Self { + time: Some(time), .. + } => Criterion::Time(*time), + Self { + maxsize: Some(size), + .. + } => Criterion::MaxSize(*size), + _ => unreachable!("guaranteed by clap ArgGroup"), + } + } +} + +pub enum Criterion { + Stamp, + File, + Time(u64), + Installed, + Toolchains(Vec), + MaxSize(u64), +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Test helper for splitting arguments and providing them to clap + fn parse(command: &str) -> Result { + let command_args = command.split_whitespace(); + dbg!(SweepArgs::try_parse_from(command_args).map(SweepArgs::into_args)) + } + + #[test] + fn test_argparsing() { + // Argument is required + assert!(parse("cargo sweep").is_err()); + assert!(parse("cargo sweep --installed").is_ok()); + assert!(parse("cargo sweep --file").is_ok()); + assert!(parse("cargo sweep --stamp").is_ok()); + assert!(parse("cargo sweep --time 30").is_ok()); + assert!(parse("cargo sweep --toolchains SAMPLE_TEXT").is_ok()); + assert!(parse("cargo sweep --maxsize 100").is_ok()); + + assert!(parse("cargo-sweep sweep").is_err()); + assert!(parse("cargo-sweep sweep --installed").is_ok()); + assert!(parse("cargo-sweep sweep --file").is_ok()); + assert!(parse("cargo-sweep sweep --stamp").is_ok()); + assert!(parse("cargo-sweep sweep --time 30").is_ok()); + assert!(parse("cargo-sweep sweep --toolchains SAMPLE_TEXT").is_ok()); + assert!(parse("cargo-sweep sweep --maxsize 100").is_ok()); + + // Argument conflicts + assert!(parse("cargo sweep --installed --maxsize 100").is_err()); + assert!(parse("cargo sweep --file --installed").is_err()); + assert!(parse("cargo sweep --stamp --file").is_err()); + assert!(parse("cargo sweep --time 30 --stamp").is_err()); + assert!(parse("cargo sweep --toolchains SAMPLE_TEXT --time 30").is_err()); + assert!(parse("cargo sweep --maxsize 100 --toolchains SAMPLE_TEXT").is_err()); + + // Test if comma separated list is parsed correctly + let args = Args { + toolchains: ["1", "2", "3"].map(ToString::to_string).to_vec(), + ..Args::default() + }; + assert_eq!(args, parse("cargo sweep --toolchains 1,2,3").unwrap()); + assert!(parse("cargo sweep --toolchains 1 2 3").is_err()); + } +} diff --git a/src/fingerprint.rs b/src/fingerprint.rs index bb4ea12..f8e7a8e 100644 --- a/src/fingerprint.rs +++ b/src/fingerprint.rs @@ -392,17 +392,17 @@ fn rustup_toolchain_list() -> Option> { pub fn remove_not_built_with( dir: &Path, - rust_version_to_keep: Option<&str>, + rust_version_to_keep: &[String], dry_run: bool, ) -> Result { debug!("cleaning: {:?} with remove_not_built_with", dir); let mut total_disk_space = 0; - let hashed_rust_version_to_keep = if let Some(names) = rust_version_to_keep { + let hashed_rust_version_to_keep = if !rust_version_to_keep.is_empty() { info!( "Using specified installed toolchains: {:?}", - names.split(',').collect::>() + rust_version_to_keep ); - lookup_from_names(names.split(',').map(Some))? + lookup_from_names(rust_version_to_keep.iter().map(Some))? } else { match rustup_toolchain_list() { Some(list) => { diff --git a/src/main.rs b/src/main.rs index c993e54..68b8312 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,5 @@ use anyhow::Context; use cargo_metadata::{Error, Metadata, MetadataCommand}; -use clap::{ - app_from_crate, crate_authors, crate_description, crate_name, crate_version, Arg, ArgGroup, - SubCommand, -}; use crossterm::tty::IsTty; use fern::colors::{Color, ColoredLevelConfig}; use log::{debug, error, info}; @@ -15,9 +11,12 @@ use std::{ }; use walkdir::WalkDir; +mod cli; mod fingerprint; mod stamp; mod util; + +use self::cli::Criterion; use self::fingerprint::{remove_not_built_with, remove_older_than, remove_older_until_fits}; use self::stamp::Timestamp; use self::util::format_bytes; @@ -131,196 +130,103 @@ fn metadata(path: &Path) -> Result { } fn main() -> anyhow::Result<()> { - let matches = app_from_crate!() - .subcommand( - SubCommand::with_name("sweep") - .arg( - Arg::with_name("verbose") - .short("v") - .long("verbose") - .help("Turn verbose information on"), - ) - .arg( - Arg::with_name("recursive") - .short("r") - .long("recursive") - .help("Apply on all projects below the given path"), - ) - .arg( - Arg::with_name("hidden") - .long("hidden") - .help("The `recursive` flag defaults to ignoring directories \ - that start with a `.`, `.git` for example is unlikely to include a \ - Cargo project, this flag changes it to look in them."), - ) - .arg( - Arg::with_name("dry-run") - .short("d") - .long("dry-run") - .help("Dry run which will not delete any files"), - ) - .arg( - Arg::with_name("stamp") - .short("s") - .long("stamp") - .help("Store timestamp file at the given path, is used by file option"), - ) - .arg( - Arg::with_name("file") - .short("f") - .long("file") - .help("Load timestamp file in the given path, cleaning everything older"), - ) - .arg( - Arg::with_name("installed") - .short("i") - .long("installed") - .help("Keep only artifacts made by Toolchains currently installed by rustup") - ) - .arg( - Arg::with_name("toolchains") - .long("toolchains") - .value_name("toolchains") - .help("Toolchains (currently installed by rustup) that should have their artifacts kept.") - .takes_value(true), - ) - .arg( - Arg::with_name("maxsize") - .long("maxsize") - .value_name("maxsize") - .help("Remove oldest artifacts until the target directory is below the specified size in MB") - .takes_value(true), - ) - .arg( - Arg::with_name("time") - .short("t") - .long("time") - .value_name("days") - .help("Number of days backwards to keep") - .takes_value(true), - ) - .group( - ArgGroup::with_name("timestamp") - .args(&["stamp", "file", "time", "installed", "toolchains", "maxsize"]) - .required(true), - ) - .arg( - Arg::with_name("path") - .index(1) - .value_name("path") - .help("Path to check"), - ), - ) - .get_matches(); - - if let Some(matches) = matches.subcommand_matches("sweep") { - let verbose = matches.is_present("verbose"); - setup_logging(verbose); - - let dry_run = matches.is_present("dry-run"); - - // Default to current invocation path. - let path = match matches.value_of("path") { - Some(p) => PathBuf::from(p), - None => env::current_dir().expect("Failed to get current directory"), - }; - - if matches.is_present("stamp") { - debug!("Writing timestamp file in: {:?}", path); - return Timestamp::new() - .store(path.as_path()) - .context("Failed to write timestamp file"); - } + let args = cli::parse(); + + let criterion = args.criterion(); + let dry_run = args.dry_run; + setup_logging(args.verbose); + + // Default to current invocation path. + let path = args + .path + .unwrap_or_else(|| env::current_dir().expect("Failed to get current directory")); + + if let Criterion::Stamp = criterion { + debug!("Writing timestamp file in: {:?}", path); + return Timestamp::new() + .store(path.as_path()) + .context("Failed to write timestamp file"); + } - let paths = if matches.is_present("recursive") { - find_cargo_projects(&path, matches.is_present("hidden")) + let paths = if args.recursive { + find_cargo_projects(&path, args.hidden) + } else { + let metadata = metadata(&path).context(format!( + "Failed to gather metadata for {:?}", + path.display() + ))?; + let out = Path::new(&metadata.target_directory).to_path_buf(); + if out.exists() { + vec![out] } else { - let metadata = metadata(&path).context(format!( - "Failed to gather metadata for {:?}", - path.display() - ))?; - let out = Path::new(&metadata.target_directory).to_path_buf(); - if out.exists() { - vec![out] - } else { - anyhow::bail!("Failed to clean {:?} as it does not exist.", out); - } - }; + anyhow::bail!("Failed to clean {:?} as it does not exist.", out); + } + }; - if matches.is_present("installed") || matches.is_present("toolchains") { - for project_path in &paths { - match remove_not_built_with(project_path, matches.value_of("toolchains"), dry_run) { - Ok(cleaned_amount) if dry_run => { - info!( - "Would clean: {} from {project_path:?}", - format_bytes(cleaned_amount) - ) - } - Ok(cleaned_amount) => info!( - "Cleaned {} from {project_path:?}", + let toolchains = match &criterion { + Criterion::Installed => Some(vec![]), + Criterion::Toolchains(vec) => Some(vec).cloned(), + _ => None, + }; + if let Some(toolchains) = toolchains { + for project_path in &paths { + match remove_not_built_with(project_path, &toolchains, dry_run) { + Ok(cleaned_amount) if dry_run => { + info!( + "Would clean: {} from {project_path:?}", format_bytes(cleaned_amount) - ), - Err(e) => error!( - "{:?}", - e.context(format!("Failed to clean {project_path:?}")) - ), - }; - } - } else if matches.is_present("maxsize") { - // TODO: consider parsing units like GB, KB ... - let size = match matches - .value_of("maxsize") - .and_then(|s| s.parse::().ok()) - { - Some(s) => s * 1024 * 1024, - None => { - anyhow::bail!("maxsize has to be a number"); + ) } + Ok(cleaned_amount) => info!( + "Cleaned {} from {project_path:?}", + format_bytes(cleaned_amount) + ), + Err(e) => error!( + "{:?}", + e.context(format!("Failed to clean {project_path:?}")) + ), }; - - for project_path in &paths { - match remove_older_until_fits(project_path, size, dry_run) { - Ok(cleaned_amount) if dry_run => { - info!( - "Would clean: {} from {project_path:?}", - format_bytes(cleaned_amount) - ) - } - Ok(cleaned_amount) => info!( - "Cleaned {} from {project_path:?}", + } + } else if let Criterion::MaxSize(size) = criterion { + for project_path in &paths { + match remove_older_until_fits(project_path, size, dry_run) { + Ok(cleaned_amount) if dry_run => { + info!( + "Would clean: {} from {project_path:?}", format_bytes(cleaned_amount) - ), - Err(e) => error!("Failed to clean {:?}: {:?}", project_path, e), - }; - } - } else { - let keep_duration = if matches.is_present("file") { - let ts = Timestamp::load(path.as_path()).expect("Failed to load timestamp file"); - Duration::from(ts) - } else { - let days_to_keep: u64 = matches - .value_of("time") - .expect("--time argument missing") - .parse() - .expect("Invalid time format"); - Duration::from_secs(days_to_keep * 24 * 3600) + ) + } + Ok(cleaned_amount) => info!( + "Cleaned {} from {project_path:?}", + format_bytes(cleaned_amount) + ), + Err(e) => error!("Failed to clean {:?}: {:?}", project_path, e), }; + } + } else { + let keep_duration = if let Criterion::File = criterion { + let ts = Timestamp::load(path.as_path()).expect("Failed to load timestamp file"); + Duration::from(ts) + } else if let Criterion::Time(days_to_keep) = criterion { + Duration::from_secs(days_to_keep * 24 * 3600) + } else { + unreachable!(); + }; - for project_path in &paths { - match remove_older_than(project_path, &keep_duration, dry_run) { - Ok(cleaned_amount) if dry_run => { - info!( - "Would clean: {} from {project_path:?}", - format_bytes(cleaned_amount) - ) - } - Ok(cleaned_amount) => info!( - "Cleaned {} from {project_path:?}", + for project_path in &paths { + match remove_older_than(project_path, &keep_duration, dry_run) { + Ok(cleaned_amount) if dry_run => { + info!( + "Would clean: {} from {project_path:?}", format_bytes(cleaned_amount) - ), - Err(e) => error!("Failed to clean {:?}: {:?}", project_path, e), - }; - } + ) + } + Ok(cleaned_amount) => info!( + "Cleaned {} from {project_path:?}", + format_bytes(cleaned_amount) + ), + Err(e) => error!("Failed to clean {:?}: {:?}", project_path, e), + }; } } diff --git a/tests/integration.rs b/tests/integration.rs index 4148617..c60708e 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -7,8 +7,7 @@ use std::{ }; use anyhow::{Context, Result}; -use assert_cmd::Command; -use assert_cmd::{assert::Assert, cargo::cargo_bin}; +use assert_cmd::{assert::Assert, cargo::cargo_bin, Command}; use fs_extra::dir::get_size; use predicates::{ prelude::PredicateBooleanExt, @@ -221,9 +220,9 @@ fn empty_project_output() -> TestResult { \), \] \[DEBUG\] cleaning: ".+debug" with remove_not_built_with_in_a_profile - \[DEBUG\] Successfully removed: ".+libsample_project-.+\.rlib" - \[DEBUG\] Successfully removed: ".+libsample_project-.+\.rmeta" - \[DEBUG\] Successfully removed: ".+sample_project-.+\.d" + \[DEBUG\] Successfully removed: ".+debug.+deps.+sample_project.+" + \[DEBUG\] Successfully removed: ".+debug.+deps.+sample_project.+" + \[DEBUG\] Successfully removed: ".+debug.+deps.+sample_project.+" \[DEBUG\] Successfully removed: ".+.fingerprint.+sample-project-.+" \[INFO\] Cleaned .+ from ".+""#, ); @@ -301,13 +300,14 @@ fn golden_reference(args: &[&str], file: &str) -> TestResult { let mut assert = run(cmd.args(args), None); assert = assert.stderr(is_empty()); - let actual = std::str::from_utf8(&assert.get_output().stdout)?; + let mut actual = String::from_utf8(assert.get_output().stdout.clone())?; if std::env::var("BLESS").as_deref() == Ok("1") { fs::write(file, actual)?; } else { let mut expected = fs::read_to_string(file).context("failed to read usage file")?; content_normalize(&mut expected); + content_normalize(&mut actual); assert_eq!(actual, expected); } Ok(()) @@ -328,9 +328,7 @@ fn path() -> TestResult { } fn content_normalize(content: &mut String) { - if !cfg!(windows) { - *content = content.replace("cargo-sweep.exe", "cargo-sweep"); - } + *content = content.replace("cargo-sweep.exe", "cargo-sweep"); *content = content.replace("\r\n", "\n"); } diff --git a/tests/standalone-usage.txt b/tests/standalone-usage.txt index 92670e2..f84185a 100644 --- a/tests/standalone-usage.txt +++ b/tests/standalone-usage.txt @@ -1,14 +1,11 @@ -cargo-sweep 0.6.2 -holmgr A tool for cleaning unused build files created by Cargo -USAGE: - cargo-sweep.exe [SUBCOMMAND] +Usage: cargo-sweep -FLAGS: - -h, --help Prints help information - -V, --version Prints version information +Commands: + sweep A tool for cleaning unused build files created by Cargo + help Print this message or the help of the given subcommand(s) -SUBCOMMANDS: - help Prints this message or the help of the given subcommand(s) - sweep +Options: + -h, --help Print help information + -V, --version Print version information diff --git a/tests/usage.txt b/tests/usage.txt index 91a24cc..421ef93 100644 --- a/tests/usage.txt +++ b/tests/usage.txt @@ -1,25 +1,20 @@ -cargo-sweep.exe-sweep +A tool for cleaning unused build files created by Cargo -USAGE: - cargo-sweep.exe sweep [FLAGS] [OPTIONS] <--stamp|--file|--time |--installed|--toolchains |--maxsize > [path] +Usage: cargo-sweep sweep [OPTIONS] <--stamp|--file|--time