pacaptr/pm/
emerge.rs

1#![doc = doc_self!()]
2
3use std::sync::LazyLock;
4
5use async_trait::async_trait;
6use indoc::indoc;
7use itertools::Itertools;
8use tap::prelude::*;
9
10use super::{NoCacheStrategy, Pm, PmHelper, PromptStrategy, Strategy};
11use crate::{config::Config, error::Result, exec::Cmd};
12
13macro_rules! doc_self {
14    () => {
15        indoc! {"
16            The [Portage Package Manager](https://wiki.gentoo.org/wiki/Portage).
17        "}
18    };
19}
20use doc_self;
21
22#[doc = doc_self!()]
23#[derive(Debug)]
24pub struct Emerge {
25    cfg: Config,
26}
27
28static STRAT_ASK: LazyLock<Strategy> = LazyLock::new(|| Strategy {
29    prompt: PromptStrategy::native_confirm(["--ask"]),
30    ..Strategy::default()
31});
32
33static STRAT_INTERACTIVE: LazyLock<Strategy> = LazyLock::new(|| Strategy {
34    prompt: PromptStrategy::native_confirm(["--interactive"]),
35    ..Strategy::default()
36});
37
38static STRAT_INSTALL: LazyLock<Strategy> = LazyLock::new(|| Strategy {
39    prompt: PromptStrategy::native_confirm(["--ask"]),
40    no_cache: NoCacheStrategy::Scc,
41    ..Strategy::default()
42});
43
44impl Emerge {
45    #[must_use]
46    #[allow(missing_docs)]
47    pub const fn new(cfg: Config) -> Self {
48        Self { cfg }
49    }
50}
51
52#[async_trait]
53impl Pm for Emerge {
54    /// Gets the name of the package manager.
55    fn name(&self) -> &'static str {
56        "emerge"
57    }
58
59    fn cfg(&self) -> &Config {
60        &self.cfg
61    }
62
63    /// Q generates a list of installed packages.
64    async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
65        self.qs(kws, flags).await
66    }
67
68    /// Qi displays local package information: name, version, description, etc.
69    async fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
70        self.si(kws, flags).await
71    }
72
73    /// Ql displays files provided by local package.
74    async fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
75        self.run(Cmd::new(["qlist"]).kws(kws).flags(flags)).await
76    }
77
78    /// Qo queries the package which provides FILE.
79    async fn qo(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
80        self.run(Cmd::new(["qfile"]).kws(kws).flags(flags)).await
81    }
82
83    /// Qs searches locally installed package for names or descriptions.
84    // According to https://www.archlinux.org/pacman/pacman.8.html#_query_options_apply_to_em_q_em_a_id_qo_a,
85    // when including multiple search terms, only packages with descriptions
86    // matching ALL of those terms are returned.
87    async fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
88        self.run(Cmd::new(["qlist", "-I"]).kws(kws).flags(flags))
89            .await
90    }
91
92    /// Qu lists packages which have an update available.
93    async fn qu(&self, _kws: &[&str], flags: &[&str]) -> Result<()> {
94        self.run(Cmd::new(["emerge", "-uDNp", "@world"]).flags(flags))
95            .await
96    }
97
98    /// R removes a single package, leaving all of its dependencies installed.
99    async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
100        Cmd::with_sudo(["emerge", "--unmerge"])
101            .kws(kws)
102            .flags(flags)
103            .pipe(|cmd| self.run_with(cmd, self.default_mode(), &STRAT_ASK))
104            .await
105    }
106
107    /// Rs removes a package and its dependencies which are not required by any
108    /// other installed package, and not explicitly installed by the user.
109    async fn rs(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
110        Cmd::with_sudo(["emerge", "--depclean"])
111            .kws(kws)
112            .flags(flags)
113            .pipe(|cmd| self.run_with(cmd, self.default_mode(), &STRAT_ASK))
114            .await
115    }
116
117    /// S installs one or more packages by name.
118    async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
119        Cmd::with_sudo(["emerge"])
120            .kws(kws)
121            .flags(flags)
122            .pipe(|cmd| self.run_with(cmd, self.default_mode(), &STRAT_INSTALL))
123            .await
124    }
125
126    /// Sc removes all the cached packages that are not currently installed, and
127    /// the unused sync database.
128    async fn sc(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
129        Cmd::with_sudo(["eclean-dist"])
130            .kws(kws)
131            .flags(flags)
132            .pipe(|cmd| self.run_with(cmd, self.default_mode(), &STRAT_INTERACTIVE))
133            .await
134    }
135
136    /// Scc removes all files from the cache.
137    async fn scc(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
138        self.sc(kws, flags).await
139    }
140
141    /// Si displays remote package information: name, version, description, etc.
142    async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
143        let kws = kws.iter().map(|kw| format!("^{kw}$")).collect_vec();
144        self.run(Cmd::new(["emerge", "-s"]).kws(kws).flags(flags))
145            .await
146    }
147
148    /// Ss searches for package(s) by searching the expression in name,
149    /// description, short description.
150    async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
151        self.run(Cmd::new(["qsearch"]).kws(kws).flags(flags)).await
152    }
153
154    /// Su updates outdated packages.
155    async fn su(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
156        Cmd::with_sudo(["emerge", "-uDN"])
157            .kws(if kws.is_empty() { &["@world"][..] } else { kws })
158            .flags(flags)
159            .pipe(|cmd| self.run_with(cmd, self.default_mode(), &STRAT_INSTALL))
160            .await
161    }
162
163    /// Suy refreshes the local package database, then updates outdated
164    /// packages.
165    async fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
166        self.sy(&[], flags).await?;
167        self.su(kws, flags).await
168    }
169
170    /// Sy refreshes the local package database.
171    async fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
172        self.run(Cmd::with_sudo(["emerge", "--sync"]).flags(flags))
173            .await?;
174        if !kws.is_empty() {
175            self.s(kws, flags).await?;
176        }
177        Ok(())
178    }
179}