Skip to main content

zoi_cli/cmd/
mark.rs

1//! Implementation of the `mark` command, which allows changing the installation
2//! reason of a package.
3
4use anyhow::{Result, anyhow};
5use colored::Colorize;
6
7use crate::pkg::{db, local, recorder, resolve, types};
8
9/// Runs the `mark` command.
10///
11/// # Errors
12///
13/// Returns an error if:
14/// - Neither `--as-dependency` nor `--as-explicit` is provided.
15/// - Package name parsing or resolution fails.
16/// - Updating the manifest reason or database fails.
17pub fn run(
18    package_names: &[String],
19    as_dependency: bool,
20    as_explicit: bool
21) -> Result<()> {
22    let new_reason = if as_dependency {
23        types::InstallReason::Dependency {
24            parent: "manual".to_string()
25        }
26    } else if as_explicit {
27        types::InstallReason::Direct
28    } else {
29        return Err(anyhow!(
30            "Either --as-dependency or --as-explicit must be provided."
31        ));
32    };
33
34    let reason_str = if as_dependency {
35        "dependency".cyan()
36    } else {
37        "explicit".green()
38    };
39
40    for name in package_names {
41        println!("Marking '{}' as {}...", name.blue().bold(), reason_str);
42
43        let request = resolve::parse_source_string(name)?;
44        let (pkg, _, _, _, registry_handle, _, _) =
45            resolve::resolve_package_and_version(name, None, true, false)?;
46        let installed_source = if let Some(sub) = request.sub_package.as_deref()
47        {
48            format!(
49                "#{}@{}/{}:{}",
50                registry_handle.as_deref().unwrap_or("local"),
51                pkg.repo,
52                pkg.name,
53                sub
54            )
55        } else {
56            format!(
57                "#{}@{}/{}",
58                registry_handle.as_deref().unwrap_or("local"),
59                pkg.repo,
60                pkg.name
61            )
62        };
63        let installed_request =
64            resolve::parse_source_string(&installed_source)?;
65        let mut candidates = Vec::new();
66        for scope in [
67            types::Scope::User,
68            types::Scope::System,
69            types::Scope::Project
70        ] {
71            candidates.extend(local::find_installed_manifests_matching(
72                &installed_request,
73                scope
74            )?);
75        }
76
77        let manifest =
78            match crate::cmd::installed_select::choose_installed_manifest(
79                name,
80                &candidates,
81                false
82            ) {
83                Ok(manifest) => manifest,
84                Err(e) => {
85                    eprintln!("{}: {}", "Error".red().bold(), e);
86                    continue;
87                }
88            };
89        let scope = manifest.scope;
90
91        local::update_manifest_reason(&manifest, new_reason.clone())?;
92
93        let handle = registry_handle
94            .as_deref()
95            .unwrap_or(&manifest.registry_handle);
96        let mut db_pkg = pkg.clone();
97        db_pkg.repo.clone_from(&manifest.repo);
98        db_pkg.scope = manifest.scope;
99        db_pkg.sub_package.clone_from(&manifest.sub_package);
100        if let Ok(conn) = db::open_connection("local") {
101            let _ = db::update_package(
102                &conn,
103                &db_pkg,
104                handle,
105                Some(scope),
106                manifest.sub_package.as_deref(),
107                Some(&new_reason)
108            );
109        }
110
111        let _ = recorder::update_package_reason(&manifest, &new_reason);
112
113        println!(
114            "Successfully marked '{}' as {}.",
115            pkg.name.cyan(),
116            reason_str
117        );
118    }
119
120    Ok(())
121}