python_check_updates/
python.rs1use crate::version::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 if 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 {
42 if let Ok(version) = Version::from_str(version_str) {
43 return Some(version);
44 }
45 }
46 }
47 }
48 }
49
50 None
51}
52
53pub async fn fetch_latest_python_version() -> Option<Version> {
55 let url = "https://endoflife.date/api/python.json";
57
58 let client = reqwest::Client::builder()
59 .timeout(std::time::Duration::from_secs(5))
60 .build()
61 .ok()?;
62
63 let response = client.get(url).send().await.ok()?;
64
65 if !response.status().is_success() {
66 return None;
67 }
68
69 let json: serde_json::Value = response.json().await.ok()?;
70
71 let cycles = json.as_array()?;
74
75 for cycle in cycles {
78 let cycle_name = cycle.get("cycle")?.as_str()?;
79 if cycle_name.starts_with("3.") {
80 let latest_str = cycle.get("latest")?.as_str()?;
81 if let Ok(version) = Version::from_str(latest_str) {
82 return Some(version);
83 }
84 }
85 }
86
87 None
88}
89
90pub async fn get_python_info(check_latest: bool) -> Option<PythonInfo> {
92 let current = detect_python_version()?;
93
94 let latest = if check_latest {
95 fetch_latest_python_version().await
96 } else {
97 None
98 };
99
100 Some(PythonInfo { current, latest })
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_detect_python_version() {
109 let version = detect_python_version();
111 if let Some(v) = version {
113 assert!(v.major >= 2);
114 }
115 }
116
117 #[test]
118 fn test_python_info_has_update() {
119 let info = PythonInfo {
120 current: Version::from_str("3.11.0").unwrap(),
121 latest: Some(Version::from_str("3.13.1").unwrap()),
122 };
123 assert!(info.has_update());
124
125 let info = PythonInfo {
126 current: Version::from_str("3.13.1").unwrap(),
127 latest: Some(Version::from_str("3.13.1").unwrap()),
128 };
129 assert!(!info.has_update());
130
131 let info = PythonInfo {
132 current: Version::from_str("3.11.0").unwrap(),
133 latest: None,
134 };
135 assert!(!info.has_update());
136 }
137}