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>)> {
178 let (i, (link_label, link_text)) = alt((
179 nom::sequence::pair(adoc_parse_curly_bracket_reference, adoc_link_text),
180 nom::combinator::map(adoc_parse_curly_bracket_reference, |s| (s, Cow::from(""))),
181 ))(i)?;
182
183 if !i.is_empty() {
185 let _ = nom::character::complete::none_of("[{")(i)?;
186 }
187
188 Ok((i, (link_text, link_label)))
189}
190
191fn adoc_link_text(i: &str) -> nom::IResult<&str, Cow<str>> {
196 nom::sequence::delimited(char('['), remove_newline_take_till(']'), char(']'))(i)
197}
198
199fn remove_newline_take_till<'a>(
205 pat: char,
206) -> impl Fn(&'a str) -> nom::IResult<&'a str, Cow<'a, str>> {
207 move |i: &str| {
208 let mut res = Cow::Borrowed("");
209 let mut j = i;
210 while !j.is_empty() {
211 let (k, s1) =
221 nom::bytes::complete::take_till(|c| c == pat || c == '\n' || c == '\\')(j)?;
222
223 res = match res {
225 Cow::Borrowed("") => Cow::Borrowed(s1),
226 Cow::Borrowed(res_str) => {
227 let mut strg = res_str.to_string();
228 strg.push_str(s1);
229 Cow::Owned(strg)
230 }
231 Cow::Owned(mut strg) => {
232 strg.push_str(s1);
233 Cow::Owned(strg)
234 }
235 };
236
237 if let (_, Some(c)) =
240 nom::combinator::opt(nom::combinator::peek(nom::character::complete::anychar))(k)?
241 {
242 let m = match c {
243 c if c == pat => return Ok((k, res)),
246 c if c == '\\' => {
248 let (l, _) = char('\\')(k)?;
250 let (l, c) = alt((char(pat), nom::combinator::success('\\')))(l)?;
254
255 let mut strg = res.to_string();
257 strg.push(c);
258 res = Cow::Owned(strg);
260 l
262 }
263 c if c == '\n' => {
265 let (l, _) = char('\n')(k)?;
267 let (l, _) = space0(l)?;
268 let _ = nom::combinator::not(char('\n'))(l)?;
270
271 let mut strg = res.to_string();
273 strg.push(' ');
274 res = Cow::Owned(strg);
276 l
278 }
279 _ => unreachable!(),
280 };
281 j = m;
282 } else {
283 j = k;
285 }
286 }
287
288 Ok(("", res))
290 }
291}
292
293fn adoc_link_reference_definition_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
299 alt((
300 adoc_parse_http_link_destination,
301 adoc_parse_escaped_link_destination,
302 ))(i)
303}
304
305fn adoc_inline_link_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
310 alt((
311 adoc_parse_http_link_destination,
312 adoc_parse_literal_link_destination,
313 adoc_parse_escaped_link_destination,
314 ))(i)
315}
316
317fn adoc_parse_http_link_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
320 let (j, s) = nom::sequence::preceded(
321 peek(alt((tag_no_case("http://"), (tag_no_case("https://"))))),
322 nom::bytes::complete::take_till1(|c| c == '[' || c == ' ' || c == '\t' || c == '\n'),
323 )(i)?;
324 Ok((j, Cow::Borrowed(s)))
325}
326
327fn adoc_parse_escaped_link_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
331 nom::combinator::map_parser(
332 nom::sequence::preceded(
333 nom::sequence::pair(
334 tag("link:"),
335 peek(alt((tag_no_case("http://"), (tag_no_case("https://"))))),
336 ),
337 nom::bytes::complete::take_till1(|c| {
338 c == '[' || c == ' ' || c == '\t' || c == '\r' || c == '\n'
339 }),
340 ),
341 percent_decode,
342 )(i)
343}
344
345fn adoc_parse_literal_link_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
348 let (j, s) = nom::sequence::preceded(
349 tag("link:"),
350 nom::sequence::delimited(tag("++"), nom::bytes::complete::take_until("++"), tag("++")),
351 )(i)?;
352 Ok((j, Cow::Borrowed(s)))
353}
354
355fn adoc_parse_curly_bracket_reference(i: &str) -> nom::IResult<&str, Cow<str>> {
360 nom::combinator::map(
361 nom::combinator::verify(
362 nom::sequence::delimited(
363 char('{'),
364 nom::bytes::complete::take_till1(|c| {
365 c == '}' || c == ' ' || c == '\t' || c == '\r'
366 }),
367 char('}'),
368 ),
369 |s: &str| s.len() <= LABEL_LEN_MAX,
370 ),
371 Cow::Borrowed,
372 )(i)
373}
374
375fn adoc_parse_colon_reference(i: &str) -> nom::IResult<&str, &str> {
382 nom::combinator::verify(
383 nom::sequence::delimited(
384 char(':'),
385 nom::bytes::complete::take_till1(|c| c == ':' || c == ' ' || c == '\t' || c == '\r'),
386 char(':'),
387 ),
388 |s: &str| s.len() <= LABEL_LEN_MAX,
389 )(i)
390}
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395 use nom::error::ErrorKind;
396 use std::matches;
397
398 #[test]
399 fn test_adoc_text2dest() {
400 assert_eq!(
401 adoc_text2dest("http://getreu.net[]"),
402 Ok((
403 "",
404 (
405 Cow::from("http://getreu.net"),
406 Cow::from("http://getreu.net"),
407 Cow::from("")
408 )
409 ))
410 );
411
412 assert_eq!(
413 adoc_text2dest("http://getreu.net[]abc"),
414 Ok((
415 "abc",
416 (
417 Cow::from("http://getreu.net"),
418 Cow::from("http://getreu.net"),
419 Cow::from("")
420 )
421 ))
422 );
423
424 assert_eq!(
425 adoc_text2dest(" \t http://getreu.net[My blog]abc"),
426 Ok((
427 "abc",
428 (
429 Cow::from("My blog"),
430 Cow::from("http://getreu.net"),
431 Cow::from("")
432 )
433 ))
434 );
435
436 assert_eq!(
437 adoc_text2dest(r#"http://getreu.net[My blog[1\]]abc"#),
438 Ok((
439 "abc",
440 (
441 Cow::from("My blog[1]"),
442 Cow::from("http://getreu.net"),
443 Cow::from("")
444 )
445 ))
446 );
447
448 assert_eq!(
449 adoc_text2dest("http://getreu.net[My\n blog]abc"),
450 Ok((
451 "abc",
452 (
453 Cow::from("My blog"),
454 Cow::from("http://getreu.net"),
455 Cow::from("")
456 )
457 ))
458 );
459
460 assert_eq!(
461 adoc_text2dest("link:http://getreu.net[My blog]abc"),
462 Ok((
463 "abc",
464 (
465 Cow::from("My blog"),
466 Cow::from("http://getreu.net"),
467 Cow::from("")
468 )
469 ))
470 );
471
472 assert_eq!(
473 adoc_text2dest("link:https://getreu.net/?q=%5Ba%20b%5D[My blog]abc"),
474 Ok((
475 "abc",
476 (
477 Cow::from("My blog"),
478 Cow::from("https://getreu.net/?q=[a b]"),
479 Cow::from("")
480 )
481 ))
482 );
483
484 assert_eq!(
485 adoc_text2dest("link:++https://getreu.net/?q=[a b]++[My blog]abc"),
486 Ok((
487 "abc",
488 (
489 Cow::from("My blog"),
490 Cow::from("https://getreu.net/?q=[a b]"),
491 Cow::from("")
492 )
493 ))
494 );
495 }
496
497 #[test]
498 fn test_adoc_label2dest() {
499 assert_eq!(
500 adoc_label2dest(":label: http://getreu.net\n"),
501 Ok((
502 "\n",
503 (
504 Cow::from("label"),
505 Cow::from("http://getreu.net"),
506 Cow::from("")
507 )
508 ))
509 );
510
511 assert_eq!(
512 adoc_label2dest(" :label: \thttp://getreu.net \t "),
513 Ok((
514 "",
515 (
516 Cow::from("label"),
517 Cow::from("http://getreu.net"),
518 Cow::from("")
519 )
520 ))
521 );
522
523 assert_eq!(
524 adoc_label2dest(" :label: \thttp://getreu.net \t abc").unwrap_err(),
525 nom::Err::Error(nom::error::Error::new("abc", ErrorKind::Char))
526 );
527 }
528
529 #[test]
530 fn test_adoc_link_text() {
531 assert_eq!(adoc_link_text("[text]abc"), Ok(("abc", Cow::from("text"))));
532
533 assert_eq!(
534 adoc_link_text("[te\nxt]abc"),
535 Ok(("abc", Cow::from("te xt")))
536 );
537
538 assert_eq!(
539 adoc_link_text("[te\n\nxt]abc"),
540 Err(nom::Err::Error(nom::error::Error::new(
541 "\nxt]abc",
542 ErrorKind::Not
543 )))
544 );
545
546 assert_eq!(
547 adoc_link_text(r#"[text[i\]]abc"#),
548 Ok(("abc", Cow::from(r#"text[i]"#.to_string())))
549 );
550
551 assert_eq!(
552 adoc_link_text("[textabc"),
553 Err(nom::Err::Error(nom::error::Error::new("", ErrorKind::Char)))
554 );
555 }
556
557 #[test]
558 fn test_remove_newline_take_till() {
559 let res = remove_newline_take_till(']')("").unwrap();
560 assert_eq!(res, ("", Cow::from("")));
561 assert!(matches!(res.1, Cow::Borrowed { .. }));
562
563 let res = remove_newline_take_till(']')("text text]abc").unwrap();
564 assert_eq!(res, ("]abc", Cow::from("text text")));
565 assert!(matches!(res.1, Cow::Borrowed { .. }));
566
567 let res = remove_newline_take_till(']')("text text").unwrap();
568 assert_eq!(res, ("", Cow::from("text text")));
569 assert!(matches!(res.1, Cow::Borrowed { .. }));
570
571 let res = remove_newline_take_till(']')(r#"te\]xt]abc"#).unwrap();
572 assert_eq!(res, ("]abc", Cow::from("te]xt")));
573 assert!(matches!(res.1, Cow::Owned { .. }));
574
575 let res = remove_newline_take_till(']')(r#"text\]]abc"#).unwrap();
576 assert_eq!(res, ("]abc", Cow::from("text]")));
577 assert!(matches!(res.1, Cow::Owned { .. }));
578
579 let res = remove_newline_take_till(']')(r#"te\xt]abc"#).unwrap();
580 assert_eq!(res, ("]abc", Cow::from(r#"te\xt"#)));
581 assert!(matches!(res.1, Cow::Owned { .. }));
582
583 let res = remove_newline_take_till(']')("text\n text]abc").unwrap();
584 assert_eq!(res, ("]abc", Cow::from("text text")));
585 assert!(matches!(res.1, Cow::Owned { .. }));
586
587 let res = remove_newline_take_till(']')("text\n text]abc").unwrap();
588 assert_eq!(res, ("]abc", Cow::from("text text")));
589 assert!(matches!(res.1, Cow::Owned { .. }));
590
591 assert_eq!(
592 remove_newline_take_till(']')("text\n\ntext]abc").unwrap_err(),
593 nom::Err::Error(nom::error::Error::new("\ntext]abc", ErrorKind::Not))
594 );
595
596 assert_eq!(
597 remove_newline_take_till(']')("text\n \n text]abc").unwrap_err(),
598 nom::Err::Error(nom::error::Error::new("\n text]abc", ErrorKind::Not))
599 );
600 }
601
602 #[test]
603 fn test_adoc_parse_http_link_destination() {
604 let res = adoc_parse_http_link_destination("http://destination/").unwrap();
605 assert_eq!(res, ("", Cow::from("http://destination/")));
606 assert!(matches!(res.1, Cow::Borrowed { .. }));
607
608 let res = adoc_parse_http_link_destination("http://destination/\nabc").unwrap();
609 assert_eq!(res, ("\nabc", Cow::from("http://destination/")));
610 assert!(matches!(res.1, Cow::Borrowed { .. }));
611
612 let res = adoc_parse_http_link_destination("http://destination/ abc").unwrap();
613 assert_eq!(res, (" abc", Cow::from("http://destination/")));
614 assert!(matches!(res.1, Cow::Borrowed { .. }));
615
616 let res = adoc_parse_http_link_destination("http://destination/[abc").unwrap();
617 assert_eq!(res, ("[abc", Cow::from("http://destination/")));
618 assert!(matches!(res.1, Cow::Borrowed { .. }));
619
620 let res = adoc_parse_http_link_destination("https://destination/[abc").unwrap();
621 assert_eq!(res, ("[abc", Cow::from("https://destination/")));
622 assert!(matches!(res.1, Cow::Borrowed { .. }));
623
624 assert_eq!(
625 adoc_parse_http_link_destination("http:/destination/[abc").unwrap_err(),
626 nom::Err::Error(nom::error::Error::new(
627 "http:/destination/[abc",
628 ErrorKind::Tag
629 ))
630 );
631 }
632
633 #[test]
634 fn test_adoc_parse_escaped_link_destination() {
635 let res = adoc_parse_escaped_link_destination("link:http://destination/").unwrap();
636 assert_eq!(res, ("", Cow::from("http://destination/")));
637 assert!(matches!(res.1, Cow::Borrowed { .. }));
638
639 let res = adoc_parse_escaped_link_destination("link:http://destination/[abc").unwrap();
640 assert_eq!(res, ("[abc", Cow::from("http://destination/")));
641 assert!(matches!(res.1, Cow::Borrowed { .. }));
642
643 let res = adoc_parse_escaped_link_destination("link:http://destination/ abc").unwrap();
644 assert_eq!(res, (" abc", Cow::from("http://destination/")));
645 assert!(matches!(res.1, Cow::Borrowed { .. }));
646
647 let res = adoc_parse_escaped_link_destination("link:http://destination/\nabc").unwrap();
648 assert_eq!(res, ("\nabc", Cow::from("http://destination/")));
649 assert!(matches!(res.1, Cow::Borrowed { .. }));
650
651 assert_eq!(
652 adoc_parse_escaped_link_destination("link:httpX:/destination/[abc").unwrap_err(),
653 nom::Err::Error(nom::error::Error::new(
654 "httpX:/destination/[abc",
655 ErrorKind::Tag
656 ))
657 );
658
659 let res = adoc_parse_escaped_link_destination("link:https://getreu.net/?q=%5Ba%20b%5D[abc")
660 .unwrap();
661 assert_eq!(res, ("[abc", Cow::from("https://getreu.net/?q=[a b]")));
662 assert!(matches!(res.1, Cow::Owned { .. }));
663
664 assert_eq!(
665 adoc_parse_escaped_link_destination("link:https://getreu.net/?q=%FF%FF[abc")
666 .unwrap_err(),
667 nom::Err::Error(nom::error::Error::new(
668 "https://getreu.net/?q=%FF%FF",
669 ErrorKind::EscapedTransform
670 ))
671 );
672 }
673
674 #[test]
675 fn test_adoc_parse_literal_link_destination() {
676 let res = adoc_parse_literal_link_destination("link:++https://getreu.net/?q=[a b]++[abc")
677 .unwrap();
678 assert_eq!(res, ("[abc", Cow::from("https://getreu.net/?q=[a b]")));
679
680 assert_eq!(
681 adoc_parse_literal_link_destination("link:++https://getreu.net/?q=[a b]+[abc")
682 .unwrap_err(),
683 nom::Err::Error(nom::error::Error::new(
684 "https://getreu.net/?q=[a b]+[abc",
685 ErrorKind::TakeUntil
686 ))
687 );
688 }
689
690 #[test]
691 fn test_adoc_text2label() {
692 let res = adoc_text2label("{label}[link text]abc").unwrap();
693 assert_eq!(res, ("abc", (Cow::from("link text"), Cow::from("label"))));
694
695 let res = adoc_text2label("{label}[]abc").unwrap();
696 assert_eq!(res, ("abc", (Cow::from(""), Cow::from("label"))));
697
698 let res = adoc_text2label("{label}abc").unwrap();
699 assert_eq!(res, ("abc", (Cow::from(""), Cow::from("label"))));
700
701 let res = adoc_text2label("{label}").unwrap();
702 assert_eq!(res, ("", (Cow::from(""), Cow::from("label"))));
703
704 let res = adoc_text2label("{label} [link text]abc").unwrap();
705 assert_eq!(
706 res,
707 (" [link text]abc", (Cow::from(""), Cow::from("label")))
708 );
709
710 assert_eq!(
711 adoc_text2label("{label}[abc").unwrap_err(),
712 nom::Err::Error(nom::error::Error::new("[abc", ErrorKind::NoneOf))
713 );
714 }
715
716 #[test]
717 fn test_adoc_parse_curly_bracket_reference() {
718 let res = adoc_parse_curly_bracket_reference("{label}").unwrap();
719 assert_eq!(res, ("", Cow::from("label")));
720
721 let res = adoc_parse_curly_bracket_reference("{label}[link text]").unwrap();
722 assert_eq!(res, ("[link text]", Cow::from("label")));
723
724 assert_eq!(
725 adoc_parse_curly_bracket_reference("").unwrap_err(),
726 nom::Err::Error(nom::error::Error::new("", ErrorKind::Char))
727 );
728
729 assert_eq!(
730 adoc_parse_curly_bracket_reference("{label }").unwrap_err(),
731 nom::Err::Error(nom::error::Error::new(" }", ErrorKind::Char))
732 );
733 assert_eq!(
734 adoc_parse_curly_bracket_reference("").unwrap_err(),
735 nom::Err::Error(nom::error::Error::new("", ErrorKind::Char))
736 );
737 }
738
739 #[test]
740 fn test_adoc_parse_colon_reference() {
741 let res = adoc_parse_colon_reference(":label:abc").unwrap();
742 assert_eq!(res, ("abc", "label"));
743
744 assert_eq!(
745 adoc_parse_colon_reference(":label abc").unwrap_err(),
746 nom::Err::Error(nom::error::Error::new(" abc", ErrorKind::Char))
747 );
748 }
749}