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}