Skip to content

Commit f01512d

Browse files
committed
Move std_links to its own module.
This code is getting long enough that it will help to organize it separately.
1 parent 86ecfd8 commit f01512d

File tree

2 files changed

+150
-143
lines changed

2 files changed

+150
-143
lines changed

mdbook-spec/src/main.rs

Lines changed: 5 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ use once_cell::sync::Lazy;
66
use regex::{Captures, Regex};
77
use semver::{Version, VersionReq};
88
use std::collections::BTreeMap;
9-
use std::fmt::Write as _;
10-
use std::fs;
11-
use std::io::{self, Write as _};
9+
use std::io;
1210
use std::path::PathBuf;
13-
use std::process::{self, Command};
11+
use std::process;
12+
13+
mod std_links;
1414

1515
/// The Regex for rules like `r[foo]`.
1616
static RULE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^r\[([^]]+)]$").unwrap());
@@ -21,31 +21,6 @@ static ADMONITION_RE: Lazy<Regex> = Lazy::new(|| {
2121
Regex::new(r"(?m)^ *> \[!(?<admon>[^]]+)\]\n(?<blockquote>(?: *> .*\n)+)").unwrap()
2222
});
2323

24-
/// A markdown link (without the brackets) that might possibly be a link to
25-
/// the standard library using rustdoc's intra-doc notation.
26-
const STD_LINK: &str = r"(?: [a-z]+@ )?
27-
(?: std|core|alloc|proc_macro|test )
28-
(?: ::[A-Za-z0-9_!:<>{}()\[\]]+ )?";
29-
30-
/// The Regex for a markdown link that might be a link to the standard library.
31-
static STD_LINK_RE: Lazy<Regex> = Lazy::new(|| {
32-
Regex::new(&format!(
33-
r"(?x)
34-
(?:
35-
( \[`[^`]+`\] ) \( ({STD_LINK}) \)
36-
)
37-
| (?:
38-
( \[`{STD_LINK}`\] )
39-
)
40-
"
41-
))
42-
.unwrap()
43-
});
44-
45-
/// The Regex used to extract the std links from the HTML generated by rustdoc.
46-
static STD_LINK_EXTRACT_RE: Lazy<Regex> =
47-
Lazy::new(|| Regex::new(r#"<li><a [^>]*href="(https://doc.rust-lang.org/[^"]+)""#).unwrap());
48-
4924
fn main() {
5025
let mut args = std::env::args().skip(1);
5126
match args.next().as_deref() {
@@ -184,119 +159,6 @@ impl Spec {
184159
})
185160
.to_string()
186161
}
187-
188-
/// Converts links to the standard library to the online documentation in
189-
/// a fashion similar to rustdoc intra-doc links.
190-
fn std_links(&self, chapter: &Chapter) -> String {
191-
// This is very hacky, but should work well enough.
192-
//
193-
// Collect all standard library links.
194-
//
195-
// links are tuples of ("[`std::foo`]", None) for links without dest,
196-
// or ("[`foo`]", "std::foo") with a dest.
197-
let mut links: Vec<_> = STD_LINK_RE
198-
.captures_iter(&chapter.content)
199-
.map(|cap| {
200-
if let Some(no_dest) = cap.get(3) {
201-
(no_dest.as_str(), None)
202-
} else {
203-
(
204-
cap.get(1).unwrap().as_str(),
205-
Some(cap.get(2).unwrap().as_str()),
206-
)
207-
}
208-
})
209-
.collect();
210-
if links.is_empty() {
211-
return chapter.content.clone();
212-
}
213-
links.sort();
214-
links.dedup();
215-
216-
// Write a Rust source file to use with rustdoc to generate intra-doc links.
217-
let tmp = tempfile::TempDir::with_prefix("mdbook-spec-").unwrap();
218-
let src_path = tmp.path().join("a.rs");
219-
// Allow redundant since there could some in-scope things that are
220-
// technically not necessary, but we don't care about (like
221-
// [`Option`](std::option::Option)).
222-
let mut src = format!(
223-
"#![deny(rustdoc::broken_intra_doc_links)]\n\
224-
#![allow(rustdoc::redundant_explicit_links)]\n"
225-
);
226-
for (link, dest) in &links {
227-
write!(src, "//! - {link}").unwrap();
228-
if let Some(dest) = dest {
229-
write!(src, "({})", dest).unwrap();
230-
}
231-
src.push('\n');
232-
}
233-
writeln!(
234-
src,
235-
"extern crate alloc;\n\
236-
extern crate proc_macro;\n\
237-
extern crate test;\n"
238-
)
239-
.unwrap();
240-
fs::write(&src_path, &src).unwrap();
241-
let output = Command::new("rustdoc")
242-
.arg("--edition=2021")
243-
.arg(&src_path)
244-
.current_dir(tmp.path())
245-
.output()
246-
.expect("rustdoc installed");
247-
if !output.status.success() {
248-
eprintln!(
249-
"error: failed to extract std links ({:?}) in chapter {} ({:?})\n",
250-
output.status,
251-
chapter.name,
252-
chapter.source_path.as_ref().unwrap()
253-
);
254-
io::stderr().write_all(&output.stderr).unwrap();
255-
process::exit(1);
256-
}
257-
258-
// Extract the links from the generated html.
259-
let generated =
260-
fs::read_to_string(tmp.path().join("doc/a/index.html")).expect("index.html generated");
261-
let urls: Vec<_> = STD_LINK_EXTRACT_RE
262-
.captures_iter(&generated)
263-
.map(|cap| cap.get(1).unwrap().as_str())
264-
.collect();
265-
if urls.len() != links.len() {
266-
eprintln!(
267-
"error: expected rustdoc to generate {} links, but found {} in chapter {} ({:?})",
268-
links.len(),
269-
urls.len(),
270-
chapter.name,
271-
chapter.source_path.as_ref().unwrap()
272-
);
273-
process::exit(1);
274-
}
275-
276-
// Replace any disambiguated links with just the disambiguation.
277-
let mut output = STD_LINK_RE
278-
.replace_all(&chapter.content, |caps: &Captures| {
279-
if let Some(dest) = caps.get(2) {
280-
// Replace destination parenthesis with a link definition (square brackets).
281-
format!("{}[{}]", &caps[1], dest.as_str())
282-
} else {
283-
caps[0].to_string()
284-
}
285-
})
286-
.to_string();
287-
288-
// Append the link definitions to the bottom of the chapter.
289-
write!(output, "\n").unwrap();
290-
for ((link, dest), url) in links.iter().zip(urls) {
291-
if let Some(dest) = dest {
292-
write!(output, "[{dest}]: {url}\n").unwrap();
293-
} else {
294-
write!(output, "{link}: {url}\n").unwrap();
295-
}
296-
}
297-
298-
output
299-
}
300162
}
301163

302164
impl Preprocessor for Spec {
@@ -315,7 +177,7 @@ impl Preprocessor for Spec {
315177
}
316178
ch.content = self.rule_definitions(&ch, &mut found_rules);
317179
ch.content = self.admonitions(&ch);
318-
ch.content = self.std_links(&ch);
180+
ch.content = std_links::std_links(&ch);
319181
}
320182
for section in &mut book.sections {
321183
let BookItem::Chapter(ch) = section else {

mdbook-spec/src/std_links.rs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
use mdbook::book::Chapter;
2+
use once_cell::sync::Lazy;
3+
use regex::{Captures, Regex};
4+
use std::fmt::Write as _;
5+
use std::fs;
6+
use std::io::{self, Write as _};
7+
use std::process::{self, Command};
8+
9+
/// A markdown link (without the brackets) that might possibly be a link to
10+
/// the standard library using rustdoc's intra-doc notation.
11+
const STD_LINK: &str = r"(?: [a-z]+@ )?
12+
(?: std|core|alloc|proc_macro|test )
13+
(?: ::[A-Za-z0-9_!:<>{}()\[\]]+ )?";
14+
15+
/// The Regex for a markdown link that might be a link to the standard library.
16+
static STD_LINK_RE: Lazy<Regex> = Lazy::new(|| {
17+
Regex::new(&format!(
18+
r"(?x)
19+
(?:
20+
( \[`[^`]+`\] ) \( ({STD_LINK}) \)
21+
)
22+
| (?:
23+
( \[`{STD_LINK}`\] )
24+
)
25+
"
26+
))
27+
.unwrap()
28+
});
29+
30+
/// The Regex used to extract the std links from the HTML generated by rustdoc.
31+
static STD_LINK_EXTRACT_RE: Lazy<Regex> =
32+
Lazy::new(|| Regex::new(r#"<li><a [^>]*href="(https://doc.rust-lang.org/[^"]+)""#).unwrap());
33+
34+
/// Converts links to the standard library to the online documentation in a
35+
/// fashion similar to rustdoc intra-doc links.
36+
pub fn std_links(chapter: &Chapter) -> String {
37+
// This is very hacky, but should work well enough.
38+
//
39+
// Collect all standard library links.
40+
//
41+
// links are tuples of ("[`std::foo`]", None) for links without dest,
42+
// or ("[`foo`]", "std::foo") with a dest.
43+
let mut links: Vec<_> = STD_LINK_RE
44+
.captures_iter(&chapter.content)
45+
.map(|cap| {
46+
if let Some(no_dest) = cap.get(3) {
47+
(no_dest.as_str(), None)
48+
} else {
49+
(
50+
cap.get(1).unwrap().as_str(),
51+
Some(cap.get(2).unwrap().as_str()),
52+
)
53+
}
54+
})
55+
.collect();
56+
if links.is_empty() {
57+
return chapter.content.clone();
58+
}
59+
links.sort();
60+
links.dedup();
61+
62+
// Write a Rust source file to use with rustdoc to generate intra-doc links.
63+
let tmp = tempfile::TempDir::with_prefix("mdbook-spec-").unwrap();
64+
let src_path = tmp.path().join("a.rs");
65+
// Allow redundant since there could some in-scope things that are
66+
// technically not necessary, but we don't care about (like
67+
// [`Option`](std::option::Option)).
68+
let mut src = format!(
69+
"#![deny(rustdoc::broken_intra_doc_links)]\n\
70+
#![allow(rustdoc::redundant_explicit_links)]\n"
71+
);
72+
for (link, dest) in &links {
73+
write!(src, "//! - {link}").unwrap();
74+
if let Some(dest) = dest {
75+
write!(src, "({})", dest).unwrap();
76+
}
77+
src.push('\n');
78+
}
79+
writeln!(
80+
src,
81+
"extern crate alloc;\n\
82+
extern crate proc_macro;\n\
83+
extern crate test;\n"
84+
)
85+
.unwrap();
86+
fs::write(&src_path, &src).unwrap();
87+
let output = Command::new("rustdoc")
88+
.arg("--edition=2021")
89+
.arg(&src_path)
90+
.current_dir(tmp.path())
91+
.output()
92+
.expect("rustdoc installed");
93+
if !output.status.success() {
94+
eprintln!(
95+
"error: failed to extract std links ({:?}) in chapter {} ({:?})\n",
96+
output.status,
97+
chapter.name,
98+
chapter.source_path.as_ref().unwrap()
99+
);
100+
io::stderr().write_all(&output.stderr).unwrap();
101+
process::exit(1);
102+
}
103+
104+
// Extract the links from the generated html.
105+
let generated =
106+
fs::read_to_string(tmp.path().join("doc/a/index.html")).expect("index.html generated");
107+
let urls: Vec<_> = STD_LINK_EXTRACT_RE
108+
.captures_iter(&generated)
109+
.map(|cap| cap.get(1).unwrap().as_str())
110+
.collect();
111+
if urls.len() != links.len() {
112+
eprintln!(
113+
"error: expected rustdoc to generate {} links, but found {} in chapter {} ({:?})",
114+
links.len(),
115+
urls.len(),
116+
chapter.name,
117+
chapter.source_path.as_ref().unwrap()
118+
);
119+
process::exit(1);
120+
}
121+
122+
// Replace any disambiguated links with just the disambiguation.
123+
let mut output = STD_LINK_RE
124+
.replace_all(&chapter.content, |caps: &Captures| {
125+
if let Some(dest) = caps.get(2) {
126+
// Replace destination parenthesis with a link definition (square brackets).
127+
format!("{}[{}]", &caps[1], dest.as_str())
128+
} else {
129+
caps[0].to_string()
130+
}
131+
})
132+
.to_string();
133+
134+
// Append the link definitions to the bottom of the chapter.
135+
write!(output, "\n").unwrap();
136+
for ((link, dest), url) in links.iter().zip(urls) {
137+
if let Some(dest) = dest {
138+
write!(output, "[{dest}]: {url}\n").unwrap();
139+
} else {
140+
write!(output, "{link}: {url}\n").unwrap();
141+
}
142+
}
143+
144+
output
145+
}

0 commit comments

Comments
 (0)