package_parser/pkgs/python/
pipfile.rs1use packageurl::PackageUrl;
2use crate::types::DependentPackage;
3use toml::de::from_str;
4use toml::Value;
5
6use crate::error::SourcePkgError;
7use crate::pkgs::common::model::{Package, PackageManifest};
8
9use std::fs::File;
10use std::io::Read;
11use std::path::Path;
12
13pub struct Pipfile {}
14
15impl Pipfile {
16 pub fn new() -> Self {
17 Self {}
18 }
19
20 fn parse(path: impl AsRef<Path>) -> Result<Package, SourcePkgError> {
21 let mut file = File::open(path)?;
22 let mut content = String::new();
23 file.read_to_string(&mut content)?;
24 let root: Value = from_str(&content)?;
25 let requirements = root
26 .as_table()
27 .and_then(|root_table| root_table.get("packages").and_then(|v| v.as_table()))
28 .map(|requires| {
29 let mut dependencies = vec![];
30 for (name, version_requirement) in requires {
31 let version = match version_requirement {
32 Value::String(version) => version,
33 Value::Table(version_table) => version_table
34 .get("version")
35 .and_then(|v| v.as_str())
36 .unwrap_or(""),
37 _ => "",
38 };
39 let dependency = DependentPackage {
40 purl: PackageUrl::new("pypi", name)
41 .expect("purl arguments are invalid")
42 .to_string(),
43 requirement: version.to_string(),
44 ..Default::default()
45 };
46 dependencies.push(dependency);
47 }
48 dependencies
49 });
50
51 let dev_requirements = root
52 .as_table()
53 .and_then(|root_table| root_table.get("dev-packages").and_then(|v| v.as_table()))
54 .map(|requires| {
55 let mut dependencies = vec![];
56 for (name, version_requirement) in requires {
57 let version = match version_requirement {
58 Value::String(version) => version,
59 Value::Table(version_table) => version_table
60 .get("version")
61 .and_then(|v| v.as_str())
62 .unwrap_or(""),
63 _ => "",
64 };
65 let dependency = DependentPackage {
66 purl: PackageUrl::new("pypi", name)
67 .expect("purl arguments are invalid")
68 .to_string(),
69 requirement: version.to_string(),
70 is_runtime: false,
71 is_optional: true,
72 ..Default::default()
73 };
74 dependencies.push(dependency);
75 }
76 dependencies
77 });
78
79 let mut requirements = requirements.unwrap_or_default();
80 let mut dev_requirements = dev_requirements.unwrap_or_default();
81 requirements.append(&mut dev_requirements);
82
83 let package = Package {
84 primary_language: "Python".into(),
85 dependencies: requirements,
86 ..Default::default()
87 };
88
89 Ok(package)
90 }
91}
92
93#[async_trait::async_trait]
94impl PackageManifest for Pipfile {
95 fn get_name(&self) -> String {
96 "pypi".to_string()
97 }
98
99 async fn recognize(&self, path: &Path) -> Result<Package, SourcePkgError> {
100 Pipfile::parse(path)
101 }
102
103 fn file_name_patterns(&self) -> &'static [&'static str] {
104 &["pipfile", "*pipfile"]
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[tokio::test]
113 async fn test_parse_pipfile() {
114 let filepath = Path::new(concat!(
115 env!("CARGO_MANIFEST_DIR"),
116 "/testdata/pypi/pipfile/Pipfile"
117 ));
118
119 let parser = Pipfile::new();
120 let p = parser.recognize(filepath).await.unwrap();
121 println!("{:?}", p);
122 }
123}