]> git.lizzy.rs Git - rust.git/blob - src/tools/error_index_generator/main.rs
Rollup merge of #106813 - oli-obk:sess_cleanup, r=GuillaumeGomez,petrochenkov
[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         "# Rust error codes index
102
103 This page lists all the error codes emitted by the Rust compiler.
104
105 "
106     );
107
108     let err_codes = error_codes();
109     let mut chapters = Vec::with_capacity(err_codes.len());
110
111     for (err_code, explanation) in err_codes.iter() {
112         if let Some(explanation) = explanation {
113             introduction.push_str(&format!(" * [{0}](./{0}.html)\n", err_code));
114
115             let content = add_rust_attribute_on_codeblock(explanation);
116             chapters.push(BookItem::Chapter(Chapter {
117                 name: err_code.to_string(),
118                 content: format!("# Error code {}\n\n{}\n", err_code, content),
119                 number: None,
120                 sub_items: Vec::new(),
121                 // We generate it into the `error_codes` folder.
122                 path: Some(PathBuf::from(&format!("{}.html", err_code))),
123                 source_path: None,
124                 parent_names: Vec::new(),
125             }));
126         } else {
127             introduction.push_str(&format!(" * {}\n", err_code));
128         }
129     }
130
131     let mut config = Config::from_str(include_str!("book_config.toml"))?;
132     config.build.build_dir = output_path.join("error_codes").to_path_buf();
133     let mut book = MDBook::load_with_config_and_summary(
134         env!("CARGO_MANIFEST_DIR"),
135         config,
136         parse_summary("")?,
137     )?;
138     let chapter = Chapter {
139         name: "Rust error codes index".to_owned(),
140         content: introduction,
141         number: None,
142         sub_items: chapters,
143         // Very important: this file is named as `error-index.html` and not `index.html`!
144         path: Some(PathBuf::from("error-index.html")),
145         source_path: None,
146         parent_names: Vec::new(),
147     };
148     book.book.sections.push(BookItem::Chapter(chapter));
149     book.build()?;
150
151     // The error-index used to be generated manually (without mdbook), and the
152     // index was located at the top level. Now that it is generated with
153     // mdbook, error-index.html has moved to error_codes/error-index.html.
154     // This adds a redirect so that old links go to the new location.
155     //
156     // We can't put this content into another file, otherwise `mdbook` will also put it into the
157     // output directory, making a duplicate.
158     fs::write(
159         output_path.join("error-index.html"),
160         r#"<!DOCTYPE html>
161 <html>
162     <head>
163         <title>Rust error codes index - Error codes index</title>
164         <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
165         <meta name="description" content="Book listing all Rust error codes">
166         <script src="error_codes/redirect.js"></script>
167     </head>
168     <body>
169         <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>.
170     </body>
171 </html>"#,
172     )?;
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 }