markdown_linkify/transform/
docs_rustlang_replacer.rs1use 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 fn apply(&self, link: &mut Link) -> anyhow::Result<()> {
26 let snippet = self
28 .pattern()
29 .replacen(&link.destination, 1, "$i")
30 .to_string();
31 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 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 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 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}