@@ -2,14 +2,24 @@ use mdbook::book::{Book, Chapter};
22use mdbook:: errors:: Error ;
33use mdbook:: preprocess:: { CmdPreprocessor , Preprocessor , PreprocessorContext } ;
44use mdbook:: BookItem ;
5+ use once_cell:: sync:: Lazy ;
56use regex:: { Captures , Regex } ;
67use semver:: { Version , VersionReq } ;
78use std:: collections:: BTreeMap ;
8- use std:: fmt:: Write as _;
9- use std:: fs;
10- use std:: io:: { self , Write as _} ;
9+ use std:: io;
1110use std:: path:: PathBuf ;
12- use std:: process:: { self , Command } ;
11+ use std:: process;
12+
13+ mod std_links;
14+
15+ /// The Regex for rules like `r[foo]`.
16+ static RULE_RE : Lazy < Regex > = Lazy :: new ( || Regex :: new ( r"(?m)^r\[([^]]+)]$" ) . unwrap ( ) ) ;
17+
18+ /// The Regex for the syntax for blockquotes that have a specific CSS class,
19+ /// like `> [!WARNING]`.
20+ static ADMONITION_RE : Lazy < Regex > = Lazy :: new ( || {
21+ Regex :: new ( r"(?m)^ *> \[!(?<admon>[^]]+)\]\n(?<blockquote>(?: *> .*\n)+)" ) . unwrap ( )
22+ } ) ;
1323
1424fn main ( ) {
1525 let mut args = std:: env:: args ( ) . skip ( 1 ) ;
@@ -56,41 +66,15 @@ fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
5666}
5767
5868struct Spec {
69+ /// Whether or not warnings should be errors (set by SPEC_DENY_WARNINGS
70+ /// environment variable).
5971 deny_warnings : bool ,
60- rule_re : Regex ,
61- admonition_re : Regex ,
62- std_link_re : Regex ,
63- std_link_extract_re : Regex ,
6472}
6573
6674impl Spec {
6775 pub fn new ( ) -> Spec {
68- // This is roughly a rustdoc intra-doc link definition.
69- let std_link = r"(?: [a-z]+@ )?
70- (?: std|core|alloc|proc_macro|test )
71- (?: ::[A-Za-z_!:<>{}()\[\]]+ )?" ;
7276 Spec {
7377 deny_warnings : std:: env:: var ( "SPEC_DENY_WARNINGS" ) . as_deref ( ) == Ok ( "1" ) ,
74- rule_re : Regex :: new ( r"(?m)^r\[([^]]+)]$" ) . unwrap ( ) ,
75- admonition_re : Regex :: new (
76- r"(?m)^ *> \[!(?<admon>[^]]+)\]\n(?<blockquote>(?: *> .*\n)+)" ,
77- )
78- . unwrap ( ) ,
79- std_link_re : Regex :: new ( & format ! (
80- r"(?x)
81- (?:
82- ( \[`[^`]+`\] ) \( ({std_link}) \)
83- )
84- | (?:
85- ( \[`{std_link}`\] )
86- )
87- "
88- ) )
89- . unwrap ( ) ,
90- std_link_extract_re : Regex :: new (
91- r#"<li><a [^>]*href="(https://doc.rust-lang.org/[^"]+)""# ,
92- )
93- . unwrap ( ) ,
9478 }
9579 }
9680
@@ -103,7 +87,7 @@ impl Spec {
10387 ) -> String {
10488 let source_path = chapter. source_path . clone ( ) . unwrap_or_default ( ) ;
10589 let path = chapter. path . clone ( ) . unwrap_or_default ( ) ;
106- self . rule_re
90+ RULE_RE
10791 . replace_all ( & chapter. content , |caps : & Captures | {
10892 let rule_id = & caps[ 1 ] ;
10993 if let Some ( ( old, _) ) =
@@ -165,7 +149,7 @@ impl Spec {
165149 /// be a CSS class is valid. The actual styling needs to be added in a CSS
166150 /// file.
167151 fn admonitions ( & self , chapter : & Chapter ) -> String {
168- self . admonition_re
152+ ADMONITION_RE
169153 . replace_all ( & chapter. content , |caps : & Captures | {
170154 let lower = caps[ "admon" ] . to_lowercase ( ) ;
171155 format ! (
@@ -175,122 +159,6 @@ impl Spec {
175159 } )
176160 . to_string ( )
177161 }
178-
179- /// Converts links to the standard library to the online documentation in
180- /// a fashion similar to rustdoc intra-doc links.
181- fn std_links ( & self , chapter : & Chapter ) -> String {
182- // This is very hacky, but should work well enough.
183- //
184- // Collect all standard library links.
185- //
186- // links are tuples of ("[`std::foo`]", None) for links without dest,
187- // or ("[`foo`]", "std::foo") with a dest.
188- let mut links: Vec < _ > = self
189- . std_link_re
190- . captures_iter ( & chapter. content )
191- . map ( |cap| {
192- if let Some ( no_dest) = cap. get ( 3 ) {
193- ( no_dest. as_str ( ) , None )
194- } else {
195- (
196- cap. get ( 1 ) . unwrap ( ) . as_str ( ) ,
197- Some ( cap. get ( 2 ) . unwrap ( ) . as_str ( ) ) ,
198- )
199- }
200- } )
201- . collect ( ) ;
202- if links. is_empty ( ) {
203- return chapter. content . clone ( ) ;
204- }
205- links. sort ( ) ;
206- links. dedup ( ) ;
207-
208- // Write a Rust source file to use with rustdoc to generate intra-doc links.
209- let tmp = tempfile:: TempDir :: with_prefix ( "mdbook-spec-" ) . unwrap ( ) ;
210- let src_path = tmp. path ( ) . join ( "a.rs" ) ;
211- // Allow redundant since there could some in-scope things that are
212- // technically not necessary, but we don't care about (like
213- // [`Option`](std::option::Option)).
214- let mut src = format ! (
215- "#![deny(rustdoc::broken_intra_doc_links)]\n \
216- #![allow(rustdoc::redundant_explicit_links)]\n "
217- ) ;
218- for ( link, dest) in & links {
219- write ! ( src, "//! - {link}" ) . unwrap ( ) ;
220- if let Some ( dest) = dest {
221- write ! ( src, "({})" , dest) . unwrap ( ) ;
222- }
223- src. push ( '\n' ) ;
224- }
225- writeln ! (
226- src,
227- "extern crate alloc;\n \
228- extern crate proc_macro;\n \
229- extern crate test;\n "
230- )
231- . unwrap ( ) ;
232- fs:: write ( & src_path, & src) . unwrap ( ) ;
233- let output = Command :: new ( "rustdoc" )
234- . arg ( "--edition=2021" )
235- . arg ( & src_path)
236- . current_dir ( tmp. path ( ) )
237- . output ( )
238- . expect ( "rustdoc installed" ) ;
239- if !output. status . success ( ) {
240- eprintln ! (
241- "error: failed to extract std links ({:?}) in chapter {} ({:?})\n " ,
242- output. status,
243- chapter. name,
244- chapter. source_path. as_ref( ) . unwrap( )
245- ) ;
246- io:: stderr ( ) . write_all ( & output. stderr ) . unwrap ( ) ;
247- process:: exit ( 1 ) ;
248- }
249-
250- // Extract the links from the generated html.
251- let generated =
252- fs:: read_to_string ( tmp. path ( ) . join ( "doc/a/index.html" ) ) . expect ( "index.html generated" ) ;
253- let urls: Vec < _ > = self
254- . std_link_extract_re
255- . captures_iter ( & generated)
256- . map ( |cap| cap. get ( 1 ) . unwrap ( ) . as_str ( ) )
257- . collect ( ) ;
258- if urls. len ( ) != links. len ( ) {
259- eprintln ! (
260- "error: expected rustdoc to generate {} links, but found {} in chapter {} ({:?})" ,
261- links. len( ) ,
262- urls. len( ) ,
263- chapter. name,
264- chapter. source_path. as_ref( ) . unwrap( )
265- ) ;
266- process:: exit ( 1 ) ;
267- }
268-
269- // Replace any disambiguated links with just the disambiguation.
270- let mut output = self
271- . std_link_re
272- . replace_all ( & chapter. content , |caps : & Captures | {
273- if let Some ( dest) = caps. get ( 2 ) {
274- // Replace destination parenthesis with a link definition (square brackets).
275- format ! ( "{}[{}]" , & caps[ 1 ] , dest. as_str( ) )
276- } else {
277- caps[ 0 ] . to_string ( )
278- }
279- } )
280- . to_string ( ) ;
281-
282- // Append the link definitions to the bottom of the chapter.
283- write ! ( output, "\n " ) . unwrap ( ) ;
284- for ( ( link, dest) , url) in links. iter ( ) . zip ( urls) {
285- if let Some ( dest) = dest {
286- write ! ( output, "[{dest}]: {url}\n " ) . unwrap ( ) ;
287- } else {
288- write ! ( output, "{link}: {url}\n " ) . unwrap ( ) ;
289- }
290- }
291-
292- output
293- }
294162}
295163
296164impl Preprocessor for Spec {
@@ -300,27 +168,28 @@ impl Preprocessor for Spec {
300168
301169 fn run ( & self , _ctx : & PreprocessorContext , mut book : Book ) -> Result < Book , Error > {
302170 let mut found_rules = BTreeMap :: new ( ) ;
303- for section in & mut book. sections {
304- let BookItem :: Chapter ( ch) = section else {
305- continue ;
171+ book. for_each_mut ( |item| {
172+ let BookItem :: Chapter ( ch) = item else {
173+ return ;
306174 } ;
307175 if ch. is_draft_chapter ( ) {
308- continue ;
176+ return ;
309177 }
310178 ch. content = self . rule_definitions ( & ch, & mut found_rules) ;
311179 ch. content = self . admonitions ( & ch) ;
312- ch. content = self . std_links ( & ch) ;
313- }
314- for section in & mut book. sections {
315- let BookItem :: Chapter ( ch) = section else {
316- continue ;
180+ ch. content = std_links:: std_links ( & ch) ;
181+ } ) ;
182+ // This is a separate pass because it relies on the modifications of
183+ // the previous passes.
184+ book. for_each_mut ( |item| {
185+ let BookItem :: Chapter ( ch) = item else {
186+ return ;
317187 } ;
318188 if ch. is_draft_chapter ( ) {
319- continue ;
189+ return ;
320190 }
321191 ch. content = self . auto_link_references ( & ch, & found_rules) ;
322- }
323-
192+ } ) ;
324193 Ok ( book)
325194 }
326195}
0 commit comments