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 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 (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
175fn 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 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 map(newline, |_| String::from("\n")),
426 )),
427 )(input)
428}
429
430fn 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
523fn 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
539fn 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
545fn 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
553pub 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 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}