cargo-upgrades 2.1.2

Checks if dependencies in Cargo.toml are up to date. Compatible with workspaces and path dependencies.
Documentation
use std::io::Write;
use std::error::Error as _;
use std::process::ExitCode;

use cargo_upgrades::*;
use yansi::{Paint, Style, Condition, Color::Red};

pub const COLOR: Condition = Condition::from(|| {
    (Condition::stdouterr_are_tty() || Condition::clicolor()) && Condition::no_color()
});

pub const ERROR: Style = Red.bold();

mod flags {
    pub const HELP: &str = CargoUpgrades::HELP_;

    xflags::xflags! {
        /// https://gitlab.com/kornelski/cargo-upgrades
        cmd cargo-upgrades {
            /// Suggest upgrades from stable to pre-release (alpha, beta) versions
            optional --pre
            /// Check this Cargo project instead of the current dir (e.g. foo/Cargo.toml)
            optional --manifest-path path: String
        }
    }
}

fn run() -> Result<ExitCode, ExitCode> {
    if !COLOR() {
        yansi::disable();
    }

    let args = std::env::args_os().skip(1).filter(|arg| arg != "upgrades").collect();
    let flags = flags::CargoUpgrades::from_vec(args).map_err(|e| {
        if e.is_help() {
            println!("{e}");
            ExitCode::SUCCESS
        } else {
            eprint!("{}: {e}\n\n{}", "error".paint(ERROR), flags::HELP);
            ExitCode::FAILURE
        }
    })?;

    let u = UpgradesChecker::new(flags.manifest_path.as_deref()).map_err(|e| {
        print_error(e);
        ExitCode::FAILURE
    })?;

    let mut printed_anything = false;
    for (package, deps) in u.outdated_dependencies(flags.pre) {
        if printed_anything {
            println!("");
        }

        println!("{}\n{}", package.name.bold(), package.manifest_path.dim());
        let mut stdout = std::io::stdout().lock();
        let mut tw = tabwriter::TabWriter::new(&mut stdout).ansi(yansi::is_enabled());
        let mut header_printed = false;
        for dep in deps {
            match dep {
                Ok(dep) => {
                    if !header_printed {
                        let _ = writeln!(&mut tw, "{}\t{}\t{}", "dependency".underline(), "current".underline(), "upgrade".underline());
                        header_printed = true;
                    }
                    let _ = writeln!(&mut tw, "{}\t{}\t{}",
                        dep.dependency.rename.as_deref().unwrap_or(&dep.dependency.name),
                        dep.matches.map(|s| s.to_string()).as_deref().unwrap_or("nothing").yellow(),
                        dep.latest.green()
                    );
                },
                Err(e) => {
                    print_error(e);
                },
            }
        }

        let _ = tw.flush();
        printed_anything = true;
    }

    if printed_anything {
        return Ok(ExitCode::from(7));
    }

    println!("{}", "All dependencies match up-to-date releases.".green());
    println!("Run `cargo update` to update to latest release.");
    Ok(ExitCode::SUCCESS)
}

fn main() -> ExitCode {
    run().unwrap_or_else(|e| e)
}

fn print_error(error: Error) {
    eprintln!("{}: {error}", "error".paint(ERROR));
    let mut source = error.source();
    while let Some(error) = source {
        eprintln!("  {error}");
        source = error.source();
    }
}