conduit_cli/core/
remover.rs1use crate::core::error::CoreResult;
2use crate::core::events::{CoreCallbacks, CoreEvent};
3use crate::core::io::project::{ConduitConfig, ConduitLock};
4use crate::core::paths::CorePaths;
5use std::collections::HashSet;
6use std::fs;
7
8pub struct RemoveReport {
9 pub removed_slug: String,
10 pub purged_slugs: Vec<String>,
11}
12
13pub fn remove_mod(
14 paths: &CorePaths,
15 slug: &str,
16 config: &mut ConduitConfig,
17 lock: &mut ConduitLock,
18 callbacks: &mut dyn CoreCallbacks,
19) -> CoreResult<Option<RemoveReport>> {
20 if !config.mods.contains_key(slug) {
21 return Ok(None);
22 }
23
24 config.mods.remove(slug);
25
26 let mut mods_to_keep = HashSet::new();
27 for root_slug in config.mods.keys() {
28 collect_dependencies(root_slug, lock, &mut mods_to_keep);
29 }
30
31 let mut purged = Vec::new();
32 let all_locked_slugs: Vec<String> = lock.locked_mods.keys().cloned().collect();
33 for locked_slug in all_locked_slugs {
34 if !mods_to_keep.contains(&locked_slug)
35 && let Some(mod_data) = lock.locked_mods.remove(&locked_slug) {
36 let dest_path = paths.mods_dir().join(&mod_data.filename);
37 if dest_path.exists() {
38 fs::remove_file(dest_path)?;
39 }
40 purged.push(locked_slug.clone());
41 callbacks.on_event(CoreEvent::Purged { slug: locked_slug });
42 }
43 }
44
45 Ok(Some(RemoveReport {
46 removed_slug: slug.to_string(),
47 purged_slugs: purged,
48 }))
49}
50
51fn collect_dependencies(slug: &str, lock: &ConduitLock, kept: &mut HashSet<String>) {
52 if kept.contains(slug) {
53 return;
54 }
55 if let Some(mod_data) = lock.locked_mods.get(slug) {
56 kept.insert(slug.to_string());
57 for dep_id in &mod_data.dependencies {
58 if let Some((dep_slug, _)) = lock.locked_mods.iter().find(|(_, m)| &m.id == dep_id) {
59 collect_dependencies(dep_slug, lock, kept);
60 }
61 }
62 }
63}