cargo cleanup tool (plugin)

This commit is contained in:
2026-04-12 13:01:48 +02:00
commit 72b6a13216
11 changed files with 416 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
use std::{
path::{Path, PathBuf},
process::Command,
};
use anyhow::Result;
use walkdir::WalkDir;
#[derive(Default)]
pub struct Slimmer {
pub dry_run: bool,
}
pub struct Report {
pub output: String,
}
impl Slimmer {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn cleanup(&self, path: impl AsRef<Path>) -> Result<Report> {
let mut output = String::new();
for target in manifests(path)? {
let mut command = self.cargo_clean_cmd(&target);
let cmd_output = command.output()?;
output.push_str(&summary(target, &cmd_output));
}
Ok(Report { output })
}
fn cargo_clean_cmd(&self, target: &PathBuf) -> Command {
let mut command = Command::new("cargo");
command.arg("clean");
command.arg("--manifest-path");
command.arg(target);
if self.dry_run {
command.arg("--dry-run");
}
command
}
}
fn summary(target: impl AsRef<Path>, cmd_output: &std::process::Output) -> String {
format!(
"{}: {}",
target.as_ref().parent().unwrap().display(),
String::from_utf8_lossy(&cmd_output.stderr).trim_start()
)
}
fn manifests(path: impl AsRef<Path>) -> Result<Vec<PathBuf>> {
let mut targets: Vec<PathBuf> = Vec::new();
let tmp = WalkDir::new(path)
.into_iter()
.filter_entry(|e| !e.path().ends_with("target/package"));
for entry in tmp {
let entry = entry?;
if entry.file_name() == "Cargo.toml" {
targets.push(entry.into_path());
}
}
Ok(targets)
}
#[cfg(test)]
mod tests {
use std::process::{ExitStatus, Output};
use super::*;
#[test]
fn manifestes_returns_toml_paths() {
let mut manifest = manifests("tests/data").unwrap();
manifest.sort();
assert_eq!(
manifest,
vec![
PathBuf::from("tests/data/proj_1/Cargo.toml"),
PathBuf::from("tests/data/proj_2/Cargo.toml"),
PathBuf::from("tests/data/proj_3/Cargo.toml"),
],
"wrong path",
);
}
#[test]
fn cargo_clean_cmd_returns_correct_command() {
let slimmer = Slimmer::new();
let cmd = slimmer.cargo_clean_cmd(&PathBuf::from("tests/data/proj_1/Cargo.toml"));
assert_eq!(cmd.get_program(), "cargo", "wrong program");
assert_eq!(
cmd.get_args().collect::<Vec<_>>(),
["clean", "--manifest-path", "tests/data/proj_1/Cargo.toml"],
"wrong args"
)
}
#[test]
fn summary_returns_correct_string() {
let cmd_output = summary(
PathBuf::from("./target/Cargo.toml"),
&Output {
stdout: Vec::new(),
stderr: String::from(" Removed 2 files, 1.6MiB total\n").into_bytes(),
status: ExitStatus::default(),
},
);
assert_eq!(
cmd_output, "./target: Removed 2 files, 1.6MiB total\n",
"wrong formatting"
);
}
}
+35
View File
@@ -0,0 +1,35 @@
use anyhow::Result;
use clap::Parser;
use cleanup::{Slimmer};
#[derive(Debug, Parser)]
#[command(bin_name = "cargo")]
enum CargoCommand {
Slim(Args),
}
#[derive(clap::Args, Debug)]
/// Runs `cargo clean` recursively to save disk space by deleting build
/// artifacts.
struct Args {
#[arg(default_value = ".")]
paths: Vec<String>,
#[arg(long)]
dry_run: bool,
}
fn main() -> Result<()> {
let CargoCommand::Slim(args) = CargoCommand::parse();
let mut slimmer = Slimmer::new();
if args.dry_run {
slimmer.dry_run = true;
}
for path in &args.paths {
let output = slimmer.cleanup(path)?;
println!("{}", output.output);
}
Ok(())
}