cargo-mate 1.7.6

Rust development companion that enhances cargo with intelligent workflows, state management, performance optimization, and comprehensive project monitoring.
Documentation
use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use std::fs;
use clap::{Parser, Subcommand};
pub mod scrub;
pub use scrub::{ScrubOptions, CargoScrubber};
#[derive(Parser, Debug)]
#[command(name = "scrub")]
#[command(about = "🧹 Scrub - System-wide Cargo project cleaner")]
pub struct ScrubCli {
    #[command(subcommand)]
    command: ScrubCommands,
    #[arg(short, long)]
    verbose: bool,
}
#[derive(Subcommand, Debug)]
enum ScrubCommands {
    Run {
        #[arg(short, long, default_value = ".")]
        start: String,
        #[arg(long)]
        dry_run: bool,
        #[arg(short, long)]
        resume: Option<String>,
        #[arg(long, default_value = "1")]
        min_depth: usize,
        #[arg(long, default_value = "10")]
        max_depth: usize,
    },
    Help,
}
pub fn run_scrub(
    dry_run: bool,
    verbose: bool,
    start: String,
    resume: Option<String>,
    min_depth: usize,
    max_depth: usize,
) -> Result<()> {
    let mut args = vec!["scrub"];
    args.push("-d");
    args.push(&start);
    if dry_run {
        args.push("--dry-run");
    }
    if verbose {
        args.push("-v");
    }
    let resume_str = resume.map(|p| p.to_string());
    if let Some(ref resume_str) = resume_str {
        args.push("-r");
        args.push(resume_str);
    }
    let min_depth_str = min_depth.to_string();
    args.push("--min-depth");
    args.push(&min_depth_str);
    let max_depth_str = max_depth.to_string();
    args.push("--max-depth");
    args.push(&max_depth_str);
    execute_scrub_command(&args)
}
fn ensure_scrub_symlink() -> Result<()> {
    let source_bin_path = get_source_binary_path()?;
    let target_link_path = get_shipwreck_bin_path()?;
    if !is_valid_symlink(&target_link_path, &source_bin_path)? {
        create_symlink(&source_bin_path, &target_link_path)?;
    }
    Ok(())
}
fn get_source_binary_path() -> Result<PathBuf> {
    let module_dir = Path::new(file!()).parent().unwrap_or(Path::new("."));
    let target_release_path = module_dir.join("target/release/scrub");
    if target_release_path.exists() {
        return Ok(target_release_path);
    }
    let current_exe = std::env::current_exe()?;
    let exe_dir = current_exe.parent().context("Failed to get parent directory")?;
    let project_root = exe_dir.parent().context("Failed to get project root")?;
    let scrub_path = project_root.join("src/scrubme/target/release/scrub");
    if scrub_path.exists() {
        return Ok(scrub_path);
    }
    Ok(PathBuf::from("scrub"))
}
fn get_shipwreck_bin_path() -> Result<PathBuf> {
    let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
    let shipwreck_bin_dir = PathBuf::from(&home).join(".shipwreck").join("bin");
    if !shipwreck_bin_dir.exists() {
        fs::create_dir_all(&shipwreck_bin_dir)?;
    }
    Ok(shipwreck_bin_dir.join("scrub"))
}
fn is_valid_symlink(link_path: &Path, target_path: &Path) -> Result<bool> {
    if !link_path.exists() {
        return Ok(false);
    }
    #[cfg(unix)]
    {
        use std::os::unix::fs::MetadataExt;
        let metadata = fs::symlink_metadata(link_path)?;
        if !metadata.file_type().is_symlink() {
            return Ok(false);
        }
        let real_path = fs::read_link(link_path)?;
        return Ok(real_path == target_path);
    }
    #[cfg(not(unix))] { Ok(link_path.exists()) }
}
fn create_symlink(source: &Path, target: &Path) -> Result<()> {
    if target.exists() {
        fs::remove_file(target)?;
    }
    #[cfg(unix)] std::os::unix::fs::symlink(source, target)?;
    #[cfg(windows)]
    {
        if fs::metadata(source)?.is_dir() {
            std::os::windows::fs::symlink_dir(source, target)?;
        } else {
            std::os::windows::fs::symlink_file(source, target)?;
        }
    }
    Ok(())
}
fn execute_scrub_command(args: &[&str]) -> Result<()> {
    use std::process::Command;
    ensure_scrub_symlink()?;
    let target_link = get_shipwreck_bin_path()?;
    let scrub_path = if target_link.exists() {
        target_link.to_string_lossy().to_string()
    } else {
        std::env::var("SCRUB_BIN_PATH")
            .unwrap_or_else(|_| {
                get_source_binary_path()
                    .unwrap_or_else(|_| PathBuf::from("scrub"))
                    .to_string_lossy()
                    .to_string()
            })
    };
    let output = Command::new(&scrub_path).args(args).output()?;
    if !output.stdout.is_empty() {
        print!("{}", String::from_utf8_lossy(& output.stdout));
    }
    if !output.stderr.is_empty() {
        eprint!("{}", String::from_utf8_lossy(& output.stderr));
    }
    Ok(())
}