]> git.lizzy.rs Git - rust.git/blob - src/tools/error_index_generator/main.rs
Simplify SaveHandler trait
[rust.git] / src / tools / error_index_generator / main.rs
1 #![feature(rustc_private)]
2
3 #![deny(rust_2018_idioms)]
4
5 extern crate env_logger;
6 extern crate syntax;
7 extern crate serialize as rustc_serialize;
8
9 use std::collections::BTreeMap;
10 use std::env;
11 use std::error::Error;
12 use std::fs::{self, read_dir, File};
13 use std::io::Write;
14 use std::path::Path;
15 use std::path::PathBuf;
16 use std::cell::RefCell;
17
18 use syntax::edition::DEFAULT_EDITION;
19 use syntax::diagnostics::metadata::{get_metadata_dir, ErrorMetadataMap, ErrorMetadata};
20
21 use rustdoc::html::markdown::{Markdown, IdMap, ErrorCodes, PLAYGROUND};
22 use rustc_serialize::json;
23
24 enum OutputFormat {
25     HTML(HTMLFormatter),
26     Markdown(MarkdownFormatter),
27     Unknown(String),
28 }
29
30 impl OutputFormat {
31     fn from(format: &str, resource_suffix: &str) -> OutputFormat {
32         match &*format.to_lowercase() {
33             "html"     => OutputFormat::HTML(HTMLFormatter(RefCell::new(IdMap::new()),
34                                                            resource_suffix.to_owned())),
35             "markdown" => OutputFormat::Markdown(MarkdownFormatter),
36             s          => OutputFormat::Unknown(s.to_owned()),
37         }
38     }
39 }
40
41 trait Formatter {
42     fn header(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>>;
43     fn title(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>>;
44     fn error_code_block(&self, output: &mut dyn Write, info: &ErrorMetadata,
45                         err_code: &str) -> Result<(), Box<dyn Error>>;
46     fn footer(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>>;
47 }
48
49 struct HTMLFormatter(RefCell<IdMap>, String);
50 struct MarkdownFormatter;
51
52 impl Formatter for HTMLFormatter {
53     fn header(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>> {
54         write!(output, r##"<!DOCTYPE html>
55 <html>
56 <head>
57 <title>Rust Compiler Error Index</title>
58 <meta charset="utf-8">
59 <!-- Include rust.css after light.css so its rules take priority. -->
60 <link rel="stylesheet" type="text/css" href="light{suffix}.css"/>
61 <link rel="stylesheet" type="text/css" href="rust.css"/>
62 <style>
63 .error-undescribed {{
64     display: none;
65 }}
66 </style>
67 </head>
68 <body>
69 "##, suffix=self.1)?;
70         Ok(())
71     }
72
73     fn title(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>> {
74         write!(output, "<h1>Rust Compiler Error Index</h1>\n")?;
75         Ok(())
76     }
77
78     fn error_code_block(&self, output: &mut dyn Write, info: &ErrorMetadata,
79                         err_code: &str) -> Result<(), Box<dyn Error>> {
80         // Enclose each error in a div so they can be shown/hidden en masse.
81         let desc_desc = match info.description {
82             Some(_) => "error-described",
83             None => "error-undescribed",
84         };
85         let use_desc = match info.use_site {
86             Some(_) => "error-used",
87             None => "error-unused",
88         };
89         write!(output, "<div class=\"{} {}\">", desc_desc, use_desc)?;
90
91         // Error title (with self-link).
92         write!(output,
93                "<h2 id=\"{0}\" class=\"section-header\"><a href=\"#{0}\">{0}</a></h2>\n",
94                err_code)?;
95
96         // Description rendered as markdown.
97         match info.description {
98             Some(ref desc) => {
99                 let mut id_map = self.0.borrow_mut();
100                 write!(output, "{}",
101                     Markdown(desc, &[], RefCell::new(&mut id_map),
102                              ErrorCodes::Yes, DEFAULT_EDITION))?
103             },
104             None => write!(output, "<p>No description.</p>\n")?,
105         }
106
107         write!(output, "</div>\n")?;
108         Ok(())
109     }
110
111     fn footer(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>> {
112         write!(output, r##"<script>
113 function onEach(arr, func) {{
114     if (arr && arr.length > 0 && func) {{
115         for (var i = 0; i < arr.length; i++) {{
116             func(arr[i]);
117         }}
118     }}
119 }}
120
121 function hasClass(elem, className) {{
122     if (elem && className && elem.className) {{
123         var elemClass = elem.className;
124         var start = elemClass.indexOf(className);
125         if (start === -1) {{
126             return false;
127         }} else if (elemClass.length === className.length) {{
128             return true;
129         }} else {{
130             if (start > 0 && elemClass[start - 1] !== ' ') {{
131                 return false;
132             }}
133             var end = start + className.length;
134             if (end < elemClass.length && elemClass[end] !== ' ') {{
135                 return false;
136             }}
137             return true;
138         }}
139         if (start > 0 && elemClass[start - 1] !== ' ') {{
140             return false;
141         }}
142         var end = start + className.length;
143         if (end < elemClass.length && elemClass[end] !== ' ') {{
144             return false;
145         }}
146         return true;
147     }}
148     return false;
149 }}
150
151 onEach(document.getElementsByClassName('rust-example-rendered'), function(e) {{
152     if (hasClass(e, 'compile_fail')) {{
153         e.addEventListener("mouseover", function(event) {{
154             e.previousElementSibling.childNodes[0].style.color = '#f00';
155         }});
156         e.addEventListener("mouseout", function(event) {{
157             e.previousElementSibling.childNodes[0].style.color = '';
158         }});
159     }} else if (hasClass(e, 'ignore')) {{
160         e.addEventListener("mouseover", function(event) {{
161             e.previousElementSibling.childNodes[0].style.color = '#ff9200';
162         }});
163         e.addEventListener("mouseout", function(event) {{
164             e.previousElementSibling.childNodes[0].style.color = '';
165         }});
166     }}
167 }});
168 </script>
169 </body>
170 </html>"##)?;
171         Ok(())
172     }
173 }
174
175 impl Formatter for MarkdownFormatter {
176     #[allow(unused_variables)]
177     fn header(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>> {
178         Ok(())
179     }
180
181     fn title(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>> {
182         write!(output, "# Rust Compiler Error Index\n")?;
183         Ok(())
184     }
185
186     fn error_code_block(&self, output: &mut dyn Write, info: &ErrorMetadata,
187                         err_code: &str) -> Result<(), Box<dyn Error>> {
188         Ok(match info.description {
189             Some(ref desc) => write!(output, "## {}\n{}\n", err_code, desc)?,
190             None => (),
191         })
192     }
193
194     #[allow(unused_variables)]
195     fn footer(&self, output: &mut dyn Write) -> Result<(), Box<dyn Error>> {
196         Ok(())
197     }
198 }
199
200 /// Loads all the metadata files from `metadata_dir` into an in-memory map.
201 fn load_all_errors(metadata_dir: &Path) -> Result<ErrorMetadataMap, Box<dyn Error>> {
202     let mut all_errors = BTreeMap::new();
203
204     for entry in read_dir(metadata_dir)? {
205         let path = entry?.path();
206
207         let metadata_str = fs::read_to_string(&path)?;
208
209         let some_errors: ErrorMetadataMap = json::decode(&metadata_str)?;
210
211         for (err_code, info) in some_errors {
212             all_errors.insert(err_code, info);
213         }
214     }
215
216     Ok(all_errors)
217 }
218
219 /// Output an HTML page for the errors in `err_map` to `output_path`.
220 fn render_error_page<T: Formatter>(err_map: &ErrorMetadataMap, output_path: &Path,
221                                    formatter: T) -> Result<(), Box<dyn Error>> {
222     let mut output_file = File::create(output_path)?;
223
224     formatter.header(&mut output_file)?;
225     formatter.title(&mut output_file)?;
226
227     for (err_code, info) in err_map {
228         formatter.error_code_block(&mut output_file, info, err_code)?;
229     }
230
231     formatter.footer(&mut output_file)
232 }
233
234 fn main_with_result(format: OutputFormat, dst: &Path) -> Result<(), Box<dyn Error>> {
235     let build_arch = env::var("CFG_BUILD")?;
236     let metadata_dir = get_metadata_dir(&build_arch);
237     let err_map = load_all_errors(&metadata_dir)?;
238     match format {
239         OutputFormat::Unknown(s)  => panic!("Unknown output format: {}", s),
240         OutputFormat::HTML(h)     => render_error_page(&err_map, dst, h)?,
241         OutputFormat::Markdown(m) => render_error_page(&err_map, dst, m)?,
242     }
243     Ok(())
244 }
245
246 fn parse_args() -> (OutputFormat, PathBuf) {
247     let mut args = env::args().skip(1);
248     let format = args.next();
249     let dst = args.next();
250     let resource_suffix = args.next().unwrap_or_else(String::new);
251     let format = format.map(|a| OutputFormat::from(&a, &resource_suffix))
252                        .unwrap_or(OutputFormat::from("html", &resource_suffix));
253     let dst = dst.map(PathBuf::from).unwrap_or_else(|| {
254         match format {
255             OutputFormat::HTML(..) => PathBuf::from("doc/error-index.html"),
256             OutputFormat::Markdown(..) => PathBuf::from("doc/error-index.md"),
257             OutputFormat::Unknown(..) => PathBuf::from("<nul>"),
258         }
259     });
260     (format, dst)
261 }
262
263 fn main() {
264     env_logger::init();
265     PLAYGROUND.with(|slot| {
266         *slot.borrow_mut() = Some((None, String::from("https://play.rust-lang.org/")));
267     });
268     let (format, dst) = parse_args();
269     let result = syntax::with_default_globals(move || {
270         main_with_result(format, &dst)
271     });
272     if let Err(e) = result {
273         panic!("{}", e.description());
274     }
275 }