cnf_lib/provider/
pacman.rs1use crate::provider::prelude::*;
7use std::str::FromStr;
8
9#[derive(Debug, ThisError, Display)]
11pub enum Error {
12 NoDatabase,
14
15 ParseError(String),
17}
18
19impl FromStr for Error {
21 type Err = Self;
22
23 fn from_str(s: &str) -> Result<Self, Self::Err> {
24 if s.contains("warning: database file") & s.contains("not exist (use '-Fy' to download)") {
25 Ok(Self::NoDatabase)
26 } else {
27 Err(Self::ParseError(s.to_string()))
28 }
29 }
30}
31
32#[derive(Default, Debug, PartialEq)]
34#[allow(missing_copy_implementations)]
36pub struct Pacman;
37
38impl fmt::Display for Pacman {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 write!(f, "pacman")
41 }
42}
43
44impl Pacman {
45 pub fn new() -> Self {
47 Default::default()
48 }
49
50 fn get_candidates_from_files_output(&self, output: String) -> ProviderResult<Vec<Candidate>> {
52 let mut results = vec![];
53
54 for line in output.lines() {
55 let mut candidate = Candidate::default();
56 for (index, piece) in line.splitn(4, '\0').enumerate() {
57 let piece = piece.to_string();
58 match index {
59 0 => candidate.origin = piece,
60 1 => candidate.package = piece,
61 2 => candidate.version = piece,
62 3 => candidate.actions.execute = cmd!(piece),
63 _ => panic!("line contained superfluous piece {}", piece),
64 }
65 }
66 if !candidate.package.is_empty() {
67 results.push(candidate);
68 }
69 }
70
71 Ok(results)
72 }
73}
74
75#[async_trait]
76impl IsProvider for Pacman {
77 async fn search_internal(
78 &self,
79 command: &str,
80 target_env: Arc<Environment>,
81 ) -> ProviderResult<Vec<Candidate>> {
82 let stdout = match target_env
83 .output_of(cmd!(
84 "pacman",
85 "-F",
86 "--noconfirm",
87 "--machinereadable",
88 command
89 ))
90 .await
91 {
92 Ok(val) => val,
93 Err(ExecutionError::NonZero { ref output, .. })
94 if (output.stdout.is_empty() && output.stderr.is_empty()) =>
95 {
96 return Err(ProviderError::NotFound(command.to_string()));
97 }
98 Err(e) => return Err(ProviderError::from(e)),
99 };
100
101 let mut candidates = self.get_candidates_from_files_output(stdout)?;
102 candidates.iter_mut().for_each(|candidate| {
103 if candidate.actions.execute.is_empty() {
104 candidate.actions.execute = cmd!(command);
105 }
106 });
107
108 Ok(candidates)
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use crate::test::prelude::*;
116
117 #[test]
118 fn initialize() {
119 let _pacman = Pacman::new();
120 }
121
122 test::default_tests!(Pacman::new());
123
124 #[test]
129 fn cache_empty() {
130 let query =
131 quick_test!(Pacman::new(), Err(ExecutionError::NonZero {
132 command: "pacman".to_string(),
133 output: std::process::Output {
134 stdout: r"".into(),
135 stderr: r"warning: database file for 'core' does not exist (use '-Fy' to download)
136warning: database file for 'extra' does not exist (use '-Fy' to download)
137warning: database file for 'community' does not exist (use '-Fy' to download)
138".into(),
139 status: ExitStatus::from_raw(1),
140 }
141 }));
142
143 assert::is_err!(query);
144 assert::err::execution!(query);
145 }
146
147 #[test]
152 fn search_nonexistent() {
153 let query = quick_test!(
154 Pacman::new(),
155 Err(ExecutionError::NonZero {
156 command: "pacman".to_string(),
157 output: std::process::Output {
158 stdout: r"".into(),
159 stderr: r"".into(),
160 status: ExitStatus::from_raw(1),
161 }
162 })
163 );
164
165 assert::is_err!(query);
166 assert::err::not_found!(query);
167 }
168
169 #[test]
174 fn matches_htop() {
175 let query = quick_test!(
176 Pacman::new(),
177 Ok("
178extra\0bash-completion\x002.11-3\0usr/share/bash-completion/completions/htop
179extra\0htop\x003.2.2-1\0usr/bin/htop
180community\0pcp\x006.0.3-1\0etc/pcp/pmlogconf/tools/htop
181community\0pcp\x006.0.3-1\0var/lib/pcp/config/pmlogconf/tools/htop
182"
183 .to_string())
184 );
185
186 let result = query.results.unwrap();
187
188 assert_eq!(result.len(), 4);
189 assert!(result[0].package.starts_with("bash-completion"));
190 assert_eq!(result[0].version, "2.11-3");
191 assert_eq!(result[0].origin, "extra");
192 assert!(result[0].description.is_empty());
193 assert_eq!(
194 result[0].actions.execute,
195 vec!["usr/share/bash-completion/completions/htop"].into()
196 );
197 assert!(result[1].package.starts_with("htop"))
198 }
199}