markdown_linkify/transform/
docsrs_replacer.rs

1use crate::link::Link;
2use crate::LinkTransformer;
3use anyhow::Context;
4use pulldown_cmark::Event;
5use select::document::Document;
6use select::predicate::Name;
7
8#[derive(Debug, Clone, Default)]
9pub struct Docsrs {
10    client: reqwest::blocking::Client,
11}
12
13impl Docsrs {
14    pub fn new() -> Self {
15        Self::default()
16    }
17}
18
19impl LinkTransformer for Docsrs {
20    fn tag(&self) -> String {
21        String::from("docsrs:")
22    }
23
24    /// Access the constructed page, then get its html title.
25    fn apply(&self, link: &mut Link) -> anyhow::Result<()> {
26        let url = self
27            .pattern()
28            .replacen(&link.destination, 1, "$i")
29            .to_string();
30        let page = self
31            .client
32            .get(&url)
33            .send()
34            .with_context(|| format!("Failed to access {url}"))?;
35        let doc = Document::from(
36            page.text()
37                .with_context(|| format!("Failed to parse document at {url}"))?
38                .as_str(),
39        );
40        let title = doc
41            .find(Name("title"))
42            .next()
43            .with_context(|| format!("Failed to get title of {url}"))?;
44        let name = title
45            .first_child()
46            .context("First child of title node not found")?
47            .as_text()
48            .context("No title set")?
49            .split_whitespace()
50            .next()
51            .context("Can't split first word of title")?
52            .to_string();
53
54        link.destination = url.to_string().into();
55        if link.title.is_empty() {
56            link.title = url.into();
57        }
58        if link.text.is_empty() {
59            link.text.push(Event::Code(name.into()));
60        }
61        Ok(())
62    }
63}