use check_updates_core::Version;
use std::process::Command;
use std::str::FromStr;
#[derive(Debug, Clone)]
pub struct PythonInfo {
pub current: Version,
pub latest: Option<Version>,
}
impl PythonInfo {
pub fn has_update(&self) -> bool {
if let Some(ref latest) = self.latest {
latest > &self.current
} else {
false
}
}
}
pub fn detect_python_version() -> Option<Version> {
let commands = [
("python3", ["--version"]),
("python", ["--version"]),
];
for (cmd, args) in &commands {
if let Ok(output) = Command::new(cmd).args(args.as_slice()).output()
&& output.status.success() {
let version_output = String::from_utf8_lossy(&output.stdout);
if let Some(version_str) = version_output
.trim()
.strip_prefix("Python ")
&& let Ok(version) = Version::from_str(version_str) {
return Some(version);
}
}
}
None
}
pub async fn fetch_latest_python_version() -> Option<Version> {
let url = "https://endoflife.date/api/python.json";
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.ok()?;
let response = client.get(url).send().await.ok()?;
if !response.status().is_success() {
return None;
}
let json: serde_json::Value = response.json().await.ok()?;
let cycles = json.as_array()?;
for cycle in cycles {
let cycle_name = cycle.get("cycle")?.as_str()?;
if cycle_name.starts_with("3.") {
let latest_str = cycle.get("latest")?.as_str()?;
if let Ok(version) = Version::from_str(latest_str) {
return Some(version);
}
}
}
None
}
pub async fn get_python_info(check_latest: bool) -> Option<PythonInfo> {
let current = detect_python_version()?;
let latest = if check_latest {
fetch_latest_python_version().await
} else {
None
};
Some(PythonInfo { current, latest })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_python_version() {
let version = detect_python_version();
if let Some(v) = version {
assert!(v.major >= 2);
}
}
#[test]
fn test_python_info_has_update() {
let info = PythonInfo {
current: Version::from_str("3.11.0").unwrap(),
latest: Some(Version::from_str("3.13.1").unwrap()),
};
assert!(info.has_update());
let info = PythonInfo {
current: Version::from_str("3.13.1").unwrap(),
latest: Some(Version::from_str("3.13.1").unwrap()),
};
assert!(!info.has_update());
let info = PythonInfo {
current: Version::from_str("3.11.0").unwrap(),
latest: None,
};
assert!(!info.has_update());
}
}