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