use crate::{
config::{Config, PkgConfig},
error::Error,
output::{Event, Output, PkgEntry, PkgEntryState, PkgInstallHints},
platform::PackageManager,
};
#[derive(Debug, Clone, Default)]
pub struct Options {
pub pattern: Vec<String>,
pub all: bool,
pub all_hints: bool,
pub verbose: bool,
}
pub fn push(config: &Config, opts: Options, output: &mut dyn Output) -> Result<(), Error> {
let ctx = config.host_context(None);
let detected: Vec<&PackageManager> = ctx.platform.all_pkg_managers().collect();
if detected.is_empty() {
output.push(Event::PkgEntry(PkgEntry::NoPackageManagerDetected {
supported: vec![
"brew".to_string(),
"dnf".to_string(),
"apt".to_string(),
"pacman".to_string(),
"cargo".to_string(),
],
}))?;
}
let conditions = config.conditions();
let needles: Vec<String> = opts.pattern.iter().map(|p| p.to_lowercase()).collect();
let mut entries: Vec<(&str, &smol_str::SmolStr, &PkgConfig)> = Vec::new();
for (key, pkg) in config.pkgs() {
if (opts.all || !pkg.is_disabled()) && pkg.evaluate_when(conditions, &ctx)? {
let label = pkg.name.as_deref().unwrap_or(key.as_str());
if needles.is_empty()
|| needles.iter().any(|n| {
label.to_lowercase().contains(n) || key.as_str().to_lowercase().contains(n)
})
{
entries.push((label, key, pkg));
}
}
}
entries.sort_by_key(|(label, _, _)| *label);
let mut eligible: Vec<(&PackageManager, Vec<String>)> =
detected.iter().map(|&m| (m, Vec::new())).collect();
for (label, key, pkg) in entries {
let state = if pkg.is_disabled() {
PkgEntryState::Disabled
} else if pkg.is_installed(conditions, &ctx)? {
PkgEntryState::Installed
} else {
PkgEntryState::Missing
};
let matched_detect = if opts.verbose {
pkg.matched_detect(conditions, &ctx)?.map(|d| d.to_string())
} else {
None
};
let install_hints = if !matches!(state, PkgEntryState::Missing) {
PkgInstallHints::default()
} else if opts.all_hints {
PkgInstallHints {
brew: pkg.install_hint.packages_for("brew").to_vec(),
dnf: pkg.install_hint.packages_for("dnf").to_vec(),
apt: pkg.install_hint.packages_for("apt").to_vec(),
pacman: pkg.install_hint.packages_for("pacman").to_vec(),
cargo: pkg.install_hint.packages_for("cargo").to_vec(),
}
} else {
let mut hints = PkgInstallHints::default();
for (idx, &mgr) in detected.iter().enumerate() {
let pkgs = pkg.install_hint.packages_for(mgr.name());
if pkgs.is_empty() {
continue;
}
match mgr.name() {
"brew" => hints.brew = pkgs.to_vec(),
"dnf" => hints.dnf = pkgs.to_vec(),
"apt" => hints.apt = pkgs.to_vec(),
"pacman" => hints.pacman = pkgs.to_vec(),
"cargo" => hints.cargo = pkgs.to_vec(),
_ => {}
}
eligible[idx].1.extend(pkgs.iter().cloned());
}
hints
};
let no_install_hint_for_host = matches!(state, PkgEntryState::Missing)
&& !detected.is_empty()
&& detected
.iter()
.all(|mgr| pkg.install_hint.packages_for(mgr.name()).is_empty());
output.push(Event::PkgEntry(PkgEntry::Pkg {
name: smol_str::SmolStr::new(label),
key: key.clone(),
description: pkg.description.clone(),
state,
matched_detect,
install_hints,
no_install_hint_for_host,
}))?;
}
if !opts.all_hints {
for (mgr, packages) in eligible {
if packages.is_empty() {
continue;
}
output.push(Event::PkgEntry(PkgEntry::AggregateInstall {
pkg_manager: mgr.name().to_string(),
command: mgr.install_command(&packages),
packages,
}))?;
}
}
Ok(())
}