1use nom::branch::alt;
5use nom::bytes::complete::{tag, take_until};
6use nom::character::complete::{digit1, space1};
7use nom::combinator::map_res;
8use nom::multi::{many0, many1};
9use nom::sequence::{preceded, terminated};
10use nom::{IResult, Parser};
11
12use crate::error::{Error, Result};
13
14pub mod error;
15
16#[non_exhaustive]
17pub struct YarnState<'a> {
18 pub version: u32,
19 pub packages: Vec<Package<'a>>,
20}
21
22#[non_exhaustive]
23pub struct Package<'a> {
24 pub resolution: &'a str,
25 pub locations: Vec<&'a str>,
26}
27
28pub fn parse(input: &str) -> Result<YarnState<'_>> {
29 statefile(input)
30}
31
32fn statefile(input: &str) -> Result<YarnState> {
33 match (header, many1(package)).parse(input) {
34 Ok((input, _)) if !input.is_empty() => Err(Error::InvalidSyntax()),
35 Err(_) => Err(Error::InvalidSyntax()),
36
37 Ok((_, (version, packages))) => Ok(YarnState { version, packages }),
38 }
39}
40
41fn header(input: &str) -> IResult<&str, u32> {
43 preceded(
44 (
45 many0((tag("#"), take_until("\n"), tag("\n"))),
46 many0(newline),
47 tag("__metadata:"),
48 newline,
49 space1,
50 tag("version: "),
51 ),
52 terminated(
53 map_res(digit1, |n: &str| n.parse()),
54 (newline, many0((space1, take_until("\n"), tag("\n")))),
55 ),
56 )
57 .parse(input)
58}
59
60fn package(input: &str) -> IResult<&str, Package> {
61 (
62 preceded(
63 (newline, tag("\"")),
64 terminated(take_until("\":"), (tag("\":"), newline)),
65 ),
66 many1(package_field),
67 )
68 .parse(input)
69 .map(|(input, (resolution, fields))| {
70 let mut locations = Vec::new();
71 for field in fields {
72 match field {
73 PackageField::Locations(l) => locations = l,
74 PackageField::Unknown => {}
75 }
76 }
77 (
78 input,
79 Package {
80 resolution,
81 locations,
82 },
83 )
84 })
85}
86
87enum PackageField<'a> {
88 Locations(Vec<&'a str>),
89 Unknown,
90}
91
92fn package_field(input: &str) -> IResult<&str, PackageField> {
93 alt((package_field_locations, package_field_unknown)).parse(input)
94}
95
96fn package_field_locations(input: &str) -> IResult<&str, PackageField> {
97 preceded(
98 (space1, tag("locations:"), newline),
99 many1(preceded(
100 (space1, tag("- \"")),
101 terminated(take_until("\""), (tag("\""), newline)),
102 )),
103 )
104 .parse(input)
105 .map(|(input, locations)| (input, PackageField::Locations(locations)))
106}
107
108fn package_field_unknown(input: &str) -> IResult<&str, PackageField> {
109 let (input, indent) = space1(input)?;
110 let (input, _) = (take_until("\n"), tag("\n")).parse(input)?;
111 let (input, _) = many0((tag(indent), space1, take_until("\n"), tag("\n"))).parse(input)?;
112 Ok((input, PackageField::Unknown))
113}
114
115fn newline(input: &str) -> IResult<&str, &str> {
116 alt((tag("\n"), tag("\r\n"))).parse(input)
117}