use std::path::Path; use crate::{ cli::RmArgs, error::{PakkerError, Result}, model::LockFile, ui_utils::{prompt_typo_suggestion, prompt_yes_no}, }; pub async fn execute( args: RmArgs, lockfile_path: &Path, _config_path: &Path, ) -> Result<()> { // Load expects directory path, so get parent directory let lockfile_dir = lockfile_path.parent().unwrap_or(Path::new(".")); let mut lockfile = LockFile::load(lockfile_dir)?; // Determine which projects to remove let inputs: Vec = 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 = 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) || 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 !args.yes { 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)? { 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(()) }