use crate::provider::prelude::*;
use std::str::FromStr;
#[derive(Debug, ThisError)]
pub enum Error {
#[error("pacman database files don't exist, please update database files")]
NoDatabase,
#[error("failed to parse stderr from pacman: '{0}'")]
ParseError(String),
}
impl FromStr for Error {
type Err = Self;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.contains("warning: database file") & s.contains("not exist (use '-Fy' to download)") {
Ok(Self::NoDatabase)
} else {
Err(Self::ParseError(s.to_string()))
}
}
}
#[derive(Default, Debug, PartialEq)]
pub struct Pacman;
impl fmt::Display for Pacman {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "pacman")
}
}
impl Pacman {
pub fn new() -> Self {
Default::default()
}
fn get_candidates_from_files_output(&self, output: String) -> ProviderResult<Vec<Candidate>> {
let mut results = vec![];
for line in output.lines() {
let mut candidate = Candidate::default();
for (index, piece) in line.splitn(4, '\0').enumerate() {
let piece = piece.to_string();
match index {
0 => candidate.origin = piece,
1 => candidate.package = piece,
2 => candidate.version = piece,
3 => candidate.actions.execute = cmd!(piece),
_ => panic!("line contained superfluous piece {}", piece),
}
}
if !candidate.package.is_empty() {
results.push(candidate);
}
}
Ok(results)
}
}
#[async_trait]
impl IsProvider for Pacman {
async fn search_internal(
&self,
command: &str,
target_env: Arc<Environment>,
) -> ProviderResult<Vec<Candidate>> {
let stdout = match target_env
.output_of(cmd!(
"pacman",
"-F",
"--noconfirm",
"--machinereadable",
command
))
.await
{
Ok(val) => val,
Err(ExecutionError::NonZero { ref output, .. })
if (output.stdout.is_empty() && output.stderr.is_empty()) =>
{
return Err(ProviderError::NotFound(command.to_string()))
}
Err(e) => return Err(ProviderError::from(e)),
};
let mut candidates = self.get_candidates_from_files_output(stdout)?;
candidates.iter_mut().for_each(|candidate| {
if candidate.actions.execute.is_empty() {
candidate.actions.execute = cmd!(command);
}
});
Ok(candidates)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::prelude::*;
#[test]
fn initialize() {
let _pacman = Pacman::new();
}
test::default_tests!(Pacman::new());
#[test]
fn cache_empty() {
let query =
quick_test!(Pacman::new(), Err(ExecutionError::NonZero {
command: "pacman".to_string(),
output: std::process::Output {
stdout: r"".into(),
stderr: r"warning: database file for 'core' does not exist (use '-Fy' to download)
warning: database file for 'extra' does not exist (use '-Fy' to download)
warning: database file for 'community' does not exist (use '-Fy' to download)
".into(),
status: ExitStatus::from_raw(1),
}
}));
assert::is_err!(query);
assert::err::execution!(query);
}
#[test]
fn search_nonexistent() {
let query = quick_test!(
Pacman::new(),
Err(ExecutionError::NonZero {
command: "pacman".to_string(),
output: std::process::Output {
stdout: r"".into(),
stderr: r"".into(),
status: ExitStatus::from_raw(1),
}
})
);
assert::is_err!(query);
assert::err::not_found!(query);
}
#[test]
fn matches_htop() {
let query = quick_test!(
Pacman::new(),
Ok("
extra\0bash-completion\02.11-3\0usr/share/bash-completion/completions/htop
extra\0htop\03.2.2-1\0usr/bin/htop
community\0pcp\06.0.3-1\0etc/pcp/pmlogconf/tools/htop
community\0pcp\06.0.3-1\0var/lib/pcp/config/pmlogconf/tools/htop
"
.to_string())
);
let result = query.results.unwrap();
assert_eq!(result.len(), 4);
assert!(result[0].package.starts_with("bash-completion"));
assert_eq!(result[0].version, "2.11-3");
assert_eq!(result[0].origin, "extra");
assert!(result[0].description.is_empty());
assert_eq!(
result[0].actions.execute,
vec!["usr/share/bash-completion/completions/htop"].into()
);
assert!(result[1].package.starts_with("htop"))
}
}