use thiserror::Error;
use crate::detect::{pick_by_name, pick_default};
use crate::manager::{PackageError, PackageManager, Runner};
#[derive(Debug, Clone, Default)]
pub struct DepGroup {
pub group: String,
pub pacman: Vec<String>,
pub apt: Vec<String>,
pub dnf: Vec<String>,
pub brew: Vec<String>,
pub scoop: Vec<String>,
pub winget: Vec<String>,
}
#[derive(Debug, Error)]
pub enum DepsError {
#[error("no package manager detected; install one or use --manager")]
NoManagerDetected,
#[error("unknown package manager: {0}")]
UnknownManager(String),
#[error("install error: {0}")]
Install(#[from] PackageError),
}
pub struct DepsOpts {
pub groups: Vec<DepGroup>,
pub manager: Option<String>,
pub group_filter: Option<String>,
pub dry_run: bool,
}
pub struct DepsReport {
pub manager_used: String,
pub installed: Vec<String>,
pub already_installed: Vec<String>,
pub skipped_unavailable: Vec<String>,
pub failed: Vec<(String, String)>,
}
fn packages_for<'a>(group: &'a DepGroup, manager_name: &str) -> &'a [String] {
match manager_name {
"pacman" => &group.pacman,
"apt" => &group.apt,
"dnf" => &group.dnf,
"brew" => &group.brew,
"scoop" => &group.scoop,
"winget" => &group.winget,
_ => &[],
}
}
pub fn install_deps(opts: &DepsOpts, runner: &dyn Runner) -> Result<DepsReport, DepsError> {
let manager: Box<dyn PackageManager> = match &opts.manager {
Some(name) => pick_by_name(name).ok_or_else(|| DepsError::UnknownManager(name.clone()))?,
None => pick_default().ok_or(DepsError::NoManagerDetected)?,
};
let manager_name = manager.name().to_owned();
let mut report = DepsReport {
manager_used: manager_name.clone(),
installed: Vec::new(),
already_installed: Vec::new(),
skipped_unavailable: Vec::new(),
failed: Vec::new(),
};
for group in &opts.groups {
if opts
.group_filter
.as_deref()
.is_some_and(|f| f != group.group)
{
continue;
}
let pkgs = packages_for(group, &manager_name);
if pkgs.is_empty() {
report.skipped_unavailable.push(group.group.clone());
continue;
}
let mut to_install: Vec<String> = Vec::new();
if opts.dry_run {
to_install.extend_from_slice(pkgs);
} else {
for pkg in pkgs {
match manager.is_installed(runner, pkg) {
Ok(true) => report.already_installed.push(pkg.clone()),
Ok(false) => to_install.push(pkg.clone()),
Err(e) => report.failed.push((pkg.clone(), e.to_string())),
}
}
}
if to_install.is_empty() {
continue;
}
if opts.dry_run {
report.installed.extend(to_install);
} else {
match manager.install(runner, &to_install) {
Ok(()) => report.installed.extend(to_install),
Err(e) => {
let msg = e.to_string();
for pkg in to_install {
report.failed.push((pkg, msg.clone()));
}
}
}
}
}
Ok(report)
}