Skip to main content

pijul_core/change/
parse.rs

1use nom::branch::alt;
2use nom::bytes::complete::*;
3use nom::character::complete::*;
4use nom::combinator::*;
5use nom::error::ParseError;
6use nom::multi::*;
7use nom::sequence::*;
8use nom::*;
9
10use crate::change::printable::*;
11use PrintableHunk::*;
12
13use super::*;
14
15fn parse_file_move_v_hunk(i: &str) -> IResult<&str, PrintableHunk> {
16    let (i, path) = preceded(delimited(space0, tag("Moved:"), space0), parse_string)(i)?;
17    let parse = |i, path| -> IResult<&str, PrintableHunk> {
18        let (i, name) = preceded(space0, parse_string)(i)?;
19        let (i, perms) = preceded(space0, parse_perms)(i)?;
20        let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
21        let (i, _) = tuple((space0, line_ending))(i)?;
22
23        let (i, del) = parse_edges(i)?;
24
25        let (i, up_context) = preceded(pair(space0, tag("up")), parse_context)(i)?;
26        let (i, down_context) = preceded(pair(space0, tag(", down")), parse_context)(i)?;
27        let (i, _) = line_ending(i)?;
28        Ok((
29            i,
30            FileMoveV {
31                path,
32                name,
33                perms,
34                pos,
35                up_context,
36                down_context,
37                del,
38            },
39        ))
40    };
41    parse(i, path)
42        .map_err(|_| nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Verify)))
43}
44
45fn parse_file_move_e_hunk(i: &str) -> IResult<&str, PrintableHunk> {
46    let (i, path) = preceded(delimited(space0, tag("Moved:"), space0), parse_string)(i)?;
47    let parse = |i, path| -> IResult<&str, PrintableHunk> {
48        let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
49        let (i, _) = tuple((space0, line_ending))(i)?;
50
51        let (i, add) = parse_edges(i)?;
52        let (i, del) = parse_edges(i)?;
53        Ok((
54            i,
55            FileMoveE {
56                path,
57                pos,
58                add,
59                del,
60            },
61        ))
62    };
63    parse(i, path)
64        .map_err(|_| nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Verify)))
65}
66
67fn parse_file_del_hunk(i: &str) -> IResult<&str, PrintableHunk> {
68    let (i, path) = preceded(
69        delimited(space0, tag("File deletion:"), space0),
70        parse_string,
71    )(i)?;
72    let parse = |i, path| -> IResult<&str, PrintableHunk> {
73        let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
74        let (i, encoding) = preceded(space0, parse_encoding)(i)?;
75        let (i, _) = tuple((space0, line_ending))(i)?;
76        let (i, del_edges) = parse_edges(i)?;
77        let (i, content_edges) = map(opt(parse_edges), |o| o.unwrap_or(Vec::new()))(i)?;
78        let (i, contents) = if encoding.is_none() {
79            debug!("encoding none");
80            // If the encoding is binary, we may have a base64 version of
81            // the file.
82            if let Ok((i, c)) = parse_contents('-', encoding.clone(), i) {
83                (i, c)
84            } else {
85                (i, Vec::new())
86            }
87        } else {
88            parse_contents('-', encoding.clone(), i)?
89        };
90        Ok((
91            i,
92            FileDel {
93                path,
94                pos,
95                encoding,
96                del_edges,
97                content_edges,
98                contents,
99            },
100        ))
101    };
102    parse(i, path)
103        .map_err(|_| nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Verify)))
104}
105
106fn parse_file_undel_hunk(i: &str) -> IResult<&str, PrintableHunk> {
107    let (i, path) = preceded(
108        delimited(space0, tag("File un-deletion:"), space0),
109        parse_string,
110    )(i)?;
111    let parse = |i, path| -> IResult<&str, PrintableHunk> {
112        let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
113        let (i, encoding) = preceded(space0, parse_encoding)(i)?;
114        let (i, _) = tuple((space0, line_ending))(i)?;
115        let (i, undel_edges) = parse_edges(i)?;
116        let (i, content_edges) = map(opt(parse_edges), |o| o.unwrap_or(Vec::new()))(i)?;
117        let (i, contents) = if let Ok(x) = parse_contents('+', encoding.clone(), i) {
118            x
119        } else {
120            // Contents is optional for FileUndel hunks.
121            (i, Vec::new())
122        };
123        Ok((
124            i,
125            FileUndel {
126                path,
127                pos,
128                encoding,
129                undel_edges,
130                content_edges,
131                contents,
132            },
133        ))
134    };
135    parse(i, path)
136        .map_err(|_| nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Verify)))
137}
138
139fn parse_file_addition_hunk(i: &str) -> IResult<&str, PrintableHunk> {
140    let (i, name) = preceded(
141        delimited(space0, tag("File addition:"), space0),
142        parse_string,
143    )(i)?;
144    let parse = |i, name| -> IResult<&str, PrintableHunk> {
145        let (i, parent) = preceded(delimited(space1, tag("in"), space1), parse_string)(i)?;
146        let (i, perms) = preceded(space0, parse_perms)(i)?;
147        let (i, encoding) = preceded(space0, parse_encoding)(i)?;
148        let (i, _) = tuple((space0, line_ending, multispace0))(i)?;
149
150        let (i, up_context) = preceded(tag("up"), parse_context)(i)?;
151        let (i, (start, end)) = delimited(space0, parse_start_end, pair(space0, line_ending))(i)?;
152        let (i, contents) = if let PrintablePerms::IsDir = perms {
153            (i, Vec::new())
154        } else {
155            parse_contents('+', encoding.clone(), i)?
156        };
157        Ok((
158            i,
159            FileAddition {
160                name,
161                parent,
162                perms,
163                encoding,
164                up_context,
165                start,
166                end,
167                contents,
168            },
169        ))
170    };
171    parse(i, name)
172        .map_err(|_| nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Verify)))
173}
174
175/// Parse a hunk header string
176fn parse_edit_hunk(i: &str) -> IResult<&str, PrintableHunk> {
177    debug!("parse_edit_hunk {:?}", i);
178    let (i, path) = preceded(delimited(space0, tag("Edit in"), space0), parse_string)(i)?;
179    let parse = |i, path| -> IResult<&str, PrintableHunk> {
180        let (i, line) = preceded(char(':'), u64)(i)?;
181        let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
182        let (i, encoding) = preceded(space0, parse_encoding)(i)?;
183        let (i, _) = tuple((space0, line_ending))(i)?;
184        let (i, change) = parse_atom(i)?;
185        let (i, contents_plus) = parse_contents('+', encoding.clone(), i)?;
186        let (i, contents_minus) = parse_contents('-', encoding.clone(), i)?;
187        let contents = if contents_plus.is_empty() {
188            if contents_minus.is_empty() {
189                Vec::new()
190            } else {
191                contents_minus
192            }
193        } else {
194            contents_plus
195        };
196        Ok((
197            i,
198            Edit {
199                path,
200                line: line as usize,
201                pos,
202                encoding,
203                change,
204                contents,
205            },
206        ))
207    };
208    parse(i, path)
209        .map_err(|_| nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Verify)))
210}
211
212fn parse_replace_hunk(i: &str) -> IResult<&str, PrintableHunk> {
213    let (i, path) = preceded(
214        delimited(space0, tag("Replacement in"), space0),
215        parse_string,
216    )(i)?;
217    let parse = |i, path| -> IResult<&str, PrintableHunk> {
218        let (i, line) = preceded(char(':'), u64)(i)?;
219        let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
220        let (i, encoding) = preceded(space0, parse_encoding)(i)?;
221        let (i, _) = tuple((space0, line_ending))(i)?;
222        // TODO: allow line_endings in between these lines
223        let (i, change) = parse_edges(i)?;
224        let (i, replacement) = parse_new_vertex(i)?;
225        let (i, change_contents) = parse_contents('-', encoding.clone(), i)?;
226        let (i, replacement_contents) = parse_contents('+', encoding.clone(), i)?;
227        Ok((
228            i,
229            Replace {
230                path,
231                line: line as usize,
232                pos,
233                encoding,
234                change,
235                replacement,
236                change_contents,
237                replacement_contents,
238            },
239        ))
240    };
241    parse(i, path)
242        .map_err(|_| nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Verify)))
243}
244
245fn parse_solve_name_conflict(i: &str) -> IResult<&str, PrintableHunk> {
246    let (i, path) = preceded(
247        delimited(space0, tag("Solving a name conflict in"), space0),
248        parse_string,
249    )(i)?;
250    let parse = |i, path| -> IResult<&str, PrintableHunk> {
251        let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
252        let (i, _) = tuple((space0, char(':'), space0))(i)?;
253        let (i, names) = separated_list0(tuple((space0, char(','), space0)), parse_string)(i)?;
254        let (i, _) = tuple((space0, line_ending))(i)?;
255        let (i, edges) = parse_edges(i)?;
256        Ok((
257            i,
258            SolveNameConflict {
259                path,
260                pos,
261                names,
262                edges,
263            },
264        ))
265    };
266    parse(i, path)
267        .map_err(|_| nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Verify)))
268}
269
270fn parse_unsolve_name_conflict(i: &str) -> IResult<&str, PrintableHunk> {
271    let (i, path) = preceded(
272        delimited(space0, tag("Un-solving a name conflict in"), space0),
273        parse_string,
274    )(i)?;
275    let parse = |i, path| -> IResult<&str, PrintableHunk> {
276        let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
277        let (i, _) = tuple((space0, char(':'), space0))(i)?;
278        let (i, names) = separated_list0(tuple((space0, char(','), space0)), parse_string)(i)?;
279        let (i, _) = tuple((space0, line_ending))(i)?;
280        let (i, edges) = parse_edges(i)?;
281        Ok((
282            i,
283            UnsolveNameConflict {
284                path,
285                pos,
286                names,
287                edges,
288            },
289        ))
290    };
291    parse(i, path)
292        .map_err(|_| nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Verify)))
293}
294
295fn parse_solve_order_conflict(i: &str) -> IResult<&str, PrintableHunk> {
296    let (i, path) = preceded(
297        delimited(space0, tag("Solving an order conflict in"), space0),
298        parse_string,
299    )(i)?;
300    let parse = |i, path| -> IResult<&str, PrintableHunk> {
301        let (i, line) = preceded(char(':'), u64)(i)?;
302        let (i, pos) = preceded(space1, parse_printable_pos)(i)?;
303        let (i, encoding) = preceded(space0, parse_encoding)(i)?;
304        let (i, _) = tuple((space0, line_ending))(i)?;
305        let (i, change) = parse_new_vertex(i)?;
306        let (i, contents) = parse_contents('+', encoding.clone(), i)?;
307        Ok((
308            i,
309            SolveOrderConflict {
310                path,
311                line: line as usize,
312                pos,
313                encoding,
314                change,
315                contents,
316            },
317        ))
318    };
319    parse(i, path)
320        .map_err(|_| nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Verify)))
321}
322
323fn parse_unsolve_order_conflict(i: &str) -> IResult<&str, PrintableHunk> {
324    let (i, path) = preceded(
325        delimited(space0, tag("Un-solving an order conflict in"), space0),
326        parse_string,
327    )(i)?;
328    let parse = |i, path| -> IResult<&str, PrintableHunk> {
329        let (i, line) = preceded(char(':'), u64)(i)?;
330        let (i, pos) = preceded(space1, parse_printable_pos)(i)?;
331        let (i, encoding) = preceded(space0, parse_encoding)(i)?;
332        let (i, _) = tuple((space0, line_ending))(i)?;
333        let (i, change) = parse_edges(i)?;
334        let (i, contents) = parse_contents('-', encoding.clone(), i)?;
335        Ok((
336            i,
337            UnsolveOrderConflict {
338                path,
339                line: line as usize,
340                pos,
341                encoding,
342                change,
343                contents,
344            },
345        ))
346    };
347    parse(i, path)
348        .map_err(|_| nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Verify)))
349}
350
351fn parse_resurrect_zombies(i: &str) -> IResult<&str, PrintableHunk> {
352    let (i, path) = preceded(
353        delimited(space0, tag("Resurrecting zombie lines in"), space0),
354        parse_string,
355    )(i)?;
356    let parse = |i, path| -> IResult<&str, PrintableHunk> {
357        let (i, line) = preceded(char(':'), u64)(i)?;
358        let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
359        let (i, encoding) = preceded(space0, parse_encoding)(i)?;
360        let (i, _) = tuple((space0, line_ending))(i)?;
361        let (i, change) = parse_edges(i)?;
362        let (i, contents) = parse_contents('+', encoding.clone(), i)?;
363        Ok((
364            i,
365            ResurrectZombies {
366                path,
367                line: line as usize,
368                pos,
369                encoding,
370                change,
371                contents,
372            },
373        ))
374    };
375    parse(i, path)
376        .map_err(|_| nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Verify)))
377}
378
379fn parse_root_addition_hunk(i: &str) -> IResult<&str, PrintableHunk> {
380    debug!("root add {:?}", i);
381    let (i, _) = delimited(space0, tag("Root add"), space0)(i)?;
382    let parse = |i| -> IResult<&str, PrintableHunk> {
383        let (i, _) = tuple((space0, line_ending, multispace0))(i)?;
384        debug!("root add {:?}", i);
385        let (i, up_context) = preceded(tag("up"), parse_context)(i)?;
386        debug!("root add {:?}", i);
387        let (i, (start, end)) = delimited(space0, parse_start_end, pair(space0, line_ending))(i)?;
388        debug!("root add {:?}", i);
389        assert_eq!(&up_context[..], &[PrintablePos(1, 0)]);
390        assert_eq!(start, end);
391        Ok((i, PrintableHunk::AddRoot { start }))
392    };
393    parse(i).map_err(|_| nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Verify)))
394}
395
396fn parse_root_deletion_hunk(i: &str) -> IResult<&str, PrintableHunk> {
397    let (i, _) = delimited(space0, tag("Root del"), space0)(i)?;
398    let parse = |i| -> IResult<&str, PrintableHunk> {
399        let (i, _) = tuple((space0, line_ending))(i)?;
400        let (i, name) = parse_edges(i)?;
401        let (i, inode) = parse_edges(i)?;
402
403        Ok((i, PrintableHunk::DelRoot { inode, name }))
404    };
405    parse(i).map_err(|_| nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Verify)))
406}
407
408fn parse_content_line(leading_char: char, input: &str) -> IResult<&str, String> {
409    preceded(
410        char(leading_char),
411        alt((
412            map(
413                delimited(tag(" "), take_till(|c| c == '\n'), newline),
414                |s: &str| s.to_string() + "\n",
415            ),
416            map(
417                delimited(tag("b"), take_till(|c| c == '\n'), newline),
418                |s: &str| s.to_string(),
419            ),
420            // Some editors trim terminal whitespace which results in change lines like:
421            //   +\n
422            // rather than the generated:
423            //   + \n
424            // which is what the first alternative above matches on
425            map(newline, |_| String::from("\n")),
426        )),
427    )(input)
428}
429
430// TODO: better error handling
431fn parse_contents(
432    leading_char: char,
433    encoding: Option<Encoding>,
434    i: &str,
435) -> IResult<&str, Vec<u8>> {
436    let (i, res) = if leading_char == '+' {
437        fold_many0(
438            complete(|i| parse_content_line(leading_char, i)),
439            String::new,
440            |s, r| s + r.as_str(),
441        )(i)?
442    } else {
443        fold_many0(
444            complete(|i| parse_content_line(leading_char, i)),
445            String::new,
446            |s, r| s + r.as_str(),
447        )(i)?
448    };
449    let (i, backslash) = opt(complete(tag("\\\n")))(i)?;
450    if let Ok(mut vec) = encode(encoding, &res) {
451        if backslash.is_some() && vec[vec.len() - 1] == b'\n' {
452            vec.pop();
453        }
454        Ok((i, vec))
455    } else {
456        Err(nom::Err::Error(nom::error::Error::new(
457            i,
458            nom::error::ErrorKind::Verify,
459        )))
460    }
461}
462
463fn encode(encoding: Option<Encoding>, contents: &str) -> Result<Vec<u8>, String> {
464    if let Some(encoding) = encoding {
465        Ok(encoding.encode(contents).to_vec())
466    } else {
467        data_encoding::BASE64
468            .decode(contents.as_bytes())
469            .map_err(|e| e.to_string())
470    }
471}
472
473fn parse_encoding(input: &str) -> IResult<&str, Option<Encoding>> {
474    map(parse_string, |e| {
475        if e != BINARY_LABEL {
476            Some(Encoding::for_label(&e))
477        } else {
478            None
479        }
480    })(input)
481}
482
483pub fn parse_numbered_hunk(input: &str) -> IResult<&str, (u64, PrintableHunk)> {
484    tuple((terminated(u64, char('.')), parse_hunk))(input)
485}
486
487pub fn parse_hunk(input: &str) -> IResult<&str, PrintableHunk> {
488    debug!("parse_hunk {:?}", input);
489    alt((
490        parse_file_move_v_hunk,
491        parse_file_move_e_hunk,
492        parse_file_del_hunk,
493        parse_file_undel_hunk,
494        parse_file_addition_hunk,
495        parse_edit_hunk,
496        parse_replace_hunk,
497        parse_solve_name_conflict,
498        parse_unsolve_name_conflict,
499        parse_solve_order_conflict,
500        parse_unsolve_order_conflict,
501        parse_resurrect_zombies,
502        parse_root_addition_hunk,
503        parse_root_deletion_hunk,
504    ))(input)
505}
506
507pub fn parse_hunks(input: &str) -> IResult<&str, Vec<(u64, PrintableHunk)>> {
508    preceded(
509        tuple((tag("# Hunks"), space0, line_ending, multispace0)),
510        many0(complete(terminated(parse_numbered_hunk, multispace0))),
511    )(input)
512}
513
514pub const BINARY_LABEL: &str = "binary";
515
516pub fn encoding_label(encoding: &Option<Encoding>) -> &str {
517    match encoding {
518        Some(encoding) => encoding.label(),
519        _ => BINARY_LABEL,
520    }
521}
522
523/// Parse an escaped character: \n, \t, \r, etc.
524fn parse_escaped_char(input: &str) -> IResult<&str, char> {
525    preceded(
526        char('\\'),
527        alt((
528            value('\n', char('n')),
529            value('\r', char('r')),
530            value('\t', char('t')),
531            value('\u{08}', char('b')),
532            value('\u{0C}', char('f')),
533            value('\\', char('\\')),
534            value('"', char('"')),
535        )),
536    )(input)
537}
538
539/// Parse a non-empty block of text that doesn't include \ or "
540fn parse_literal<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> {
541    let not_quote_slash = is_not("\"\\");
542    verify(not_quote_slash, |s: &str| !s.is_empty())(input)
543}
544
545/// Combine parse_literal and parse_escaped_char into a StringFragment.
546fn parse_fragment<'a>(input: &'a str) -> IResult<&'a str, StringFragment<'a>> {
547    alt((
548        map(parse_literal, StringFragment::Literal),
549        map(parse_escaped_char, StringFragment::EscapedChar),
550    ))(input)
551}
552
553/// Parse a string. Use a loop of parse_fragment and push all of the fragments
554/// into an output string.
555pub fn parse_string(input: &str) -> IResult<&str, String> {
556    let build_string = fold_many0(parse_fragment, String::new, |mut string, fragment| {
557        match fragment {
558            StringFragment::Literal(s) => string.push_str(s),
559            StringFragment::EscapedChar(c) => string.push(c),
560        }
561        string
562    });
563    delimited(char('"'), build_string, char('"'))(input)
564}
565
566fn parse_perms(input: &str) -> IResult<&str, PrintablePerms> {
567    alt((
568        value(PrintablePerms::IsDir, tag("+dx")),
569        value(PrintablePerms::IsExecutable, tag("+x")),
570        value(PrintablePerms::IsFile, tag("")),
571    ))(input)
572}
573
574fn parse_printable_pos(input: &str) -> IResult<&str, PrintablePos> {
575    map(separated_pair(u64, char('.'), u64), |(a, b)| {
576        PrintablePos(a as usize, b)
577    })(input)
578}
579
580fn parse_context(input: &str) -> IResult<&str, Vec<PrintablePos>> {
581    delimited(space0, separated_list0(space1, parse_printable_pos), space0)(input)
582}
583
584fn parse_start_end(input: &str) -> IResult<&str, (u64, u64)> {
585    preceded(
586        pair(tag(", new"), space1),
587        separated_pair(u64, char(':'), u64),
588    )(input)
589}
590
591fn parse_edge_flags(i: &str) -> IResult<&str, PrintableEdgeFlags> {
592    let (i, block) = map(opt(char('B')), |x| x.is_some())(i)?;
593    let (i, folder) = map(opt(char('F')), |x| x.is_some())(i)?;
594    let (i, deleted) = map(opt(char('D')), |x| x.is_some())(i)?;
595    Ok((
596        i,
597        PrintableEdgeFlags {
598            block,
599            folder,
600            deleted,
601        },
602    ))
603}
604
605fn parse_new_vertex(i: &str) -> IResult<&str, PrintableNewVertex> {
606    map(
607        tuple((
608            space0,
609            preceded(tag("up"), parse_context),
610            terminated(tag(", new"), space0),
611            terminated(u64, char(':')),
612            terminated(u64, space0),
613            preceded(tag(", down"), parse_context),
614            line_ending,
615        )),
616        |(_, up_context, _, start, end, down_context, _)| PrintableNewVertex {
617            up_context,
618            start,
619            end,
620            down_context,
621        },
622    )(i)
623}
624
625fn parse_edge(i: &str) -> IResult<&str, PrintableEdge> {
626    map(
627        tuple((
628            terminated(parse_edge_flags, char(':')),
629            terminated(parse_edge_flags, char(' ')),
630            terminated(parse_printable_pos, tag(" -> ")),
631            terminated(parse_printable_pos, tag(":")),
632            terminated(u64, tag("/")),
633            terminated(u64, space0),
634        )),
635        |(previous, flag, from, to_start, to_end, introduced_by)| PrintableEdge {
636            previous,
637            flag,
638            from,
639            to_start,
640            to_end,
641            introduced_by: introduced_by as usize,
642        },
643    )(i)
644}
645
646fn parse_atom(i: &str) -> IResult<&str, PrintableAtom> {
647    alt((
648        map(parse_new_vertex, PrintableAtom::NewVertex),
649        map(parse_edges, PrintableAtom::Edges),
650    ))(i)
651}
652
653fn parse_edges(input: &str) -> IResult<&str, Vec<PrintableEdge>> {
654    terminated(
655        separated_list0(delimited(space0, char(','), space0), complete(parse_edge)),
656        pair(space0, line_ending),
657    )(input)
658}
659
660pub fn parse_header(input: &str) -> IResult<&str, Result<ChangeHeader, toml::de::Error>> {
661    map(
662        alt((take_until("# Dependencies"), take_until("# Hunks"))),
663        toml::de::from_str,
664    )(input)
665}
666
667pub fn parse_comment(i: &str) -> IResult<&str, ()> {
668    let (i, _) = char('#')(i)?;
669    let (i, _) = take_until("\n")(i)?;
670    Ok((i, ()))
671}
672
673pub fn parse_dependency(i: &str) -> IResult<&str, PrintableDep> {
674    let (i, mut type_) = delimited(
675        char('['),
676        alt((
677            map(u64, |n| DepType::Numbered(n as usize, false)),
678            value(DepType::ExtraKnown, char('*')),
679            value(DepType::ExtraUnknown, take_till(|c| c != ']')),
680        )),
681        char(']'),
682    )(i)?;
683
684    let (i, plus) = terminated(
685        alt((value(true, char('+')), value(false, char(' ')))),
686        space0,
687    )(i)?;
688
689    // TODO: get rid of this confusing mutation
690    type_ = if let DepType::Numbered(n, _) = type_ {
691        DepType::Numbered(n, plus)
692    } else {
693        type_
694    };
695
696    let (i, hash) = delimited(
697        space0,
698        take_while(|c: char| c.is_ascii_alphanumeric()),
699        tuple((space0, opt(parse_comment), line_ending)),
700    )(i)?;
701    Ok((
702        i,
703        PrintableDep {
704            type_,
705            hash: hash.to_string(),
706        },
707    ))
708}
709
710pub fn parse_dependencies(input: &str) -> IResult<&str, Vec<PrintableDep>> {
711    alt((
712        preceded(
713            tuple((tag("# Dependencies"), space0, line_ending, multispace0)),
714            many0(terminated(parse_dependency, multispace0)),
715        ),
716        value(Vec::new(), multispace0),
717    ))(input)
718}