package_parser/pkgs/
renv.rs1use 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 #[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}