use crate::state::{PackageItem, Source};
use super::utils::{shell_single_quote, validate_package_names};
#[must_use]
pub const fn aur_install_helper_flags(reinstall: bool) -> &'static str {
if reinstall {
"-S --aur --noconfirm"
} else {
"-S --aur --needed --noconfirm"
}
}
#[must_use]
pub fn aur_install_body(flags: &str, n: &str) -> String {
format!(
"(if command -v paru >/dev/null 2>&1; then \
paru {flags} {n}; \
elif command -v yay >/dev/null 2>&1; then \
yay {flags} {n}; \
else \
echo 'No AUR helper (paru/yay) found.'; \
fi)"
)
}
pub fn build_install_command(
item: &PackageItem,
password: Option<&str>,
dry_run: bool,
) -> Result<(String, bool), String> {
validate_package_names(
std::slice::from_ref(&item.name),
"install command construction",
)?;
let quoted_name = shell_single_quote(&item.name);
match &item.source {
Source::Official { .. } => {
let tool = crate::logic::privilege::active_tool()?;
let reinstall = crate::index::is_installed(&item.name);
let base_cmd = if reinstall {
format!("pacman -S --noconfirm {quoted_name}")
} else {
format!("pacman -S --needed --noconfirm {quoted_name}")
};
let hold_tail = "; echo; echo 'Finished.'; echo 'Press any key to close...'; read -rn1 -s _ || (echo; echo 'Press Ctrl+C to close'; sleep infinity)";
if dry_run {
let cmd = format!(
"{}{hold_tail}",
crate::logic::privilege::build_privilege_command(tool, &base_cmd)
);
let quoted = shell_single_quote(&cmd);
let bash = format!("echo DRY RUN: {quoted}");
return Ok((bash, true));
}
let pass = password.unwrap_or("");
if pass.is_empty() {
let bash = format!(
"{}{hold_tail}",
crate::logic::privilege::build_privilege_command(tool, &base_cmd)
);
Ok((bash, true))
} else {
let piped = crate::logic::privilege::build_password_pipe(tool, pass, &base_cmd);
let priv_cmd = piped.unwrap_or_else(|| {
crate::logic::privilege::build_privilege_command(tool, &base_cmd)
});
let bash = format!("{priv_cmd}{hold_tail}");
Ok((bash, true))
}
}
Source::Aur => {
let hold_tail = "; echo; echo 'Press any key to close...'; read -rn1 -s _ || (echo; echo 'Press Ctrl+C to close'; sleep infinity)";
let reinstall = crate::index::is_installed(&item.name);
let flags = aur_install_helper_flags(reinstall);
let aur_cmd = if dry_run {
let cmd =
format!("paru {flags} {quoted_name} || yay {flags} {quoted_name}{hold_tail}");
let quoted = shell_single_quote(&cmd);
format!("echo DRY RUN: {quoted}")
} else {
format!(
"{body}{hold}",
body = aur_install_body(flags, "ed_name),
hold = hold_tail
)
};
Ok((aur_cmd, false))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn install_build_install_command_official_variants() {
let tool = crate::logic::privilege::active_tool().expect("privilege tool");
let bin = tool.binary_name();
let pkg = PackageItem {
name: "ripgrep".into(),
version: "14".into(),
description: String::new(),
source: Source::Official {
repo: "extra".into(),
arch: "x86_64".into(),
},
popularity: None,
out_of_date: None,
orphaned: false,
};
let (cmd1, uses_sudo1) = build_install_command(&pkg, None, false).expect("build");
assert!(uses_sudo1);
let quoted_name = crate::install::shell_single_quote("ripgrep");
assert!(
cmd1.contains(&format!(
"{bin} pacman -S --needed --noconfirm {quoted_name}"
)),
"expected quoted package name in: {cmd1}"
);
assert!(cmd1.contains("Press any key to close"));
let (cmd2, uses_sudo2) = build_install_command(&pkg, Some("pa's"), false).expect("build");
assert!(uses_sudo2);
if tool.capabilities().supports_stdin_password {
assert!(
cmd2.contains(&format!(
"{bin} -S pacman -S --needed --noconfirm {quoted_name}"
)),
"expected quoted package name in password pipe command: {cmd2}"
);
} else {
assert!(
cmd2.contains(&format!(
"{bin} pacman -S --needed --noconfirm {quoted_name}"
)),
"doas fallback should use plain command: {cmd2}"
);
}
let (cmd3, uses_sudo3) = build_install_command(&pkg, None, true).expect("build");
assert!(uses_sudo3);
assert!(cmd3.starts_with("echo DRY RUN: '"));
assert!(
cmd3.contains(&format!("{bin} pacman -S --needed --noconfirm"))
&& cmd3.contains("ripgrep"),
"expected dry-run output to include command and package name: {cmd3}"
);
}
#[test]
fn install_build_install_command_aur_variants() {
let pkg = PackageItem {
name: "yay-bin".into(),
version: "1".into(),
description: String::new(),
source: Source::Aur,
popularity: None,
out_of_date: None,
orphaned: false,
};
let (cmd1, uses_sudo1) = build_install_command(&pkg, None, false).expect("build");
assert!(!uses_sudo1);
assert!(cmd1.contains("command -v paru"));
assert!(cmd1.contains("paru -S --aur --needed --noconfirm 'yay-bin'"));
assert!(cmd1.contains("yay -S --aur --needed --noconfirm 'yay-bin'"));
assert!(cmd1.contains("elif command -v yay"));
assert!(cmd1.contains("No AUR helper"));
assert!(cmd1.contains("Press any key to close"));
let (cmd2, uses_sudo2) = build_install_command(&pkg, None, true).expect("build");
assert!(!uses_sudo2);
assert!(cmd2.starts_with("echo DRY RUN: '"));
assert!(cmd2.contains("paru -S --aur --needed --noconfirm"));
assert!(cmd2.contains("yay-bin"));
}
}