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;
15 use std::path::{Component, Path, PathBuf};
19 scx: &mut SharedContext<'_>,
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))
29 /// Helper struct to render all source code to HTML pages
30 struct SourceCollector<'a, 'tcx> {
31 scx: &'a mut SharedContext<'tcx>,
33 /// Root destination to place all HTML output into
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.source.filename(self.sess()).is_real()
45 // skip non-local files
46 && item.source.cnum(self.sess()) == LOCAL_CRATE
48 let filename = item.source.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) {
58 "warning: source code was requested to be rendered, \
59 but processing `{}` had an error: {}",
62 println!(" skipping rendering of source code");
67 // FIXME: if `include_sources` isn't set and DocFolder didn't require consuming the crate by value,
68 // we could return None here without having to walk the rest of the crate.
69 Some(self.fold_item_recur(item))
73 impl SourceCollector<'_, 'tcx> {
74 fn sess(&self) -> &'tcx Session {
78 /// Renders the given filename into its corresponding HTML source file.
79 fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> {
80 let p = match *filename {
81 FileName::Real(ref file) => file.local_path().to_path_buf(),
84 if self.scx.local_sources.contains_key(&*p) {
85 // We've already emitted this source
89 let contents = match fs::read_to_string(&p) {
90 Ok(contents) => contents,
92 return Err(Error::new(e, &p));
96 // Remove the utf-8 BOM if any
97 let contents = if contents.starts_with('\u{feff}') { &contents[3..] } else { &contents };
99 // Create the intermediate directories
100 let mut cur = self.dst.clone();
101 let mut root_path = String::from("../../");
102 let mut href = String::new();
103 clean_path(&self.scx.src_root, &p, false, |component| {
105 root_path.push_str("../");
106 href.push_str(&component.to_string_lossy());
109 self.scx.ensure_dir(&cur)?;
111 let src_fname = p.file_name().expect("source has no filename").to_os_string();
112 let mut fname = src_fname.clone();
115 href.push_str(&fname.to_string_lossy());
117 let title = format!("{} - source", src_fname.to_string_lossy());
118 let desc = format!("Source of the Rust file `{}`.", filename);
119 let page = layout::Page {
122 root_path: &root_path,
123 static_root_path: self.scx.static_root_path.as_deref(),
125 keywords: BASIC_KEYWORDS,
126 resource_suffix: &self.scx.resource_suffix,
127 extra_scripts: &[&format!("source-files{}", self.scx.resource_suffix)],
128 static_extra_scripts: &[&format!("source-script{}", self.scx.resource_suffix)],
130 let v = layout::render(
134 |buf: &mut _| print_src(buf, contents, self.scx.edition),
135 &self.scx.style_files,
137 self.scx.fs.write(&cur, v.as_bytes())?;
138 self.scx.local_sources.insert(p, href);
143 /// Takes a path to a source file and cleans the path to it. This canonicalizes
144 /// things like ".." to components which preserve the "top down" hierarchy of a
145 /// static HTML tree. Each component in the cleaned path will be passed as an
146 /// argument to `f`. The very last component of the path (ie the file name) will
147 /// be passed to `f` if `keep_filename` is true, and ignored otherwise.
148 crate fn clean_path<F>(src_root: &Path, p: &Path, keep_filename: bool, mut f: F)
152 // make it relative, if possible
153 let p = p.strip_prefix(src_root).unwrap_or(p);
155 let mut iter = p.components().peekable();
157 while let Some(c) = iter.next() {
158 if !keep_filename && iter.peek().is_none() {
163 Component::ParentDir => f("up".as_ref()),
164 Component::Normal(c) => f(c),
170 /// Wrapper struct to render the source code of a file. This will do things like
171 /// adding line numbers to the left-hand side.
172 fn print_src(buf: &mut Buffer, s: &str, edition: Edition) {
173 let lines = s.lines().count();
180 buf.write_str("<pre class=\"line-numbers\">");
182 write!(buf, "<span id=\"{0}\">{0:1$}</span>\n", i, cols);
184 buf.write_str("</pre>");
185 highlight::render_with_highlighting(s, buf, None, None, None, edition);