fn parse_pacman_upgrade_triple(trimmed: &str) -> Option<(String, String, String)> {
let arrow_pos = trimmed.find(" -> ")?;
let before_arrow = trimmed[..arrow_pos].trim();
let after_arrow = trimmed[arrow_pos + 4..].trim();
let new_version = after_arrow.rsplit(" -> ").next()?.trim();
if new_version.is_empty() {
return None;
}
let first_ws = before_arrow.find(char::is_whitespace)?;
let name = before_arrow[..first_ws].trim();
if name.is_empty() {
return None;
}
let old_version = before_arrow[first_ws..].trim();
if old_version.is_empty() {
return None;
}
Some((
name.to_string(),
old_version.to_string(),
new_version.to_string(),
))
}
pub fn parse_checkupdates(output: &[u8]) -> Vec<(String, String, String)> {
String::from_utf8_lossy(output)
.lines()
.filter_map(|line| {
let trimmed = line.trim();
if trimmed.is_empty() {
None
} else {
parse_pacman_upgrade_triple(trimmed)
}
})
.collect()
}
pub fn parse_checkupdates_tool(output: &[u8]) -> Vec<(String, String)> {
String::from_utf8_lossy(output)
.lines()
.filter_map(|line| {
let trimmed = line.trim();
if trimmed.is_empty() {
None
} else {
let parts: Vec<&str> = trimmed.split_whitespace().collect();
if parts.len() >= 2 {
let name = parts[0].to_string();
let new_version = parts[1..].join(" "); Some((name, new_version))
} else {
None
}
}
})
.collect()
}
pub fn get_installed_version(package_name: &str) -> Option<String> {
use std::process::{Command, Stdio};
let output = Command::new("pacman")
.args(["-Q", package_name])
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.output()
.ok()?;
if output.status.success() {
let text = String::from_utf8_lossy(&output.stdout);
let parts: Vec<&str> = text.split_whitespace().collect();
if parts.len() >= 2 {
Some(parts[1..].join(" "))
} else {
None
}
} else {
None
}
}
pub fn parse_qua(output: &[u8]) -> Vec<(String, String, String)> {
String::from_utf8_lossy(output)
.lines()
.filter_map(|line| {
let trimmed = line.trim();
if trimmed.is_empty() {
None
} else {
parse_pacman_upgrade_triple(trimmed)
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::parse_checkupdates;
#[test]
fn test_parse_checkupdates_extracts_correct_versions() {
let test_cases = vec![
("bat 0.26.0-1 -> 0.26.0-2", "bat", "0.26.0-1", "0.26.0-2"),
(
"comgr 2:6.4.4-2 -> 2:7.1.0-1",
"comgr",
"2:6.4.4-2",
"2:7.1.0-1",
),
(
"composable-kernel 6.4.4-1 -> 7.1.0-1",
"composable-kernel",
"6.4.4-1",
"7.1.0-1",
),
];
for (input, expected_name, expected_old, expected_new) in test_cases {
let output = input.as_bytes();
let entries = parse_checkupdates(output);
assert_eq!(entries.len(), 1, "Failed to parse: {input}");
let (name, old_version, new_version) = &entries[0];
assert_eq!(name, expected_name, "Wrong name for: {input}");
assert_eq!(old_version, expected_old, "Wrong old_version for: {input}");
assert_eq!(new_version, expected_new, "Wrong new_version for: {input}");
}
}
#[test]
fn test_parse_checkupdates_multiple_packages() {
let input = "bat 0.26.0-1 -> 0.26.0-2\ncomgr 2:6.4.4-2 -> 2:7.1.0-1\n";
let output = input.as_bytes();
let entries = parse_checkupdates(output);
assert_eq!(entries.len(), 2);
assert_eq!(
entries[0],
(
"bat".to_string(),
"0.26.0-1".to_string(),
"0.26.0-2".to_string()
)
);
assert_eq!(
entries[1],
(
"comgr".to_string(),
"2:6.4.4-2".to_string(),
"2:7.1.0-1".to_string()
)
);
}
#[test]
fn test_parse_checkupdates_collapses_chained_new_version() {
let line = "libraw 0.22.0-2 -> 0.22.0-2 -> 0.22.1-1\n";
let entries = parse_checkupdates(line.as_bytes());
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].0, "libraw");
assert_eq!(entries[0].1, "0.22.0-2");
assert_eq!(entries[0].2, "0.22.1-1");
}
}