1use crate::manager::{PackageError, PackageManager, RunOutcome, Runner};
4
5pub struct Brew;
7
8impl PackageManager for Brew {
9 fn name(&self) -> &'static str {
10 "brew"
11 }
12
13 fn is_available(&self) -> bool {
14 which::which("brew").is_ok()
15 }
16
17 fn exists(&self, runner: &dyn Runner, pkg: &str) -> Result<bool, PackageError> {
24 if let Some((tap, _)) = pkg.rsplit_once('/').filter(|(tap, _)| tap.contains('/')) {
25 let RunOutcome { status, .. } = runner.run("brew", &["tap", tap])?;
26 if status != 0 {
27 return Ok(false);
28 }
29 }
30 let RunOutcome { status, stdout, .. } = runner.run("brew", &["info", "--json=v2", pkg])?;
31 if status != 0 {
32 return Ok(false);
33 }
34 let info: serde_json::Value = serde_json::from_str(&stdout)
35 .map_err(|e| PackageError::Io(std::io::Error::other(format!("brew info JSON: {e}"))))?;
36 Ok(["formulae", "casks"]
37 .iter()
38 .filter_map(|kind| info[kind].as_array())
39 .flatten()
40 .any(|entry| entry["disabled"] != true))
41 }
42
43 fn is_installed(&self, runner: &dyn Runner, pkg: &str) -> Result<bool, PackageError> {
46 let RunOutcome { status, stdout, .. } = runner.run("brew", &["list", "--versions", pkg])?;
47 Ok(status == 0 && !stdout.trim().is_empty())
48 }
49
50 fn install(&self, runner: &dyn Runner, packages: &[String]) -> Result<(), PackageError> {
51 let mut args = vec!["install"];
52 let pkg_refs: Vec<&str> = packages.iter().map(String::as_str).collect();
53 args.extend_from_slice(&pkg_refs);
54 let RunOutcome { status, stderr, .. } = runner.run("brew", &args)?;
55 if status != 0 {
56 return Err(PackageError::ExitFailure { status, stderr });
57 }
58 Ok(())
59 }
60}