Skip to main content

package_parser/pkgs/dart/
pubspec.rs

1use packageurl::PackageUrl;
2use crate::types::DependentPackage;
3use serde_yaml::from_reader;
4use serde_yaml::Value;
5
6use crate::error::SourcePkgError;
7use crate::pkgs::common::model::{Package, PackageManifest};
8
9use std::fs::File;
10use std::path::Path;
11
12pub struct PubSpec {}
13
14impl PubSpec {
15    pub fn new() -> Self {
16        Self {}
17    }
18
19    fn collect_dependencies(
20        dep_root: &Value,
21        scope: &'static str,
22        is_optional: bool,
23        is_runtime: bool,
24    ) -> Vec<DependentPackage> {
25        match dep_root {
26            Value::Mapping(deps) => {
27                let mut requirements = vec![];
28                for (name, version) in deps {
29                    let name: String = match name {
30                        Value::String(s) => s.into(),
31                        _ => "".into(),
32                    };
33                    let version: String = match version {
34                        Value::String(s) => s.into(),
35                        _ => "".into(),
36                    };
37                    if !name.is_empty() {
38                        requirements.push(DependentPackage {
39                            purl: PackageUrl::new("pub", name)
40                                .expect("purl arguments are invalid")
41                                .to_string(),
42                            requirement: version,
43                            scope: scope.into(),
44                            is_optional,
45                            is_runtime,
46                            ..Default::default()
47                        });
48                    }
49                }
50
51                requirements
52            }
53            _ => vec![],
54        }
55    }
56
57    fn parse(path: impl AsRef<Path>) -> Result<Package, SourcePkgError> {
58        let path = path.as_ref();
59
60        let lock_path = path.with_file_name("pubspec.lock");
61        if lock_path.exists() {
62            log::info!("Found pubspec.lock, parsing it instead of pubspec.yaml");
63
64            match super::pubspec_lock::parse(lock_path) {
65                Ok(p) => return Ok(p),
66                Err(e) => {
67                    log::warn!("Failed to parse pubspec.lock: {}", e);
68                }
69            }
70        }
71
72        let mut file = File::open(path)?;
73        let root: Value = from_reader(&mut file)?;
74
75        let name = match &root["name"] {
76            Value::String(s) => s.into(),
77            _ => "".into(),
78        };
79        let version = match &root["version"] {
80            Value::String(s) => s.into(),
81            _ => "".into(),
82        };
83        let declared_license = match &root["license"] {
84            Value::String(s) => s.into(),
85            _ => "".into(),
86        };
87
88        let mut requirements =
89            Self::collect_dependencies(&root["dependencies"], "dependencies", false, true);
90
91        let mut dev_requirements =
92            Self::collect_dependencies(&root["dev_dependencies"], "dev_dependencies", true, false);
93
94        let mut env_requirements =
95            Self::collect_dependencies(&root["dev_dependencies"], "environment", false, true);
96
97        requirements.append(&mut dev_requirements);
98        requirements.append(&mut env_requirements);
99
100        let package = Package {
101            name,
102            version,
103            declared_license,
104            dependencies: requirements,
105            ..Default::default()
106        };
107
108        Ok(package)
109    }
110}
111
112#[async_trait::async_trait]
113impl PackageManifest for PubSpec {
114    fn get_name(&self) -> String {
115        "pubspec".to_string()
116    }
117
118    async fn recognize(&self, path: &Path) -> Result<Package, SourcePkgError> {
119        Self::parse(path)
120    }
121
122    fn file_name_patterns(&self) -> &'static [&'static str] {
123        &["pubspec.yaml"]
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn parse_pubspec() {
133        let filepath = Path::new(concat!(
134            env!("CARGO_MANIFEST_DIR"),
135            "/testdata/pubspec/specs/authors-pubspec.yaml"
136        ));
137
138        let p = PubSpec::parse(filepath).unwrap();
139        println!("{:?}", p);
140    }
141
142    #[test]
143    fn parse_pubspec_publish() {
144        let filepath = Path::new(concat!(
145            env!("CARGO_MANIFEST_DIR"),
146            "/testdata/pubspec/specs/publish-pubspec.yaml"
147        ));
148
149        let p = PubSpec::parse(filepath).unwrap();
150        println!("{:?}", p);
151    }
152}