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;
17 use std::path::{Component, Path, PathBuf};
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))
27 crate fn collect_local_sources<'tcx>(
31 ) -> (clean::Crate, FxHashMap<PathBuf, String>) {
32 let mut lsc = LocalSourcesCollector { tcx, local_sources: FxHashMap::default(), src_root };
34 let krate = lsc.fold_crate(krate);
35 (krate, lsc.local_sources)
38 struct LocalSourcesCollector<'a, 'tcx> {
40 local_sources: FxHashMap<PathBuf, String>,
44 fn is_real_and_local(span: clean::Span, sess: &Session) -> bool {
45 span.filename(sess).is_real() && span.cnum(sess) == LOCAL_CRATE
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) {
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(),
64 if self.local_sources.contains_key(&*p) {
65 // We've already emitted this source
69 let mut href = String::new();
70 clean_path(self.src_root, &p, false, |component| {
71 href.push_str(&component.to_string_lossy());
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);
82 impl DocFolder for LocalSourcesCollector<'_, '_> {
83 fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
84 self.add_local_source(&item);
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))
92 /// Helper struct to render all source code to HTML pages
93 struct SourceCollector<'a, 'tcx> {
94 cx: &'a mut Context<'tcx>,
96 /// Root destination to place all HTML output into
98 emitted_local_sources: FxHashSet<PathBuf>,
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);
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) {
123 self.cx.shared.tcx.sess.span_err(
126 "failed to render source code for `{}`: {}",
127 filename.prefer_local(),
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))
141 impl SourceCollector<'_, 'tcx> {
142 /// Renders the given filename into its corresponding HTML source file.
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()
153 unreachable!("only the current crate should have sources emitted");
158 if self.emitted_local_sources.contains(&*p) {
159 // We've already emitted this source
163 let contents = match fs::read_to_string(&p) {
164 Ok(contents) => contents,
166 return Err(Error::new(e, &p));
170 // Remove the utf-8 BOM if any
171 let contents = contents.strip_prefix('\u{feff}').unwrap_or(&contents);
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| {
178 root_path.push_str("../");
181 self.cx.shared.ensure_dir(&cur)?;
183 let src_fname = p.file_name().expect("source has no filename").to_os_string();
184 let mut fname = src_fname.clone();
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 {
193 root_path: &root_path,
194 static_root_path: self.cx.shared.static_root_path.as_deref(),
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)],
201 let v = layout::render(
202 &self.cx.shared.templates,
203 &self.cx.shared.layout,
210 self.cx.shared.edition(),
215 SourceContext::Standalone,
218 &self.cx.shared.style_files,
220 self.cx.shared.fs.write(cur, v)?;
221 self.emitted_local_sources.insert(p);
226 /// Takes a path to a source file and cleans the path to it. This canonicalizes
227 /// things like ".." to components which preserve the "top down" hierarchy of a
228 /// static HTML tree. Each component in the cleaned path will be passed as an
229 /// argument to `f`. The very last component of the path (ie the file name) will
230 /// be passed to `f` if `keep_filename` is true, and ignored otherwise.
231 crate fn clean_path<F>(src_root: &Path, p: &Path, keep_filename: bool, mut f: F)
235 // make it relative, if possible
236 let p = p.strip_prefix(src_root).unwrap_or(p);
238 let mut iter = p.components().peekable();
240 while let Some(c) = iter.next() {
241 if !keep_filename && iter.peek().is_none() {
246 Component::ParentDir => f("up".as_ref()),
247 Component::Normal(c) => f(c),
253 crate enum SourceContext {
255 Embedded { offset: usize },
258 /// Wrapper struct to render the source code of a file. This will do things like
259 /// adding line numbers to the left-hand side.
264 file_span: rustc_span::Span,
265 context: &Context<'_>,
267 decoration_info: Option<highlight::DecorationInfo>,
268 source_context: SourceContext,
270 let lines = s.lines().count();
271 let mut line_numbers = Buffer::empty_from(buf);
278 line_numbers.write_str("<pre class=\"line-numbers\">");
280 match source_context {
281 SourceContext::Standalone => {
282 writeln!(line_numbers, "<span id=\"{0}\">{0:1$}</span>", i, cols)
284 SourceContext::Embedded { offset } => {
285 writeln!(line_numbers, "<span>{0:1$}</span>", i + offset, cols)
289 line_numbers.write_str("</pre>");
290 highlight::render_with_highlighting(
298 Some(highlight::ContextInfo { context, file_span, root_path }),