From 499b96b3437a4b20e5d5bde92e03554230dd22e1 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 27 Jun 2024 08:04:28 -0700 Subject: [PATCH 1/7] Move regular expressions to be global statics. This just makes it a little easier to refer to them. --- mdbook-spec/Cargo.lock | 1 + mdbook-spec/Cargo.toml | 1 + mdbook-spec/src/main.rs | 78 ++++++++++++++++++++++------------------- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/mdbook-spec/Cargo.lock b/mdbook-spec/Cargo.lock index d77a440..971edaf 100644 --- a/mdbook-spec/Cargo.lock +++ b/mdbook-spec/Cargo.lock @@ -388,6 +388,7 @@ version = "0.0.0" dependencies = [ "anyhow", "mdbook", + "once_cell", "pathdiff", "regex", "semver", diff --git a/mdbook-spec/Cargo.toml b/mdbook-spec/Cargo.toml index bbfd903..50a4b3d 100644 --- a/mdbook-spec/Cargo.toml +++ b/mdbook-spec/Cargo.toml @@ -8,6 +8,7 @@ license = "MIT OR Apache-2.0" [dependencies] anyhow = "1.0.79" mdbook = { version = "0.4.36", default-features = false } +once_cell = "1.19.0" pathdiff = "0.2.1" regex = "1.10.3" semver = "1.0.21" diff --git a/mdbook-spec/src/main.rs b/mdbook-spec/src/main.rs index 7e617f0..f5506e9 100644 --- a/mdbook-spec/src/main.rs +++ b/mdbook-spec/src/main.rs @@ -2,6 +2,7 @@ use mdbook::book::{Book, Chapter}; use mdbook::errors::Error; use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext}; use mdbook::BookItem; +use once_cell::sync::Lazy; use regex::{Captures, Regex}; use semver::{Version, VersionReq}; use std::collections::BTreeMap; @@ -11,6 +12,40 @@ use std::io::{self, Write as _}; use std::path::PathBuf; use std::process::{self, Command}; +/// The Regex for rules like `r[foo]`. +static RULE_RE: Lazy = Lazy::new(|| Regex::new(r"(?m)^r\[([^]]+)]$").unwrap()); + +/// The Regex for the syntax for blockquotes that have a specific CSS class, +/// like `> [!WARNING]`. +static ADMONITION_RE: Lazy = Lazy::new(|| { + Regex::new(r"(?m)^ *> \[!(?[^]]+)\]\n(?
(?: *> .*\n)+)").unwrap() +}); + +/// A markdown link (without the brackets) that might possibly be a link to +/// the standard library using rustdoc's intra-doc notation. +const STD_LINK: &str = r"(?: [a-z]+@ )? + (?: std|core|alloc|proc_macro|test ) + (?: ::[A-Za-z_!:<>{}()\[\]]+ )?"; + +/// The Regex for a markdown link that might be a link to the standard library. +static STD_LINK_RE: Lazy = Lazy::new(|| { + Regex::new(&format!( + r"(?x) + (?: + ( \[`[^`]+`\] ) \( ({STD_LINK}) \) + ) + | (?: + ( \[`{STD_LINK}`\] ) + ) + " + )) + .unwrap() +}); + +/// The Regex used to extract the std links from the HTML generated by rustdoc. +static STD_LINK_EXTRACT_RE: Lazy = + Lazy::new(|| Regex::new(r#"
  • ]*href="(https://doc.rust-lang.org/[^"]+)""#).unwrap()); + fn main() { let mut args = std::env::args().skip(1); match args.next().as_deref() { @@ -56,41 +91,15 @@ fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> { } struct Spec { + /// Whether or not warnings should be errors (set by SPEC_DENY_WARNINGS + /// environment variable). deny_warnings: bool, - rule_re: Regex, - admonition_re: Regex, - std_link_re: Regex, - std_link_extract_re: Regex, } impl Spec { pub fn new() -> Spec { - // This is roughly a rustdoc intra-doc link definition. - let std_link = r"(?: [a-z]+@ )? - (?: std|core|alloc|proc_macro|test ) - (?: ::[A-Za-z_!:<>{}()\[\]]+ )?"; Spec { deny_warnings: std::env::var("SPEC_DENY_WARNINGS").as_deref() == Ok("1"), - rule_re: Regex::new(r"(?m)^r\[([^]]+)]$").unwrap(), - admonition_re: Regex::new( - r"(?m)^ *> \[!(?[^]]+)\]\n(?
    (?: *> .*\n)+)", - ) - .unwrap(), - std_link_re: Regex::new(&format!( - r"(?x) - (?: - ( \[`[^`]+`\] ) \( ({std_link}) \) - ) - | (?: - ( \[`{std_link}`\] ) - ) - " - )) - .unwrap(), - std_link_extract_re: Regex::new( - r#"
  • ]*href="(https://doc.rust-lang.org/[^"]+)""#, - ) - .unwrap(), } } @@ -103,7 +112,7 @@ impl Spec { ) -> String { let source_path = chapter.source_path.clone().unwrap_or_default(); let path = chapter.path.clone().unwrap_or_default(); - self.rule_re + RULE_RE .replace_all(&chapter.content, |caps: &Captures| { let rule_id = &caps[1]; if let Some((old, _)) = @@ -165,7 +174,7 @@ impl Spec { /// be a CSS class is valid. The actual styling needs to be added in a CSS /// file. fn admonitions(&self, chapter: &Chapter) -> String { - self.admonition_re + ADMONITION_RE .replace_all(&chapter.content, |caps: &Captures| { let lower = caps["admon"].to_lowercase(); format!( @@ -185,8 +194,7 @@ impl Spec { // // links are tuples of ("[`std::foo`]", None) for links without dest, // or ("[`foo`]", "std::foo") with a dest. - let mut links: Vec<_> = self - .std_link_re + let mut links: Vec<_> = STD_LINK_RE .captures_iter(&chapter.content) .map(|cap| { if let Some(no_dest) = cap.get(3) { @@ -250,8 +258,7 @@ impl Spec { // Extract the links from the generated html. let generated = fs::read_to_string(tmp.path().join("doc/a/index.html")).expect("index.html generated"); - let urls: Vec<_> = self - .std_link_extract_re + let urls: Vec<_> = STD_LINK_EXTRACT_RE .captures_iter(&generated) .map(|cap| cap.get(1).unwrap().as_str()) .collect(); @@ -267,8 +274,7 @@ impl Spec { } // Replace any disambiguated links with just the disambiguation. - let mut output = self - .std_link_re + let mut output = STD_LINK_RE .replace_all(&chapter.content, |caps: &Captures| { if let Some(dest) = caps.get(2) { // Replace destination parenthesis with a link definition (square brackets). From 86ecfd879d07ce85aad508e87b655b97a5979dcf Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 27 Jun 2024 08:05:03 -0700 Subject: [PATCH 2/7] Fix std links that have digits in them. --- mdbook-spec/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mdbook-spec/src/main.rs b/mdbook-spec/src/main.rs index f5506e9..69d762b 100644 --- a/mdbook-spec/src/main.rs +++ b/mdbook-spec/src/main.rs @@ -25,7 +25,7 @@ static ADMONITION_RE: Lazy = Lazy::new(|| { /// the standard library using rustdoc's intra-doc notation. const STD_LINK: &str = r"(?: [a-z]+@ )? (?: std|core|alloc|proc_macro|test ) - (?: ::[A-Za-z_!:<>{}()\[\]]+ )?"; + (?: ::[A-Za-z0-9_!:<>{}()\[\]]+ )?"; /// The Regex for a markdown link that might be a link to the standard library. static STD_LINK_RE: Lazy = Lazy::new(|| { From f01512d5053d362be0ec7e3478813e446c8b2344 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 27 Jun 2024 08:06:09 -0700 Subject: [PATCH 3/7] Move std_links to its own module. This code is getting long enough that it will help to organize it separately. --- mdbook-spec/src/main.rs | 148 ++--------------------------------- mdbook-spec/src/std_links.rs | 145 ++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 143 deletions(-) create mode 100644 mdbook-spec/src/std_links.rs diff --git a/mdbook-spec/src/main.rs b/mdbook-spec/src/main.rs index 69d762b..a12e95c 100644 --- a/mdbook-spec/src/main.rs +++ b/mdbook-spec/src/main.rs @@ -6,11 +6,11 @@ use once_cell::sync::Lazy; use regex::{Captures, Regex}; use semver::{Version, VersionReq}; use std::collections::BTreeMap; -use std::fmt::Write as _; -use std::fs; -use std::io::{self, Write as _}; +use std::io; use std::path::PathBuf; -use std::process::{self, Command}; +use std::process; + +mod std_links; /// The Regex for rules like `r[foo]`. static RULE_RE: Lazy = Lazy::new(|| Regex::new(r"(?m)^r\[([^]]+)]$").unwrap()); @@ -21,31 +21,6 @@ static ADMONITION_RE: Lazy = Lazy::new(|| { Regex::new(r"(?m)^ *> \[!(?[^]]+)\]\n(?
    (?: *> .*\n)+)").unwrap() }); -/// A markdown link (without the brackets) that might possibly be a link to -/// the standard library using rustdoc's intra-doc notation. -const STD_LINK: &str = r"(?: [a-z]+@ )? - (?: std|core|alloc|proc_macro|test ) - (?: ::[A-Za-z0-9_!:<>{}()\[\]]+ )?"; - -/// The Regex for a markdown link that might be a link to the standard library. -static STD_LINK_RE: Lazy = Lazy::new(|| { - Regex::new(&format!( - r"(?x) - (?: - ( \[`[^`]+`\] ) \( ({STD_LINK}) \) - ) - | (?: - ( \[`{STD_LINK}`\] ) - ) - " - )) - .unwrap() -}); - -/// The Regex used to extract the std links from the HTML generated by rustdoc. -static STD_LINK_EXTRACT_RE: Lazy = - Lazy::new(|| Regex::new(r#"
  • ]*href="(https://doc.rust-lang.org/[^"]+)""#).unwrap()); - fn main() { let mut args = std::env::args().skip(1); match args.next().as_deref() { @@ -184,119 +159,6 @@ impl Spec { }) .to_string() } - - /// Converts links to the standard library to the online documentation in - /// a fashion similar to rustdoc intra-doc links. - fn std_links(&self, chapter: &Chapter) -> String { - // This is very hacky, but should work well enough. - // - // Collect all standard library links. - // - // links are tuples of ("[`std::foo`]", None) for links without dest, - // or ("[`foo`]", "std::foo") with a dest. - let mut links: Vec<_> = STD_LINK_RE - .captures_iter(&chapter.content) - .map(|cap| { - if let Some(no_dest) = cap.get(3) { - (no_dest.as_str(), None) - } else { - ( - cap.get(1).unwrap().as_str(), - Some(cap.get(2).unwrap().as_str()), - ) - } - }) - .collect(); - if links.is_empty() { - return chapter.content.clone(); - } - links.sort(); - links.dedup(); - - // Write a Rust source file to use with rustdoc to generate intra-doc links. - let tmp = tempfile::TempDir::with_prefix("mdbook-spec-").unwrap(); - let src_path = tmp.path().join("a.rs"); - // Allow redundant since there could some in-scope things that are - // technically not necessary, but we don't care about (like - // [`Option`](std::option::Option)). - let mut src = format!( - "#![deny(rustdoc::broken_intra_doc_links)]\n\ - #![allow(rustdoc::redundant_explicit_links)]\n" - ); - for (link, dest) in &links { - write!(src, "//! - {link}").unwrap(); - if let Some(dest) = dest { - write!(src, "({})", dest).unwrap(); - } - src.push('\n'); - } - writeln!( - src, - "extern crate alloc;\n\ - extern crate proc_macro;\n\ - extern crate test;\n" - ) - .unwrap(); - fs::write(&src_path, &src).unwrap(); - let output = Command::new("rustdoc") - .arg("--edition=2021") - .arg(&src_path) - .current_dir(tmp.path()) - .output() - .expect("rustdoc installed"); - if !output.status.success() { - eprintln!( - "error: failed to extract std links ({:?}) in chapter {} ({:?})\n", - output.status, - chapter.name, - chapter.source_path.as_ref().unwrap() - ); - io::stderr().write_all(&output.stderr).unwrap(); - process::exit(1); - } - - // Extract the links from the generated html. - let generated = - fs::read_to_string(tmp.path().join("doc/a/index.html")).expect("index.html generated"); - let urls: Vec<_> = STD_LINK_EXTRACT_RE - .captures_iter(&generated) - .map(|cap| cap.get(1).unwrap().as_str()) - .collect(); - if urls.len() != links.len() { - eprintln!( - "error: expected rustdoc to generate {} links, but found {} in chapter {} ({:?})", - links.len(), - urls.len(), - chapter.name, - chapter.source_path.as_ref().unwrap() - ); - process::exit(1); - } - - // Replace any disambiguated links with just the disambiguation. - let mut output = STD_LINK_RE - .replace_all(&chapter.content, |caps: &Captures| { - if let Some(dest) = caps.get(2) { - // Replace destination parenthesis with a link definition (square brackets). - format!("{}[{}]", &caps[1], dest.as_str()) - } else { - caps[0].to_string() - } - }) - .to_string(); - - // Append the link definitions to the bottom of the chapter. - write!(output, "\n").unwrap(); - for ((link, dest), url) in links.iter().zip(urls) { - if let Some(dest) = dest { - write!(output, "[{dest}]: {url}\n").unwrap(); - } else { - write!(output, "{link}: {url}\n").unwrap(); - } - } - - output - } } impl Preprocessor for Spec { @@ -315,7 +177,7 @@ impl Preprocessor for Spec { } ch.content = self.rule_definitions(&ch, &mut found_rules); ch.content = self.admonitions(&ch); - ch.content = self.std_links(&ch); + ch.content = std_links::std_links(&ch); } for section in &mut book.sections { let BookItem::Chapter(ch) = section else { diff --git a/mdbook-spec/src/std_links.rs b/mdbook-spec/src/std_links.rs new file mode 100644 index 0000000..e0bc16c --- /dev/null +++ b/mdbook-spec/src/std_links.rs @@ -0,0 +1,145 @@ +use mdbook::book::Chapter; +use once_cell::sync::Lazy; +use regex::{Captures, Regex}; +use std::fmt::Write as _; +use std::fs; +use std::io::{self, Write as _}; +use std::process::{self, Command}; + +/// A markdown link (without the brackets) that might possibly be a link to +/// the standard library using rustdoc's intra-doc notation. +const STD_LINK: &str = r"(?: [a-z]+@ )? + (?: std|core|alloc|proc_macro|test ) + (?: ::[A-Za-z0-9_!:<>{}()\[\]]+ )?"; + +/// The Regex for a markdown link that might be a link to the standard library. +static STD_LINK_RE: Lazy = Lazy::new(|| { + Regex::new(&format!( + r"(?x) + (?: + ( \[`[^`]+`\] ) \( ({STD_LINK}) \) + ) + | (?: + ( \[`{STD_LINK}`\] ) + ) + " + )) + .unwrap() +}); + +/// The Regex used to extract the std links from the HTML generated by rustdoc. +static STD_LINK_EXTRACT_RE: Lazy = + Lazy::new(|| Regex::new(r#"
  • ]*href="(https://doc.rust-lang.org/[^"]+)""#).unwrap()); + +/// Converts links to the standard library to the online documentation in a +/// fashion similar to rustdoc intra-doc links. +pub fn std_links(chapter: &Chapter) -> String { + // This is very hacky, but should work well enough. + // + // Collect all standard library links. + // + // links are tuples of ("[`std::foo`]", None) for links without dest, + // or ("[`foo`]", "std::foo") with a dest. + let mut links: Vec<_> = STD_LINK_RE + .captures_iter(&chapter.content) + .map(|cap| { + if let Some(no_dest) = cap.get(3) { + (no_dest.as_str(), None) + } else { + ( + cap.get(1).unwrap().as_str(), + Some(cap.get(2).unwrap().as_str()), + ) + } + }) + .collect(); + if links.is_empty() { + return chapter.content.clone(); + } + links.sort(); + links.dedup(); + + // Write a Rust source file to use with rustdoc to generate intra-doc links. + let tmp = tempfile::TempDir::with_prefix("mdbook-spec-").unwrap(); + let src_path = tmp.path().join("a.rs"); + // Allow redundant since there could some in-scope things that are + // technically not necessary, but we don't care about (like + // [`Option`](std::option::Option)). + let mut src = format!( + "#![deny(rustdoc::broken_intra_doc_links)]\n\ + #![allow(rustdoc::redundant_explicit_links)]\n" + ); + for (link, dest) in &links { + write!(src, "//! - {link}").unwrap(); + if let Some(dest) = dest { + write!(src, "({})", dest).unwrap(); + } + src.push('\n'); + } + writeln!( + src, + "extern crate alloc;\n\ + extern crate proc_macro;\n\ + extern crate test;\n" + ) + .unwrap(); + fs::write(&src_path, &src).unwrap(); + let output = Command::new("rustdoc") + .arg("--edition=2021") + .arg(&src_path) + .current_dir(tmp.path()) + .output() + .expect("rustdoc installed"); + if !output.status.success() { + eprintln!( + "error: failed to extract std links ({:?}) in chapter {} ({:?})\n", + output.status, + chapter.name, + chapter.source_path.as_ref().unwrap() + ); + io::stderr().write_all(&output.stderr).unwrap(); + process::exit(1); + } + + // Extract the links from the generated html. + let generated = + fs::read_to_string(tmp.path().join("doc/a/index.html")).expect("index.html generated"); + let urls: Vec<_> = STD_LINK_EXTRACT_RE + .captures_iter(&generated) + .map(|cap| cap.get(1).unwrap().as_str()) + .collect(); + if urls.len() != links.len() { + eprintln!( + "error: expected rustdoc to generate {} links, but found {} in chapter {} ({:?})", + links.len(), + urls.len(), + chapter.name, + chapter.source_path.as_ref().unwrap() + ); + process::exit(1); + } + + // Replace any disambiguated links with just the disambiguation. + let mut output = STD_LINK_RE + .replace_all(&chapter.content, |caps: &Captures| { + if let Some(dest) = caps.get(2) { + // Replace destination parenthesis with a link definition (square brackets). + format!("{}[{}]", &caps[1], dest.as_str()) + } else { + caps[0].to_string() + } + }) + .to_string(); + + // Append the link definitions to the bottom of the chapter. + write!(output, "\n").unwrap(); + for ((link, dest), url) in links.iter().zip(urls) { + if let Some(dest) = dest { + write!(output, "[{dest}]: {url}\n").unwrap(); + } else { + write!(output, "{link}: {url}\n").unwrap(); + } + } + + output +} From cbe568e0317317e550970412a2aa8489e12530e7 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 27 Jun 2024 08:11:46 -0700 Subject: [PATCH 4/7] Split std_links into multiple functions. The std_links function was getting a little long, this should help organize a little. --- mdbook-spec/src/std_links.rs | 124 ++++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 52 deletions(-) diff --git a/mdbook-spec/src/std_links.rs b/mdbook-spec/src/std_links.rs index e0bc16c..1d38142 100644 --- a/mdbook-spec/src/std_links.rs +++ b/mdbook-spec/src/std_links.rs @@ -5,6 +5,7 @@ use std::fmt::Write as _; use std::fs; use std::io::{self, Write as _}; use std::process::{self, Command}; +use tempfile::TempDir; /// A markdown link (without the brackets) that might possibly be a link to /// the standard library using rustdoc's intra-doc notation. @@ -34,12 +35,65 @@ static STD_LINK_EXTRACT_RE: Lazy = /// Converts links to the standard library to the online documentation in a /// fashion similar to rustdoc intra-doc links. pub fn std_links(chapter: &Chapter) -> String { - // This is very hacky, but should work well enough. - // - // Collect all standard library links. - // - // links are tuples of ("[`std::foo`]", None) for links without dest, - // or ("[`foo`]", "std::foo") with a dest. + let links = collect_markdown_links(chapter); + if links.is_empty() { + return chapter.content.clone(); + } + + // Write a Rust source file to use with rustdoc to generate intra-doc links. + let tmp = TempDir::with_prefix("mdbook-spec-").unwrap(); + run_rustdoc(&tmp, &links, &chapter); + + // Extract the links from the generated html. + let generated = + fs::read_to_string(tmp.path().join("doc/a/index.html")).expect("index.html generated"); + let urls: Vec<_> = STD_LINK_EXTRACT_RE + .captures_iter(&generated) + .map(|cap| cap.get(1).unwrap().as_str()) + .collect(); + if urls.len() != links.len() { + eprintln!( + "error: expected rustdoc to generate {} links, but found {} in chapter {} ({:?})", + links.len(), + urls.len(), + chapter.name, + chapter.source_path.as_ref().unwrap() + ); + process::exit(1); + } + + // Replace any disambiguated links with just the disambiguation. + let mut output = STD_LINK_RE + .replace_all(&chapter.content, |caps: &Captures| { + if let Some(dest) = caps.get(2) { + // Replace destination parenthesis with a link definition (square brackets). + format!("{}[{}]", &caps[1], dest.as_str()) + } else { + caps[0].to_string() + } + }) + .to_string(); + + // Append the link definitions to the bottom of the chapter. + write!(output, "\n").unwrap(); + for ((link, dest), url) in links.iter().zip(urls) { + if let Some(dest) = dest { + write!(output, "[{dest}]: {url}\n").unwrap(); + } else { + write!(output, "{link}: {url}\n").unwrap(); + } + } + + output +} + +/// Collects all markdown links. +/// +/// Returns a `Vec` of `(link, Option)` where markdown text like +/// ``[`std::fmt`]`` would return that as a link. The dest is optional, for +/// example ``[`Option`](std::option::Option)`` would have the part in +/// parentheses as the dest. +fn collect_markdown_links(chapter: &Chapter) -> Vec<(&str, Option<&str>)> { let mut links: Vec<_> = STD_LINK_RE .captures_iter(&chapter.content) .map(|cap| { @@ -54,13 +108,21 @@ pub fn std_links(chapter: &Chapter) -> String { }) .collect(); if links.is_empty() { - return chapter.content.clone(); + return vec![]; } links.sort(); links.dedup(); + links +} - // Write a Rust source file to use with rustdoc to generate intra-doc links. - let tmp = tempfile::TempDir::with_prefix("mdbook-spec-").unwrap(); +/// Generates links using rustdoc. +/// +/// This takes the given links and creates a temporary Rust source file +/// containing those links within doc-comments, and then runs rustdoc to +/// generate intra-doc links on them. +/// +/// The output will be in the given `tmp` directory. +fn run_rustdoc(tmp: &TempDir, links: &[(&str, Option<&str>)], chapter: &Chapter) { let src_path = tmp.path().join("a.rs"); // Allow redundant since there could some in-scope things that are // technically not necessary, but we don't care about (like @@ -69,7 +131,7 @@ pub fn std_links(chapter: &Chapter) -> String { "#![deny(rustdoc::broken_intra_doc_links)]\n\ #![allow(rustdoc::redundant_explicit_links)]\n" ); - for (link, dest) in &links { + for (link, dest) in links { write!(src, "//! - {link}").unwrap(); if let Some(dest) = dest { write!(src, "({})", dest).unwrap(); @@ -100,46 +162,4 @@ pub fn std_links(chapter: &Chapter) -> String { io::stderr().write_all(&output.stderr).unwrap(); process::exit(1); } - - // Extract the links from the generated html. - let generated = - fs::read_to_string(tmp.path().join("doc/a/index.html")).expect("index.html generated"); - let urls: Vec<_> = STD_LINK_EXTRACT_RE - .captures_iter(&generated) - .map(|cap| cap.get(1).unwrap().as_str()) - .collect(); - if urls.len() != links.len() { - eprintln!( - "error: expected rustdoc to generate {} links, but found {} in chapter {} ({:?})", - links.len(), - urls.len(), - chapter.name, - chapter.source_path.as_ref().unwrap() - ); - process::exit(1); - } - - // Replace any disambiguated links with just the disambiguation. - let mut output = STD_LINK_RE - .replace_all(&chapter.content, |caps: &Captures| { - if let Some(dest) = caps.get(2) { - // Replace destination parenthesis with a link definition (square brackets). - format!("{}[{}]", &caps[1], dest.as_str()) - } else { - caps[0].to_string() - } - }) - .to_string(); - - // Append the link definitions to the bottom of the chapter. - write!(output, "\n").unwrap(); - for ((link, dest), url) in links.iter().zip(urls) { - if let Some(dest) = dest { - write!(output, "[{dest}]: {url}\n").unwrap(); - } else { - write!(output, "{link}: {url}\n").unwrap(); - } - } - - output } From feae5f97b7c71781e2cdfa78ac80168e27949728 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 27 Jun 2024 08:15:41 -0700 Subject: [PATCH 5/7] Don't override explicit link definitions. If the author has an explicit link definition, don't allow std_links to override it. --- mdbook-spec/src/std_links.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/mdbook-spec/src/std_links.rs b/mdbook-spec/src/std_links.rs index 1d38142..d3f1c2a 100644 --- a/mdbook-spec/src/std_links.rs +++ b/mdbook-spec/src/std_links.rs @@ -1,6 +1,7 @@ use mdbook::book::Chapter; use once_cell::sync::Lazy; use regex::{Captures, Regex}; +use std::collections::HashSet; use std::fmt::Write as _; use std::fs; use std::io::{self, Write as _}; @@ -32,6 +33,15 @@ static STD_LINK_RE: Lazy = Lazy::new(|| { static STD_LINK_EXTRACT_RE: Lazy = Lazy::new(|| Regex::new(r#"
  • ]*href="(https://doc.rust-lang.org/[^"]+)""#).unwrap()); +/// The Regex for a markdown link definition. +static LINK_DEF_RE: Lazy = Lazy::new(|| { + // This is a pretty lousy regex for a link definition. It doesn't + // handle things like blockquotes, code blocks, etc. Using a + // markdown parser isn't really feasible here, it would be nice to + // improve this. + Regex::new(r#"(?m)^(?