Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit f865d5a

Browse files
authoredSep 2, 2022
Rollup merge of rust-lang#101166 - GuillaumeGomez:error-index-mdbook, r=notriddle
Generate error index with mdbook instead of raw HTML pages This is a follow-up of rust-lang#100922. This comes from a remark from ```@estebank``` who said that the search was a nice thing on the previous version and that it wasn't possible anymore. An easy way to come around this limitation was to use `mdbook`, which is what I did here. Now some explanations on the code. I'll explain how I developed this and why I reached this solution. First I did it very basically by simply setting the source directory and the output directory, generated a `SUMMARY.md` manually which listed all error codes and that was it. Two problems arose from this: 1. A lot of new HTML files were generated at the top level 2. An `index.html` file was generated at the top-level (the summary in short). So for `1.`, it's not great to have too many files at the top-level as it could create file conflicts more easily. And for `2.`, this is actually a huge issue because <doc.rust-lang.org> generates an `index.html` file with a links to a few different resources, so it should never be overwritten. <s>Unfortunately, `mdbook` **always** generates an `index.html` file so the only solution I could see (except for sending them a contribution, I'll maybe do that later) was to temporaly move a potentially existing `index.html` file and then puts it back once done. For this last part, to ensure that we don't return *before* it has been put back, I wrapped the `mdbook` generation code inside `render_html_inner` which is called from `render_html` which in turn handle the "save" of `index.html`.</s> EDIT: `mdbook` completely deletes ALL the content in the target directory so I instead generate into a sub directory and then I move the files to the real target directory. To keep compatibility with the old version, I also put the `<script>` ```@notriddle``` nicely provided in the previous PR only into the `error-index.html` file to prevent unneeded repetition. I didn't use `mdbook` `additional-js` option because the JS is included at the end of all HTML files, which we don't want for two reasons: 1. It's slow. 2. We only want it to be run in `error-index.html` (even if we also ensure in the JS itself too!). <s>Now the last part: why we generate the summary twice. We actually generate it once to tell `mdbook` what the book will look like and a second time because a create a new chapter content which will actually list all the error codes (with the updated paths).</s> EDIT: I removed the need for two summaries. You can test it [here](https://rustdoc.crud.net/imperio/error-index-mdbook/error-index.html). r? ```@notriddle```
2 parents 4c05e77 + f5857d5 commit f865d5a

File tree

8 files changed

+196
-142
lines changed

8 files changed

+196
-142
lines changed
 

