markdown_linkify/transform/
docs_rustlang_replacer.rs

1use anyhow::Context;
2use pulldown_cmark::Event;
3use regex::Regex;
4use tempfile::TempDir;
5
6use crate::{link::Link, LinkTransformer};
7
8#[derive(Debug, Clone, Default)]
9pub struct DocsRustlang;
10
11impl DocsRustlang {
12    pub fn new() -> Self {
13        Self::default()
14    }
15}
16
17impl LinkTransformer for DocsRustlang {
18    fn tag(&self) -> String {
19        String::from("rust:")
20    }
21
22    /// Generate a barebones rust project with our input text in a doc comment,
23    /// run rustdoc on it,
24    /// parse out the result link from the generated html.
25    fn apply(&self, link: &mut Link) -> anyhow::Result<()> {
26        // Extract item name
27        let snippet = self
28            .pattern()
29            .replacen(&link.destination, 1, "$i")
30            .to_string();
31        // Create temporary directory with rust file using our item in the docs
32        let tmp_dir = TempDir::new()?;
33        let test_file_path = tmp_dir.path().join("snippet.rs");
34        std::fs::write(&test_file_path, format!("//! [{snippet}]"))?;
35
36        // Invoke rustdoc for creating our docs
37        let output = std::process::Command::new("rustdoc")
38            .arg("-Z")
39            .arg("unstable-options")
40            .arg("--extern-html-root-url")
41            .arg("core=https://doc.rust-lang.org/stable/")
42            .arg("--extern-html-root-url")
43            .arg("alloc=https://doc.rust-lang.org/stable/")
44            .arg("--extern-html-root-url")
45            .arg("std=https://doc.rust-lang.org/stable/")
46            .arg("--extern-html-root-takes-precedence")
47            .arg("--out-dir")
48            .arg(tmp_dir.path())
49            .arg(test_file_path)
50            .spawn()
51            .expect("Failed to spawn rustdoc")
52            .wait()
53            .expect("Failed awaiting rustdoc result");
54
55        if !output.success() {
56            eprintln!("Warning: Rustdoc exited with error {output:?}");
57        }
58
59        // Read generated html
60        let result_file_path = tmp_dir.path().join("snippet").join("index.html");
61        let html = std::fs::read_to_string(result_file_path)?;
62
63        // Find URL in generated html
64        let regex = Regex::new(r#"(?<l>https://doc.rust-lang.org/[^"]+)""#).expect("Invalid regex");
65        let (_full, [url]) = regex
66            .captures(&html)
67            .with_context(|| format!("No captures found for {snippet}"))?
68            .extract();
69
70        link.destination = url.to_string().into();
71        if link.title.is_empty() {
72            link.title = url.to_string().into();
73        }
74        if link.text.is_empty() {
75            link.text = vec![Event::Code(snippet.into())];
76        }
77        Ok(())
78    }
79}