pakker/src/cli/commands/rm.rs
NotAShelf 61ced09d25
treewide: fix clippy lints
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I411be69ff31f9cb39cd4cdebc8985b366a6a6964
2026-04-21 19:27:36 +03:00

160 lines
4.3 KiB
Rust

use std::path::Path;
use crate::{
cli::RmArgs,
error::{PakkerError, Result},
model::LockFile,
ui_utils::{prompt_typo_suggestion, prompt_yes_no},
};
pub fn execute(
args: &RmArgs,
global_yes: bool,
lockfile_path: &Path,
_config_path: &Path,
) -> Result<()> {
let skip_prompts = global_yes;
// Load expects directory path, so get parent directory
let lockfile_dir = lockfile_path.parent().unwrap_or_else(|| Path::new("."));
let mut lockfile = LockFile::load(lockfile_dir)?;
// Determine which projects to remove
let inputs: Vec<String> = if args.all {
log::info!("Removing all projects from lockfile");
lockfile
.projects
.iter()
.filter_map(|p| {
p.pakku_id
.clone()
.or_else(|| p.slug.values().next().cloned())
})
.collect()
} else {
args.inputs.clone()
};
if inputs.is_empty() {
return if args.all {
Err(PakkerError::ProjectNotFound(
"No projects found in lockfile".to_string(),
))
} else {
Err(PakkerError::ProjectNotFound(
"No projects specified".to_string(),
))
};
}
log::info!("Removing projects: {inputs:?}");
let mut removed_count = 0;
let mut removed_ids = Vec::new();
let mut projects_to_remove = Vec::new();
// Collect all known project identifiers for typo suggestions
let all_slugs: Vec<String> = lockfile
.projects
.iter()
.flat_map(|p| {
let mut ids = Vec::new();
if let Some(ref pakku_id) = p.pakku_id {
ids.push(pakku_id.clone());
}
ids.extend(p.slug.values().cloned());
ids.extend(p.name.values().cloned());
ids.extend(p.aliases.iter().cloned());
ids
})
.collect();
// First, identify all projects to remove
let mut resolved_inputs = Vec::new();
for input in &inputs {
// Find project by various identifiers
if lockfile.projects.iter().any(|p| {
p.pakku_id.as_deref() == Some(input)
|| p.slug.values().any(|s| s == input)
|| p.name.values().any(|n| n.eq_ignore_ascii_case(input))
|| p.aliases.contains(input)
}) {
resolved_inputs.push(input.clone());
} else if !args.all {
// Try typo suggestion
if let Ok(Some(suggestion)) =
prompt_typo_suggestion(input, &all_slugs, skip_prompts)
{
log::info!("Using suggested project: {suggestion}");
resolved_inputs.push(suggestion);
} else {
log::warn!("Project not found: {input}");
}
}
}
// Now find the actual projects from resolved inputs
for input in &resolved_inputs {
if let Some(project) = lockfile.projects.iter().find(|p| {
p.pakku_id.as_deref() == Some(input)
|| p.slug.values().any(|s| s == input)
|| p.name.values().any(|n| n.eq_ignore_ascii_case(input))
|| p.aliases.contains(input)
}) {
projects_to_remove.push(project.get_name());
}
}
// Replace inputs with resolved_inputs for actual removal
let inputs = resolved_inputs;
if projects_to_remove.is_empty() {
return Err(PakkerError::ProjectNotFound(
"None of the specified projects found".to_string(),
));
}
// Ask for confirmation unless --yes flag is provided or --all with no
// projects
if !skip_prompts {
println!("The following projects will be removed:");
for name in &projects_to_remove {
println!(" - {name}");
}
if !prompt_yes_no("Do you want to continue?", false, skip_prompts)? {
println!("Removal cancelled.");
return Ok(());
}
}
// Now actually remove the projects
for input in &inputs {
if let Some(pos) = lockfile.projects.iter().position(|p| {
p.pakku_id.as_deref() == Some(input)
|| p.slug.values().any(|s| s == input)
|| p.name.values().any(|n| n.eq_ignore_ascii_case(input))
|| p.aliases.contains(input)
}) {
let project = lockfile.projects.remove(pos);
log::info!("Removed: {}", project.get_name());
if let Some(pakku_id) = project.pakku_id.clone() {
removed_ids.push(pakku_id);
}
removed_count += 1;
}
}
// Clean up pakku_links from all remaining projects
for project in &mut lockfile.projects {
project
.pakku_links
.retain(|link| !removed_ids.contains(link));
}
// Save lockfile
lockfile.save(lockfile_dir)?;
log::info!("Successfully removed {removed_count} project(s)");
Ok(())
}