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