Rewrite error index generator to greatly reduce the size of the pages

This commit is contained in:
Guillaume Gomez 2022-08-23 18:27:18 +02:00
parent a9bb589cd6
commit 8ad36c45f8
2 changed files with 111 additions and 227 deletions

View File

@ -1,31 +0,0 @@
use std::path::PathBuf;
use std::{env, fs};
use walkdir::WalkDir;
fn main() {
// The src directory (we are in src/tools/error_index_generator)
// Note that we could skip one of the .. but this ensures we at least loosely find the right
// directory.
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let error_codes_path = "../../../compiler/rustc_error_codes/src/error_codes.rs";
println!("cargo:rerun-if-changed={}", error_codes_path);
let file = fs::read_to_string(error_codes_path)
.unwrap()
.replace(": include_str!(\"./error_codes/", ": include_str!(\"./");
let contents = format!("(|| {{\n{}\n}})()", file);
fs::write(&out_dir.join("all_error_codes.rs"), &contents).unwrap();
// We copy the md files as well to the target directory.
for entry in WalkDir::new("../../../compiler/rustc_error_codes/src/error_codes") {
let entry = entry.unwrap();
match entry.path().extension() {
Some(s) if s == "md" => {}
_ => continue,
}
println!("cargo:rerun-if-changed={}", entry.path().to_str().unwrap());
let md_content = fs::read_to_string(entry.path()).unwrap();
fs::write(&out_dir.join(entry.file_name()), &md_content).unwrap();
}
}

View File

