package_parser/pkgs/ruby/
gemfilelock.rs1use nom::{
2 branch::alt,
3 bytes::complete::{tag, take, take_until},
4 character::complete::{line_ending, none_of, space0},
5 combinator::recognize,
6 error::VerboseError,
7 multi::{many1, many_till},
8 sequence::{delimited, tuple},
9 IResult,
10};
11use packageurl::PackageUrl;
12
13use crate::error::SourcePkgError;
14use crate::pkgs::common::model::{DependentPackage, Package};
15
16use std::fs::File;
17use std::io::Read;
18use std::path::Path;
19
20type NomResult<T, U> = IResult<T, U, VerboseError<T>>;
21
22fn take_till_line_end(input: &str) -> NomResult<&str, &str> {
23 recognize(tuple((
24 alt((take_until("\n"), take_until("\r\n"))),
25 take(1usize),
26 )))(input)
27}
28
29fn take_till_blank_line(input: &str) -> NomResult<&str, &str> {
30 recognize(alt((take_until("\n\n"), take_until("\r\n\r\n"))))(input)
31}
32
33fn gem_header(input: &str) -> NomResult<&str, &str> {
34 let (input, _) = recognize(take_until("GEM"))(input)?;
35 recognize(tuple((tag("GEM"), line_ending)))(input)
36}
37
38fn specs(input: &str) -> NomResult<&str, &str> {
39 let (input, _consumed) = recognize(many_till(
40 take_till_line_end,
41 recognize(tuple((space0, tag("specs:"), line_ending))),
42 ))(input)?;
43
44 take_till_blank_line(input)
45}
46
47fn package_name(input: &str) -> NomResult<&str, &str> {
48 let (input, _) = recognize(space0)(input)?;
49 recognize(take_until(" "))(input)
50}
51
52fn package_version(input: &str) -> NomResult<&str, &str> {
53 let (input, _) = space0(input)?;
54 delimited(tag("("), recognize(many1(none_of(" \t()"))), tag(")"))(input)
55}
56
57fn package(input: &str) -> Option<(String, String)> {
58 let (input, name) = package_name(input).ok()?;
59 let (_, version) = package_version(input).ok()?;
60
61 Some((name.to_string(), version.to_string()))
62}
63
64fn parse(input: &str) -> Result<Vec<(String, String)>, SourcePkgError> {
65 let (input, _) =
66 gem_header(input).map_err(|e| SourcePkgError::GenericsError2(e.to_string()))?;
67
68 let (_, consumed) = specs(input).map_err(|e| SourcePkgError::GenericsError2(e.to_string()))?;
69
70 let pkgs = consumed.lines().filter_map(package).collect::<Vec<_>>();
71
72 Ok(pkgs)
73}
74
75pub fn parse_file(path: impl AsRef<Path>) -> Result<Package, SourcePkgError> {
81 let mut fs = File::open(path)?;
82 let mut content = String::new();
83 fs.read_to_string(&mut content)?;
84
85 let pkgs = parse(&content)?;
86
87 let mut dependencies = vec![];
88
89 for (name, version) in pkgs {
90 let dependency = DependentPackage {
91 purl: PackageUrl::new("gem", name).unwrap().to_string(),
92 requirement: version,
93 is_resolved: true,
94 ..Default::default()
95 };
96
97 dependencies.push(dependency);
98 }
99
100 let package = Package {
101 dependencies,
102 ..Default::default()
103 };
104
105 Ok(package)
106}
107
108#[cfg(test)]
109mod test {
110 use super::*;
111
112 #[tokio::test]
113 async fn test_gemfile_lock() {
114 let filepath = Path::new(concat!(
115 env!("CARGO_MANIFEST_DIR"),
116 "/testdata/gemfile_lock/as_deps/Gemfile.lock"
117 ));
118
119 let p = parse_file(filepath).unwrap();
120 println!("{:?}", p);
121 }
122}