Skip to content

Commit

Permalink
Migrate to clap 4 (#87)
Browse files Browse the repository at this point in the history
* create cli.rs

* migrate to clap 4

* add clap --version and descrition for subcommand

now when running it like `cargo sweep`, the --version is available,
which is necessary for when cargo-sweep is ran via cargo, also, --help
shows the crate description (from TOML manifest)

* fix tests on Windows and MacOS

by considering that files can be outputted in different order

* fix golden tests for Windows

* re-add TODO note

* refac: remove unnecessary match

* remove parenthesis from doc comment
  • Loading branch information
marcospb19 authored Feb 1, 2023
1 parent fd4dc55 commit a36602f
Show file tree
Hide file tree
Showing 8 changed files with 512 additions and 285 deletions.
278 changes: 223 additions & 55 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
163 changes: 163 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -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<PathBuf>,

/// 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<u64>,

/// 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<u64>,

/// Toolchains currently installed by rustup that should have their artifacts kept
#[arg(long, value_delimiter = ',')]
toolchains: Vec<String>,

/// 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<String>),
MaxSize(u64),
}

#[cfg(test)]
mod tests {
use super::*;

/// Test helper for splitting arguments and providing them to clap
fn parse(command: &str) -> Result<Args, clap::Error> {
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());
}
}
8 changes: 4 additions & 4 deletions src/fingerprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,17 +392,17 @@ fn rustup_toolchain_list() -> Option<Vec<String>> {

pub fn remove_not_built_with(
dir: &Path,
rust_version_to_keep: Option<&str>,
rust_version_to_keep: &[String],
dry_run: bool,
) -> Result<u64, Error> {
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::<Vec<_>>()
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) => {
Expand Down
Loading

0 comments on commit a36602f

Please sign in to comment.