]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/sources.rs
Rollup merge of #83646 - glittershark:bound-map, r=m-ou-se
[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::{SharedContext, BASIC_KEYWORDS};
9 use rustc_hir::def_id::LOCAL_CRATE;
10 use rustc_session::Session;
11 use rustc_span::edition::Edition;
12 use rustc_span::source_map::FileName;
13 use std::ffi::OsStr;
14 use std::fs;
15 use std::path::{Component, Path, PathBuf};
16
17 crate fn render(
18     dst: &Path,
19     scx: &mut SharedContext<'_>,
20     krate: clean::Crate,
21 ) -> Result<clean::Crate, Error> {
22     info!("emitting source files");
23     let dst = dst.join("src").join(&*krate.name.as_str());
24     scx.ensure_dir(&dst)?;
25     let mut folder = SourceCollector { dst, scx };
26     Ok(folder.fold_crate(krate))
27 }
28
29 /// Helper struct to render all source code to HTML pages
30 struct SourceCollector<'a, 'tcx> {
31     scx: &'a mut SharedContext<'tcx>,
32
33     /// Root destination to place all HTML output into
34     dst: PathBuf,
35 }
36
37 impl DocFolder for SourceCollector<'_, '_> {
38     fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
39         // If we're not rendering sources, there's nothing to do.
40         // If we're including source files, and we haven't seen this file yet,
41         // then we need to render it out to the filesystem.
42         if self.scx.include_sources
43             // skip all synthetic "files"
44             && item.span(self.scx.tcx).filename(self.sess()).is_real()
45             // skip non-local files
46             && item.span(self.scx.tcx).cnum(self.sess()) == LOCAL_CRATE
47         {
48             let filename = item.span(self.scx.tcx).filename(self.sess());
49             // If it turns out that we couldn't read this file, then we probably
50             // can't read any of the files (generating html output from json or
51             // something like that), so just don't include sources for the
52             // entire crate. The other option is maintaining this mapping on a
53             // per-file basis, but that's probably not worth it...
54             self.scx.include_sources = match self.emit_source(&filename) {
55                 Ok(()) => true,
56                 Err(e) => {
57                     self.scx.tcx.sess.span_err(
58                         item.span(self.scx.tcx).inner(),
59                         &format!(
60                             "failed to render source code for `{}`: {}",
61                             filename.prefer_local(),
62                             e
63                         ),
64                     );
65                     false
66                 }
67             };
68         }
69         // FIXME: if `include_sources` isn't set and DocFolder didn't require consuming the crate by value,
70         // we could return None here without having to walk the rest of the crate.
71         Some(self.fold_item_recur(item))
72     }
73 }
74
75 impl SourceCollector<'_, 'tcx> {
76     fn sess(&self) -> &'tcx Session {
77         &self.scx.tcx.sess
78     }
79
80     /// Renders the given filename into its corresponding HTML source file.
81     fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> {
82         let p = match *filename {
83             FileName::Real(ref file) => {
84                 if let Some(local_path) = file.local_path() {
85                     local_path.to_path_buf()
86                 } else {
87                     unreachable!("only the current crate should have sources emitted");
88                 }
89             }
90             _ => return Ok(()),
91         };
92         if self.scx.local_sources.contains_key(&*p) {
93             // We've already emitted this source
94             return Ok(());
95         }
96
97         let contents = match fs::read_to_string(&p) {
98             Ok(contents) => contents,
99             Err(e) => {
100                 return Err(Error::new(e, &p));
101             }
102         };
103
104         // Remove the utf-8 BOM if any
105         let contents = if contents.starts_with('\u{feff}') { &contents[3..] } else { &contents };
106
107         // Create the intermediate directories
108         let mut cur = self.dst.clone();
109         let mut root_path = String::from("../../");
110         let mut href = String::new();
111         clean_path(&self.scx.src_root, &p, false, |component| {
112             cur.push(component);
113             root_path.push_str("../");
114             href.push_str(&component.to_string_lossy());
115             href.push('/');
116         });
117         self.scx.ensure_dir(&cur)?;
118
119         let src_fname = p.file_name().expect("source has no filename").to_os_string();
120         let mut fname = src_fname.clone();
121         fname.push(".html");
122         cur.push(&fname);
123         href.push_str(&fname.to_string_lossy());
124
125         let title = format!("{} - source", src_fname.to_string_lossy());
126         let desc = format!("Source of the Rust file `{}`.", filename.prefer_remapped());
127         let page = layout::Page {
128             title: &title,
129             css_class: "source",
130             root_path: &root_path,
131             static_root_path: self.scx.static_root_path.as_deref(),
132             description: &desc,
133             keywords: BASIC_KEYWORDS,
134             resource_suffix: &self.scx.resource_suffix,
135             extra_scripts: &[&format!("source-files{}", self.scx.resource_suffix)],
136             static_extra_scripts: &[&format!("source-script{}", self.scx.resource_suffix)],
137         };
138         let v = layout::render(
139             &self.scx.layout,
140             &page,
141             "",
142             |buf: &mut _| print_src(buf, contents, self.scx.edition()),
143             &self.scx.style_files,
144         );
145         self.scx.fs.write(&cur, v.as_bytes())?;
146         self.scx.local_sources.insert(p, href);
147         Ok(())
148     }
149 }
150
151 /// Takes a path to a source file and cleans the path to it. This canonicalizes
152 /// things like ".." to components which preserve the "top down" hierarchy of a
153 /// static HTML tree. Each component in the cleaned path will be passed as an
154 /// argument to `f`. The very last component of the path (ie the file name) will
155 /// be passed to `f` if `keep_filename` is true, and ignored otherwise.
156 crate fn clean_path<F>(src_root: &Path, p: &Path, keep_filename: bool, mut f: F)
157 where
158     F: FnMut(&OsStr),
159 {
160     // make it relative, if possible
161     let p = p.strip_prefix(src_root).unwrap_or(p);
162
163     let mut iter = p.components().peekable();
164
165     while let Some(c) = iter.next() {
166         if !keep_filename && iter.peek().is_none() {
167             break;
168         }
169
170         match c {
171             Component::ParentDir => f("up".as_ref()),
172             Component::Normal(c) => f(c),
173             _ => continue,
174         }
175     }
176 }
177
178 /// Wrapper struct to render the source code of a file. This will do things like
179 /// adding line numbers to the left-hand side.
180 fn print_src(buf: &mut Buffer, s: &str, edition: Edition) {
181     let lines = s.lines().count();
182     let mut line_numbers = Buffer::empty_from(buf);
183     let mut cols = 0;
184     let mut tmp = lines;
185     while tmp > 0 {
186         cols += 1;
187         tmp /= 10;
188     }
189     line_numbers.write_str("<pre class=\"line-numbers\">");
190     for i in 1..=lines {
191         writeln!(line_numbers, "<span id=\"{0}\">{0:1$}</span>", i, cols);
192     }
193     line_numbers.write_str("</pre>");
194     highlight::render_with_highlighting(s, buf, None, None, None, edition, Some(line_numbers));
195 }