diff --git a/mdbook-spec/src/lib.rs b/mdbook-spec/src/lib.rs index 9eac0e4ed..7b06704f4 100644 --- a/mdbook-spec/src/lib.rs +++ b/mdbook-spec/src/lib.rs @@ -24,6 +24,10 @@ static ADMONITION_RE: Lazy<Regex> = Lazy::new(|| { Regex::new(r"(?m)^ *> \[!(?<admon>[^]]+)\]\n(?<blockquote>(?: *>.*\n)+)").unwrap() }); +/// A primitive regex to find link reference definitions. +static MD_LINK_REFERENCE_DEFINITION: Lazy<Regex> = + Lazy::new(|| Regex::new(r"(?m)^\[(?<label>[^]]+)]: (?<dest>.*)").unwrap()); + pub fn handle_preprocessing() -> Result<(), Error> { let pre = Spec::new(None)?; let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?; @@ -114,6 +118,34 @@ impl Spec { Ok(Spec { rust_root }) } + /// Converts link reference definitions that point to a rule to the correct link. + /// + /// For example: + /// ```markdown + /// See [this rule]. + /// + /// [this rule]: expr.array + /// ``` + /// + /// This will convert the `[this rule]` definition to point to the actual link. + fn rule_link_references(&self, chapter: &Chapter, rules: &Rules) -> String { + let current_path = chapter.path.as_ref().unwrap().parent().unwrap(); + MD_LINK_REFERENCE_DEFINITION + .replace_all(&chapter.content, |caps: &Captures<'_>| { + let dest = &caps["dest"]; + if let Some((_source_path, path)) = rules.def_paths.get(dest) { + let label = &caps["label"]; + let relative = pathdiff::diff_paths(path, current_path).unwrap(); + // Adjust paths for Windows. + let relative = relative.display().to_string().replace('\\', "/"); + format!("[{label}]: {relative}#r-{dest}") + } else { + caps.get(0).unwrap().as_str().to_string() + } + }) + .to_string() + } + /// Generates link references to all rules on all pages, so you can easily /// refer to rules anywhere in the book. fn auto_link_references(&self, chapter: &Chapter, rules: &Rules) -> String { @@ -255,6 +287,7 @@ impl Preprocessor for Spec { return; } ch.content = self.admonitions(&ch, &mut diag); + ch.content = self.rule_link_references(&ch, &rules); ch.content = self.auto_link_references(&ch, &rules); ch.content = self.render_rule_definitions(&ch.content, &tests, &git_ref); if ch.name == "Test summary" { diff --git a/src/introduction.md b/src/introduction.md index 88666a236..d80b8e9f5 100644 --- a/src/introduction.md +++ b/src/introduction.md @@ -116,7 +116,7 @@ These conventions are documented here. See [Notation] for more detail. r[example.rule.label] -* Rule identifiers appear before each language rule enclosed in square brackets. These identifiers provide a way to refer to a specific rule in the language. The rule identifier uses periods to separate sections from most general to most specific ([destructors.scope.nesting.function-body] for example). On narrow screens, the rule name will collapse to display `[*]`. +* Rule identifiers appear before each language rule enclosed in square brackets. These identifiers provide a way to refer to and link to a specific rule in the language ([e.g.][example rule]). The rule identifier uses periods to separate sections from most general to most specific ([destructors.scope.nesting.function-body] for example). On narrow screens, the rule name will collapse to display `[*]`. The rule name can be clicked to link to that rule. @@ -144,6 +144,7 @@ We also want the reference to be as normative as possible, so if you see anythin [_Expression_]: expressions.md [cargo book]: ../cargo/index.html [cargo reference]: ../cargo/reference/index.html +[example rule]: example.rule.label [expressions chapter]: expressions.html [file an issue]: https://github.com/rust-lang/reference/issues [lifetime of temporaries]: expressions.html#temporaries