use std::collections::HashSet;
use std::str::FromStr;
use clap::Parser;
use clap_verbosity_flag::{Level, Verbosity};
use itertools::Itertools;
use miette::IntoDiagnostic;
use rattler_conda_types::{MatchSpec, PackageName};
use crate::cli::global::install::{
find_and_map_executable_scripts, find_designated_package, BinDir, BinEnvDir, BinScriptMapping,
};
use crate::prefix::Prefix;
#[derive(Parser, Debug)]
#[clap(arg_required_else_help = true)]
pub struct Args {
#[arg(num_args = 1..)]
package: Vec<String>,
#[command(flatten)]
verbose: Verbosity,
}
pub async fn execute(args: Args) -> miette::Result<()> {
let specs = args
.package
.into_iter()
.map(|package_str| MatchSpec::from_str(&package_str))
.collect::<Result<Vec<_>, _>>()
.into_diagnostic()?;
let packages = specs
.into_iter()
.map(|spec| {
spec.name
.clone()
.ok_or_else(|| miette::miette!("could not find package name in MatchSpec {}", spec))
})
.collect::<Result<Vec<_>, _>>()?;
for package_name in packages {
remove_global_package(package_name, &args.verbose).await?;
}
Ok(())
}
async fn remove_global_package(
package_name: PackageName,
verbose: &Verbosity,
) -> miette::Result<()> {
let BinEnvDir(bin_prefix) = BinEnvDir::from_existing(&package_name).await?;
let prefix = Prefix::new(bin_prefix.clone());
let prefix_package = find_designated_package(&prefix, &package_name).await?;
let paths_to_remove: Vec<_> =
find_and_map_executable_scripts(&prefix, &prefix_package, &BinDir::from_existing().await?)
.await?
.into_iter()
.map(
|BinScriptMapping {
global_binary_path: path,
..
}| path,
)
.collect::<HashSet<_>>()
.into_iter()
.collect();
let dirs_to_remove: Vec<_> = vec![bin_prefix];
if verbose.log_level().unwrap_or(Level::Error) >= Level::Warn {
let whitespace = console::Emoji(" ", "").to_string();
let names_to_remove = dirs_to_remove
.iter()
.map(|dir| dir.to_string_lossy())
.chain(paths_to_remove.iter().map(|path| path.to_string_lossy()))
.join(&format!("\n{whitespace} - "));
eprintln!(
"{} Removing the following files and directories:\n{whitespace} - {names_to_remove}",
console::style("!").yellow().bold(),
)
}
let mut errors = vec![];
for file in paths_to_remove {
if let Err(e) = tokio::fs::remove_file(&file).await.into_diagnostic() {
errors.push((file, e))
}
}
for dir in dirs_to_remove {
if let Err(e) = tokio::fs::remove_dir_all(&dir).await.into_diagnostic() {
errors.push((dir, e))
}
}
if errors.is_empty() {
eprintln!(
"{}Successfully removed global package {}",
console::style(console::Emoji("✔ ", "")).green(),
console::style(package_name.as_source()).bold(),
);
} else {
let whitespace = console::Emoji(" ", "").to_string();
let error_string = errors
.into_iter()
.map(|(file, e)| format!("{} (on {})", e, file.to_string_lossy()))
.join(&format!("\n{whitespace} - "));
miette::bail!(
"got multiple errors trying to remove global package {}:\n{} - {}",
package_name.as_source(),
whitespace,
error_string,
);
}
Ok(())
}