use std::collections::BTreeMap;
use std::io::{self, BufRead};
use clap::ColorChoice;
use color_eyre::eyre::Context;
use color_eyre::{Result, Section};
use semver::Version;
use tabled::Tabled;
use crate::cargo;
use crate::commands::styled_table;
use crate::config::{CargoCratesToml, DetailedPackageReq, EffectiveJettisonConfig};
pub fn run(
config: &EffectiveJettisonConfig,
cargo_color: ColorChoice,
cargo_verbosity: i8,
) -> Result<()> {
let to_uninstall = needing_uninstall(
CargoCratesToml::parse_file()
.wrap_err("Failed to parse Cargo's .crates.toml file.")?
.into_name_versions(),
&config.packages,
);
log_uninstallation_plan(&to_uninstall);
if to_uninstall.is_empty() {
return Ok(());
}
if config.args.no_confirm || config.args.dry_run {
log::warn!(
"Skipping interactive confirmation, as requested with `{}`.",
if config.args.no_confirm {
"--no-confirm"
} else {
"--dry-run"
},
);
} else if ask_confirmation().wrap_err("Failed to ask for interactive confirmation.")? {
log::info!("Aborting.");
return Ok(());
}
cargo::uninstall_all(
to_uninstall.into_keys(),
config.args.no_fail_fast,
config.args.dry_run,
cargo_color,
cargo_verbosity,
)
.wrap_err_with(|| {
format!(
"{} package failed to uninstall.",
if config.args.no_fail_fast {
"At least one"
} else {
"Some"
}
)
})?;
Ok(())
}
fn needing_uninstall(
mut installed: BTreeMap<String, Version>,
configured: &BTreeMap<String, DetailedPackageReq>,
) -> BTreeMap<String, Version> {
log::debug!("Computing uninstallation plan...");
installed.remove(clap::crate_name!());
installed
.into_iter()
.filter(|(pkg, _)| !configured.contains_key(pkg))
.collect()
}
fn log_uninstallation_plan(to_uninstall: &BTreeMap<String, Version>) {
if to_uninstall.is_empty() {
log::info!("No package to uninstall: all installed are configured as well.");
} else {
log::info!(
"Will uninstall:\n{}",
styled_table(to_uninstall.iter().map(|(pkg, ver)| PackageEntry {
name: pkg.clone(),
version: ver.to_string(),
})),
);
}
}
#[derive(Tabled)]
struct PackageEntry {
#[tabled(rename = "Name")]
name: String,
#[tabled(rename = "Version")]
version: String,
}
fn ask_confirmation() -> Result<bool> {
log_confirmation();
let stdin = io::stdin().lock();
for line in stdin.lines() {
let line = line
.wrap_err("Failed to read from stdin.")
.note("This shouldn't happen easily at this point.")
.suggestion("Read the underlying error message.")?;
if line.is_empty() || line == "y" {
return Ok(false);
} else if line == "n" {
return Ok(true);
}
log_confirmation();
}
Ok(false)
}
#[inline]
fn log_confirmation() {
log::warn!("This will remove all the packages listed above: do you confirm? [Y/n]");
}