Skip to main content

package_parser/pkgs/
renv.rs

1use std::{collections::{HashMap, HashSet}, path::Path};
2
3use packageurl::PackageUrl;
4use crate::types::{DependentPackage, Package, Relation};
5use serde::Deserialize;
6
7use crate::{error::SourcePkgError, PackageManifest};
8
9#[derive(Deserialize, Debug)]
10#[serde(rename_all = "PascalCase")]
11struct LockFile {
12    #[serde(default)]
13    packages: HashMap<String, LockPackage>,
14}
15
16#[derive(Deserialize, Debug)]
17#[serde(rename_all = "PascalCase")]
18struct LockPackage {
19    package: String,
20    version: String,
21    // source: String,
22    #[serde(default)]
23    requirements: Vec<String>,
24}
25
26impl LockPackage {
27    fn to_purl(&self) -> String {
28        let mut purl = PackageUrl::new("cran", &self.package).unwrap();
29        purl.with_version(&self.version);
30
31        purl.to_string()
32    }
33
34    fn depends_on(&self, package: &str) -> bool {
35        self.requirements.iter().any(|x| x.as_str() == package)
36    }
37}
38
39pub struct RenvLock {}
40
41impl RenvLock {
42    pub fn new() -> Self {
43        Self {}
44    }
45
46    fn parse(path: &Path) -> Result<Package, SourcePkgError> {
47        let reader = std::fs::File::open(path).map_err(SourcePkgError::Io)?;
48
49        let parsed: LockFile =
50            serde_json::from_reader(reader).map_err(SourcePkgError::JsonParse)?;
51
52        let mut deps = vec![];
53
54        for pkg in parsed.packages.values() {
55            let purl = pkg.to_purl();
56
57            let mut parents = HashSet::new();
58
59            for parent in parsed.packages.values() {
60                if parent.depends_on(&pkg.package) {
61                    parents.insert(parent.to_purl());
62                }
63            }
64
65            let mut d = DependentPackage {
66                purl,
67                requirement: pkg.version.clone(),
68                scope: "main".into(),
69                is_resolved: true,
70                is_runtime: true,
71                is_optional: false,
72                relation: maplit::hashset! { Relation::Direct },
73                parents,
74                reachable: Default::default(),
75            };
76
77            if !d.parents.is_empty() {
78                d.relation.insert(Relation::Indirect);
79            }
80
81            deps.push(d);
82        }
83
84        Ok(Package {
85            dependencies: deps,
86            ..Default::default()
87        })
88    }
89}
90
91#[async_trait::async_trait]
92impl PackageManifest for RenvLock {
93    fn get_name(&self) -> String {
94        "renv".into()
95    }
96
97    async fn recognize(&self, path: &Path) -> Result<Package, SourcePkgError> {
98        Self::parse(path)
99    }
100
101    fn file_name_patterns(&self) -> &'static [&'static str] {
102        &["renv.lock"]
103    }
104}
105
106
107#[cfg(test)]
108mod test {
109    use std::path::PathBuf;
110
111    use super::RenvLock;
112
113    #[test]
114    fn v4_2_2() {
115        let filepath = PathBuf::from(concat!(
116            env!("CARGO_MANIFEST_DIR"),
117            "/testdata/renv/422-1.lock"
118        ));
119
120        dbg!(RenvLock::parse(&filepath).unwrap());
121    }
122
123    #[test]
124    fn v3_6_3() {
125        let filepath = PathBuf::from(concat!(
126            env!("CARGO_MANIFEST_DIR"),
127            "/testdata/renv/363-1.lock"
128        ));
129
130        dbg!(RenvLock::parse(&filepath).unwrap());
131    }
132}