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