@ -3,11 +3,11 @@
extern crate rustc_driver;
extern crate rustc_span;
use std::cell::RefCell;
use std::collections::BTreeMap;
use crate::error_codes::error_codes;
use std::env;
use std::error::Error;
use std::fs::File;
use std::fs::{create_dir_all, File};
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
@ -16,49 +16,81 @@ use rustc_span::edition::DEFAULT_EDITION;
use rustdoc::html::markdown::{ErrorCodes, HeadingOffset, IdMap, Markdown, Playground};
pub struct ErrorMetadata {
pub description: Option<String>,
macro_rules! register_diagnostics {
($($error_code:ident: $message:expr,)+ ; $($undocumented:ident,)* ) => {
pub fn error_codes() -> Vec<(&'static str, Option<&'static str>)> {
let mut errors: Vec<(&str, Option<&str>)> = vec![
$((stringify!($error_code), Some($message)),)+
$((stringify!($undocumented), None),)+
];
errors.sort();
errors
}
}
}
/// Mapping from error codes to metadata that can be (de)serialized.
pub type ErrorMetadataMap = BTreeMap<String, ErrorMetadata>;
#[path = "../../../compiler/rustc_error_codes/src/error_codes.rs"]
mod error_codes;
enum OutputFormat {
HTML(HTMLFormatter),
Markdown(MarkdownFormatter),
Markdown,
Unknown(String),
}
impl OutputFormat {
fn from(format: &str, resource_suffix: &str) -> OutputFormat {
match &*format.to_lowercase() {
"html" => OutputFormat::HTML(HTMLFormatter(
RefCell::new(IdMap::new()),
resource_suffix.to_owned(),
)),
"markdown" => OutputFormat::Markdown(MarkdownFormatter),
"html" => OutputFormat::HTML(HTMLFormatter(resource_suffix.to_owned())),
"markdown" => OutputFormat::Markdown,
s => OutputFormat::Unknown(s.to_owned()),
}
}
}
trait Formatter {
fn header(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>>;
fn title(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>>;
fn error_code_block(
struct HTMLFormatter(String);
impl HTMLFormatter {
fn create_error_code_file(
&self,
output: &mut dyn Write,
info: &ErrorMetadata,
err_code: &str,
) -> Result<(), Box<dyn Error>>;
fn footer(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>>;
}
explanation: &str,
parent_dir: &Path,
) -> Result<(), Box<dyn Error>> {
let mut output_file = File::create(parent_dir.join(err_code).with_extension("html"))?;
struct HTMLFormatter(RefCell<IdMap>, String);
struct MarkdownFormatter;
self.header(&mut output_file, "../")?;
self.title(&mut output_file, &format!("Error code {}", err_code))?;
impl Formatter for HTMLFormatter {
fn header(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>> {
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,
"<p>\
<a style='text-align: center;display: block;width: 100%;' \
href='../error-index.html'>Back to list of error codes</a>\
</p>",
)?;
self.footer(&mut output_file)
}
fn header(&self, output: &mut dyn Write, extra: &str) -> Result<(), Box<dyn Error>> {
write!(
output,
r##"<!DOCTYPE html>
@ -67,9 +99,9 @@ impl Formatter for HTMLFormatter {
<title>Rust Compiler Error Index</title>
<meta charset="utf-8">
<!-- Include rust.css after light.css so its rules take priority. -->
<link rel="stylesheet" type="text/css" href="rustdoc{suffix}.css"/>
<link rel="stylesheet" type="text/css" href="light{suffix}.css"/>
<link rel="stylesheet" type="text/css" href="rust.css"/>
<link rel="stylesheet" type="text/css" href="{extra}rustdoc{suffix}.css"/>
<link rel="stylesheet" type="text/css" href="{extra}light{suffix}.css"/>
<link rel="stylesheet" type="text/css" href="{extra}rust.css"/>
<style>
.error-undescribed {{
display: none;
@ -78,177 +110,80 @@ impl Formatter for HTMLFormatter {
</head>
<body>
"##,
suffix = self.1
suffix = self.0,
)?;
Ok(())
}
fn title(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>> {
write!(output, "<h1>Rust Compiler Error Index</h1>\n")?;
Ok(())
}
fn error_code_block(
&self,
output: &mut dyn Write,
info: &ErrorMetadata,
err_code: &str,
) -> Result<(), Box<dyn Error>> {
// Enclose each error in a div so they can be shown/hidden en masse.
let desc_desc = match info.description {
Some(_) => "error-described",
None => "error-undescribed",
};
write!(output, "<div class=\"{}\">", desc_desc)?;
// Error title (with self-link).
write!(
output,
"<h2 id=\"{0}\" class=\"section-header\"><a href=\"#{0}\">{0}</a></h2>\n",
err_code
)?;
// Description rendered as markdown.
match info.description {
Some(ref desc) => {
let mut id_map = self.0.borrow_mut();
let playground = Playground {
crate_name: None,
url: String::from("https://play.rust-lang.org/"),
};
write!(
output,
"{}",
Markdown {
content: desc,
links: &[],
ids: &mut id_map,
error_codes: ErrorCodes::Yes,
edition: DEFAULT_EDITION,
playground: &Some(playground),
heading_offset: HeadingOffset::H1,
}
.into_string()
)?
}
None => write!(output, "<p>No description.</p>\n")?,
}
write!(output, "</div>\n")?;
fn title(&self, output: &mut dyn Write, title: &str) -> Result<(), Box<dyn Error>> {
write!(output, "<h1>{}</h1>\n", title)?;
Ok(())
}
fn footer(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>> {
write!(
output,
r##"<script>
function onEach(arr, func) {{
if (arr && arr.length > 0 && func) {{
var length = arr.length;
var i;
for (i = 0; i < length; ++i) {{
if (func(arr[i])) {{
return true;
}}
}}
}}
return false;
}}
function onEachLazy(lazyArray, func) {{
return onEach(
Array.prototype.slice.call(lazyArray),
func);
}}
function hasClass(elem, className) {{
return elem && elem.classList && elem.classList.contains(className);
}}
onEachLazy(document.getElementsByClassName('rust-example-rendered'), function(e) {{
if (hasClass(e, 'compile_fail')) {{
e.addEventListener("mouseover", function(event) {{
e.parentElement.previousElementSibling.childNodes[0].style.color = '#f00';
}});
e.addEventListener("mouseout", function(event) {{
e.parentElement.previousElementSibling.childNodes[0].style.color = '';
}});
}} else if (hasClass(e, 'ignore')) {{
e.addEventListener("mouseover", function(event) {{
e.parentElement.previousElementSibling.childNodes[0].style.color = '#ff9200';
}});
e.addEventListener("mouseout", function(event) {{
e.parentElement.previousElementSibling.childNodes[0].style.color = '';
}});
}}
}});
</script>
</body>
</html>"##
)?;
Ok(())
}
}
impl Formatter for MarkdownFormatter {
#[allow(unused_variables)]
fn header(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>> {
Ok(())
}
fn title(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>> {
write!(output, "# Rust Compiler Error Index\n")?;
Ok(())
}
fn error_code_block(
&self,
output: &mut dyn Write,
info: &ErrorMetadata,
err_code: &str,
) -> Result<(), Box<dyn Error>> {
Ok(match info.description {
Some(ref desc) => write!(output, "## {}\n{}\n", err_code, desc)?,
None => (),
})
}
#[allow(unused_variables)]
fn footer(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>> {
write!(output, "</body></html>")?;
Ok(())
}
}
/// Output an HTML page for the errors in `err_map` to `output_path`.
fn render_error_page<T: Formatter>(
err_map: &ErrorMetadataMap,
output_path: &Path,
formatter: T,
) -> Result<(), Box<dyn Error>> {
fn render_markdown(output_path: &Path) -> Result<(), Box<dyn Error>> {
let mut output_file = File::create(output_path)?;
formatter.header(&mut output_file)?;
formatter.title(&mut output_file)?;
write!(output_file, "# Rust Compiler Error Index\n")?;
for (err_code, info) in err_map {
formatter.error_code_block(&mut output_file, info, err_code)?;
for (err_code, description) in error_codes().iter() {
match description {
Some(ref desc) => write!(output_file, "## {}\n{}\n", err_code, desc)?,
None => {}
}
}
Ok(())
}
fn render_html(output_path: &Path, formatter: HTMLFormatter) -> Result<(), Box<dyn Error>> {
let mut output_file = File::create(output_path)?;
let error_codes_dir = "error_codes";
let parent = output_path.parent().expect("There should have a parent").join(error_codes_dir);
if !parent.exists() {
create_dir_all(&parent)?;
}
formatter.header(&mut output_file, "")?;
formatter.title(&mut output_file, "Rust Compiler Error Index")?;
write!(
output_file,
"<p>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.</p>\
<ul>",
)?;
for (err_code, explanation) in error_codes().iter() {
if let Some(explanation) = explanation {
write!(
output_file,
"<li><a href='./{0}/{1}.html'>{1}</a></li>",
error_codes_dir, err_code
)?;
formatter.create_error_code_file(err_code, explanation, &parent)?;
} else {
write!(output_file, "<li>{}</li>", err_code)?;
}
}
write!(output_file, "</ul>")?;
formatter.footer(&mut output_file)
}
fn main_with_result(format: OutputFormat, dst: &Path) -> Result<(), Box<dyn Error>> {
let long_codes = register_all();
let mut err_map = BTreeMap::new();
for (code, desc) in long_codes {
err_map.insert(code.to_string(), ErrorMetadata { description: desc.map(String::from) });
}
match format {
OutputFormat::Unknown(s) => panic!("Unknown output format: {}", s),
OutputFormat::HTML(h) => render_error_page(&err_map, dst, h)?,
OutputFormat::Markdown(m) => render_error_page(&err_map, dst, m)?,
OutputFormat::HTML(h) => render_html(dst, h),
OutputFormat::Markdown => render_markdown(dst),
}
Ok(())
}
fn parse_args() -> (OutputFormat, PathBuf) {
@ -261,7 +196,7 @@ fn parse_args() -> (OutputFormat, PathBuf) {
.unwrap_or(OutputFormat::from("html", &resource_suffix));
let dst = dst.map(PathBuf::from).unwrap_or_else(|| match format {
OutputFormat::HTML(..) => PathBuf::from("doc/error-index.html"),
OutputFormat::Markdown(..) => PathBuf::from("doc/error-index.md"),
OutputFormat::Markdown => PathBuf::from("doc/error-index.md"),
OutputFormat::Unknown(..) => PathBuf::from("<nul>"),
});
(format, dst)
@ -276,23 +211,3 @@ fn main() {
panic!("{}", e.to_string());
}
}
fn register_all() -> Vec<(&'static str, Option<&'static str>)> {
let mut long_codes: Vec<(&'static str, Option<&'static str>)> = Vec::new();
macro_rules! register_diagnostics {
($($ecode:ident: $message:expr,)* ; $($code:ident,)*) => (
$(
{long_codes.extend([
(stringify!($ecode), Some($message)),
].iter());}
)*
$(
{long_codes.extend([
stringify!($code),
].iter().cloned().map(|s| (s, None)).collect::<Vec<_>>());}
)*
)
}
include!(concat!(env!("OUT_DIR"), "/all_error_codes.rs"));
long_codes
}