]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/sources.rs
Auto merge of #88219 - jyn514:parallel-io, r=GuillaumeGomez
[rust.git] / src / librustdoc / html / sources.rs
1 use crate::clean;
2 use crate::docfs::PathError;
3 use crate::error::Error;
4 use crate::fold::DocFolder;
5 use crate::html::format::Buffer;
6 use crate::html::highlight;
7 use crate::html::layout;
8 use crate::html::render::{Context, BASIC_KEYWORDS};
9 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
10 use rustc_hir::def_id::LOCAL_CRATE;
11 use rustc_middle::ty::TyCtxt;
12 use rustc_session::Session;
13 use rustc_span::edition::Edition;
14 use rustc_span::source_map::FileName;
15 use std::ffi::OsStr;
16 use std::fs;
17 use std::path::{Component, Path, PathBuf};
18
19 crate fn render(cx: &mut Context<'_>, krate: clean::Crate) -> Result<clean::Crate, Error> {
20     info!("emitting source files");
21     let dst = cx.dst.join("src").join(&*krate.name.as_str());
22     cx.shared.ensure_dir(&dst)?;
23     let mut folder = SourceCollector { dst, cx, emitted_local_sources: FxHashSet::default() };
24     Ok(folder.fold_crate(krate))
25 }
26
27 crate fn collect_local_sources<'tcx>(
28     tcx: TyCtxt<'tcx>,
29     src_root: &Path,
30     krate: clean::Crate,
31 ) -> (clean::Crate, FxHashMap<PathBuf, String>) {
32     let mut lsc = LocalSourcesCollector { tcx, local_sources: FxHashMap::default(), src_root };
33
34     let krate = lsc.fold_crate(krate);
35     (krate, lsc.local_sources)
36 }
37
38 struct LocalSourcesCollector<'a, 'tcx> {
39     tcx: TyCtxt<'tcx>,
40     local_sources: FxHashMap<PathBuf, String>,
41     src_root: &'a Path,
42 }
43
44 fn is_real_and_local(span: clean::Span, sess: &Session) -> bool {
45     span.filename(sess).is_real() && span.cnum(sess) == LOCAL_CRATE
46 }
47
48 impl LocalSourcesCollector<'_, '_> {
49     fn add_local_source(&mut self, item: &clean::Item) {
50         let sess = self.tcx.sess;
51         let span = item.span(self.tcx);
52         // skip all synthetic "files"
53         if !is_real_and_local(span, sess) {
54             return;
55         }
56         let filename = span.filename(sess);
57         let p = match filename {
58             FileName::Real(ref file) => match file.local_path() {
59                 Some(p) => p.to_path_buf(),
60                 _ => return,
61             },
62             _ => return,
63         };
64         if self.local_sources.contains_key(&*p) {
65             // We've already emitted this source
66             return;
67         }
68
69         let mut href = String::new();
70         clean_path(&self.src_root, &p, false, |component| {
71             href.push_str(&component.to_string_lossy());
72             href.push('/');
73         });
74
75         let mut src_fname = p.file_name().expect("source has no filename").to_os_string();
76         src_fname.push(".html");
77         href.push_str(&src_fname.to_string_lossy());
78         self.local_sources.insert(p, href);
79     }
80 }
81
82 impl DocFolder for LocalSourcesCollector<'_, '_> {
83     fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
84         self.add_local_source(&item);
85
86         // FIXME: if `include_sources` isn't set and DocFolder didn't require consuming the crate by value,
87         // we could return None here without having to walk the rest of the crate.
88         Some(self.fold_item_recur(item))
89     }
90 }
91
92 /// Helper struct to render all source code to HTML pages
93 struct SourceCollector<'a, 'tcx> {
94     cx: &'a mut Context<'tcx>,
95
96     /// Root destination to place all HTML output into
97     dst: PathBuf,
98     emitted_local_sources: FxHashSet<PathBuf>,
99 }
100
101 impl DocFolder for SourceCollector<'_, '_> {
102     fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
103         let tcx = self.cx.tcx();
104         let span = item.span(tcx);
105         let sess = tcx.sess;
106
107         // If we're not rendering sources, there's nothing to do.
108         // If we're including source files, and we haven't seen this file yet,
109         // then we need to render it out to the filesystem.
110         if self.cx.include_sources && is_real_and_local(span, sess) {
111             let filename = span.filename(sess);
112             let span = span.inner();
113             let pos = sess.source_map().lookup_source_file(span.lo());
114             let file_span = span.with_lo(pos.start_pos).with_hi(pos.end_pos);
115             // If it turns out that we couldn't read this file, then we probably
116             // can't read any of the files (generating html output from json or
117             // something like that), so just don't include sources for the
118             // entire crate. The other option is maintaining this mapping on a
119             // per-file basis, but that's probably not worth it...
120             self.cx.include_sources = match self.emit_source(&filename, file_span) {
121                 Ok(()) => true,
122                 Err(e) => {
123                     self.cx.shared.tcx.sess.span_err(
124                         span,
125                         &format!(
126                             "failed to render source code for `{}`: {}",
127                             filename.prefer_local(),
128                             e,
129                         ),
130                     );
131                     false
132                 }
133             };
134         }
135         // FIXME: if `include_sources` isn't set and DocFolder didn't require consuming the crate by value,
136         // we could return None here without having to walk the rest of the crate.
137         Some(self.fold_item_recur(item))
138     }
139 }
140
141 impl SourceCollector<'_, 'tcx> {
142     /// Renders the given filename into its corresponding HTML source file.
143     fn emit_source(
144         &mut self,
145         filename: &FileName,
146         file_span: rustc_span::Span,
147     ) -> Result<(), Error> {
148         let p = match *filename {
149             FileName::Real(ref file) => {
150                 if let Some(local_path) = file.local_path() {
151                     local_path.to_path_buf()
152                 } else {
153                     unreachable!("only the current crate should have sources emitted");
154                 }
155             }
156             _ => return Ok(()),
157         };
158         if self.emitted_local_sources.contains(&*p) {
159             // We've already emitted this source
160             return Ok(());
161         }
162
163         let contents = match fs::read_to_string(&p) {
164             Ok(contents) => contents,
165             Err(e) => {
166                 return Err(Error::new(e, &p));
167             }
168         };
169
170         // Remove the utf-8 BOM if any
171         let contents = if contents.starts_with('\u{feff}') { &contents[3..] } else { &contents };
172
173         // Create the intermediate directories
174         let mut cur = self.dst.clone();
175         let mut root_path = String::from("../../");
176         clean_path(&self.cx.shared.src_root, &p, false, |component| {
177             cur.push(component);
178             root_path.push_str("../");
179         });
180
181         self.cx.shared.ensure_dir(&cur)?;
182
183         let src_fname = p.file_name().expect("source has no filename").to_os_string();
184         let mut fname = src_fname.clone();
185         fname.push(".html");
186         cur.push(&fname);
187
188         let title = format!("{} - source", src_fname.to_string_lossy());
189         let desc = format!("Source of the Rust file `{}`.", filename.prefer_remapped());
190         let page = layout::Page {
191             title: &title,
192             css_class: "source",
193             root_path: &root_path,
194             static_root_path: self.cx.shared.static_root_path.as_deref(),
195             description: &desc,
196             keywords: BASIC_KEYWORDS,
197             resource_suffix: &self.cx.shared.resource_suffix,
198             extra_scripts: &[&format!("source-files{}", self.cx.shared.resource_suffix)],
199             static_extra_scripts: &[&format!("source-script{}", self.cx.shared.resource_suffix)],
200         };
201         let v = layout::render(
202             &self.cx.shared.templates,
203             &self.cx.shared.layout,
204             &page,
205             "",
206             |buf: &mut _| {
207                 print_src(buf, contents, self.cx.shared.edition(), file_span, &self.cx, &root_path)
208             },
209             &self.cx.shared.style_files,
210         );
211         self.cx.shared.fs.write(cur, v)?;
212         self.emitted_local_sources.insert(p);
213         Ok(())
214     }
215 }
216
217 /// Takes a path to a source file and cleans the path to it. This canonicalizes
218 /// things like ".." to components which preserve the "top down" hierarchy of a
219 /// static HTML tree. Each component in the cleaned path will be passed as an
220 /// argument to `f`. The very last component of the path (ie the file name) will
221 /// be passed to `f` if `keep_filename` is true, and ignored otherwise.
222 crate fn clean_path<F>(src_root: &Path, p: &Path, keep_filename: bool, mut f: F)
223 where
224     F: FnMut(&OsStr),
225 {
226     // make it relative, if possible
227     let p = p.strip_prefix(src_root).unwrap_or(p);
228
229     let mut iter = p.components().peekable();
230
231     while let Some(c) = iter.next() {
232         if !keep_filename && iter.peek().is_none() {
233             break;
234         }
235
236         match c {
237             Component::ParentDir => f("up".as_ref()),
238             Component::Normal(c) => f(c),
239             _ => continue,
240         }
241     }
242 }
243
244 /// Wrapper struct to render the source code of a file. This will do things like
245 /// adding line numbers to the left-hand side.
246 fn print_src(
247     buf: &mut Buffer,
248     s: &str,
249     edition: Edition,
250     file_span: rustc_span::Span,
251     context: &Context<'_>,
252     root_path: &str,
253 ) {
254     let lines = s.lines().count();
255     let mut line_numbers = Buffer::empty_from(buf);
256     let mut cols = 0;
257     let mut tmp = lines;
258     while tmp > 0 {
259         cols += 1;
260         tmp /= 10;
261     }
262     line_numbers.write_str("<pre class=\"line-numbers\">");
263     for i in 1..=lines {
264         writeln!(line_numbers, "<span id=\"{0}\">{0:1$}</span>", i, cols);
265     }
266     line_numbers.write_str("</pre>");
267     highlight::render_with_highlighting(
268         s,
269         buf,
270         None,
271         None,
272         None,
273         edition,
274         Some(line_numbers),
275         Some(highlight::ContextInfo { context, file_span, root_path }),
276     );
277 }