use crate::manager::{PackageError, PackageManager, RunOutcome, Runner};
pub const AUR_INFO_URL: &str = "https://aur.archlinux.org/rpc/v5/info";
fn in_aur(runner: &dyn Runner, pkg: &str) -> Result<bool, PackageError> {
let url = format!("{AUR_INFO_URL}?arg[]={}", percent_encode(pkg));
let RunOutcome {
status,
stdout,
stderr,
} = runner.run("curl", &["-fsSL", &url])?;
if status != 0 {
return Err(PackageError::ExitFailure { status, stderr });
}
let body: serde_json::Value = serde_json::from_str(&stdout)
.map_err(|e| PackageError::Io(std::io::Error::other(format!("AUR response: {e}"))))?;
Ok(body["results"]
.as_array()
.is_some_and(|results| results.iter().any(|r| r["Name"] == pkg)))
}
fn percent_encode(value: &str) -> String {
value
.bytes()
.map(|b| match b {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => {
(b as char).to_string()
}
_ => format!("%{b:02X}"),
})
.collect()
}
pub struct Pacman;
impl Pacman {
fn binary(&self) -> &'static str {
if which::which("paru").is_ok() {
"paru"
} else {
"pacman"
}
}
}
impl PackageManager for Pacman {
fn name(&self) -> &'static str {
"pacman"
}
fn is_available(&self) -> bool {
which::which("pacman").is_ok()
}
fn exists(&self, runner: &dyn Runner, pkg: &str) -> Result<bool, PackageError> {
let RunOutcome { status, .. } = runner.run("pacman", &["-Si", pkg])?;
if status == 0 {
return Ok(true);
}
in_aur(runner, pkg)
}
fn is_installed(&self, runner: &dyn Runner, pkg: &str) -> Result<bool, PackageError> {
let RunOutcome { status, .. } = runner.run("pacman", &["-Q", pkg])?;
match status {
0 => Ok(true),
_ => Ok(false),
}
}
fn install(&self, runner: &dyn Runner, packages: &[String]) -> Result<(), PackageError> {
let bin = self.binary();
let mut args = vec!["-S", "--noconfirm"];
args.extend(packages.iter().map(String::as_str));
let RunOutcome { status, stderr, .. } = runner.run_as_root(bin, &args)?;
if status != 0 {
return Err(PackageError::ExitFailure { status, stderr });
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn percent_encode_keeps_unreserved_and_escapes_the_rest() {
assert_eq!(percent_encode("hjkl-bin"), "hjkl-bin");
assert_eq!(percent_encode("libc++"), "libc%2B%2B");
assert_eq!(percent_encode("a b@1"), "a%20b%401");
}
}