parse_hyperlinks/parser/
parse.rs

1//! This module implements parsers to extract hyperlinks and link reference
2//! definitions from text input. The parsers search for Markdown,
3//! ReStructuredText, Asciidoc, Wikitext and HTML formatted links.
4#![allow(dead_code)]
5#![allow(clippy::type_complexity)]
6
7use crate::parser::Link;
8use crate::parser::asciidoc::adoc_label2dest_link;
9use crate::parser::asciidoc::adoc_text2dest_link;
10use crate::parser::asciidoc::adoc_text2label_link;
11use crate::parser::html::html_text2dest_link;
12use crate::parser::html_img::html_img_link;
13use crate::parser::html_img::html_img2dest_link;
14use crate::parser::markdown::md_label2dest_link;
15use crate::parser::markdown::md_text2dest_link;
16use crate::parser::markdown::md_text2label_link;
17use crate::parser::markdown_img::md_img_link;
18use crate::parser::markdown_img::md_img2dest_link;
19use crate::parser::restructured_text::rst_label2dest_link;
20use crate::parser::restructured_text::rst_label2label_link;
21use crate::parser::restructured_text::rst_text_label2dest_link;
22use crate::parser::restructured_text::rst_text2dest_link;
23use crate::parser::restructured_text::rst_text2label_link;
24use crate::parser::wikitext::wikitext_text2dest_link;
25use nom::Parser;
26use nom::branch::alt;
27use nom::bytes::complete::take_till;
28use nom::character::complete::anychar;
29use std::borrow::Cow;
30
31/// Link max label. This limits the damage of a forgotten closing brackets.
32/// [CommonMark Spec](https://spec.commonmark.org/0.30/#link-label)
33pub const LABEL_LEN_MAX: usize = 999;
34
35/// Consumes the input until it finds a Markdown, RestructuredText, Asciidoc or
36/// HTML formatted _inline link_ (`Text2Dest`) or _link reference definition_
37/// (`Label2Dest`).
38///
39/// Returns `Ok((remaining_input, (link_text_or_label, link_destination,
40/// link_title)))`. The parser recognizes only stand alone _inline links_ and
41/// _link reference definitions_, but no _reference links_.
42///
43/// # Limitations:
44/// Link reference labels are never resolved into link text. This limitation only
45/// concerns this parser. Others are not affected.
46///
47/// Very often this limitation has no effect at all. This is the case, when the
48/// _link text_ and the _link label_ are identical:
49///
50/// ```md
51/// abc [link text/label] abc
52///
53/// [link text/label]: /url "title"
54/// ```
55///
56/// But in general, the _link text_ and the _link label_ can be different:
57///
58/// ```md
59/// abc [link text][link label] abc
60///
61/// [link label]: /url "title"
62/// ```
63///
64/// When a link reference definition is found, the parser outputs it's link label
65/// instead of the link text, which is strictly speaking only correct when both
66/// are identical. Note, the same applies to RestructuredText's link reference
67/// definitions too.
68///
69/// Another limitation is that ReStructuredText's anonymous links are not supported.
70///
71///
72/// # Basic usage
73///
74/// ```
75/// use parse_hyperlinks::parser::parse::take_text2dest_label2dest;
76/// use std::borrow::Cow;
77///
78/// let i = r#"[a]: b 'c'
79///            .. _d: e
80///            ---[f](g 'h')---`i <j>`_---
81///            ---<a href="l" title="m">k</a>"#;
82///
83/// let (i, r) = take_text2dest_label2dest(i).unwrap();
84/// assert_eq!(r, (Cow::from("a"), Cow::from("b"), Cow::from("c")));
85/// let (i, r) = take_text2dest_label2dest(i).unwrap();
86/// assert_eq!(r, (Cow::from("d"), Cow::from("e"), Cow::from("")));
87/// let (i, r) = take_text2dest_label2dest(i).unwrap();
88/// assert_eq!(r, (Cow::from("f"), Cow::from("g"), Cow::from("h")));
89/// let (i, r) = take_text2dest_label2dest(i).unwrap();
90/// assert_eq!(r, (Cow::from("i"), Cow::from("j"), Cow::from("")));
91/// let (i, r) = take_text2dest_label2dest(i).unwrap();
92/// assert_eq!(r, (Cow::from("k"), Cow::from("l"), Cow::from("m")));
93/// ```
94/// The parser might silently consume some additional bytes after the actual finding: This happens,
95/// when directly after a finding a `md_link_ref` or `rst_link_ref` appears. These must be ignored,
96/// as they are only allowed at the beginning of a line. The skip has to happen at this moment, as
97/// the next parser does not know if the first byte it gets, is it at the beginning of a line or
98/// not.
99///
100/// Technically, this parser is a wrapper around `take_link()`, that erases the
101/// link type information and ignores all _reference links_. In case the input
102/// text contains _link reference definitions_, this function is be faster than
103/// the `parse_hyperlinks::iterator::Hyperlink` iterator.
104///
105/// Note: This function is depreciated and will be removed in some later release.
106/// Use `take_link()` instead.
107pub fn take_text2dest_label2dest(i: &str) -> nom::IResult<&str, (Cow<str>, Cow<str>, Cow<str>)> {
108    let mut j = i;
109    loop {
110        match take_link(j) {
111            Ok((j, (_, Link::Text2Dest(lte, ld, lti)))) => return Ok((j, (lte, ld, lti))),
112            Ok((j, (_, Link::TextLabel2Dest(lte, ld, lti)))) => return Ok((j, (lte, ld, lti))),
113            Ok((j, (_, Link::Label2Dest(ll, ld, lti)))) => return Ok((j, (ll, ld, lti))),
114            // We ignore `Link::Ref()` and `Link::RefAlias`. Instead we continue parsing.
115            Ok((k, _)) => {
116                j = k;
117                continue;
118            }
119            Err(e) => return Err(e),
120        };
121    }
122}
123
124/// Consumes the input until it finds a Markdown, RestructuredText, Asciidoc or
125/// HTML formatted _inline link_ (`Text2Dest`), _reference link_ (`Text2Label`),
126/// _link reference definition_ (`Label2Dest`) or _reference alias_ (`Label2Label`).
127///
128/// The parser consumes the finding and returns
129/// `Ok((remaining_input, (skipped_input, Link)))` or some error.
130///
131/// # Markdown
132///
133/// ```
134/// use parse_hyperlinks::parser::Link;
135/// use parse_hyperlinks::parser::parse::take_link;
136/// use std::borrow::Cow;
137///
138/// let i = r#"abc[text1][label1]abc
139/// abc[text2](destination2 "title2")
140/// [label1]: destination1 'title1'
141/// "#;
142///
143/// let (i, r) = take_link(i).unwrap();
144/// assert_eq!(r.0, "abc");
145/// assert_eq!(r.1, Link::Text2Label(Cow::from("text1"), Cow::from("label1")));
146/// let (i, r) = take_link(i).unwrap();
147/// assert_eq!(r.0, "abc\nabc");
148/// assert_eq!(r.1, Link::Text2Dest(Cow::from("text2"), Cow::from("destination2"), Cow::from("title2")));
149/// let (i, r) = take_link(i).unwrap();
150/// assert_eq!(r.0, "\n");
151/// assert_eq!(r.1, Link::Label2Dest(Cow::from("label1"), Cow::from("destination1"), Cow::from("title1")));
152/// ```
153/// # reStructuredText
154///
155/// ```
156/// use parse_hyperlinks::parser::Link;
157/// use parse_hyperlinks::parser::parse::take_link;
158/// use std::borrow::Cow;
159///
160/// let i = r#"abc
161/// abc `text0 <destination0>`_abc
162/// abc `text1 <destination1>`__abc
163/// abc `text2 <label2_>`_abc
164/// abc text3__ abc
165/// .. _label1: destination1
166/// .. __: destination3
167/// __ destination4
168/// "#;
169///
170/// let (i, r) = take_link(i).unwrap();
171/// assert_eq!(r.0, "abc\nabc ");
172/// assert_eq!(r.1, Link::TextLabel2Dest(Cow::from("text0"), Cow::from("destination0"), Cow::from("")));
173/// let (i, r) = take_link(i).unwrap();
174/// assert_eq!(r.1, Link::Text2Dest(Cow::from("text1"), Cow::from("destination1"), Cow::from("")));
175/// let (i, r) = take_link(i).unwrap();
176/// assert_eq!(r.1, Link::Text2Label(Cow::from("text2"), Cow::from("label2")));
177/// let (i, r) = take_link(i).unwrap();
178/// assert_eq!(r.1, Link::Text2Label(Cow::from("text3"), Cow::from("_")));
179/// let (i, r) = take_link(i).unwrap();
180/// assert_eq!(r.1, Link::Label2Dest(Cow::from("label1"), Cow::from("destination1"), Cow::from("")));
181/// let (i, r) = take_link(i).unwrap();
182/// assert_eq!(r.1, Link::Label2Dest(Cow::from("_"), Cow::from("destination3"), Cow::from("")));
183/// let (i, r) = take_link(i).unwrap();
184/// assert_eq!(r.1, Link::Label2Dest(Cow::from("_"), Cow::from("destination4"), Cow::from("")));
185/// ```
186/// # Asciidoc
187///
188/// ```
189/// use parse_hyperlinks::parser::Link;
190/// use parse_hyperlinks::parser::parse::take_link;
191/// use std::borrow::Cow;
192///
193/// let i = r#"abc
194/// abc https://destination0[text0]abc
195/// abc link:https://destination1[text1]abc
196/// abc{label2}[text2]abc
197/// abc{label3}abc
198/// :label4: https://destination4
199/// "#;
200///
201/// let (i, r) = take_link(i).unwrap();
202/// assert_eq!(r.0, "abc\nabc ");
203/// assert_eq!(r.1, Link::Text2Dest(Cow::from("text0"), Cow::from("https://destination0"), Cow::from("")));
204/// let (i, r) = take_link(i).unwrap();
205/// assert_eq!(r.1, Link::Text2Dest(Cow::from("text1"), Cow::from("https://destination1"), Cow::from("")));
206/// let (i, r) = take_link(i).unwrap();
207/// assert_eq!(r.1, Link::Text2Label(Cow::from("text2"), Cow::from("label2")));
208/// let (i, r) = take_link(i).unwrap();
209/// assert_eq!(r.1, Link::Text2Label(Cow::from(""), Cow::from("label3")));
210/// let (i, r) = take_link(i).unwrap();
211/// assert_eq!(r.1, Link::Label2Dest(Cow::from("label4"), Cow::from("https://destination4"), Cow::from("")));
212/// ```
213///
214/// # HTML
215///
216/// ```
217/// use parse_hyperlinks::parser::Link;
218/// use parse_hyperlinks::parser::parse::take_link;
219/// use std::borrow::Cow;
220///
221/// let i = r#"abc<a href="destination1" title="title1">text1</a>abc
222/// abc<a href="destination2" title="title2">text2</a>abc
223/// "#;
224///
225/// let (i, r) = take_link(i).unwrap();
226/// assert_eq!(r.0, "abc");
227/// assert_eq!(r.1, Link::Text2Dest(Cow::from("text1"), Cow::from("destination1"), Cow::from("title1")));
228/// let (i, r) = take_link(i).unwrap();
229/// assert_eq!(r.0, "abc\nabc");
230/// assert_eq!(r.1, Link::Text2Dest(Cow::from("text2"), Cow::from("destination2"), Cow::from("title2")));
231/// ```
232pub fn take_link(i: &str) -> nom::IResult<&str, (&str, Link)> {
233    let mut j = i;
234    let mut skip_count = 0;
235    let mut input_start = true;
236    let mut line_start;
237    let mut whitespace;
238    let res = loop {
239        // Are we on a new line character? consume it.
240        line_start = false;
241        // Does never fail.
242        let (k, count) = nom::multi::many0_count(nom::character::complete::newline).parse(j)?;
243        debug_assert_eq!(j.len() - k.len(), count);
244        if count > 0 {
245            skip_count += j.len() - k.len();
246            j = k;
247            line_start = true;
248        };
249
250        // Are we at the beginning of a line?
251        if line_start || input_start {
252            if let Ok((k, r)) = alt((
253                // Now we search for `label2*`.
254                // For both parser is the indent meaningful. We mustn't consume them.
255                rst_label2label_link,
256                rst_label2dest_link,
257            ))
258            .parse(j)
259            {
260                break (k, r);
261            };
262        };
263
264        // Are we on a whitespace? Now consume them.
265        whitespace = false;
266        if let (k, Some(_)) = nom::combinator::opt(nom::character::complete::space1).parse(j)? {
267            skip_count += j.len() - k.len();
268            j = k;
269            whitespace = true;
270        }
271
272        // Are we at the beginning of a line?
273        if line_start || input_start {
274            if let Ok((k, r)) = alt((
275                // Now we search for `label2*`.
276                // These parsers do not care about the indent, as long it is
277                // only whitespace.
278                md_label2dest_link,
279                adoc_label2dest_link,
280            ))
281            .parse(j)
282            {
283                break (k, r);
284            };
285        };
286        // Start searching for links.
287
288        // Regular `text` links can start everywhere.
289        if let Ok((k, r)) = alt((
290            // Start with `text2dest`.
291            md_img_link,
292            md_img2dest_link,
293            md_text2dest_link,
294            // This should be first, because it is very specific.
295            wikitext_text2dest_link,
296            // `rst_text2dest` must be always placed before `rst_text2label`.
297            rst_text2dest_link,
298            rst_text_label2dest_link,
299            adoc_text2label_link,
300            html_img_link,
301            html_img2dest_link,
302            html_text2dest_link,
303        ))
304        .parse(j)
305        {
306            break (k, r);
307        };
308
309        if whitespace || line_start || input_start {
310            // There must be at least one more byte. If it is one of `([<'"`, skip it.
311            let k = if let (k, Some(_)) =
312                nom::combinator::opt(nom::character::complete::one_of("([<'\"")).parse(j)?
313            {
314                // Skip that char.
315                k
316            } else {
317                // Change nothing.
318                j
319            };
320
321            // `rst_text2label` must be always placed after `rst_text2dest`.
322            // `md_text2label` must be always placed after `adoc_text2label` and `adoc_text2dest`,
323            // because the former consumes `[*]`.
324            if let Ok((l, r)) = alt((rst_text2label_link, adoc_text2dest_link)).parse(k) {
325                // If ever we have skipped a char, remember it now.
326                skip_count += j.len() - k.len();
327                break (l, r);
328            };
329        };
330
331        // This parser is so unspecific, that it must be the last.
332        if let Ok((k, r)) = md_text2label_link(j) {
333            break (k, r);
334        };
335
336        // This makes sure that we advance.
337        let (k, _) = anychar(j)?;
338        skip_count += j.len() - k.len();
339        j = k;
340
341        // This might not consume bytes and never fails.
342        let (k, _) = take_till(|c|
343            // After this, we should check for: `md_label2dest`, `rst_label2dest`, `rst_text2label`, `adoc_text2dest`.
344            c == '\n'
345            // After this, possible start for `adoc_text2dest` or `rst_text2label`:
346            || c == ' ' || c == '\t'
347            // These are candidates for `rst_text2label`, `rst_text_label2dest` `rst_text2dest`:
348            || c == '`'
349            // These could be the start of all `md_img` link types.
350            || c == '!'
351            // These could be the start of all `md_*` link types.
352            || c == '['
353            // These could be the start of the `adoc_text2label` link type.
354            || c == '{'
355            // And this could be an HTML hyperlink:
356            || c == '<')(j)?;
357
358        skip_count += j.len() - k.len();
359        j = k;
360        input_start = false;
361    };
362
363    // Before we return `res`, we need to check again for `md_link_ref` and
364    // `rst_link_ref` and consume them silently, without returning their result.
365    // These are only allowed at the beginning of a line and we know here, that
366    // we are not. We have to act now, because the next parser can not tell if
367    // its first byte is at the beginning of the line, because it does not know
368    // if it was called for the first time ore not. By consuming more now, we
369    // make sure that no `md_link_ref` and `rst_link_ref` is mistakenly
370    // recognized in the middle of a line.
371    // It is sufficient to do this check once, because both parser guarantee to
372    // consume the whole line in case of success.
373    let (mut l, link) = res;
374    match link {
375        Link::Label2Dest(_, _, _) | Link::Label2Label(_, _) => {}
376        _ => {
377            // Just consume, the result does not matter.
378            let (m, _) =
379                nom::combinator::opt(alt((rst_label2dest_link, md_label2dest_link))).parse(l)?;
380            l = m;
381        }
382    };
383
384    let skipped_input = &i[0..skip_count];
385
386    Ok((l, (skipped_input, link)))
387}
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392
393    #[test]
394    fn test_take_link() {
395        let expected = nom::Err::Error(nom::error::Error::new("", nom::error::ErrorKind::Eof));
396        let err = take_link("").unwrap_err();
397        assert_eq!(err, expected);
398
399        let i = r#"[md label1]: md_destination1 "md title1"
400abc [md text2](md_destination2 "md title2")[md text3]: abc[md text4]: abc
401   [md label5]: md_destination5 "md title5"
402abc `rst text1 <rst_destination1>`__abc
403abc `rst text2 <rst_label2_>`_ .. _norst: no .. _norst: no
404.. _rst label3: rst_destination3
405  .. _rst label4: rst_d
406     estination4
407__ rst_label5_
408abc `rst text_label6 <rst_destination6>`_abc
409<a href="html_destination1"
410   title="html title1">html text1</a>
411abc https://adoc_destination1[adoc text1] abc
412abc {adoc-label2}abc {adoc-label3}[adoc text3]abc
413 :adoc-label4: https://adoc_destination4
414abc{adoc-label5}abc https://adoc_destination6 abc
415abc[https://wikitext.link Wikitext Testlink]abc
416"#;
417
418        let expected = Link::Label2Dest(
419            Cow::from("md label1"),
420            Cow::from("md_destination1"),
421            Cow::from("md title1"),
422        );
423        let (i, (_, res)) = take_link(i).unwrap();
424        assert_eq!(res, expected);
425
426        let expected = Link::Text2Dest(
427            Cow::from("md text2"),
428            Cow::from("md_destination2"),
429            Cow::from("md title2"),
430        );
431        let (i, (skipped, res)) = take_link(i).unwrap();
432        assert_eq!(skipped, "\nabc ");
433        assert_eq!(res, expected);
434
435        let expected = Link::Text2Label(Cow::from("md text3"), Cow::from("md text3"));
436        let (i, (_, res)) = take_link(i).unwrap();
437        assert_eq!(res, expected);
438
439        let expected = Link::Text2Label(Cow::from("md text4"), Cow::from("md text4"));
440        let (i, (_, res)) = take_link(i).unwrap();
441        assert_eq!(res, expected);
442
443        let expected = Link::Label2Dest(
444            Cow::from("md label5"),
445            Cow::from("md_destination5"),
446            Cow::from("md title5"),
447        );
448        let (i, (_, res)) = take_link(i).unwrap();
449        assert_eq!(res, expected);
450
451        let expected = Link::Text2Dest(
452            Cow::from("rst text1"),
453            Cow::from("rst_destination1"),
454            Cow::from(""),
455        );
456        let (i, (_, res)) = take_link(i).unwrap();
457        assert_eq!(res, expected);
458
459        let expected = Link::Text2Label(Cow::from("rst text2"), Cow::from("rst_label2"));
460        let (i, (_, res)) = take_link(i).unwrap();
461        assert_eq!(res, expected);
462
463        let expected = Link::Label2Dest(
464            Cow::from("rst label3"),
465            Cow::from("rst_destination3"),
466            Cow::from(""),
467        );
468        let (i, (_, res)) = take_link(i).unwrap();
469        assert_eq!(res, expected);
470
471        let expected = Link::Label2Dest(
472            Cow::from("rst label4"),
473            Cow::from("rst_destination4"),
474            Cow::from(""),
475        );
476        let (i, (_, res)) = take_link(i).unwrap();
477        assert_eq!(res, expected);
478
479        let expected = Link::Label2Label(Cow::from("_"), Cow::from("rst_label5"));
480        let (i, (_, res)) = take_link(i).unwrap();
481        assert_eq!(res, expected);
482
483        let expected = Link::TextLabel2Dest(
484            Cow::from("rst text_label6"),
485            Cow::from("rst_destination6"),
486            Cow::from(""),
487        );
488        let (i, (_, res)) = take_link(i).unwrap();
489        assert_eq!(res, expected);
490
491        let expected = Link::Text2Dest(
492            Cow::from("html text1"),
493            Cow::from("html_destination1"),
494            Cow::from("html title1"),
495        );
496        let (i, (_, res)) = take_link(i).unwrap();
497        assert_eq!(res, expected);
498
499        let expected = Link::Text2Dest(
500            Cow::from("adoc text1"),
501            Cow::from("https://adoc_destination1"),
502            Cow::from(""),
503        );
504        let (i, (_, res)) = take_link(i).unwrap();
505        assert_eq!(res, expected);
506
507        let expected = Link::Text2Label(Cow::from(""), Cow::from("adoc-label2"));
508        let (i, (_, res)) = take_link(i).unwrap();
509        assert_eq!(res, expected);
510
511        let expected = Link::Text2Label(Cow::from("adoc text3"), Cow::from("adoc-label3"));
512        let (i, (_, res)) = take_link(i).unwrap();
513        assert_eq!(res, expected);
514
515        let expected = Link::Label2Dest(
516            Cow::from("adoc-label4"),
517            Cow::from("https://adoc_destination4"),
518            Cow::from(""),
519        );
520        let (i, (_, res)) = take_link(i).unwrap();
521        assert_eq!(res, expected);
522
523        let expected = Link::Text2Label(Cow::from(""), Cow::from("adoc-label5"));
524        let (i, (skipped, res)) = take_link(i).unwrap();
525        assert_eq!(res, expected);
526        assert_eq!(skipped, "\nabc");
527
528        let expected = Link::Text2Dest(
529            Cow::from("https://adoc_destination6"),
530            Cow::from("https://adoc_destination6"),
531            Cow::from(""),
532        );
533        let (i, (skipped, res)) = take_link(i).unwrap();
534        assert_eq!(res, expected);
535        assert_eq!(skipped, "abc ");
536
537        let expected = Link::Text2Dest(
538            Cow::from("Wikitext Testlink"),
539            Cow::from("https://wikitext.link"),
540            Cow::from(""),
541        );
542        let (_i, (skipped, res)) = take_link(i).unwrap();
543        assert_eq!(res, expected);
544        assert_eq!(skipped, " abc\nabc");
545    }
546
547    #[test]
548    fn test_take_link2() {
549        // New input:
550        // Do we find the same at the input start also?
551        let i = ".. _`My: home page`: http://getreu.net\nabc";
552        let expected = Link::Label2Dest(
553            Cow::from("My: home page"),
554            Cow::from("http://getreu.net"),
555            Cow::from(""),
556        );
557        let (i, (_, res)) = take_link(i).unwrap();
558        assert_eq!(res, expected);
559        assert_eq!(i, "\nabc");
560
561        let i = "https://adoc_link_destination[adoc link text]abc";
562        let expected = Link::Text2Dest(
563            Cow::from("adoc link text"),
564            Cow::from("https://adoc_link_destination"),
565            Cow::from(""),
566        );
567        let (i, (_, res)) = take_link(i).unwrap();
568        assert_eq!(res, expected);
569        assert_eq!(i, "abc");
570    }
571
572    #[test]
573    fn test_take_link3() {
574        let i = r#"   [md label3]: md_destination3 "md title3"
575        [md label1]: md_destination1 "md title1"
576        [md label2]: md_destination2 "md title2"
577        abc[md text_label3]abc[md text_label4]
578        "#;
579
580        let expected = Link::Label2Dest(
581            Cow::from("md label3"),
582            Cow::from("md_destination3"),
583            Cow::from("md title3"),
584        );
585        let (i, (_, res)) = take_link(i).unwrap();
586        assert_eq!(res, expected);
587
588        let expected = Link::Label2Dest(
589            Cow::from("md label1"),
590            Cow::from("md_destination1"),
591            Cow::from("md title1"),
592        );
593        let (i, (_, res)) = take_link(i).unwrap();
594        assert_eq!(res, expected);
595
596        let expected = Link::Label2Dest(
597            Cow::from("md label2"),
598            Cow::from("md_destination2"),
599            Cow::from("md title2"),
600        );
601        let (i, (_, res)) = take_link(i).unwrap();
602        assert_eq!(res, expected);
603
604        let expected = Link::Text2Label(Cow::from("md text_label3"), Cow::from("md text_label3"));
605        let (i, (_, res)) = take_link(i).unwrap();
606        assert_eq!(res, expected);
607
608        let expected = Link::Text2Label(Cow::from("md text_label4"), Cow::from("md text_label4"));
609        let (_i, (_, res)) = take_link(i).unwrap();
610        assert_eq!(res, expected);
611    }
612
613    #[test]
614    fn test_take_link4() {
615        let i = r#"
616.. _label4: label3_
617label2__
618__ label5
619"#;
620
621        let expected = Link::Label2Label(Cow::from("label4"), Cow::from("label3"));
622        let (i, (_, res)) = take_link(i).unwrap();
623        assert_eq!(res, expected);
624
625        let expected = Link::Text2Label(Cow::from("label2"), Cow::from("_"));
626        let (i, (_, res)) = take_link(i).unwrap();
627        assert_eq!(res, expected);
628
629        let expected = Link::Label2Dest(Cow::from("_"), Cow::from("label5"), Cow::from(""));
630        let (_i, (_, res)) = take_link(i).unwrap();
631        assert_eq!(res, expected);
632    }
633
634    #[test]
635    fn test_take_link5() {
636        let i = r#"[http://getreu.net](<http://blog.getreu.net>)abc"
637[http://getreu.net](<http://blog.getreu.net>)def"
638ghi[http://getreu.net](<http://blog.getreu.net>)jkl"#;
639
640        let expected = Link::Text2Dest(
641            Cow::from("http://getreu.net"),
642            Cow::from("http://blog.getreu.net"),
643            Cow::from(""),
644        );
645        let (i, (_, res)) = take_link(i).unwrap();
646        assert_eq!(res, expected);
647
648        let (_i, (_, res)) = take_link(i).unwrap();
649        assert_eq!(res, expected);
650
651        let (_i, (_, res)) = take_link(i).unwrap();
652        assert_eq!(res, expected);
653    }
654
655    #[test]
656    fn test_take_link6() {
657        let i = r#"abc<http://getreu%20Ü.net>def
658"#;
659
660        let expected = Link::Text2Dest(
661            Cow::from("http://getreu Ü.net"),
662            Cow::from("http://getreu Ü.net"),
663            Cow::from(""),
664        );
665        let (_i, (_, res)) = take_link(i).unwrap();
666        assert_eq!(res, expected);
667    }
668
669    #[test]
670    fn test_take_link7() {
671        let i = "abc[def![alt](img.png)ghi](doc.md \"title\")abc\
672            abc![alt2](dest2)abc";
673
674        let expected = Link::Image2Dest(
675            Cow::from("def"),
676            Cow::from("alt"),
677            Cow::from("img.png"),
678            Cow::from("ghi"),
679            Cow::from("doc.md"),
680            Cow::from("title"),
681        );
682        let (i, (_, res)) = take_link(i).unwrap();
683        assert_eq!(res, expected);
684
685        let expected = Link::Image(Cow::from("alt2"), Cow::from("dest2"));
686        let (i, (skipped, res)) = take_link(i).unwrap();
687        assert_eq!(res, expected);
688        assert_eq!(skipped, "abcabc");
689        assert_eq!(i, "abc");
690    }
691
692    #[test]
693    fn test_take_link8() {
694        let i = "[into_bytes](https://doc.rust-lang.org/)";
695
696        let expected = Link::Text2Dest(
697            Cow::from("into_bytes"),
698            Cow::from("https://doc.rust-lang.org/"),
699            Cow::from(""),
700        );
701        let (i, (skipped, res)) = take_link(i).unwrap();
702        assert_eq!(i, "");
703        assert_eq!(skipped, "");
704        assert_eq!(res, expected);
705
706        //
707        let i = "[into\\_bytes](https://doc.rust-lang.org/)";
708
709        let expected = Link::Text2Dest(
710            Cow::from("into_bytes"),
711            Cow::from("https://doc.rust-lang.org/"),
712            Cow::from(""),
713        );
714        let (i, (skipped, res)) = take_link(i).unwrap();
715        assert_eq!(i, "");
716        assert_eq!(skipped, "");
717        assert_eq!(res, expected);
718    }
719}