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