yarn_state/
lib.rs

1// SPDX-FileCopyrightText: 2024 The Chaste Authors
2// SPDX-License-Identifier: Apache-2.0 OR BSD-2-Clause
3
4use 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
41// Returns version number
42fn 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}