1#![allow(dead_code)]
3#![allow(clippy::type_complexity)]
4
5use crate::parser::parse::LABEL_LEN_MAX;
6use crate::parser::percent_decode;
7use crate::parser::Link;
8use nom::branch::alt;
9use nom::bytes::complete::tag;
10use nom::bytes::complete::tag_no_case;
11use nom::character::complete::char;
12use nom::character::complete::space0;
13use nom::combinator::peek;
14use std::borrow::Cow;
15
16pub fn adoc_text2dest_link(i: &str) -> nom::IResult<&str, Link> {
19 let (i, (te, de, ti)) = adoc_text2dest(i)?;
20 Ok((i, Link::Text2Dest(te, de, ti)))
21}
22
23pub fn adoc_text2dest(i: &str) -> nom::IResult<&str, (Cow<str>, Cow<str>, Cow<str>)> {
57 let (i, (link_destination, link_text)) = nom::sequence::preceded(
58 space0,
59 nom::sequence::pair(
60 adoc_inline_link_destination,
61 nom::combinator::opt(adoc_link_text),
62 ),
63 )(i)?;
64
65 let link_text = if let Some(lt) = link_text {
66 if lt.is_empty() {
67 link_destination.clone()
68 } else {
69 lt
70 }
71 } else {
72 link_destination.clone()
73 };
74
75 Ok((i, (link_text, link_destination, Cow::Borrowed(""))))
76}
77
78pub fn adoc_label2dest_link(i: &str) -> nom::IResult<&str, Link> {
81 let (i, (te, de, ti)) = adoc_label2dest(i)?;
82 Ok((i, Link::Label2Dest(te, de, ti)))
83}
84
85pub fn adoc_label2dest(i: &str) -> nom::IResult<&str, (Cow<str>, Cow<str>, Cow<str>)> {
107 let (i, (link_label, link_destination)) = nom::sequence::preceded(
108 space0,
109 nom::sequence::pair(
110 adoc_parse_colon_reference,
111 nom::sequence::delimited(
112 nom::character::complete::space1,
113 adoc_link_reference_definition_destination,
114 nom::character::complete::space0,
115 ),
116 ),
117 )(i)?;
118
119 if !i.is_empty() {
120 let _ = peek::<&str, _, nom::error::Error<_>, _>(nom::character::complete::newline)(i)?;
121 };
122
123 Ok((
124 i,
125 (
126 Cow::Borrowed(link_label),
127 link_destination,
128 Cow::Borrowed(""),
129 ),
130 ))
131}
132
133pub fn adoc_text2label_link(i: &str) -> nom::IResult<&str, Link> {
136 let (i, (te, la)) = adoc_text2label(i)?;
137 Ok((i, Link::Text2Label(te, la)))
138}
139
140pub fn adoc_text2label(i: &str) -> nom::IResult<&str, (Cow<str>, Cow<str>)> {
179 let (i, (link_label, link_text)) = alt((
180 nom::sequence::pair(adoc_parse_curly_bracket_reference, adoc_link_text),
181 nom::combinator::map(adoc_parse_curly_bracket_reference, |s| (s, Cow::from(""))),
182 ))(i)?;
183
184 if !i.is_empty() {
186 let _ = nom::character::complete::none_of("[{")(i)?;
187 }
188
189 Ok((i, (link_text, link_label)))
190}
191
192fn adoc_link_text(i: &str) -> nom::IResult<&str, Cow<str>> {
197 nom::sequence::delimited(char('['), remove_newline_take_till(']'), char(']'))(i)
198}
199
200fn remove_newline_take_till<'a>(
206 pat: char,
207) -> impl Fn(&'a str) -> nom::IResult<&'a str, Cow<'a, str>> {
208 move |i: &str| {
209 let mut res = Cow::Borrowed("");
210 let mut j = i;
211 while !j.is_empty() {
212 let (k, s1) =
222 nom::bytes::complete::take_till(|c| c == pat || c == '\n' || c == '\\')(j)?;
223
224 res = match res {
226 Cow::Borrowed("") => Cow::Borrowed(s1),
227 Cow::Borrowed(res_str) => {
228 let mut strg = res_str.to_string();
229 strg.push_str(s1);
230 Cow::Owned(strg)
231 }
232 Cow::Owned(mut strg) => {
233 strg.push_str(s1);
234 Cow::Owned(strg)
235 }
236 };
237
238 if let (_, Some(c)) =
241 nom::combinator::opt(nom::combinator::peek(nom::character::complete::anychar))(k)?
242 {
243 let m = match c {
244 c if c == pat => return Ok((k, res)),
247 '\\' => {
249 let (l, _) = char('\\')(k)?;
251 let (l, c) = alt((char(pat), nom::combinator::success('\\')))(l)?;
255
256 let mut strg = res.to_string();
258 strg.push(c);
259 res = Cow::Owned(strg);
261 l
263 }
264 '\n' => {
266 let (l, _) = char('\n')(k)?;
268 let (l, _) = space0(l)?;
269 let _ = nom::combinator::not(char('\n'))(l)?;
271
272 let mut strg = res.to_string();
274 strg.push(' ');
275 res = Cow::Owned(strg);
277 l
279 }
280 _ => unreachable!(),
281 };
282 j = m;
283 } else {
284 j = k;
286 }
287 }
288
289 Ok(("", res))
291 }
292}
293
294fn adoc_link_reference_definition_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
300 alt((
301 adoc_parse_http_link_destination,
302 adoc_parse_escaped_link_destination,
303 ))(i)
304}
305
306fn adoc_inline_link_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
311 alt((
312 adoc_parse_http_link_destination,
313 adoc_parse_literal_link_destination,
314 adoc_parse_escaped_link_destination,
315 ))(i)
316}
317
318fn adoc_parse_http_link_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
321 let (j, s) = nom::sequence::preceded(
322 peek(alt((tag_no_case("http://"), (tag_no_case("https://"))))),
323 nom::bytes::complete::take_till1(|c| c == '[' || c == ' ' || c == '\t' || c == '\n'),
324 )(i)?;
325 Ok((j, Cow::Borrowed(s)))
326}
327
328fn adoc_parse_escaped_link_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
332 nom::combinator::map_parser(
333 nom::sequence::preceded(
334 nom::sequence::pair(
335 tag("link:"),
336 peek(alt((tag_no_case("http://"), (tag_no_case("https://"))))),
337 ),
338 nom::bytes::complete::take_till1(|c| {
339 c == '[' || c == ' ' || c == '\t' || c == '\r' || c == '\n'
340 }),
341 ),
342 percent_decode,
343 )(i)
344}
345
346fn adoc_parse_literal_link_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
349 let (j, s) = nom::sequence::preceded(
350 tag("link:"),
351 nom::sequence::delimited(tag("++"), nom::bytes::complete::take_until("++"), tag("++")),
352 )(i)?;
353 Ok((j, Cow::Borrowed(s)))
354}
355
356fn adoc_parse_curly_bracket_reference(i: &str) -> nom::IResult<&str, Cow<str>> {
361 nom::combinator::map(
362 nom::combinator::verify(
363 nom::sequence::delimited(
364 char('{'),
365 nom::bytes::complete::take_till1(|c| {
366 c == '}' || c == ' ' || c == '\t' || c == '\r'
367 }),
368 char('}'),
369 ),
370 |s: &str| s.len() <= LABEL_LEN_MAX,
371 ),
372 Cow::Borrowed,
373 )(i)
374}
375
376fn adoc_parse_colon_reference(i: &str) -> nom::IResult<&str, &str> {
383 nom::combinator::verify(
384 nom::sequence::delimited(
385 char(':'),
386 nom::bytes::complete::take_till1(|c| c == ':' || c == ' ' || c == '\t' || c == '\r'),
387 char(':'),
388 ),
389 |s: &str| s.len() <= LABEL_LEN_MAX,
390 )(i)
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396 use nom::error::ErrorKind;
397 use std::matches;
398
399 #[test]
400 fn test_adoc_text2dest() {
401 assert_eq!(
402 adoc_text2dest("http://getreu.net[]"),
403 Ok((
404 "",
405 (
406 Cow::from("http://getreu.net"),
407 Cow::from("http://getreu.net"),
408 Cow::from("")
409 )
410 ))
411 );
412
413 assert_eq!(
414 adoc_text2dest("http://getreu.net[]abc"),
415 Ok((
416 "abc",
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(" \t http://getreu.net[My blog]abc"),
427 Ok((
428 "abc",
429 (
430 Cow::from("My blog"),
431 Cow::from("http://getreu.net"),
432 Cow::from("")
433 )
434 ))
435 );
436
437 assert_eq!(
438 adoc_text2dest(r#"http://getreu.net[My blog[1\]]abc"#),
439 Ok((
440 "abc",
441 (
442 Cow::from("My blog[1]"),
443 Cow::from("http://getreu.net"),
444 Cow::from("")
445 )
446 ))
447 );
448
449 assert_eq!(
450 adoc_text2dest("http://getreu.net[My\n blog]abc"),
451 Ok((
452 "abc",
453 (
454 Cow::from("My blog"),
455 Cow::from("http://getreu.net"),
456 Cow::from("")
457 )
458 ))
459 );
460
461 assert_eq!(
462 adoc_text2dest("link:http://getreu.net[My 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:https://getreu.net/?q=%5Ba%20b%5D[My blog]abc"),
475 Ok((
476 "abc",
477 (
478 Cow::from("My blog"),
479 Cow::from("https://getreu.net/?q=[a b]"),
480 Cow::from("")
481 )
482 ))
483 );
484
485 assert_eq!(
486 adoc_text2dest("link:++https://getreu.net/?q=[a b]++[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
498 #[test]
499 fn test_adoc_label2dest() {
500 assert_eq!(
501 adoc_label2dest(":label: http://getreu.net\n"),
502 Ok((
503 "\n",
504 (
505 Cow::from("label"),
506 Cow::from("http://getreu.net"),
507 Cow::from("")
508 )
509 ))
510 );
511
512 assert_eq!(
513 adoc_label2dest(" :label: \thttp://getreu.net \t "),
514 Ok((
515 "",
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 abc").unwrap_err(),
526 nom::Err::Error(nom::error::Error::new("abc", ErrorKind::Char))
527 );
528 }
529
530 #[test]
531 fn test_adoc_link_text() {
532 assert_eq!(adoc_link_text("[text]abc"), Ok(("abc", Cow::from("text"))));
533
534 assert_eq!(
535 adoc_link_text("[te\nxt]abc"),
536 Ok(("abc", Cow::from("te xt")))
537 );
538
539 assert_eq!(
540 adoc_link_text("[te\n\nxt]abc"),
541 Err(nom::Err::Error(nom::error::Error::new(
542 "\nxt]abc",
543 ErrorKind::Not
544 )))
545 );
546
547 assert_eq!(
548 adoc_link_text(r#"[text[i\]]abc"#),
549 Ok(("abc", Cow::from(r#"text[i]"#.to_string())))
550 );
551
552 assert_eq!(
553 adoc_link_text("[textabc"),
554 Err(nom::Err::Error(nom::error::Error::new("", ErrorKind::Char)))
555 );
556 }
557
558 #[test]
559 fn test_remove_newline_take_till() {
560 let res = remove_newline_take_till(']')("").unwrap();
561 assert_eq!(res, ("", Cow::from("")));
562 assert!(matches!(res.1, Cow::Borrowed { .. }));
563
564 let res = remove_newline_take_till(']')("text text]abc").unwrap();
565 assert_eq!(res, ("]abc", Cow::from("text text")));
566 assert!(matches!(res.1, Cow::Borrowed { .. }));
567
568 let res = remove_newline_take_till(']')("text text").unwrap();
569 assert_eq!(res, ("", Cow::from("text text")));
570 assert!(matches!(res.1, Cow::Borrowed { .. }));
571
572 let res = remove_newline_take_till(']')(r#"te\]xt]abc"#).unwrap();
573 assert_eq!(res, ("]abc", Cow::from("te]xt")));
574 assert!(matches!(res.1, Cow::Owned { .. }));
575
576 let res = remove_newline_take_till(']')(r#"text\]]abc"#).unwrap();
577 assert_eq!(res, ("]abc", Cow::from("text]")));
578 assert!(matches!(res.1, Cow::Owned { .. }));
579
580 let res = remove_newline_take_till(']')(r#"te\xt]abc"#).unwrap();
581 assert_eq!(res, ("]abc", Cow::from(r#"te\xt"#)));
582 assert!(matches!(res.1, Cow::Owned { .. }));
583
584 let res = remove_newline_take_till(']')("text\n text]abc").unwrap();
585 assert_eq!(res, ("]abc", Cow::from("text text")));
586 assert!(matches!(res.1, Cow::Owned { .. }));
587
588 let res = remove_newline_take_till(']')("text\n text]abc").unwrap();
589 assert_eq!(res, ("]abc", Cow::from("text text")));
590 assert!(matches!(res.1, Cow::Owned { .. }));
591
592 assert_eq!(
593 remove_newline_take_till(']')("text\n\ntext]abc").unwrap_err(),
594 nom::Err::Error(nom::error::Error::new("\ntext]abc", ErrorKind::Not))
595 );
596
597 assert_eq!(
598 remove_newline_take_till(']')("text\n \n text]abc").unwrap_err(),
599 nom::Err::Error(nom::error::Error::new("\n text]abc", ErrorKind::Not))
600 );
601 }
602
603 #[test]
604 fn test_adoc_parse_http_link_destination() {
605 let res = adoc_parse_http_link_destination("http://destination/").unwrap();
606 assert_eq!(res, ("", Cow::from("http://destination/")));
607 assert!(matches!(res.1, Cow::Borrowed { .. }));
608
609 let res = adoc_parse_http_link_destination("http://destination/\nabc").unwrap();
610 assert_eq!(res, ("\nabc", Cow::from("http://destination/")));
611 assert!(matches!(res.1, Cow::Borrowed { .. }));
612
613 let res = adoc_parse_http_link_destination("http://destination/ abc").unwrap();
614 assert_eq!(res, (" abc", Cow::from("http://destination/")));
615 assert!(matches!(res.1, Cow::Borrowed { .. }));
616
617 let res = adoc_parse_http_link_destination("http://destination/[abc").unwrap();
618 assert_eq!(res, ("[abc", Cow::from("http://destination/")));
619 assert!(matches!(res.1, Cow::Borrowed { .. }));
620
621 let res = adoc_parse_http_link_destination("https://destination/[abc").unwrap();
622 assert_eq!(res, ("[abc", Cow::from("https://destination/")));
623 assert!(matches!(res.1, Cow::Borrowed { .. }));
624
625 assert_eq!(
626 adoc_parse_http_link_destination("http:/destination/[abc").unwrap_err(),
627 nom::Err::Error(nom::error::Error::new(
628 "http:/destination/[abc",
629 ErrorKind::Tag
630 ))
631 );
632 }
633
634 #[test]
635 fn test_adoc_parse_escaped_link_destination() {
636 let res = adoc_parse_escaped_link_destination("link:http://destination/").unwrap();
637 assert_eq!(res, ("", Cow::from("http://destination/")));
638 assert!(matches!(res.1, Cow::Borrowed { .. }));
639
640 let res = adoc_parse_escaped_link_destination("link:http://destination/[abc").unwrap();
641 assert_eq!(res, ("[abc", Cow::from("http://destination/")));
642 assert!(matches!(res.1, Cow::Borrowed { .. }));
643
644 let res = adoc_parse_escaped_link_destination("link:http://destination/ abc").unwrap();
645 assert_eq!(res, (" abc", Cow::from("http://destination/")));
646 assert!(matches!(res.1, Cow::Borrowed { .. }));
647
648 let res = adoc_parse_escaped_link_destination("link:http://destination/\nabc").unwrap();
649 assert_eq!(res, ("\nabc", Cow::from("http://destination/")));
650 assert!(matches!(res.1, Cow::Borrowed { .. }));
651
652 assert_eq!(
653 adoc_parse_escaped_link_destination("link:httpX:/destination/[abc").unwrap_err(),
654 nom::Err::Error(nom::error::Error::new(
655 "httpX:/destination/[abc",
656 ErrorKind::Tag
657 ))
658 );
659
660 let res = adoc_parse_escaped_link_destination("link:https://getreu.net/?q=%5Ba%20b%5D[abc")
661 .unwrap();
662 assert_eq!(res, ("[abc", Cow::from("https://getreu.net/?q=[a b]")));
663 assert!(matches!(res.1, Cow::Owned { .. }));
664
665 assert_eq!(
666 adoc_parse_escaped_link_destination("link:https://getreu.net/?q=%FF%FF[abc")
667 .unwrap_err(),
668 nom::Err::Error(nom::error::Error::new(
669 "https://getreu.net/?q=%FF%FF",
670 ErrorKind::EscapedTransform
671 ))
672 );
673 }
674
675 #[test]
676 fn test_adoc_parse_literal_link_destination() {
677 let res = adoc_parse_literal_link_destination("link:++https://getreu.net/?q=[a b]++[abc")
678 .unwrap();
679 assert_eq!(res, ("[abc", Cow::from("https://getreu.net/?q=[a b]")));
680
681 assert_eq!(
682 adoc_parse_literal_link_destination("link:++https://getreu.net/?q=[a b]+[abc")
683 .unwrap_err(),
684 nom::Err::Error(nom::error::Error::new(
685 "https://getreu.net/?q=[a b]+[abc",
686 ErrorKind::TakeUntil
687 ))
688 );
689 }
690
691 #[test]
692 fn test_adoc_text2label() {
693 let res = adoc_text2label("{label}[link text]abc").unwrap();
694 assert_eq!(res, ("abc", (Cow::from("link text"), Cow::from("label"))));
695
696 let res = adoc_text2label("{label}[]abc").unwrap();
697 assert_eq!(res, ("abc", (Cow::from(""), Cow::from("label"))));
698
699 let res = adoc_text2label("{label}abc").unwrap();
700 assert_eq!(res, ("abc", (Cow::from(""), Cow::from("label"))));
701
702 let res = adoc_text2label("{label}").unwrap();
703 assert_eq!(res, ("", (Cow::from(""), Cow::from("label"))));
704
705 let res = adoc_text2label("{label} [link text]abc").unwrap();
706 assert_eq!(
707 res,
708 (" [link text]abc", (Cow::from(""), Cow::from("label")))
709 );
710
711 assert_eq!(
712 adoc_text2label("{label}[abc").unwrap_err(),
713 nom::Err::Error(nom::error::Error::new("[abc", ErrorKind::NoneOf))
714 );
715 }
716
717 #[test]
718 fn test_adoc_parse_curly_bracket_reference() {
719 let res = adoc_parse_curly_bracket_reference("{label}").unwrap();
720 assert_eq!(res, ("", Cow::from("label")));
721
722 let res = adoc_parse_curly_bracket_reference("{label}[link text]").unwrap();
723 assert_eq!(res, ("[link text]", Cow::from("label")));
724
725 assert_eq!(
726 adoc_parse_curly_bracket_reference("").unwrap_err(),
727 nom::Err::Error(nom::error::Error::new("", ErrorKind::Char))
728 );
729
730 assert_eq!(
731 adoc_parse_curly_bracket_reference("{label }").unwrap_err(),
732 nom::Err::Error(nom::error::Error::new(" }", ErrorKind::Char))
733 );
734 assert_eq!(
735 adoc_parse_curly_bracket_reference("").unwrap_err(),
736 nom::Err::Error(nom::error::Error::new("", ErrorKind::Char))
737 );
738 }
739
740 #[test]
741 fn test_adoc_parse_colon_reference() {
742 let res = adoc_parse_colon_reference(":label:abc").unwrap();
743 assert_eq!(res, ("abc", "label"));
744
745 assert_eq!(
746 adoc_parse_colon_reference(":label abc").unwrap_err(),
747 nom::Err::Error(nom::error::Error::new(" abc", ErrorKind::Char))
748 );
749 }
750}