Skip to main content

package_parser/pkgs/python/
pipfile.rs

1use 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}