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