1#![allow(dead_code)]
3#![allow(clippy::type_complexity)]
4
5use crate::parser::Link;
6use crate::parser::parse::LABEL_LEN_MAX;
7use crate::parser::percent_decode;
8use nom::Parser;
9use nom::branch::alt;
10use nom::bytes::complete::tag;
11use nom::bytes::complete::tag_no_case;
12use nom::character::complete::char;
13use nom::character::complete::space0;
14use nom::combinator::peek;
15use std::borrow::Cow;
16
17pub fn adoc_text2dest_link(i: &str) -> nom::IResult<&str, Link> {
20 let (i, (te, de, ti)) = adoc_text2dest(i)?;
21 Ok((i, Link::Text2Dest(te, de, ti)))
22}
23
24pub fn adoc_text2dest(i: &str) -> nom::IResult<&str, (Cow<str>, Cow<str>, Cow<str>)> {
58 let (i, (link_destination, link_text)) = nom::sequence::preceded(
59 space0,
60 nom::sequence::pair(
61 adoc_inline_link_destination,
62 nom::combinator::opt(adoc_link_text),
63 ),
64 )
65 .parse(i)?;
66
67 let link_text = if let Some(lt) = link_text {
68 if lt.is_empty() {
69 link_destination.clone()
70 } else {
71 lt
72 }
73 } else {
74 link_destination.clone()
75 };
76
77 Ok((i, (link_text, link_destination, Cow::Borrowed(""))))
78}
79
80pub fn adoc_label2dest_link(i: &str) -> nom::IResult<&str, Link> {
83 let (i, (te, de, ti)) = adoc_label2dest(i)?;
84 Ok((i, Link::Label2Dest(te, de, ti)))
85}
86
87pub fn adoc_label2dest(i: &str) -> nom::IResult<&str, (Cow<str>, Cow<str>, Cow<str>)> {
109 let (i, (link_label, link_destination)) = nom::sequence::preceded(
110 space0,
111 nom::sequence::pair(
112 adoc_parse_colon_reference,
113 nom::sequence::delimited(
114 nom::character::complete::space1,
115 adoc_link_reference_definition_destination,
116 nom::character::complete::space0,
117 ),
118 ),
119 )
120 .parse(i)?;
121
122 if !i.is_empty() {
123 let _ = peek::<&str, _>(nom::character::complete::newline).parse(i)?;
124 };
125
126 Ok((
127 i,
128 (
129 Cow::Borrowed(link_label),
130 link_destination,
131 Cow::Borrowed(""),
132 ),
133 ))
134}
135
136pub fn adoc_text2label_link(i: &str) -> nom::IResult<&str, Link> {
139 let (i, (te, la)) = adoc_text2label(i)?;
140 Ok((i, Link::Text2Label(te, la)))
141}
142
143pub fn adoc_text2label(i: &str) -> nom::IResult<&str, (Cow<str>, Cow<str>)> {
182 let (i, (link_label, link_text)) = alt((
183 nom::sequence::pair(adoc_parse_curly_bracket_reference, adoc_link_text),
184 nom::combinator::map(adoc_parse_curly_bracket_reference, |s| (s, Cow::from(""))),
185 ))
186 .parse(i)?;
187
188 if !i.is_empty() {
190 let _ = nom::character::complete::none_of("[{")(i)?;
191 }
192
193 Ok((i, (link_text, link_label)))
194}
195
196fn adoc_link_text(i: &str) -> nom::IResult<&str, Cow<str>> {
201 nom::sequence::delimited(char('['), remove_newline_take_till(']'), char(']')).parse(i)
202}
203
204fn remove_newline_take_till<'a>(
210 pat: char,
211) -> impl Fn(&'a str) -> nom::IResult<&'a str, Cow<'a, str>> {
212 move |i: &str| {
213 let mut res = Cow::Borrowed("");
214 let mut j = i;
215 while !j.is_empty() {
216 let (k, s1) =
226 nom::bytes::complete::take_till(|c| c == pat || c == '\n' || c == '\\')(j)?;
227
228 res = match res {
230 Cow::Borrowed("") => Cow::Borrowed(s1),
231 Cow::Borrowed(res_str) => {
232 let mut strg = res_str.to_string();
233 strg.push_str(s1);
234 Cow::Owned(strg)
235 }
236 Cow::Owned(mut strg) => {
237 strg.push_str(s1);
238 Cow::Owned(strg)
239 }
240 };
241
242 if let (_, Some(c)) =
245 nom::combinator::opt(nom::combinator::peek(nom::character::complete::anychar))
246 .parse(k)?
247 {
248 let m = match c {
249 c if c == pat => return Ok((k, res)),
252 '\\' => {
254 let (l, _) = char('\\')(k)?;
256 let (l, c) = alt((char(pat), nom::combinator::success('\\'))).parse(l)?;
260
261 let mut strg = res.to_string();
263 strg.push(c);
264 res = Cow::Owned(strg);
266 l
268 }
269 '\n' => {
271 let (l, _) = char('\n')(k)?;
273 let (l, _) = space0(l)?;
274 let _ = nom::combinator::not(char('\n')).parse(l)?;
276
277 let mut strg = res.to_string();
279 strg.push(' ');
280 res = Cow::Owned(strg);
282 l
284 }
285 _ => unreachable!(),
286 };
287 j = m;
288 } else {
289 j = k;
291 }
292 }
293
294 Ok(("", res))
296 }
297}
298
299fn adoc_link_reference_definition_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
305 alt((
306 adoc_parse_http_link_destination,
307 adoc_parse_escaped_link_destination,
308 ))
309 .parse(i)
310}
311
312fn adoc_inline_link_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
317 alt((
318 adoc_parse_http_link_destination,
319 adoc_parse_literal_link_destination,
320 adoc_parse_escaped_link_destination,
321 ))
322 .parse(i)
323}
324
325fn adoc_parse_http_link_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
328 let (j, s) = nom::sequence::preceded(
329 peek(alt((tag_no_case("http://"), (tag_no_case("https://"))))),
330 nom::bytes::complete::take_till1(|c| c == '[' || c == ' ' || c == '\t' || c == '\n'),
331 )
332 .parse(i)?;
333 Ok((j, Cow::Borrowed(s)))
334}
335
336fn adoc_parse_escaped_link_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
340 nom::combinator::map_parser(
341 nom::sequence::preceded(
342 nom::sequence::pair(
343 tag("link:"),
344 peek(alt((tag_no_case("http://"), (tag_no_case("https://"))))),
345 ),
346 nom::bytes::complete::take_till1(|c| {
347 c == '[' || c == ' ' || c == '\t' || c == '\r' || c == '\n'
348 }),
349 ),
350 percent_decode,
351 )
352 .parse(i)
353}
354
355fn adoc_parse_literal_link_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
358 let (j, s) = nom::sequence::preceded(
359 tag("link:"),
360 nom::sequence::delimited(tag("++"), nom::bytes::complete::take_until("++"), tag("++")),
361 )
362 .parse(i)?;
363 Ok((j, Cow::Borrowed(s)))
364}
365
366fn adoc_parse_curly_bracket_reference(i: &str) -> nom::IResult<&str, Cow<str>> {
371 nom::combinator::map(
372 nom::combinator::verify(
373 nom::sequence::delimited(
374 char('{'),
375 nom::bytes::complete::take_till1(|c| {
376 c == '}' || c == ' ' || c == '\t' || c == '\r'
377 }),
378 char('}'),
379 ),
380 |s: &str| s.len() <= LABEL_LEN_MAX,
381 ),
382 Cow::Borrowed,
383 )
384 .parse(i)
385}
386
387fn adoc_parse_colon_reference(i: &str) -> nom::IResult<&str, &str> {
394 nom::combinator::verify(
395 nom::sequence::delimited(
396 char(':'),
397 nom::bytes::complete::take_till1(|c| c == ':' || c == ' ' || c == '\t' || c == '\r'),
398 char(':'),
399 ),
400 |s: &str| s.len() <= LABEL_LEN_MAX,
401 )
402 .parse(i)
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408 use nom::error::ErrorKind;
409 use std::matches;
410
411 #[test]
412 fn test_adoc_text2dest() {
413 assert_eq!(
414 adoc_text2dest("http://getreu.net[]"),
415 Ok((
416 "",
417 (
418 Cow::from("http://getreu.net"),
419 Cow::from("http://getreu.net"),
420 Cow::from("")
421 )
422 ))
423 );
424
425 assert_eq!(
426 adoc_text2dest("http://getreu.net[]abc"),
427 Ok((
428 "abc",
429 (
430 Cow::from("http://getreu.net"),
431 Cow::from("http://getreu.net"),
432 Cow::from("")
433 )
434 ))
435 );
436
437 assert_eq!(
438 adoc_text2dest(" \t http://getreu.net[My blog]abc"),
439 Ok((
440 "abc",
441 (
442 Cow::from("My blog"),
443 Cow::from("http://getreu.net"),
444 Cow::from("")
445 )
446 ))
447 );
448
449 assert_eq!(
450 adoc_text2dest(r#"http://getreu.net[My blog[1\]]abc"#),
451 Ok((
452 "abc",
453 (
454 Cow::from("My blog[1]"),
455 Cow::from("http://getreu.net"),
456 Cow::from("")
457 )
458 ))
459 );
460
461 assert_eq!(
462 adoc_text2dest("http://getreu.net[My\n blog]abc"),
463 Ok((
464 "abc",
465 (
466 Cow::from("My blog"),
467 Cow::from("http://getreu.net"),
468 Cow::from("")
469 )
470 ))
471 );
472
473 assert_eq!(
474 adoc_text2dest("link:http://getreu.net[My blog]abc"),
475 Ok((
476 "abc",
477 (
478 Cow::from("My blog"),
479 Cow::from("http://getreu.net"),
480 Cow::from("")
481 )
482 ))
483 );
484
485 assert_eq!(
486 adoc_text2dest("link:https://getreu.net/?q=%5Ba%20b%5D[My blog]abc"),
487 Ok((
488 "abc",
489 (
490 Cow::from("My blog"),
491 Cow::from("https://getreu.net/?q=[a b]"),
492 Cow::from("")
493 )
494 ))
495 );
496
497 assert_eq!(
498 adoc_text2dest("link:++https://getreu.net/?q=[a b]++[My blog]abc"),
499 Ok((
500 "abc",
501 (
502 Cow::from("My blog"),
503 Cow::from("https://getreu.net/?q=[a b]"),
504 Cow::from("")
505 )
506 ))
507 );
508 }
509
510 #[test]
511 fn test_adoc_label2dest() {
512 assert_eq!(
513 adoc_label2dest(":label: http://getreu.net\n"),
514 Ok((
515 "\n",
516 (
517 Cow::from("label"),
518 Cow::from("http://getreu.net"),
519 Cow::from("")
520 )
521 ))
522 );
523
524 assert_eq!(
525 adoc_label2dest(" :label: \thttp://getreu.net \t "),
526 Ok((
527 "",
528 (
529 Cow::from("label"),
530 Cow::from("http://getreu.net"),
531 Cow::from("")
532 )
533 ))
534 );
535
536 assert_eq!(
537 adoc_label2dest(" :label: \thttp://getreu.net \t abc").unwrap_err(),
538 nom::Err::Error(nom::error::Error::new("abc", ErrorKind::Char))
539 );
540 }
541
542 #[test]
543 fn test_adoc_link_text() {
544 assert_eq!(adoc_link_text("[text]abc"), Ok(("abc", Cow::from("text"))));
545
546 assert_eq!(
547 adoc_link_text("[te\nxt]abc"),
548 Ok(("abc", Cow::from("te xt")))
549 );
550
551 assert_eq!(
552 adoc_link_text("[te\n\nxt]abc"),
553 Err(nom::Err::Error(nom::error::Error::new(
554 "\nxt]abc",
555 ErrorKind::Not
556 )))
557 );
558
559 assert_eq!(
560 adoc_link_text(r#"[text[i\]]abc"#),
561 Ok(("abc", Cow::from(r#"text[i]"#.to_string())))
562 );
563
564 assert_eq!(
565 adoc_link_text("[textabc"),
566 Err(nom::Err::Error(nom::error::Error::new("", ErrorKind::Char)))
567 );
568 }
569
570 #[test]
571 fn test_remove_newline_take_till() {
572 let res = remove_newline_take_till(']')("").unwrap();
573 assert_eq!(res, ("", Cow::from("")));
574 assert!(matches!(res.1, Cow::Borrowed { .. }));
575
576 let res = remove_newline_take_till(']')("text text]abc").unwrap();
577 assert_eq!(res, ("]abc", Cow::from("text text")));
578 assert!(matches!(res.1, Cow::Borrowed { .. }));
579
580 let res = remove_newline_take_till(']')("text text").unwrap();
581 assert_eq!(res, ("", Cow::from("text text")));
582 assert!(matches!(res.1, Cow::Borrowed { .. }));
583
584 let res = remove_newline_take_till(']')(r#"te\]xt]abc"#).unwrap();
585 assert_eq!(res, ("]abc", Cow::from("te]xt")));
586 assert!(matches!(res.1, Cow::Owned { .. }));
587
588 let res = remove_newline_take_till(']')(r#"text\]]abc"#).unwrap();
589 assert_eq!(res, ("]abc", Cow::from("text]")));
590 assert!(matches!(res.1, Cow::Owned { .. }));
591
592 let res = remove_newline_take_till(']')(r#"te\xt]abc"#).unwrap();
593 assert_eq!(res, ("]abc", Cow::from(r#"te\xt"#)));
594 assert!(matches!(res.1, Cow::Owned { .. }));
595
596 let res = remove_newline_take_till(']')("text\n text]abc").unwrap();
597 assert_eq!(res, ("]abc", Cow::from("text text")));
598 assert!(matches!(res.1, Cow::Owned { .. }));
599
600 let res = remove_newline_take_till(']')("text\n text]abc").unwrap();
601 assert_eq!(res, ("]abc", Cow::from("text text")));
602 assert!(matches!(res.1, Cow::Owned { .. }));
603
604 assert_eq!(
605 remove_newline_take_till(']')("text\n\ntext]abc").unwrap_err(),
606 nom::Err::Error(nom::error::Error::new("\ntext]abc", ErrorKind::Not))
607 );
608
609 assert_eq!(
610 remove_newline_take_till(']')("text\n \n text]abc").unwrap_err(),
611 nom::Err::Error(nom::error::Error::new("\n text]abc", ErrorKind::Not))
612 );
613 }
614
615 #[test]
616 fn test_adoc_parse_http_link_destination() {
617 let res = adoc_parse_http_link_destination("http://destination/").unwrap();
618 assert_eq!(res, ("", Cow::from("http://destination/")));
619 assert!(matches!(res.1, Cow::Borrowed { .. }));
620
621 let res = adoc_parse_http_link_destination("http://destination/\nabc").unwrap();
622 assert_eq!(res, ("\nabc", Cow::from("http://destination/")));
623 assert!(matches!(res.1, Cow::Borrowed { .. }));
624
625 let res = adoc_parse_http_link_destination("http://destination/ abc").unwrap();
626 assert_eq!(res, (" abc", Cow::from("http://destination/")));
627 assert!(matches!(res.1, Cow::Borrowed { .. }));
628
629 let res = adoc_parse_http_link_destination("http://destination/[abc").unwrap();
630 assert_eq!(res, ("[abc", Cow::from("http://destination/")));
631 assert!(matches!(res.1, Cow::Borrowed { .. }));
632
633 let res = adoc_parse_http_link_destination("https://destination/[abc").unwrap();
634 assert_eq!(res, ("[abc", Cow::from("https://destination/")));
635 assert!(matches!(res.1, Cow::Borrowed { .. }));
636
637 assert_eq!(
638 adoc_parse_http_link_destination("http:/destination/[abc").unwrap_err(),
639 nom::Err::Error(nom::error::Error::new(
640 "http:/destination/[abc",
641 ErrorKind::Tag
642 ))
643 );
644 }
645
646 #[test]
647 fn test_adoc_parse_escaped_link_destination() {
648 let res = adoc_parse_escaped_link_destination("link:http://destination/").unwrap();
649 assert_eq!(res, ("", Cow::from("http://destination/")));
650 assert!(matches!(res.1, Cow::Borrowed { .. }));
651
652 let res = adoc_parse_escaped_link_destination("link:http://destination/[abc").unwrap();
653 assert_eq!(res, ("[abc", Cow::from("http://destination/")));
654 assert!(matches!(res.1, Cow::Borrowed { .. }));
655
656 let res = adoc_parse_escaped_link_destination("link:http://destination/ abc").unwrap();
657 assert_eq!(res, (" abc", Cow::from("http://destination/")));
658 assert!(matches!(res.1, Cow::Borrowed { .. }));
659
660 let res = adoc_parse_escaped_link_destination("link:http://destination/\nabc").unwrap();
661 assert_eq!(res, ("\nabc", Cow::from("http://destination/")));
662 assert!(matches!(res.1, Cow::Borrowed { .. }));
663
664 assert_eq!(
665 adoc_parse_escaped_link_destination("link:httpX:/destination/[abc").unwrap_err(),
666 nom::Err::Error(nom::error::Error::new(
667 "httpX:/destination/[abc",
668 ErrorKind::Tag
669 ))
670 );
671
672 let res = adoc_parse_escaped_link_destination("link:https://getreu.net/?q=%5Ba%20b%5D[abc")
673 .unwrap();
674 assert_eq!(res, ("[abc", Cow::from("https://getreu.net/?q=[a b]")));
675 assert!(matches!(res.1, Cow::Owned { .. }));
676
677 assert_eq!(
678 adoc_parse_escaped_link_destination("link:https://getreu.net/?q=%FF%FF[abc")
679 .unwrap_err(),
680 nom::Err::Error(nom::error::Error::new(
681 "https://getreu.net/?q=%FF%FF",
682 ErrorKind::EscapedTransform
683 ))
684 );
685 }
686
687 #[test]
688 fn test_adoc_parse_literal_link_destination() {
689 let res = adoc_parse_literal_link_destination("link:++https://getreu.net/?q=[a b]++[abc")
690 .unwrap();
691 assert_eq!(res, ("[abc", Cow::from("https://getreu.net/?q=[a b]")));
692
693 assert_eq!(
694 adoc_parse_literal_link_destination("link:++https://getreu.net/?q=[a b]+[abc")
695 .unwrap_err(),
696 nom::Err::Error(nom::error::Error::new(
697 "https://getreu.net/?q=[a b]+[abc",
698 ErrorKind::TakeUntil
699 ))
700 );
701 }
702
703 #[test]
704 fn test_adoc_text2label() {
705 let res = adoc_text2label("{label}[link text]abc").unwrap();
706 assert_eq!(res, ("abc", (Cow::from("link text"), Cow::from("label"))));
707
708 let res = adoc_text2label("{label}[]abc").unwrap();
709 assert_eq!(res, ("abc", (Cow::from(""), Cow::from("label"))));
710
711 let res = adoc_text2label("{label}abc").unwrap();
712 assert_eq!(res, ("abc", (Cow::from(""), Cow::from("label"))));
713
714 let res = adoc_text2label("{label}").unwrap();
715 assert_eq!(res, ("", (Cow::from(""), Cow::from("label"))));
716
717 let res = adoc_text2label("{label} [link text]abc").unwrap();
718 assert_eq!(
719 res,
720 (" [link text]abc", (Cow::from(""), Cow::from("label")))
721 );
722
723 assert_eq!(
724 adoc_text2label("{label}[abc").unwrap_err(),
725 nom::Err::Error(nom::error::Error::new("[abc", ErrorKind::NoneOf))
726 );
727 }
728
729 #[test]
730 fn test_adoc_parse_curly_bracket_reference() {
731 let res = adoc_parse_curly_bracket_reference("{label}").unwrap();
732 assert_eq!(res, ("", Cow::from("label")));
733
734 let res = adoc_parse_curly_bracket_reference("{label}[link text]").unwrap();
735 assert_eq!(res, ("[link text]", Cow::from("label")));
736
737 assert_eq!(
738 adoc_parse_curly_bracket_reference("").unwrap_err(),
739 nom::Err::Error(nom::error::Error::new("", ErrorKind::Char))
740 );
741
742 assert_eq!(
743 adoc_parse_curly_bracket_reference("{label }").unwrap_err(),
744 nom::Err::Error(nom::error::Error::new(" }", ErrorKind::Char))
745 );
746 assert_eq!(
747 adoc_parse_curly_bracket_reference("").unwrap_err(),
748 nom::Err::Error(nom::error::Error::new("", ErrorKind::Char))
749 );
750 }
751
752 #[test]
753 fn test_adoc_parse_colon_reference() {
754 let res = adoc_parse_colon_reference(":label:abc").unwrap();
755 assert_eq!(res, ("abc", "label"));
756
757 assert_eq!(
758 adoc_parse_colon_reference(":label abc").unwrap_err(),
759 nom::Err::Error(nom::error::Error::new(" abc", ErrorKind::Char))
760 );
761 }
762}