python_packaging/
package_metadata.rs1use {
12 anyhow::{Context, Result},
13 mailparse::parse_mail,
14};
15
16pub struct PythonPackageMetadata {
18 headers: Vec<(String, String)>,
19}
20
21impl PythonPackageMetadata {
22 pub fn from_metadata(data: &[u8]) -> Result<PythonPackageMetadata> {
24 let message = parse_mail(data).context("parsing metadata file")?;
25
26 let headers = message
27 .headers
28 .iter()
29 .map(|header| (header.get_key(), header.get_value()))
30 .collect::<Vec<_>>();
31
32 Ok(PythonPackageMetadata { headers })
33 }
34
35 pub fn find_first_header(&self, key: &str) -> Option<&str> {
37 for (k, v) in &self.headers {
38 if k == key {
39 return Some(v);
40 }
41 }
42
43 None
44 }
45
46 #[allow(unused)]
48 pub fn find_all_headers(&self, key: &str) -> Vec<&str> {
49 self.headers
50 .iter()
51 .filter_map(|(k, v)| if k == key { Some(v.as_ref()) } else { None })
52 .collect::<Vec<_>>()
53 }
54
55 pub fn name(&self) -> Option<&str> {
56 self.find_first_header("Name")
57 }
58
59 pub fn version(&self) -> Option<&str> {
60 self.find_first_header("Version")
61 }
62
63 #[allow(unused)]
64 pub fn license(&self) -> Option<&str> {
65 self.find_first_header("License")
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn test_parse_metadata() -> Result<()> {
75 let data = concat!(
76 "Metadata-Version: 2.1\n",
77 "Name: black\n",
78 "Version: 19.10b0\n",
79 "Summary: The uncompromising code formatter.\n",
80 "Home-page: https://github.com/psf/black\n",
81 "Author: Ćukasz Langa\n",
82 "Author-email: lukasz@langa.pl\n",
83 "License: MIT\n",
84 "Requires-Dist: click (>=6.5)\n",
85 "Requires-Dist: attrs (>=18.1.0)\n",
86 "Requires-Dist: appdirs\n",
87 "\n",
88 "\n",
89 "\n",
90 "<h2 align=\"center\">The Uncompromising Code Formatter</h2>\n",
91 ).as_bytes();
92
93 let m = PythonPackageMetadata::from_metadata(data)?;
94
95 assert_eq!(m.name(), Some("black"));
96 assert_eq!(m.version(), Some("19.10b0"));
97 assert_eq!(m.license(), Some("MIT"));
98 assert_eq!(
99 m.find_all_headers("Requires-Dist"),
100 vec!["click (>=6.5)", "attrs (>=18.1.0)", "appdirs"]
101 );
102 assert_eq!(m.find_first_header("Missing"), None);
103
104 Ok(())
105 }
106}