cli: wire MultiError in add/rm; add typo suggestions

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I98240ec0f9e3932a46e79f82f32cd5d36a6a6964
This commit is contained in:
raf 2026-02-12 23:20:34 +03:00
commit 79a82d6ab8
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
2 changed files with 79 additions and 7 deletions

View file

@ -4,7 +4,7 @@ use crate::{
cli::RmArgs,
error::{PakkerError, Result},
model::LockFile,
ui_utils::prompt_yes_no,
ui_utils::{prompt_typo_suggestion, prompt_yes_no},
};
pub async fn execute(
@ -44,15 +44,52 @@ pub async fn execute(
};
}
log::info!("Removing projects: {:?}", inputs);
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) {
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)
@ -60,11 +97,12 @@ pub async fn execute(
|| p.aliases.contains(input)
}) {
projects_to_remove.push(project.get_name());
} else if !args.all {
log::warn!("Project not found: {input}");
}
}
// 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(),