znippy_plugin_python/
wheel.rs1#[derive(Debug, Clone, PartialEq)]
9pub struct WheelInfo {
10 pub name: String,
11 pub version: String,
12 pub build_tag: Option<String>,
13 pub python_tag: String,
14 pub abi_tag: String,
15 pub platform_tag: String,
16}
17
18impl WheelInfo {
19 pub fn normalized_name(&self) -> String {
21 normalize_name(&self.name)
22 }
23
24 pub fn dist_info_dir(&self) -> String {
26 format!("{}-{}.dist-info", self.name, self.version)
27 }
28}
29
30pub fn normalize_name(s: &str) -> String {
32 s.to_lowercase().replace(['_', '.', '-'], "-")
33}
34
35pub fn parse_wheel_filename(filename: &str) -> Option<WheelInfo> {
39 let stem = filename.strip_suffix(".whl")?;
40 let parts: Vec<&str> = stem.split('-').collect();
41
42 match parts.len() {
45 5 => Some(WheelInfo {
46 name: parts[0].to_string(),
47 version: parts[1].to_string(),
48 build_tag: None,
49 python_tag: parts[2].to_string(),
50 abi_tag: parts[3].to_string(),
51 platform_tag: parts[4].to_string(),
52 }),
53 6 => Some(WheelInfo {
54 name: parts[0].to_string(),
55 version: parts[1].to_string(),
56 build_tag: Some(parts[2].to_string()),
57 python_tag: parts[3].to_string(),
58 abi_tag: parts[4].to_string(),
59 platform_tag: parts[5].to_string(),
60 }),
61 n if n >= 5 => {
63 let platform_tag = parts[n - 1].to_string();
71 let abi_tag = parts[n - 2].to_string();
72 let python_tag = parts[n - 3].to_string();
73
74 let version_end = n - 3;
76 if version_end >= 3 && parts[version_end - 1].chars().next().map(|c| c.is_ascii_digit()).unwrap_or(false) {
77 let maybe_build = parts[version_end - 1];
79 Some(WheelInfo {
81 name: parts[..version_end - 2].join("_"),
82 version: parts[version_end - 2].to_string(),
83 build_tag: Some(maybe_build.to_string()),
84 python_tag,
85 abi_tag,
86 platform_tag,
87 })
88 } else {
89 Some(WheelInfo {
90 name: parts[..version_end - 1].join("_"),
91 version: parts[version_end - 1].to_string(),
92 build_tag: None,
93 python_tag,
94 abi_tag,
95 platform_tag,
96 })
97 }
98 }
99 _ => None,
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn simple_wheel() {
109 let info = parse_wheel_filename("numpy-1.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl").unwrap();
110 assert_eq!(info.name, "numpy");
111 assert_eq!(info.version, "1.26.0");
112 assert_eq!(info.build_tag, None);
113 assert_eq!(info.python_tag, "cp311");
114 assert_eq!(info.abi_tag, "cp311");
115 assert_eq!(info.platform_tag, "manylinux_2_17_x86_64.manylinux2014_x86_64");
116 }
117
118 #[test]
119 fn pure_python_wheel() {
120 let info = parse_wheel_filename("requests-2.31.0-py3-none-any.whl").unwrap();
121 assert_eq!(info.name, "requests");
122 assert_eq!(info.version, "2.31.0");
123 assert_eq!(info.python_tag, "py3");
124 assert_eq!(info.abi_tag, "none");
125 assert_eq!(info.platform_tag, "any");
126 }
127
128 #[test]
129 fn with_build_tag() {
130 let info = parse_wheel_filename("package-1.0.0-1-cp39-cp39-linux_x86_64.whl").unwrap();
131 assert_eq!(info.name, "package");
132 assert_eq!(info.version, "1.0.0");
133 assert_eq!(info.build_tag, Some("1".to_string()));
134 assert_eq!(info.python_tag, "cp39");
135 }
136
137 #[test]
138 fn normalized_name() {
139 let info = parse_wheel_filename("My_Cool.Package-1.0-py3-none-any.whl").unwrap();
140 assert_eq!(info.normalized_name(), "my-cool-package");
141 }
142
143 #[test]
144 fn not_a_wheel() {
145 assert!(parse_wheel_filename("requests-2.31.0.tar.gz").is_none());
146 assert!(parse_wheel_filename("something.zip").is_none());
147 }
148
149 #[test]
150 fn dist_info_dir() {
151 let info = parse_wheel_filename("numpy-1.26.0-cp311-cp311-linux_x86_64.whl").unwrap();
152 assert_eq!(info.dist_info_dir(), "numpy-1.26.0.dist-info");
153 }
154}