package_parser/pkgs/
opam.rs1use opam_file_rs::parse as opam_file_parse;
2use opam_file_rs::value::{OpamFileItem, OpamFileSection, RelOp, RelOpKind, Value, ValueKind};
3use packageurl::PackageUrl;
4
5use crate::error::SourcePkgError;
6use crate::pkgs::common::model::{Package, PackageManifest, Party};
7
8use std::fs::File;
9use std::io::Read;
10use std::path::Path;
11
12use super::common::model::DependentPackage;
13
14pub struct OcamlOpam {}
15
16#[derive(Debug)]
17struct OcamlOpamFile {
18 pub homepage: Option<String>,
19 pub description: Option<String>,
20 pub maintainers: Vec<Party>,
21 pub dev_repo: Option<String>,
22 pub license: Option<String>,
23 pub dependencies: Vec<DependentPackage>,
24}
25
26impl OcamlOpamFile {
27 fn new() -> Self {
28 Self {
29 homepage: None,
30 description: None,
31 maintainers: vec![],
32 dev_repo: None,
33 license: None,
34 dependencies: vec![],
35 }
36 }
37}
38
39impl OcamlOpam {
40 pub fn new() -> Self {
41 Self {}
42 }
43
44 fn parse_opam_description(value: Value) -> Option<String> {
45 match value.kind {
46 ValueKind::String(value) => Some(value),
47 _ => {
48 log::error!("unexpected value kind : {:?}", value.kind);
49 None
50 }
51 }
52 }
53
54 fn parse_opam_maintainer(value: Value) -> Vec<Party> {
55 match value.kind {
56 ValueKind::List(values) => {
57 let mut maintainers = Vec::new();
58 for maintainer in values {
59 match maintainer.kind {
60 ValueKind::String(maintainer) => {
61 maintainers.push(Party {
62 typ: "".into(),
63 name: "".into(),
64 email: maintainer.to_string(),
65 url: "".into(),
66 ..Default::default()
67 });
68 }
69 _ => {
70 log::error!("unexpected value kind : {:?}", maintainer.kind);
71 }
72 }
73 }
74
75 maintainers
76 }
77 _ => {
78 log::error!("unexpected value kind : {:?}", value.kind);
79 vec![]
80 }
81 }
82 }
83
84 fn parse_opam_homepage(value: Value) -> Option<String> {
85 match value.kind {
86 ValueKind::String(value) => Some(value),
87 _ => {
88 log::error!("unexpected value kind : {:?}", value.kind);
89 None
90 }
91 }
92 }
93
94 fn parse_opam_devrepo(value: Value) -> Option<String> {
95 match value.kind {
96 ValueKind::String(value) => Some(value),
97 _ => {
98 log::error!("unexpected value kind : {:?}", value.kind);
99 None
100 }
101 }
102 }
103
104 fn parse_opam_license(value: Value) -> Option<String> {
105 match value.kind {
106 ValueKind::String(value) => Some(value),
107 _ => {
108 log::error!("unexpected value kind : {:?}", value.kind);
109 None
110 }
111 }
112 }
113
114 fn opam_relop_to_string(op: RelOp) -> String {
115 match op.kind {
116 RelOpKind::Eq => "=".into(),
117 RelOpKind::Neq => "!=".into(),
118 RelOpKind::Lt => "<".into(),
119 RelOpKind::Leq => "<=".into(),
120 RelOpKind::Gt => ">".into(),
121 RelOpKind::Geq => ">=".into(),
122 RelOpKind::Sem => "~".into(),
123 }
124 }
125
126 fn parse_opam_depend_option_item(name: Value, props: Vec<Value>) -> Option<DependentPackage> {
127 match name.kind {
128 ValueKind::String(name) => {
129 let mut version = None;
130 let mut relop_string = None;
131 for prop in props {
132 match prop.kind {
133 ValueKind::PrefixRelOp(rel_op, version_value) => match version_value.kind {
134 ValueKind::String(version_value) => {
135 version = Some(version_value);
136 relop_string = Some(Self::opam_relop_to_string(rel_op));
137 }
138 _ => {
139 log::error!("unexpected value kind : {:?}", version_value.kind);
140 }
141 },
142 _ => {
143 log::error!("unexpected value kind : {:?}", prop.kind);
144 }
145 }
146 }
147 return Some(DependentPackage {
148 purl: PackageUrl::new("opam", name.as_str())
149 .expect("purl arguments are invalid")
150 .to_string(),
151 requirement: format!(
152 "{}{}",
153 relop_string.unwrap_or_default(),
154 version.unwrap_or_default()
155 )
156 .trim()
157 .to_string(),
158 ..Default::default()
159 });
160 }
161 _ => {
162 log::error!("unexpected value kind : {:?}", name.kind);
163 }
164 }
165
166 None
167 }
168
169 fn parse_opam_depends(value: Value) -> Vec<DependentPackage> {
170 match value.kind {
198 ValueKind::List(values) => {
199 let mut dependencies = Vec::new();
200 for dependency in values {
201 match dependency.kind {
202 ValueKind::Option(name, props) => {
203 let dep = Self::parse_opam_depend_option_item(*name, props);
204 if let Some(dep) = dep {
205 dependencies.push(dep);
206 }
207 }
208 _ => {
209 log::error!("unexpected value kind : {:?}", dependency.kind);
210 }
211 }
212 }
213
214 dependencies
215 }
216 _ => {
217 log::error!("unexpected value kind : {:?}", value.kind);
218 vec![]
219 }
220 }
221 }
222
223 fn parse_opam_section(_section: OpamFileSection) {}
224
225 fn parse_opam_variable(opam_file: &mut OcamlOpamFile, section_name: String, value: Value) {
226 if section_name == "description" {
227 opam_file.description = Self::parse_opam_description(value);
228 } else if section_name == "maintainer" {
229 opam_file.maintainers = Self::parse_opam_maintainer(value);
230 } else if section_name == "homepage" {
231 opam_file.homepage = Self::parse_opam_homepage(value);
232 } else if section_name == "dev-repo" {
233 opam_file.dev_repo = Self::parse_opam_devrepo(value);
234 } else if section_name == "license" {
235 opam_file.license = Self::parse_opam_license(value);
236 } else if section_name == "depends" {
237 opam_file.dependencies = Self::parse_opam_depends(value);
238 } else if section_name == "authors" {
239 opam_file.maintainers = Self::parse_opam_maintainer(value);
240 }
241 }
242
243 fn parse_ocaml_opam(path: impl AsRef<Path>) -> Result<Package, SourcePkgError> {
244 let mut fs = File::open(path)?;
245 let mut content = String::new();
246 fs.read_to_string(&mut content)?;
247 let mut opam_file = OcamlOpamFile::new();
248 let opam = opam_file_parse(&content)?;
249 for opam_item in opam.file_contents {
250 match opam_item {
251 OpamFileItem::Section(_, section) => {
252 Self::parse_opam_section(section);
253 }
254 OpamFileItem::Variable(_, string_value, value) => {
255 Self::parse_opam_variable(&mut opam_file, string_value, value);
256 }
257 }
258 }
259
260 let package = Package {
261 declared_license: opam_file.license.unwrap_or_default(),
262 dependencies: opam_file.dependencies,
263 ..Default::default()
264 };
265
266 Ok(package)
267 }
268}
269
270#[async_trait::async_trait]
271impl PackageManifest for OcamlOpam {
272 fn get_name(&self) -> String {
273 "opam".into()
274 }
275
276 async fn recognize(&self, path: &Path) -> Result<Package, SourcePkgError> {
277 Self::parse_ocaml_opam(path)
278 }
279
280 fn file_name_patterns(&self) -> &'static [&'static str] {
281 &["*.opam"]
282 }
283}
284
285#[cfg(test)]
286mod test {
287 use super::*;
288 use std::path::Path;
289
290 #[test]
291 fn test_opam() {
292 let filepath = Path::new(concat!(
293 env!("CARGO_MANIFEST_DIR"),
294 "/testdata/opam/sample1/sample1.opam"
295 ));
296
297 OcamlOpam::parse_ocaml_opam(filepath).unwrap();
298 }
299}