‎Cargo.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -1259,7 +1259,7 @@ dependencies = [
12591259
name = "error_index_generator"
12601260
version = "0.0.0"
12611261
dependencies = [
1262-
"rustdoc",
1262+
"mdbook",
12631263
]
12641264

12651265
[[package]]

‎src/bootstrap/doc.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ impl Step for ErrorIndex {
793793
t!(fs::create_dir_all(&out));
794794
let mut index = tool::ErrorIndex::command(builder);
795795
index.arg("html");
796-
index.arg(out.join("error-index.html"));
796+
index.arg(out);
797797
index.arg(&builder.version);
798798

799799
builder.run(&mut index);

‎src/tools/error_index_generator/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ version = "0.0.0"
44
edition = "2021"
55

66
[dependencies]
7-
rustdoc = { path = "../../librustdoc" }
7+
mdbook = { version = "0.4", default-features = false, features = ["search"] }
88

99
[[bin]]
1010
name = "error_index_generator"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[book]
2+
title = "Error codes index"
3+
description = "Book listing all Rust error codes"
4+
src = ""
5+
6+
[output.html]
7+
git-repository-url = "https://github.com/rust-lang/rust/"
8+
additional-css = ["error-index.css"]
9+
additional-js = ["error-index.js"]
10+
11+
[output.html.search]
12+
enable = true
13+
limit-results = 20
14+
use-boolean-and = true
15+
boost-title = 2
16+
boost-hierarchy = 2
17+
boost-paragraph = 1
18+
expand = true
19+
heading-split-level = 0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
code.compile_fail {
2+
border-left: 2px solid red;
3+
}
4+
5+
pre .tooltip {
6+
position: absolute;
7+
left: -25px;
8+
top: 0;
9+
z-index: 1;
10+
color: red;
11+
cursor: pointer;
12+
}
13+
pre .tooltip::after {
14+
display: none;
15+
content: "This example deliberately fails to compile";
16+
background-color: #000;
17+
color: #fff;
18+
border-color: #000;
19+
text-align: center;
20+
padding: 5px 3px 3px 3px;
21+
border-radius: 6px;
22+
margin-left: 5px;
23+
}
24+
pre .tooltip::before {
25+
display: none;
26+
border-color: transparent black transparent transparent;
27+
content: " ";
28+
position: absolute;
29+
top: 50%;
30+
left: 16px;
31+
margin-top: -5px;
32+
border-width: 5px;
33+
border-style: solid;
34+
}
35+
36+
pre .tooltip:hover::before, pre .tooltip:hover::after {
37+
display: inline;
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
for (const elem of document.querySelectorAll("pre.playground")) {
2+
if (elem.querySelector(".compile_fail") === null) {
3+
continue;
4+
}
5+
const child = document.createElement("div");
6+
child.className = "tooltip";
7+
child.textContent = "ⓘ";
8+
elem.appendChild(child);
9+
}
+111-139
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
#![feature(rustc_private)]
22

33
extern crate rustc_driver;
4-
extern crate rustc_span;
54

5+
// We use the function we generate from `register_diagnostics!`.
66
use crate::error_codes::error_codes;
77

88
use std::env;
99
use std::error::Error;
10-
use std::fs::{create_dir_all, File};
10+
use std::fs::{self, File};
1111
use std::io::Write;
1212
use std::path::Path;
1313
use std::path::PathBuf;
1414

15-
use rustc_span::edition::DEFAULT_EDITION;
15+
use std::str::FromStr;
1616

17-
use rustdoc::html::markdown::{ErrorCodes, HeadingOffset, IdMap, Markdown, Playground};
17+
use mdbook::book::{parse_summary, BookItem, Chapter};
18+
use mdbook::{Config, MDBook};
1819

1920
macro_rules! register_diagnostics {
2021
($($error_code:ident: $message:expr,)+ ; $($undocumented:ident,)* ) => {
@@ -33,104 +34,21 @@ macro_rules! register_diagnostics {
3334
mod error_codes;
3435

3536
enum OutputFormat {
36-
HTML(HTMLFormatter),
37+
HTML,
3738
Markdown,
3839
Unknown(String),
3940
}
4041

4142
impl OutputFormat {
42-
fn from(format: &str, resource_suffix: &str) -> OutputFormat {
43+
fn from(format: &str) -> OutputFormat {
4344
match &*format.to_lowercase() {
44-
"html" => OutputFormat::HTML(HTMLFormatter(resource_suffix.to_owned())),
45+
"html" => OutputFormat::HTML,
4546
"markdown" => OutputFormat::Markdown,
4647
s => OutputFormat::Unknown(s.to_owned()),
4748
}
4849
}
4950
}
5051

51-
struct HTMLFormatter(String);
52-
53-
impl HTMLFormatter {
54-
fn create_error_code_file(
55-
&self,
56-
err_code: &str,
57-
explanation: &str,
58-
parent_dir: &Path,
59-
) -> Result<(), Box<dyn Error>> {
60-
let mut output_file = File::create(parent_dir.join(err_code).with_extension("html"))?;
61-
62-
self.header(&mut output_file, "../", "")?;
63-
self.title(&mut output_file, &format!("Error code {}", err_code))?;
64-
65-
let mut id_map = IdMap::new();
66-
let playground =
67-
Playground { crate_name: None, url: String::from("https://play.rust-lang.org/") };
68-
write!(
69-
output_file,
70-
"{}",
71-
Markdown {
72-
content: explanation,
73-
links: &[],
74-
ids: &mut id_map,
75-
error_codes: ErrorCodes::Yes,
76-
edition: DEFAULT_EDITION,
77-
playground: &Some(playground),
78-
heading_offset: HeadingOffset::H1,
79-
}
80-
.into_string()
81-
)?;
82-
write!(
83-
output_file,
84-
"<p>\
85-
<a style='text-align: center;display: block;width: 100%;' \
86-
href='../error-index.html'>Back to list of error codes</a>\
87-
</p>",
88-
)?;
89-
90-
self.footer(&mut output_file)
91-
}
92-
93-
fn header(
94-
&self,
95-
output: &mut dyn Write,
96-
extra_path: &str,
97-
extra: &str,
98-
) -> Result<(), Box<dyn Error>> {
99-
write!(
100-
output,
101-
r##"<!DOCTYPE html>
102-
<html>
103-
<head>
104-
<title>Rust Compiler Error Index</title>
105-
<meta charset="utf-8">
106-
<!-- Include rust.css after light.css so its rules take priority. -->
107-
<link rel="stylesheet" type="text/css" href="{extra_path}rustdoc{suffix}.css"/>
108-
<link rel="stylesheet" type="text/css" href="{extra_path}light{suffix}.css"/>
109-
<link rel="stylesheet" type="text/css" href="{extra_path}rust.css"/>
110-
<style>
111-
.error-undescribed {{
112-
display: none;
113-
}}
114-
</style>{extra}
115-
</head>
116-
<body>
117-
"##,
118-
suffix = self.0,
119-
)?;
120-
Ok(())
121-
}
122-
123-
fn title(&self, output: &mut dyn Write, title: &str) -> Result<(), Box<dyn Error>> {
124-
write!(output, "<h1>{}</h1>\n", title)?;
125-
Ok(())
126-
}
127-
128-
fn footer(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>> {
129-
write!(output, "</body></html>")?;
130-
Ok(())
131-
}
132-
}
133-
13452
/// Output an HTML page for the errors in `err_map` to `output_path`.
13553
fn render_markdown(output_path: &Path) -> Result<(), Box<dyn Error>> {
13654
let mut output_file = File::create(output_path)?;
@@ -147,61 +65,119 @@ fn render_markdown(output_path: &Path) -> Result<(), Box<dyn Error>> {
14765
Ok(())
14866
}
14967

150-
fn render_html(output_path: &Path, formatter: HTMLFormatter) -> Result<(), Box<dyn Error>> {
151-
let mut output_file = File::create(output_path)?;
68+
// By default, mdbook doesn't consider code blocks as Rust ones contrary to rustdoc so we have
69+
// to manually add `rust` attribute whenever needed.
70+
fn add_rust_attribute_on_codeblock(explanation: &str) -> String {
71+
// Very hacky way to add the rust attribute on all code blocks.
72+
let mut skip = true;
73+
explanation.split("\n```").fold(String::new(), |mut acc, part| {
74+
if !acc.is_empty() {
75+
acc.push_str("\n```");
76+
}
77+
if !skip {
78+
if let Some(attrs) = part.split('\n').next() {
79+
if !attrs.contains("rust")
80+
&& (attrs.is_empty()
81+
|| attrs.contains("compile_fail")
82+
|| attrs.contains("ignore")
83+
|| attrs.contains("edition"))
84+
{
85+
if !attrs.is_empty() {
86+
acc.push_str("rust,");
87+
} else {
88+
acc.push_str("rust");
89+
}
90+
}
91+
}
92+
}
93+
skip = !skip;
94+
acc.push_str(part);
95+
acc
96+
})
97+
}
15298

153-
let error_codes_dir = "error_codes";
99+
fn render_html(output_path: &Path) -> Result<(), Box<dyn Error>> {
100+
let mut introduction = format!(
101+
"<script src='redirect.js'></script>
102+
# Rust error codes index
154103
155-
let parent = output_path.parent().expect("There should have a parent").join(error_codes_dir);
104+
This page lists all the error codes emitted by the Rust compiler.
156105
157-
if !parent.exists() {
158-
create_dir_all(&parent)?;
159-
}
106+
"
107+
);
160108

161-
formatter.header(
162-
&mut output_file,
163-
"",
164-
&format!(
165-
r#"<script>(function() {{
166-
if (window.location.hash) {{
167-
let code = window.location.hash.replace(/^#/, '');
168-
// We have to make sure this pattern matches to avoid inadvertently creating an
169-
// open redirect.
170-
if (/^E[0-9]+$/.test(code)) {{
171-
window.location = './{error_codes_dir}/' + code + '.html';
172-
}}
173-
}}
174-
}})()</script>"#
175-
),
176-
)?;
177-
formatter.title(&mut output_file, "Rust Compiler Error Index")?;
109+
let err_codes = error_codes();
110+
let mut chapters = Vec::with_capacity(err_codes.len());
178111

179-
write!(
180-
output_file,
181-
"<p>This page lists all the error codes emitted by the Rust compiler. If you want a full \
182-
explanation on an error code, click on it.</p>\
183-
<ul>",
184-
)?;
185-
for (err_code, explanation) in error_codes().iter() {
112+
for (err_code, explanation) in err_codes.iter() {
186113
if let Some(explanation) = explanation {
187-
write!(
188-
output_file,
189-
"<li><a href='./{0}/{1}.html'>{1}</a></li>",
190-
error_codes_dir, err_code
191-
)?;
192-
formatter.create_error_code_file(err_code, explanation, &parent)?;
114+
introduction.push_str(&format!(" * [{0}](./{0}.html)\n", err_code));
115+
116+
let content = add_rust_attribute_on_codeblock(explanation);
117+
chapters.push(BookItem::Chapter(Chapter {
118+
name: err_code.to_string(),
119+
content: format!("# Error code {}\n\n{}\n", err_code, content),
120+
number: None,
121+
sub_items: Vec::new(),
122+
// We generate it into the `error_codes` folder.
123+
path: Some(PathBuf::from(&format!("{}.html", err_code))),
124+
source_path: None,
125+
parent_names: Vec::new(),
126+
}));
193127
} else {
194-
write!(output_file, "<li>{}</li>", err_code)?;
128+
introduction.push_str(&format!(" * {}\n", err_code));
195129
}
196130
}
197-
write!(output_file, "</ul>")?;
198-
formatter.footer(&mut output_file)
131+
132+
let mut config = Config::from_str(include_str!("book_config.toml"))?;
133+
config.build.build_dir = output_path.join("error_codes").to_path_buf();
134+
let mut book = MDBook::load_with_config_and_summary(
135+
env!("CARGO_MANIFEST_DIR"),
136+
config,
137+
parse_summary("")?,
138+
)?;
139+
let chapter = Chapter {
140+
name: "Rust error codes index".to_owned(),
141+
content: introduction,
142+
number: None,
143+
sub_items: chapters,
144+
// Very important: this file is named as `error-index.html` and not `index.html`!
145+
path: Some(PathBuf::from("error-index.html")),
146+
source_path: None,
147+
parent_names: Vec::new(),
148+
};
149+
book.book.sections.push(BookItem::Chapter(chapter));
150+
book.build()?;
151+
152+
// We can't put this content into another file, otherwise `mbdbook` will also put it into the
153+
// output directory, making a duplicate.
154+
fs::write(
155+
output_path.join("error-index.html"),
156+
r#"<!DOCTYPE html>
157+
<html>
158+
<head>
159+
<title>Rust error codes index - Error codes index</title>
160+
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
161+
<meta name="description" content="Book listing all Rust error codes">
162+
<script src="error_codes/redirect.js"></script>
163+
</head>
164+
<body>
165+
<div>If you are not automatically redirected to the error code index, please <a id="index-link" href="./error_codes/error-index.html">here</a>.
166+
<script>document.getElementById("index-link").click()</script>
167+
</body>
168+
</html>"#,
169+
)?;
170+
171+
// No need for a 404 file, it's already handled by the server.
172+
fs::remove_file(output_path.join("error_codes/404.html"))?;
173+
174+
Ok(())
199175
}
200176

201177
fn main_with_result(format: OutputFormat, dst: &Path) -> Result<(), Box<dyn Error>> {
202178
match format {
203179
OutputFormat::Unknown(s) => panic!("Unknown output format: {}", s),
204-
OutputFormat::HTML(h) => render_html(dst, h),
180+
OutputFormat::HTML => render_html(dst),
205181
OutputFormat::Markdown => render_markdown(dst),
206182
}
207183
}
@@ -210,12 +186,9 @@ fn parse_args() -> (OutputFormat, PathBuf) {
210186
let mut args = env::args().skip(1);
211187
let format = args.next();
212188
let dst = args.next();
213-
let resource_suffix = args.next().unwrap_or_else(String::new);
214-
let format = format
215-
.map(|a| OutputFormat::from(&a, &resource_suffix))
216-
.unwrap_or(OutputFormat::from("html", &resource_suffix));
189+
let format = format.map(|a| OutputFormat::from(&a)).unwrap_or(OutputFormat::from("html"));
217190
let dst = dst.map(PathBuf::from).unwrap_or_else(|| match format {
218-
OutputFormat::HTML(..) => PathBuf::from("doc/error-index.html"),
191+
OutputFormat::HTML => PathBuf::from("doc"),
219192
OutputFormat::Markdown => PathBuf::from("doc/error-index.md"),
220193
OutputFormat::Unknown(..) => PathBuf::from("<nul>"),
221194
});
@@ -225,9 +198,8 @@ fn parse_args() -> (OutputFormat, PathBuf) {
225198
fn main() {
226199
rustc_driver::init_env_logger("RUST_LOG");
227200
let (format, dst) = parse_args();
228-
let result =
229-
rustc_span::create_default_session_globals_then(move || main_with_result(format, &dst));
201+
let result = main_with_result(format, &dst);
230202
if let Err(e) = result {
231-
panic!("{}", e.to_string());
203+
panic!("{:?}", e);
232204
}
233205
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
(function() {
2+
if (window.location.hash) {
3+
let code = window.location.hash.replace(/^#/, '');
4+
// We have to make sure this pattern matches to avoid inadvertently creating an
5+
// open redirect.
6+
if (!/^E[0-9]+$/.test(code)) {
7+
return;
8+
}
9+
if (window.location.pathname.indexOf("/error_codes/") !== -1) {
10+
// We're not at the top level, so we don't prepend with "./error_codes/".
11+
window.location = './' + code + '.html';
12+
} else {
13+
window.location = './error_codes/' + code + '.html';
14+
}
15+
}
16+
})()

0 commit comments

Comments
 (0)
Please sign in to comment.