1use check_updates_core::Version;
2use std::process::Command;
3use std::str::FromStr;
4
5#[derive(Debug, Clone)]
7pub struct PythonInfo {
8 pub current: Version,
10 pub latest: Option<Version>,
12}
13
14impl PythonInfo {
15 pub fn has_update(&self) -> bool {
17 if let Some(ref latest) = self.latest {
18 latest > &self.current
19 } else {
20 false
21 }
22 }
23}
24
25pub fn detect_python_version() -> Option<Version> {
27 let commands = [
29 ("python3", ["--version"]),
30 ("python", ["--version"]),
31 ];
32
33 for (cmd, args) in &commands {
34 if let Ok(output) = Command::new(cmd).args(args.as_slice()).output()
35 && output.status.success() {
36 let version_output = String::from_utf8_lossy(&output.stdout);
37 if let Some(version_str) = version_output
39 .trim()
40 .strip_prefix("Python ")
41 && let Ok(version) = Version::from_str(version_str) {
42 return Some(version);
43 }
44 }
45 }
46
47 None
48}
49
50pub async fn fetch_latest_python_version() -> Option<Version> {
52 let url = "https://endoflife.date/api/python.json";
54
55 let client = reqwest::Client::builder()
56 .timeout(std::time::Duration::from_secs(5))
57 .build()
58 .ok()?;
59
60 let response = client.get(url).send().await.ok()?;
61
62 if !response.status().is_success() {
63 return None;
64 }
65
66 let json: serde_json::Value = response.json().await.ok()?;
67
68 let cycles = json.as_array()?;
71
72 for cycle in cycles {
75 let cycle_name = cycle.get("cycle")?.as_str()?;
76 if cycle_name.starts_with("3.") {
77 let latest_str = cycle.get("latest")?.as_str()?;
78 if let Ok(version) = Version::from_str(latest_str) {
79 return Some(version);
80 }
81 }
82 }
83
84 None
85}
86
87pub async fn get_python_info(check_latest: bool) -> Option<PythonInfo> {
89 let current = detect_python_version()?;
90
91 let latest = if check_latest {
92 fetch_latest_python_version().await
93 } else {
94 None
95 };
96
97 Some(PythonInfo { current, latest })
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_detect_python_version() {
106 let version = detect_python_version();
108 if let Some(v) = version {
110 assert!(v.major >= 2);
111 }
112 }
113
114 #[test]
115 fn test_python_info_has_update() {
116 let info = PythonInfo {
117 current: Version::from_str("3.11.0").unwrap(),
118 latest: Some(Version::from_str("3.13.1").unwrap()),
119 };
120 assert!(info.has_update());
121
122 let info = PythonInfo {
123 current: Version::from_str("3.13.1").unwrap(),
124 latest: Some(Version::from_str("3.13.1").unwrap()),
125 };
126 assert!(!info.has_update());
127
128 let info = PythonInfo {
129 current: Version::from_str("3.11.0").unwrap(),
130 latest: None,
131 };
132 assert!(!info.has_update());
133 }
134}