use crate::{
config::{Config, PkgConfig},
error::Error,
output::{Event, Output, PkgEntry, PkgEntryState, PkgInstallHints},
pkg_manager::{self, DetectedPackageManager},
};
#[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 primary = pkg_manager::detect(ctx.path, &ctx.os)?;
let supplementary = pkg_manager::detect_supplementary(ctx.path)?;
let detected: Vec<DetectedPackageManager> = primary
.into_iter()
.chain(supplementary.iter().copied())
.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<(DetectedPackageManager, 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.brew.packages.clone(),
dnf5: pkg.install_hint.dnf5.packages.clone(),
apt: pkg.install_hint.apt.packages.clone(),
pacman: pkg.install_hint.pacman.packages.clone(),
cargo: pkg.install_hint.cargo.packages.clone(),
}
} else {
let mut hints = PkgInstallHints::default();
for (idx, &mgr) in detected.iter().enumerate() {
let pkgs = mgr.packages_for(&pkg.install_hint);
if pkgs.is_empty() {
continue;
}
match mgr {
DetectedPackageManager::Brew => hints.brew = pkgs.to_vec(),
DetectedPackageManager::Dnf5 => hints.dnf5 = pkgs.to_vec(),
DetectedPackageManager::Apt => hints.apt = pkgs.to_vec(),
DetectedPackageManager::Pacman => hints.pacman = pkgs.to_vec(),
DetectedPackageManager::Cargo => hints.cargo = pkgs.to_vec(),
}
eligible[idx].1.extend(pkgs.iter().cloned());
}
hints
};
output.push(Event::PkgEntry(PkgEntry::Pkg {
name: smol_str::SmolStr::new(label),
key: key.clone(),
description: pkg.description.clone(),
state,
matched_detect,
install_hints,
}))?;
}
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(())
}