use crate::logic::deps::is_package_installed_or_provided;
use crate::state::modal::DependencyStatus;
use std::collections::HashSet;
use std::process::{Command, Stdio};
pub(super) fn determine_status(
name: &str,
version_req: &str,
installed: &HashSet<String>,
provided: &HashSet<String>,
upgradable: &HashSet<String>,
) -> DependencyStatus {
if !is_package_installed_or_provided(name, installed, provided) {
return DependencyStatus::ToInstall;
}
let is_upgradable = upgradable.contains(name);
if !version_req.is_empty() {
if let Ok(installed_version) = get_installed_version(name) {
if !version_satisfies(&installed_version, version_req) {
return DependencyStatus::ToUpgrade {
current: installed_version,
required: version_req.to_string(),
};
}
if is_upgradable {
let available_version =
get_available_version(name).unwrap_or_else(|| "newer".to_string());
return DependencyStatus::ToUpgrade {
current: installed_version,
required: available_version,
};
}
return DependencyStatus::Installed {
version: installed_version,
};
}
}
if is_upgradable {
match get_installed_version(name) {
Ok(current_version) => {
let available_version =
get_available_version(name).unwrap_or_else(|| "newer".to_string());
return DependencyStatus::ToUpgrade {
current: current_version,
required: available_version,
};
}
Err(_) => {
return DependencyStatus::ToUpgrade {
current: "installed".to_string(),
required: "newer".to_string(),
};
}
}
}
get_installed_version(name).map_or_else(
|_| DependencyStatus::Installed {
version: "installed".to_string(),
},
|version| DependencyStatus::Installed { version },
)
}
pub(super) fn get_available_version(name: &str) -> Option<String> {
let output = Command::new("pacman")
.args(["-Si", name])
.env("LC_ALL", "C")
.env("LANG", "C")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.ok()?;
if !output.status.success() {
return None;
}
let text = String::from_utf8_lossy(&output.stdout);
for line in text.lines() {
if line.starts_with("Version")
&& let Some(colon_pos) = line.find(':')
{
let version = line[colon_pos + 1..].trim();
let version = version.split('-').next().unwrap_or(version);
return Some(version.to_string());
}
}
None
}
pub fn get_installed_version(name: &str) -> Result<String, String> {
let output = Command::new("pacman")
.args(["-Q", name])
.env("LC_ALL", "C")
.env("LANG", "C")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.map_err(|e| format!("pacman -Q failed: {e}"))?;
if !output.status.success() {
return Err("Package not found".to_string());
}
let text = String::from_utf8_lossy(&output.stdout);
if let Some(line) = text.lines().next() {
if let Some(space_pos) = line.find(' ') {
let version = line[space_pos + 1..].trim();
let version = version.split('-').next().unwrap_or(version);
return Ok(version.to_string());
}
}
Err("Could not parse version".to_string())
}
#[must_use]
pub fn version_satisfies(installed: &str, requirement: &str) -> bool {
requirement.strip_prefix(">=").map_or_else(
|| {
requirement.strip_prefix("<=").map_or_else(
|| {
requirement.strip_prefix("=").map_or_else(
|| {
requirement.strip_prefix(">").map_or_else(
|| {
requirement.strip_prefix("<").map_or_else(
|| {
true
},
|req_ver| installed < req_ver,
)
},
|req_ver| installed > req_ver,
)
},
|req_ver| installed == req_ver,
)
},
|req_ver| installed <= req_ver,
)
},
|req_ver| installed >= req_ver,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_satisfies_relational_operators() {
assert!(version_satisfies("2.0", ">=1.5"));
assert!(!version_satisfies("1.0", ">=1.5"));
assert!(version_satisfies("1.5", "<=1.5"));
assert!(version_satisfies("1.6", ">1.5"));
assert!(!version_satisfies("1.4", ">1.5"));
assert!(version_satisfies("1.5", "=1.5"));
assert!(!version_satisfies("1.6", "<1.5"));
}
#[test]
fn version_satisfies_defaults_to_true_without_constraint() {
assert!(version_satisfies("2.0", ""));
assert!(version_satisfies("2.0", "n/a"));
}
}