1#[derive(Debug)]
18pub struct PythonVersionInfo {
19 pub major: u8,
21 pub minor: u8,
23 pub patch: u8,
25 pub suffix: Option<&'static str>,
27}
28
29impl PythonVersionInfo {
30 pub(crate) fn from_str(
32 version_number_str: &'static str,
33 ) -> Result<PythonVersionInfo, &'static str> {
34 fn split_and_parse_number(version_part: &str) -> (u8, Option<&str>) {
35 match version_part.find(|c: char| !c.is_ascii_digit()) {
36 None => (version_part.parse().unwrap(), None),
37 Some(version_part_suffix_start) => {
38 let (version_part, version_part_suffix) =
39 version_part.split_at(version_part_suffix_start);
40 (version_part.parse().unwrap(), Some(version_part_suffix))
41 }
42 }
43 }
44
45 let mut parts = version_number_str.splitn(3, '.');
46 let major_str = parts.next().ok_or("Python major version missing")?;
47 let minor_str = parts.next().ok_or("Python minor version missing")?;
48 let patch_str = parts.next();
49
50 let major = major_str
51 .parse()
52 .map_err(|_| "Python major version not an integer")?;
53 let (minor, suffix) = split_and_parse_number(minor_str);
54 if suffix.is_some() {
55 assert!(patch_str.is_none());
56 return Ok(PythonVersionInfo {
57 major,
58 minor,
59 patch: 0,
60 suffix,
61 });
62 }
63
64 let (patch, suffix) = patch_str.map(split_and_parse_number).unwrap_or_default();
65 Ok(PythonVersionInfo {
66 major,
67 minor,
68 patch,
69 suffix,
70 })
71 }
72}
73
74impl PartialEq<(u8, u8)> for PythonVersionInfo {
75 fn eq(&self, other: &(u8, u8)) -> bool {
76 self.major == other.0 && self.minor == other.1
77 }
78}
79
80impl PartialEq<(u8, u8, u8)> for PythonVersionInfo {
81 fn eq(&self, other: &(u8, u8, u8)) -> bool {
82 self.major == other.0 && self.minor == other.1 && self.patch == other.2
83 }
84}
85
86impl PartialOrd<(u8, u8)> for PythonVersionInfo {
87 fn partial_cmp(&self, other: &(u8, u8)) -> Option<std::cmp::Ordering> {
88 (self.major, self.minor).partial_cmp(other)
89 }
90}
91
92impl PartialOrd<(u8, u8, u8)> for PythonVersionInfo {
93 fn partial_cmp(&self, other: &(u8, u8, u8)) -> Option<std::cmp::Ordering> {
94 (self.major, self.minor, self.patch).partial_cmp(other)
95 }
96}
97
98#[cfg(test)]
99mod test {
100 use super::*;
101 use crate::Python;
102 #[test]
103 fn test_python_version_info() {
104 Python::attach(|py| {
105 let version = py.version_info();
106 #[cfg(Py_3_8)]
107 assert!(version >= (3, 8));
108 #[cfg(Py_3_8)]
109 assert!(version >= (3, 8, 0));
110 #[cfg(Py_3_9)]
111 assert!(version >= (3, 9));
112 #[cfg(Py_3_9)]
113 assert!(version >= (3, 9, 0));
114 #[cfg(Py_3_10)]
115 assert!(version >= (3, 10));
116 #[cfg(Py_3_10)]
117 assert!(version >= (3, 10, 0));
118 #[cfg(Py_3_11)]
119 assert!(version >= (3, 11));
120 #[cfg(Py_3_11)]
121 assert!(version >= (3, 11, 0));
122 #[cfg(Py_3_12)]
123 assert!(version >= (3, 12));
124 #[cfg(Py_3_12)]
125 assert!(version >= (3, 12, 0));
126 #[cfg(Py_3_13)]
127 assert!(version >= (3, 13));
128 #[cfg(Py_3_13)]
129 assert!(version >= (3, 13, 0));
130 #[cfg(Py_3_14)]
131 assert!(version >= (3, 14));
132 #[cfg(Py_3_14)]
133 assert!(version >= (3, 14, 0));
134 });
135 }
136
137 #[test]
138 fn test_python_version_info_parse() {
139 assert!(PythonVersionInfo::from_str("3.5.0a1").unwrap() >= (3, 5, 0));
140 assert!(PythonVersionInfo::from_str("3.5+").unwrap() >= (3, 5, 0));
141 assert_eq!(PythonVersionInfo::from_str("3.5+").unwrap(), (3, 5, 0));
142 assert_ne!(PythonVersionInfo::from_str("3.5+").unwrap(), (3, 5, 1));
143 assert!(PythonVersionInfo::from_str("3.5.2a1+").unwrap() < (3, 5, 3));
144 assert_eq!(PythonVersionInfo::from_str("3.5.2a1+").unwrap(), (3, 5, 2));
145 assert_eq!(PythonVersionInfo::from_str("3.5.2a1+").unwrap(), (3, 5));
146 assert_eq!(PythonVersionInfo::from_str("3.5+").unwrap(), (3, 5));
147 assert!(PythonVersionInfo::from_str("3.5.2a1+").unwrap() < (3, 6));
148 assert!(PythonVersionInfo::from_str("3.5.2a1+").unwrap() > (3, 4));
149 assert!(PythonVersionInfo::from_str("3.11.3+chromium.29").unwrap() >= (3, 11, 3));
150 assert_eq!(
151 PythonVersionInfo::from_str("3.11.3+chromium.29")
152 .unwrap()
153 .suffix,
154 Some("+chromium.29")
155 );
156 }
157}