cargo-machete 0.1.0

Find unused dependencies with this one weird trick!
mod search_unused;

#[cfg(feature = "cargo_udeps")]
mod cargo_udeps;

use crate::search_unused::find_unused;
use log::info;
use std::fs;
use walkdir::WalkDir;

struct PackageAnalysis {
    manifest: cargo_toml::Manifest,
    package_name: String,
    unused: Vec<String>,
    errors: Vec<anyhow::Error>,
}

impl PackageAnalysis {
    fn new(name: String, manifest: cargo_toml::Manifest) -> Self {
        Self {
            manifest,
            package_name: name,
            unused: Default::default(),
            errors: Default::default(),
        }
    }
}

fn main() -> anyhow::Result<()> {
    pretty_env_logger::init();

    let mut fix = false;

    #[cfg(feature = "cargo_udeps")]
    let mut check_udeps = false;

    let args = std::env::args();
    for arg in args {
        if arg == "--fix" || arg == "fix" {
            fix = true;
        }

        if arg == "--check" || arg == "check" {
            #[cfg(feature = "cargo_udeps")]
            {
                check_udeps = true;
            }

            #[cfg(not(feature = "cargo_udeps"))]
            {
                eprintln!("--check only works if compiling with cargo_udeps feature");
                std::process::exit(-1);
            }
        }
    }

    println!("Analyzing crates and their dependencies...");

    let cwd = std::env::current_dir()?;
    for entry in WalkDir::new(cwd) {
        let entry = entry?;
        if entry.file_name() == "Cargo.toml" {
            let path = entry.into_path();
            match find_unused(&path) {
                Ok(Some(mut analysis)) => {
                    if analysis.unused.is_empty() {
                        continue;
                    }

                    #[cfg(feature = "cargo_udeps")]
                    if check_udeps {
                        crate::cargo_udeps::compare(&mut analysis)?;
                    }

                    println!("{} -- {}:", analysis.package_name, path.to_string_lossy());

                    for dep in &analysis.unused {
                        println!("\t{}", dep)
                    }

                    if fix {
                        info!("rewriting Cargo.toml");
                        for dep in analysis.unused {
                            analysis.manifest.dependencies.remove(&dep);
                            let serialized = toml::to_string(&analysis.manifest)?;
                            fs::write(path.clone(), serialized)?;
                        }
                    }
                }

                Ok(None) => {
                    println!("{} -- didn't find any package", path.to_string_lossy());
                }

                Err(err) => {
                    eprintln!("error when handling {}: {}", path.display(), err);
                }
            }
        }
    }

    Ok(())
}