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[defghi](doc.md \"title\")abc\
672 abcabc";
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}