rix/parsers/
derivations.rs

1use crate::derivations::{Derivation, DerivationOutput};
2use nom::branch::alt;
3use nom::bytes::complete::{is_not, tag};
4use nom::character::complete::char;
5use nom::combinator::{map, opt, value, verify};
6use nom::multi::{fold_many0, separated_list0};
7use nom::sequence::{delimited, pair, preceded, tuple};
8use nom::IResult;
9use std::collections::{BTreeMap, BTreeSet};
10
11pub fn parse_derivation(input: &str) -> IResult<&str, Derivation> {
12    delimited(tag("Derive("), parse_derivation_args, char(')'))(input)
13}
14
15fn parse_derivation_args(input: &str) -> IResult<&str, Derivation> {
16    let (input, (outputs, _, input_drvs, _, input_srcs, _, system, _, builder, _, args, _, env)) =
17        tuple((
18            parse_derivation_outputs,
19            char(','),
20            parse_input_derivations,
21            char(','),
22            parse_string_set,
23            char(','),
24            parse_string,
25            char(','),
26            parse_string,
27            char(','),
28            parse_strings,
29            char(','),
30            parse_env,
31        ))(input)?;
32    Ok((
33        input,
34        Derivation {
35            args,
36            builder,
37            env,
38            input_drvs,
39            input_srcs,
40            outputs,
41            system,
42        },
43    ))
44}
45
46fn parse_derivation_outputs(input: &str) -> IResult<&str, BTreeMap<String, DerivationOutput>> {
47    let derivation_outputs = fold_many0(
48        pair(parse_derivation_output, opt(char(','))),
49        BTreeMap::new,
50        |mut drv_outputs, ((name, drv_output), _)| {
51            drv_outputs.insert(name, drv_output);
52            drv_outputs
53        },
54    );
55    delimited(char('['), derivation_outputs, char(']'))(input)
56}
57
58fn parse_derivation_output(input: &str) -> IResult<&str, (String, DerivationOutput)> {
59    let (input, (_, derivation_name, _, path, _, hash_algo, _, hash, _)) = tuple((
60        char('('),
61        parse_string,
62        char(','),
63        parse_string,
64        char(','),
65        parse_string,
66        char(','),
67        parse_string,
68        char(')'),
69    ))(input)?;
70    Ok((
71        input,
72        (
73            derivation_name,
74            DerivationOutput {
75                hash: if hash.is_empty() { None } else { Some(hash) },
76                hash_algo: if hash_algo.is_empty() {
77                    None
78                } else {
79                    Some(hash_algo)
80                },
81                path: path,
82            },
83        ),
84    ))
85}
86
87fn parse_string(input: &str) -> IResult<&str, String> {
88    delimited(char('"'), parse_string_inside_quotes, char('"'))(input)
89}
90
91fn parse_input_derivations(input: &str) -> IResult<&str, BTreeMap<String, BTreeSet<String>>> {
92    let input_derivations = fold_many0(
93        tuple((
94            char('('),
95            parse_string,
96            char(','),
97            parse_string_set,
98            char(')'),
99            opt(char(',')),
100        )),
101        BTreeMap::new,
102        |mut input_drvs, (_, drv, _, input_type, _, _)| {
103            input_drvs.insert(drv, input_type);
104            input_drvs
105        },
106    );
107    delimited(char('['), input_derivations, char(']'))(input)
108}
109
110fn parse_string_set(input: &str) -> IResult<&str, BTreeSet<String>> {
111    let string_set = fold_many0(
112        pair(parse_string, opt(char(','))),
113        BTreeSet::new,
114        |mut strings, (string, _)| {
115            strings.insert(string);
116            strings
117        },
118    );
119    delimited(char('['), string_set, char(']'))(input)
120}
121
122fn parse_strings(input: &str) -> IResult<&str, Vec<String>> {
123    delimited(
124        char('['),
125        separated_list0(char(','), parse_string),
126        char(']'),
127    )(input)
128}
129
130fn parse_env(input: &str) -> IResult<&str, BTreeMap<String, String>> {
131    let env_vars = fold_many0(
132        tuple((
133            char('('),
134            parse_string,
135            char(','),
136            parse_string,
137            char(')'),
138            opt(char(',')),
139        )),
140        BTreeMap::new,
141        |mut env_vars, (_, name, _, value, _, _)| {
142            env_vars.insert(name, value);
143            env_vars
144        },
145    );
146    delimited(char('['), env_vars, char(']'))(input)
147}
148
149enum StringFragment<'a> {
150    Literal(&'a str),
151    EscapedChar(char),
152}
153
154fn parse_string_inside_quotes(input: &str) -> IResult<&str, String> {
155    fold_many0(
156        parse_string_fragment,
157        String::new,
158        |mut string, fragment| {
159            match fragment {
160                StringFragment::Literal(str_literal) => string.push_str(str_literal),
161                StringFragment::EscapedChar(escaped_char) => string.push(escaped_char),
162            }
163            string
164        },
165    )(input)
166}
167
168fn parse_string_fragment(input: &str) -> IResult<&str, StringFragment> {
169    alt((parse_literal, parse_escaped_char))(input)
170}
171
172fn parse_literal(input: &str) -> IResult<&str, StringFragment> {
173    let non_empty_literal = verify(is_not("\"\\"), |matched_str: &str| !matched_str.is_empty());
174    map(non_empty_literal, StringFragment::Literal)(input)
175}
176
177fn parse_escaped_char(input: &str) -> IResult<&str, StringFragment> {
178    let escaped_char = preceded(
179        char('\\'),
180        alt((
181            char('"'),
182            char('\\'),
183            value('\n', char('n')),
184            value('\r', char('r')),
185            value('\t', char('t')),
186        )),
187    );
188    map(escaped_char, StringFragment::EscapedChar)(input)
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn test_parse_derivation() {
197        let expected_derivation = Derivation {
198            args: to_string_vec(&["-e", "/builder.sh"]),
199            builder: "/bash".to_owned(),
200            env: vec![
201                ("ENV1".to_owned(), "val1".to_owned()),
202                ("ENV2".to_owned(), "val2".to_owned()),
203            ]
204            .into_iter()
205            .collect(),
206            input_drvs: vec![
207                ("/drv1".to_owned(), to_string_set(&["out"])),
208                ("/drv2".to_owned(), to_string_set(&["dev"])),
209            ]
210            .into_iter()
211            .collect(),
212            input_srcs: to_string_set(&["/builder.sh"]),
213            outputs: vec![("out".to_owned(), to_drv_out("sha256", "abc", "/foo"))]
214                .into_iter()
215                .collect(),
216            system: "x86_64-linux".to_owned(),
217        };
218        assert_eq!(
219            parse_derivation(
220                r#"Derive([("out","/foo","sha256","abc")],[("/drv1",["out"]),("/drv2",["dev"])],["/builder.sh"],"x86_64-linux","/bash",["-e","/builder.sh"],[("ENV1","val1"),("ENV2","val2")])"#
221            ),
222            Ok(("", expected_derivation,)),
223        );
224    }
225
226    #[test]
227    fn test_parse_string() {
228        assert_eq!(parse_string(r#""ab""#), Ok(("", "ab".to_owned())));
229        assert_eq!(parse_string(r#""\"""#), Ok(("", "\"".to_owned())));
230        assert_eq!(parse_string(r#""\\""#), Ok(("", "\\".to_owned())));
231        assert_eq!(parse_string(r#""\n""#), Ok(("", "\n".to_owned())));
232        assert_eq!(parse_string(r#""\r""#), Ok(("", "\r".to_owned())));
233        assert_eq!(parse_string(r#""\t""#), Ok(("", "\t".to_owned())));
234
235        assert_eq!(
236            parse_string(r#""Foo\tbar\n\rmoo\\zar\"""#),
237            Ok(("", "Foo\tbar\n\rmoo\\zar\"".to_owned()))
238        );
239    }
240
241    #[test]
242    fn test_parse_string_invalid() {
243        assert_eq!(
244            parse_string("").unwrap_err(),
245            nom::Err::Error(nom::error::Error::new("", nom::error::ErrorKind::Char)),
246            "Parsing an empty input as a string literal must fail",
247        );
248        assert_eq!(
249            parse_string("a").unwrap_err(),
250            nom::Err::Error(nom::error::Error::new("a", nom::error::ErrorKind::Char)),
251            "Parsing a string literal that doesn't start with a double-quote must fail",
252        );
253        assert_eq!(
254            parse_string("\"").unwrap_err(),
255            nom::Err::Error(nom::error::Error::new("", nom::error::ErrorKind::Char)),
256            "Parsing an unclosed string literal should fail",
257        );
258    }
259
260    #[test]
261    fn test_parse_derivation_output() {
262        assert_eq!(
263            parse_derivation_output(r#"("foo","store_path","sha256","hash")"#),
264            Ok((
265                "",
266                ("foo".to_owned(), to_drv_out("sha256", "hash", "store_path")),
267            )),
268        );
269    }
270
271    #[test]
272    fn test_parse_derivation_outputs() {
273        let actual = parse_derivation_outputs(r#"[("a","b","c","d"),("e","f","g","h")]"#);
274        let expected = vec![
275            ("a".to_owned(), to_drv_out("c", "d", "b")),
276            ("e".to_owned(), to_drv_out("g", "h", "f")),
277        ];
278        assert_eq!(actual, Ok(("", expected.into_iter().collect())));
279    }
280
281    #[test]
282    fn test_parse_input_derivations() {
283        let actual = parse_input_derivations(r#"[("a",["b","c"]),("e",["f","g"])]"#);
284        let expected = vec![
285            ("a".to_owned(), to_string_set(&["b", "c"])),
286            ("e".to_owned(), to_string_set(&["f", "g"])),
287        ];
288        assert_eq!(actual, Ok(("", expected.into_iter().collect())));
289    }
290
291    #[test]
292    fn test_parse_string_set() {
293        let actual = parse_string_set(r#"["a","b","b"]"#);
294        let expected = to_string_set(&["a", "b"]);
295        assert_eq!(actual, Ok(("", expected)));
296    }
297
298    #[test]
299    fn test_parse_strings() {
300        let actual = parse_strings(r#"["a","b","a"]"#);
301        let expected = to_string_vec(&["a", "b", "a"]);
302        assert_eq!(actual, Ok(("", expected)));
303    }
304
305    #[test]
306    fn test_parse_env() {
307        let actual = parse_env(r#"[("A","a"),("B","b")]"#);
308        let expected = vec![
309            ("A".to_owned(), "a".to_owned()),
310            ("B".to_owned(), "b".to_owned()),
311        ];
312        assert_eq!(actual, Ok(("", expected.into_iter().collect())));
313    }
314
315    fn to_drv_out(hash_algo: &str, hash: &str, path: &str) -> DerivationOutput {
316        DerivationOutput {
317            hash: Some(hash.to_owned()),
318            hash_algo: Some(hash_algo.to_owned()),
319            path: path.to_owned(),
320        }
321    }
322
323    fn to_string_vec(strings: &[&str]) -> Vec<String> {
324        strings.iter().cloned().map(String::from).collect()
325    }
326
327    fn to_string_set(strings: &[&str]) -> BTreeSet<String> {
328        strings.iter().cloned().map(String::from).collect()
329    }
330}