use owo_colors::OwoColorize;
use std::cmp::Ordering;
use std::io::Write;
use std::path::{Path, PathBuf};
fn parse_package_filename(filename: &str) -> Option<(&str, &str)> {
let clean = filename
.strip_suffix(".pkg.tar.zst")
.or_else(|| filename.strip_suffix(".pkg.tar.xz"))?;
let parts: Vec<&str> = clean.split('-').collect();
if parts.len() < 4 {
return None;
}
let name_parts_count = parts.len() - 3;
let name_len = parts[..name_parts_count].join("-").len();
let pkg_name = &clean[..name_len];
let version_start = name_len + 1;
let arch_start = clean.rfind('-')?;
let version_str = &clean[version_start..arch_start];
Some((pkg_name, version_str))
}
pub fn select_downgrade_target(package: &str) -> Option<PathBuf> {
println!(
"{} searching caches for '{}'...",
"::".blue(),
package.bold()
);
let mut candidate_entries: Vec<(PathBuf, String)> = Vec::new();
let native_cache = Path::new("/var/cache/pacman/pkg");
if let Ok(entries) = std::fs::read_dir(native_cache) {
for entry in entries.filter_map(Result::ok) {
let file_name = entry.file_name().to_string_lossy().to_string();
if let Some((parsed_name, version)) = parse_package_filename(&file_name)
&& parsed_name == package
{
candidate_entries.push((entry.path(), version.to_string()));
}
}
}
if let Some(home) = dirs::home_dir() {
let aur_cache = home.join(format!(".cache/haj/aur/{}", package));
if let Ok(entries) = std::fs::read_dir(&aur_cache) {
for entry in entries.filter_map(Result::ok) {
let file_name = entry.file_name().to_string_lossy().to_string();
if let Some((parsed_name, version)) = parse_package_filename(&file_name)
&& parsed_name == package
{
candidate_entries.push((entry.path(), version.to_string()));
}
}
}
}
if candidate_entries.is_empty() {
println!(
"{} no cached versions found for '{}'.",
"✗".red(),
package.bold()
);
return None;
}
candidate_entries.sort_by(|a, b| {
let cmp = alpm::vercmp(a.1.as_str(), b.1.as_str());
match cmp {
Ordering::Greater => Ordering::Less,
Ordering::Less => Ordering::Greater,
Ordering::Equal => Ordering::Equal,
}
});
println!("{}", "\navailable versions:".bold().white());
for (i, (path, ver)) in candidate_entries.iter().enumerate() {
let is_aur = path.to_string_lossy().contains(".cache/haj");
let tag = if is_aur { " (aur)" } else { "" };
println!(
" {}) {} v{}{}",
(i + 1).to_string().cyan(),
package.magenta().bold(),
ver.green(),
tag.dimmed()
);
}
print!(
"\n{} select a version [1-{}]: ",
"❓".magenta().bold(),
candidate_entries.len()
);
let _ = std::io::stdout().flush();
let mut input = String::new();
let _ = std::io::stdin().read_line(&mut input);
if let Ok(idx) = input.trim().parse::<usize>()
&& idx > 0
&& idx <= candidate_entries.len()
{
return Some(candidate_entries[idx - 1].0.clone());
}
println!("{} invalid selection or aborted.", "✗".red());
None
}