From 117169799ffb6df9a86f4d3e933968274022bdd3 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 29 Aug 2022 18:05:21 +0200 Subject: [PATCH 1/2] Generate error index with mdbook instead of raw HTML pages --- Cargo.lock | 2 +- src/bootstrap/doc.rs | 2 +- src/tools/error_index_generator/Cargo.toml | 2 +- .../error_index_generator/book_config.toml | 19 ++ .../error_index_generator/error-index.css | 38 +++ .../error_index_generator/error-index.js | 9 + src/tools/error_index_generator/main.rs | 273 +++++++++--------- src/tools/error_index_generator/redirect.js | 10 + 8 files changed, 214 insertions(+), 141 deletions(-) create mode 100644 src/tools/error_index_generator/book_config.toml create mode 100644 src/tools/error_index_generator/error-index.css create mode 100644 src/tools/error_index_generator/error-index.js create mode 100644 src/tools/error_index_generator/redirect.js diff --git a/Cargo.lock b/Cargo.lock index 7c3879fdd98d4..8dff674a630ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1268,7 +1268,7 @@ dependencies = [ name = "error_index_generator" version = "0.0.0" dependencies = [ - "rustdoc", + "mdbook", ] [[package]] diff --git a/src/bootstrap/doc.rs b/src/bootstrap/doc.rs index 2852442d0be6f..f909ecc0ab858 100644 --- a/src/bootstrap/doc.rs +++ b/src/bootstrap/doc.rs @@ -793,7 +793,7 @@ impl Step for ErrorIndex { t!(fs::create_dir_all(&out)); let mut index = tool::ErrorIndex::command(builder); index.arg("html"); - index.arg(out.join("error-index.html")); + index.arg(out); index.arg(&builder.version); builder.run(&mut index); diff --git a/src/tools/error_index_generator/Cargo.toml b/src/tools/error_index_generator/Cargo.toml index b9fd852f742cf..f4dac6e947e32 100644 --- a/src/tools/error_index_generator/Cargo.toml +++ b/src/tools/error_index_generator/Cargo.toml @@ -4,7 +4,7 @@ version = "0.0.0" edition = "2021" [dependencies] -rustdoc = { path = "../../librustdoc" } +mdbook = { version = "0.4", default-features = false, features = ["search"] } [[bin]] name = "error_index_generator" diff --git a/src/tools/error_index_generator/book_config.toml b/src/tools/error_index_generator/book_config.toml new file mode 100644 index 0000000000000..885100ae3a449 --- /dev/null +++ b/src/tools/error_index_generator/book_config.toml @@ -0,0 +1,19 @@ +[book] +title = "Error codes index" +description = "Book listing all Rust error codes" +src = "" + +[output.html] +git-repository-url = "https://github.com/rust-lang/rust/" +additional-css = ["error-index.css"] +additional-js = ["error-index.js"] + +[output.html.search] +enable = true +limit-results = 20 +use-boolean-and = true +boost-title = 2 +boost-hierarchy = 2 +boost-paragraph = 1 +expand = true +heading-split-level = 0 diff --git a/src/tools/error_index_generator/error-index.css b/src/tools/error_index_generator/error-index.css new file mode 100644 index 0000000000000..8975af82de03b --- /dev/null +++ b/src/tools/error_index_generator/error-index.css @@ -0,0 +1,38 @@ +code.compile_fail { + border-left: 2px solid red; +} + +pre .tooltip { + position: absolute; + left: -25px; + top: 0; + z-index: 1; + color: red; + cursor: pointer; +} +pre .tooltip::after { + display: none; + content: "This example deliberately fails to compile"; + background-color: #000; + color: #fff; + border-color: #000; + text-align: center; + padding: 5px 3px 3px 3px; + border-radius: 6px; + margin-left: 5px; +} +pre .tooltip::before { + display: none; + border-color: transparent black transparent transparent; + content: " "; + position: absolute; + top: 50%; + left: 16px; + margin-top: -5px; + border-width: 5px; + border-style: solid; +} + +pre .tooltip:hover::before, pre .tooltip:hover::after { + display: inline; +} diff --git a/src/tools/error_index_generator/error-index.js b/src/tools/error_index_generator/error-index.js new file mode 100644 index 0000000000000..39b371be04b95 --- /dev/null +++ b/src/tools/error_index_generator/error-index.js @@ -0,0 +1,9 @@ +for (const elem of document.querySelectorAll("pre.playground")) { + if (elem.querySelector(".compile_fail") === null) { + continue; + } + const child = document.createElement("div"); + child.className = "tooltip"; + child.textContent = "ⓘ"; + elem.appendChild(child); +} diff --git a/src/tools/error_index_generator/main.rs b/src/tools/error_index_generator/main.rs index 68c46700361a8..5451e45b28be0 100644 --- a/src/tools/error_index_generator/main.rs +++ b/src/tools/error_index_generator/main.rs @@ -1,20 +1,21 @@ #![feature(rustc_private)] extern crate rustc_driver; -extern crate rustc_span; +// We use the function we generate from `register_diagnostics!`. use crate::error_codes::error_codes; use std::env; use std::error::Error; -use std::fs::{create_dir_all, File}; +use std::fs::{self, create_dir_all, File}; use std::io::Write; use std::path::Path; use std::path::PathBuf; -use rustc_span::edition::DEFAULT_EDITION; +use std::str::FromStr; -use rustdoc::html::markdown::{ErrorCodes, HeadingOffset, IdMap, Markdown, Playground}; +use mdbook::book::{parse_summary, BookItem, Chapter}; +use mdbook::{Config, MDBook}; macro_rules! register_diagnostics { ($($error_code:ident: $message:expr,)+ ; $($undocumented:ident,)* ) => { @@ -33,104 +34,21 @@ macro_rules! register_diagnostics { mod error_codes; enum OutputFormat { - HTML(HTMLFormatter), + HTML, Markdown, Unknown(String), } impl OutputFormat { - fn from(format: &str, resource_suffix: &str) -> OutputFormat { + fn from(format: &str) -> OutputFormat { match &*format.to_lowercase() { - "html" => OutputFormat::HTML(HTMLFormatter(resource_suffix.to_owned())), + "html" => OutputFormat::HTML, "markdown" => OutputFormat::Markdown, s => OutputFormat::Unknown(s.to_owned()), } } } -struct HTMLFormatter(String); - -impl HTMLFormatter { - fn create_error_code_file( - &self, - err_code: &str, - explanation: &str, - parent_dir: &Path, - ) -> Result<(), Box> { - let mut output_file = File::create(parent_dir.join(err_code).with_extension("html"))?; - - self.header(&mut output_file, "../", "")?; - self.title(&mut output_file, &format!("Error code {}", err_code))?; - - let mut id_map = IdMap::new(); - let playground = - Playground { crate_name: None, url: String::from("https://play.rust-lang.org/") }; - write!( - output_file, - "{}", - Markdown { - content: explanation, - links: &[], - ids: &mut id_map, - error_codes: ErrorCodes::Yes, - edition: DEFAULT_EDITION, - playground: &Some(playground), - heading_offset: HeadingOffset::H1, - } - .into_string() - )?; - write!( - output_file, - "

\ - Back to list of error codes\ -

", - )?; - - self.footer(&mut output_file) - } - - fn header( - &self, - output: &mut dyn Write, - extra_path: &str, - extra: &str, - ) -> Result<(), Box> { - write!( - output, - r##" - - -Rust Compiler Error Index - - - - - -{extra} - - -"##, - suffix = self.0, - )?; - Ok(()) - } - - fn title(&self, output: &mut dyn Write, title: &str) -> Result<(), Box> { - write!(output, "

{}

\n", title)?; - Ok(()) - } - - fn footer(&self, output: &mut dyn Write) -> Result<(), Box> { - write!(output, "")?; - Ok(()) - } -} - /// Output an HTML page for the errors in `err_map` to `output_path`. fn render_markdown(output_path: &Path) -> Result<(), Box> { let mut output_file = File::create(output_path)?; @@ -147,61 +65,144 @@ fn render_markdown(output_path: &Path) -> Result<(), Box> { Ok(()) } -fn render_html(output_path: &Path, formatter: HTMLFormatter) -> Result<(), Box> { - let mut output_file = File::create(output_path)?; +fn move_folder(source: &Path, target: &Path) -> Result<(), Box> { + let entries = + fs::read_dir(source)?.map(|res| res.map(|e| e.path())).collect::, _>>()?; - let error_codes_dir = "error_codes"; + for entry in entries { + let file_name = entry.file_name().expect("file_name() failed").to_os_string(); + let output = target.join(file_name); + if entry.is_file() { + fs::rename(entry, output)?; + } else { + if !output.exists() { + create_dir_all(&output)?; + } + move_folder(&entry, &output)?; + } + } + + fs::remove_dir(&source)?; + + Ok(()) +} - let parent = output_path.parent().expect("There should have a parent").join(error_codes_dir); +fn render_html(output_path: &Path) -> Result<(), Box> { + // We need to render into a temporary folder to prevent `mdbook` from removing everything + // in the output folder (including other completely unrelated things). + let tmp_output = output_path.join("tmp"); - if !parent.exists() { - create_dir_all(&parent)?; + if !tmp_output.exists() { + create_dir_all(&tmp_output)?; } - formatter.header( - &mut output_file, - "", - &format!( - r#""# - ), - )?; - formatter.title(&mut output_file, "Rust Compiler Error Index")?; + render_html_inner(&tmp_output)?; - write!( - output_file, - "

This page lists all the error codes emitted by the Rust compiler. If you want a full \ - explanation on an error code, click on it.

\ -
    ", - )?; - for (err_code, explanation) in error_codes().iter() { + move_folder(&tmp_output, output_path)?; + + Ok(()) +} + +// By default, mdbook doesn't consider code blocks as Rust ones contrary to rustdoc so we have +// to manually add `rust` attribute whenever needed. +fn add_rust_attribute_on_codeblock(explanation: &str) -> String { + // Very hacky way to add the rust attribute on all code blocks. + let mut skip = true; + explanation.split("\n```").fold(String::new(), |mut acc, part| { + if !acc.is_empty() { + acc.push_str("\n```"); + } + if !skip { + if let Some(attrs) = part.split('\n').next() { + if !attrs.contains("rust") + && (attrs.is_empty() + || attrs.contains("compile_fail") + || attrs.contains("ignore") + || attrs.contains("edition")) + { + if !attrs.is_empty() { + acc.push_str("rust,"); + } else { + acc.push_str("rust"); + } + } + } + } + skip = !skip; + acc.push_str(part); + acc + }) +} + +fn render_html_inner(output_path: &Path) -> Result<(), Box> { + // We need to have a little difference between `summary` and `introduction` because the "draft" + // chapters (the ones looking like `[a]()`) are not handled correctly when being put into a + // `Chapter` directly: they generate a link whereas they shouldn't. + let mut introduction = format!( + " +# Rust error codes index + +This page lists all the error codes emitted by the Rust compiler. + +", + include_str!("redirect.js") + ); + + let err_codes = error_codes(); + let mut chapters = Vec::with_capacity(err_codes.len()); + + for (err_code, explanation) in err_codes.iter() { if let Some(explanation) = explanation { - write!( - output_file, - "
  • {1}
  • ", - error_codes_dir, err_code - )?; - formatter.create_error_code_file(err_code, explanation, &parent)?; + introduction.push_str(&format!(" * [{0}](./error_codes/{0}.html)\n", err_code)); + + let content = add_rust_attribute_on_codeblock(explanation); + chapters.push(BookItem::Chapter(Chapter { + name: err_code.to_string(), + content: format!("# Error code {}\n\n{}\n", err_code, content), + number: None, + sub_items: Vec::new(), + // We generate it into the `error_codes` folder. + path: Some(PathBuf::from(&format!("error_codes/{}.html", err_code))), + source_path: None, + parent_names: Vec::new(), + })); } else { - write!(output_file, "
  • {}
  • ", err_code)?; + introduction.push_str(&format!(" * {}\n", err_code)); } } - write!(output_file, "
")?; - formatter.footer(&mut output_file) + + let mut config = Config::from_str(include_str!("book_config.toml"))?; + config.build.build_dir = output_path.to_path_buf(); + let mut book = MDBook::load_with_config_and_summary( + env!("CARGO_MANIFEST_DIR"), + config, + parse_summary("")?, + )?; + let chapter = Chapter { + name: "Rust error codes index".to_owned(), + content: introduction, + number: None, + sub_items: chapters, + // Very important: this file is named as `error-index.html` and not `index.html`! + path: Some(PathBuf::from("error-index.html")), + source_path: None, + parent_names: Vec::new(), + }; + book.book.sections.push(BookItem::Chapter(chapter)); + book.build()?; + + // We don't need this file since it's handled by doc.rust-lang.org directly. + let _ = fs::remove_file(output_path.join("404.html")); + // We don't want this file either because it would overwrite the already existing `index.html`. + let _ = fs::remove_file(output_path.join("index.html")); + + Ok(()) } fn main_with_result(format: OutputFormat, dst: &Path) -> Result<(), Box> { match format { OutputFormat::Unknown(s) => panic!("Unknown output format: {}", s), - OutputFormat::HTML(h) => render_html(dst, h), + OutputFormat::HTML => render_html(dst), OutputFormat::Markdown => render_markdown(dst), } } @@ -210,12 +211,9 @@ fn parse_args() -> (OutputFormat, PathBuf) { let mut args = env::args().skip(1); let format = args.next(); let dst = args.next(); - let resource_suffix = args.next().unwrap_or_else(String::new); - let format = format - .map(|a| OutputFormat::from(&a, &resource_suffix)) - .unwrap_or(OutputFormat::from("html", &resource_suffix)); + let format = format.map(|a| OutputFormat::from(&a)).unwrap_or(OutputFormat::from("html")); let dst = dst.map(PathBuf::from).unwrap_or_else(|| match format { - OutputFormat::HTML(..) => PathBuf::from("doc/error-index.html"), + OutputFormat::HTML => PathBuf::from("doc"), OutputFormat::Markdown => PathBuf::from("doc/error-index.md"), OutputFormat::Unknown(..) => PathBuf::from(""), }); @@ -225,9 +223,8 @@ fn parse_args() -> (OutputFormat, PathBuf) { fn main() { rustc_driver::init_env_logger("RUST_LOG"); let (format, dst) = parse_args(); - let result = - rustc_span::create_default_session_globals_then(move || main_with_result(format, &dst)); + let result = main_with_result(format, &dst); if let Err(e) = result { - panic!("{}", e.to_string()); + panic!("{:?}", e); } } diff --git a/src/tools/error_index_generator/redirect.js b/src/tools/error_index_generator/redirect.js new file mode 100644 index 0000000000000..e6e910658e483 --- /dev/null +++ b/src/tools/error_index_generator/redirect.js @@ -0,0 +1,10 @@ +(function() {{ + if (window.location.hash) {{ + let code = window.location.hash.replace(/^#/, ''); + // We have to make sure this pattern matches to avoid inadvertently creating an + // open redirect. + if (/^E[0-9]+$/.test(code)) {{ + window.location = './error_codes/' + code + '.html'; + }} + }} +}})() From f5857d5c5e1d2fde302f330d11c5cdea8005eb2a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 31 Aug 2022 21:00:05 +0200 Subject: [PATCH 2/2] Move error code book into a sub folder --- src/tools/error_index_generator/main.rs | 81 +++++++-------------- src/tools/error_index_generator/redirect.js | 18 +++-- 2 files changed, 40 insertions(+), 59 deletions(-) diff --git a/src/tools/error_index_generator/main.rs b/src/tools/error_index_generator/main.rs index 5451e45b28be0..1bde8e007826d 100644 --- a/src/tools/error_index_generator/main.rs +++ b/src/tools/error_index_generator/main.rs @@ -7,7 +7,7 @@ use crate::error_codes::error_codes; use std::env; use std::error::Error; -use std::fs::{self, create_dir_all, File}; +use std::fs::{self, File}; use std::io::Write; use std::path::Path; use std::path::PathBuf; @@ -65,44 +65,6 @@ fn render_markdown(output_path: &Path) -> Result<(), Box> { Ok(()) } -fn move_folder(source: &Path, target: &Path) -> Result<(), Box> { - let entries = - fs::read_dir(source)?.map(|res| res.map(|e| e.path())).collect::, _>>()?; - - for entry in entries { - let file_name = entry.file_name().expect("file_name() failed").to_os_string(); - let output = target.join(file_name); - if entry.is_file() { - fs::rename(entry, output)?; - } else { - if !output.exists() { - create_dir_all(&output)?; - } - move_folder(&entry, &output)?; - } - } - - fs::remove_dir(&source)?; - - Ok(()) -} - -fn render_html(output_path: &Path) -> Result<(), Box> { - // We need to render into a temporary folder to prevent `mdbook` from removing everything - // in the output folder (including other completely unrelated things). - let tmp_output = output_path.join("tmp"); - - if !tmp_output.exists() { - create_dir_all(&tmp_output)?; - } - - render_html_inner(&tmp_output)?; - - move_folder(&tmp_output, output_path)?; - - Ok(()) -} - // By default, mdbook doesn't consider code blocks as Rust ones contrary to rustdoc so we have // to manually add `rust` attribute whenever needed. fn add_rust_attribute_on_codeblock(explanation: &str) -> String { @@ -134,18 +96,14 @@ fn add_rust_attribute_on_codeblock(explanation: &str) -> String { }) } -fn render_html_inner(output_path: &Path) -> Result<(), Box> { - // We need to have a little difference between `summary` and `introduction` because the "draft" - // chapters (the ones looking like `[a]()`) are not handled correctly when being put into a - // `Chapter` directly: they generate a link whereas they shouldn't. +fn render_html(output_path: &Path) -> Result<(), Box> { let mut introduction = format!( - " + " # Rust error codes index This page lists all the error codes emitted by the Rust compiler. -", - include_str!("redirect.js") +" ); let err_codes = error_codes(); @@ -153,7 +111,7 @@ This page lists all the error codes emitted by the Rust compiler. for (err_code, explanation) in err_codes.iter() { if let Some(explanation) = explanation { - introduction.push_str(&format!(" * [{0}](./error_codes/{0}.html)\n", err_code)); + introduction.push_str(&format!(" * [{0}](./{0}.html)\n", err_code)); let content = add_rust_attribute_on_codeblock(explanation); chapters.push(BookItem::Chapter(Chapter { @@ -162,7 +120,7 @@ This page lists all the error codes emitted by the Rust compiler. number: None, sub_items: Vec::new(), // We generate it into the `error_codes` folder. - path: Some(PathBuf::from(&format!("error_codes/{}.html", err_code))), + path: Some(PathBuf::from(&format!("{}.html", err_code))), source_path: None, parent_names: Vec::new(), })); @@ -172,7 +130,7 @@ This page lists all the error codes emitted by the Rust compiler. } let mut config = Config::from_str(include_str!("book_config.toml"))?; - config.build.build_dir = output_path.to_path_buf(); + config.build.build_dir = output_path.join("error_codes").to_path_buf(); let mut book = MDBook::load_with_config_and_summary( env!("CARGO_MANIFEST_DIR"), config, @@ -191,10 +149,27 @@ This page lists all the error codes emitted by the Rust compiler. book.book.sections.push(BookItem::Chapter(chapter)); book.build()?; - // We don't need this file since it's handled by doc.rust-lang.org directly. - let _ = fs::remove_file(output_path.join("404.html")); - // We don't want this file either because it would overwrite the already existing `index.html`. - let _ = fs::remove_file(output_path.join("index.html")); + // We can't put this content into another file, otherwise `mbdbook` will also put it into the + // output directory, making a duplicate. + fs::write( + output_path.join("error-index.html"), + r#" + + + Rust error codes index - Error codes index + + + + + +
If you are not automatically redirected to the error code index, please here. + + +"#, + )?; + + // No need for a 404 file, it's already handled by the server. + fs::remove_file(output_path.join("error_codes/404.html"))?; Ok(()) } diff --git a/src/tools/error_index_generator/redirect.js b/src/tools/error_index_generator/redirect.js index e6e910658e483..8c907f5795d32 100644 --- a/src/tools/error_index_generator/redirect.js +++ b/src/tools/error_index_generator/redirect.js @@ -1,10 +1,16 @@ -(function() {{ - if (window.location.hash) {{ +(function() { + if (window.location.hash) { let code = window.location.hash.replace(/^#/, ''); // We have to make sure this pattern matches to avoid inadvertently creating an // open redirect. - if (/^E[0-9]+$/.test(code)) {{ + if (!/^E[0-9]+$/.test(code)) { + return; + } + if (window.location.pathname.indexOf("/error_codes/") !== -1) { + // We're not at the top level, so we don't prepend with "./error_codes/". + window.location = './' + code + '.html'; + } else { window.location = './error_codes/' + code + '.html'; - }} - }} -}})() + } + } +})()