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