]> git.lizzy.rs Git - rust.git/blob - src/tools/error_index_generator/main.rs
Auto merge of #100848 - xfix:use-metadata-for-slice-len, r=thomcc
[rust.git] / src / tools / error_index_generator / main.rs
1 #![feature(rustc_private)]
2
3 extern crate rustc_driver;
4
5 // We use the function we generate from `register_diagnostics!`.
6 use crate::error_codes::error_codes;
7
8 use std::env;
9 use std::error::Error;
10 use std::fs::{self, File};
11 use std::io::Write;
12 use std::path::Path;
13 use std::path::PathBuf;
14
15 use std::str::FromStr;
16
17 use mdbook::book::{parse_summary, BookItem, Chapter};
18 use mdbook::{Config, MDBook};
19
20 macro_rules! register_diagnostics {
21     ($($error_code:ident: $message:expr,)+ ; $($undocumented:ident,)* ) => {
22         pub fn error_codes() -> Vec<(&'static str, Option<&'static str>)> {
23             let mut errors: Vec<(&str, Option<&str>)> = vec![
24                 $((stringify!($error_code), Some($message)),)+
25                 $((stringify!($undocumented), None),)+
26             ];
27             errors.sort();
28             errors
29         }
30     }
31 }
32
33 #[path = "../../../compiler/rustc_error_codes/src/error_codes.rs"]
34 mod error_codes;
35
36 enum OutputFormat {
37     HTML,
38     Markdown,
39     Unknown(String),
40 }
41
42 impl OutputFormat {
43     fn from(format: &str) -> OutputFormat {
44         match &*format.to_lowercase() {
45             "html" => OutputFormat::HTML,
46             "markdown" => OutputFormat::Markdown,
47             s => OutputFormat::Unknown(s.to_owned()),
48         }
49     }
50 }
51
52 /// Output an HTML page for the errors in `err_map` to `output_path`.
53 fn render_markdown(output_path: &Path) -> Result<(), Box<dyn Error>> {
54     let mut output_file = File::create(output_path)?;
55
56     write!(output_file, "# Rust Compiler Error Index\n")?;
57
58     for (err_code, description) in error_codes().iter() {
59         match description {
60             Some(ref desc) => write!(output_file, "## {}\n{}\n", err_code, desc)?,
61             None => {}
62         }
63     }
64
65     Ok(())
66 }
67
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 }
98
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
103
104 This page lists all the error codes emitted by the Rust compiler.
105
106 "
107     );
108
109     let err_codes = error_codes();
110     let mut chapters = Vec::with_capacity(err_codes.len());
111
112     for (err_code, explanation) in err_codes.iter() {
113         if let Some(explanation) = explanation {
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             }));
127         } else {
128             introduction.push_str(&format!(" * {}\n", err_code));
129         }
130     }
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(())
175 }
176
177 fn main_with_result(format: OutputFormat, dst: &Path) -> Result<(), Box<dyn Error>> {
178     match format {
179         OutputFormat::Unknown(s) => panic!("Unknown output format: {}", s),
180         OutputFormat::HTML => render_html(dst),
181         OutputFormat::Markdown => render_markdown(dst),
182     }
183 }
184
185 fn parse_args() -> (OutputFormat, PathBuf) {
186     let mut args = env::args().skip(1);
187     let format = args.next();
188     let dst = args.next();
189     let format = format.map(|a| OutputFormat::from(&a)).unwrap_or(OutputFormat::from("html"));
190     let dst = dst.map(PathBuf::from).unwrap_or_else(|| match format {
191         OutputFormat::HTML => PathBuf::from("doc"),
192         OutputFormat::Markdown => PathBuf::from("doc/error-index.md"),
193         OutputFormat::Unknown(..) => PathBuf::from("<nul>"),
194     });
195     (format, dst)
196 }
197
198 fn main() {
199     rustc_driver::init_env_logger("RUST_LOG");
200     let (format, dst) = parse_args();
201     let result = main_with_result(format, &dst);
202     if let Err(e) = result {
203         panic!("{:?}", e);
204     }
205 }