@@ -2,14 +2,24 @@ use mdbook::book::{Book, Chapter};
2
2
use mdbook:: errors:: Error ;
3
3
use mdbook:: preprocess:: { CmdPreprocessor , Preprocessor , PreprocessorContext } ;
4
4
use mdbook:: BookItem ;
5
+ use once_cell:: sync:: Lazy ;
5
6
use regex:: { Captures , Regex } ;
6
7
use semver:: { Version , VersionReq } ;
7
8
use std:: collections:: BTreeMap ;
8
- use std:: fmt:: Write as _;
9
- use std:: fs;
10
- use std:: io:: { self , Write as _} ;
9
+ use std:: io;
11
10
use 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
+ } ) ;
13
23
14
24
fn main ( ) {
15
25
let mut args = std:: env:: args ( ) . skip ( 1 ) ;
@@ -56,41 +66,15 @@ fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
56
66
}
57
67
58
68
struct Spec {
69
+ /// Whether or not warnings should be errors (set by SPEC_DENY_WARNINGS
70
+ /// environment variable).
59
71
deny_warnings : bool ,
60
- rule_re : Regex ,
61
- admonition_re : Regex ,
62
- std_link_re : Regex ,
63
- std_link_extract_re : Regex ,
64
72
}
65
73
66
74
impl Spec {
67
75
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_!:<>{}()\[\]]+ )?" ;
72
76
Spec {
73
77
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 ( ) ,
94
78
}
95
79
}
96
80
@@ -103,7 +87,7 @@ impl Spec {
103
87
) -> String {
104
88
let source_path = chapter. source_path . clone ( ) . unwrap_or_default ( ) ;
105
89
let path = chapter. path . clone ( ) . unwrap_or_default ( ) ;
106
- self . rule_re
90
+ RULE_RE
107
91
. replace_all ( & chapter. content , |caps : & Captures | {
108
92
let rule_id = & caps[ 1 ] ;
109
93
if let Some ( ( old, _) ) =
@@ -165,7 +149,7 @@ impl Spec {
165
149
/// be a CSS class is valid. The actual styling needs to be added in a CSS
166
150
/// file.
167
151
fn admonitions ( & self , chapter : & Chapter ) -> String {
168
- self . admonition_re
152
+ ADMONITION_RE
169
153
. replace_all ( & chapter. content , |caps : & Captures | {
170
154
let lower = caps[ "admon" ] . to_lowercase ( ) ;
171
155
format ! (
@@ -175,122 +159,6 @@ impl Spec {
175
159
} )
176
160
. to_string ( )
177
161
}
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
- }
294
162
}
295
163
296
164
impl Preprocessor for Spec {
@@ -300,27 +168,28 @@ impl Preprocessor for Spec {
300
168
301
169
fn run ( & self , _ctx : & PreprocessorContext , mut book : Book ) -> Result < Book , Error > {
302
170
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 ;
306
174
} ;
307
175
if ch. is_draft_chapter ( ) {
308
- continue ;
176
+ return ;
309
177
}
310
178
ch. content = self . rule_definitions ( & ch, & mut found_rules) ;
311
179
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 ;
317
187
} ;
318
188
if ch. is_draft_chapter ( ) {
319
- continue ;
189
+ return ;
320
190
}
321
191
ch. content = self . auto_link_references ( & ch, & found_rules) ;
322
- }
323
-
192
+ } ) ;
324
193
Ok ( book)
325
194
}
326
195
}
0 commit comments