summaryrefslogtreecommitdiffstats
path: root/src/librustdoc/html/sources.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /src/librustdoc/html/sources.rs
parentInitial commit. (diff)
downloadrustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz
rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/librustdoc/html/sources.rs')
-rw-r--r--src/librustdoc/html/sources.rs303
1 files changed, 303 insertions, 0 deletions
diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs
new file mode 100644
index 000000000..d0fd637ba
--- /dev/null
+++ b/src/librustdoc/html/sources.rs
@@ -0,0 +1,303 @@
+use crate::clean;
+use crate::docfs::PathError;
+use crate::error::Error;
+use crate::html::format::Buffer;
+use crate::html::highlight;
+use crate::html::layout;
+use crate::html::render::{Context, BASIC_KEYWORDS};
+use crate::visit::DocVisitor;
+
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir::def_id::LOCAL_CRATE;
+use rustc_middle::ty::TyCtxt;
+use rustc_session::Session;
+use rustc_span::edition::Edition;
+use rustc_span::source_map::FileName;
+
+use std::ffi::OsStr;
+use std::fs;
+use std::path::{Component, Path, PathBuf};
+use std::rc::Rc;
+
+pub(crate) fn render(cx: &mut Context<'_>, krate: &clean::Crate) -> Result<(), Error> {
+ info!("emitting source files");
+
+ let dst = cx.dst.join("src").join(krate.name(cx.tcx()).as_str());
+ cx.shared.ensure_dir(&dst)?;
+
+ let mut collector = SourceCollector { dst, cx, emitted_local_sources: FxHashSet::default() };
+ collector.visit_crate(krate);
+ Ok(())
+}
+
+pub(crate) fn collect_local_sources<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ src_root: &Path,
+ krate: &clean::Crate,
+) -> FxHashMap<PathBuf, String> {
+ let mut lsc = LocalSourcesCollector { tcx, local_sources: FxHashMap::default(), src_root };
+ lsc.visit_crate(krate);
+ lsc.local_sources
+}
+
+struct LocalSourcesCollector<'a, 'tcx> {
+ tcx: TyCtxt<'tcx>,
+ local_sources: FxHashMap<PathBuf, String>,
+ src_root: &'a Path,
+}
+
+fn is_real_and_local(span: clean::Span, sess: &Session) -> bool {
+ span.cnum(sess) == LOCAL_CRATE && span.filename(sess).is_real()
+}
+
+impl LocalSourcesCollector<'_, '_> {
+ fn add_local_source(&mut self, item: &clean::Item) {
+ let sess = self.tcx.sess;
+ let span = item.span(self.tcx);
+ // skip all synthetic "files"
+ if !is_real_and_local(span, sess) {
+ return;
+ }
+ let filename = span.filename(sess);
+ let p = if let FileName::Real(file) = filename {
+ match file.into_local_path() {
+ Some(p) => p,
+ None => return,
+ }
+ } else {
+ return;
+ };
+ if self.local_sources.contains_key(&*p) {
+ // We've already emitted this source
+ return;
+ }
+
+ let mut href = String::new();
+ clean_path(self.src_root, &p, false, |component| {
+ href.push_str(&component.to_string_lossy());
+ href.push('/');
+ });
+
+ let mut src_fname = p.file_name().expect("source has no filename").to_os_string();
+ src_fname.push(".html");
+ href.push_str(&src_fname.to_string_lossy());
+ self.local_sources.insert(p, href);
+ }
+}
+
+impl DocVisitor for LocalSourcesCollector<'_, '_> {
+ fn visit_item(&mut self, item: &clean::Item) {
+ self.add_local_source(item);
+
+ self.visit_item_recur(item)
+ }
+}
+
+/// Helper struct to render all source code to HTML pages
+struct SourceCollector<'a, 'tcx> {
+ cx: &'a mut Context<'tcx>,
+
+ /// Root destination to place all HTML output into
+ dst: PathBuf,
+ emitted_local_sources: FxHashSet<PathBuf>,
+}
+
+impl DocVisitor for SourceCollector<'_, '_> {
+ fn visit_item(&mut self, item: &clean::Item) {
+ if !self.cx.include_sources {
+ return;
+ }
+
+ let tcx = self.cx.tcx();
+ let span = item.span(tcx);
+ let sess = tcx.sess;
+
+ // If we're not rendering sources, there's nothing to do.
+ // If we're including source files, and we haven't seen this file yet,
+ // then we need to render it out to the filesystem.
+ if is_real_and_local(span, sess) {
+ let filename = span.filename(sess);
+ let span = span.inner();
+ let pos = sess.source_map().lookup_source_file(span.lo());
+ let file_span = span.with_lo(pos.start_pos).with_hi(pos.end_pos);
+ // If it turns out that we couldn't read this file, then we probably
+ // can't read any of the files (generating html output from json or
+ // something like that), so just don't include sources for the
+ // entire crate. The other option is maintaining this mapping on a
+ // per-file basis, but that's probably not worth it...
+ self.cx.include_sources = match self.emit_source(&filename, file_span) {
+ Ok(()) => true,
+ Err(e) => {
+ self.cx.shared.tcx.sess.span_err(
+ span,
+ &format!(
+ "failed to render source code for `{}`: {}",
+ filename.prefer_local(),
+ e,
+ ),
+ );
+ false
+ }
+ };
+ }
+
+ self.visit_item_recur(item)
+ }
+}
+
+impl SourceCollector<'_, '_> {
+ /// Renders the given filename into its corresponding HTML source file.
+ fn emit_source(
+ &mut self,
+ filename: &FileName,
+ file_span: rustc_span::Span,
+ ) -> Result<(), Error> {
+ let p = match *filename {
+ FileName::Real(ref file) => {
+ if let Some(local_path) = file.local_path() {
+ local_path.to_path_buf()
+ } else {
+ unreachable!("only the current crate should have sources emitted");
+ }
+ }
+ _ => return Ok(()),
+ };
+ if self.emitted_local_sources.contains(&*p) {
+ // We've already emitted this source
+ return Ok(());
+ }
+
+ let contents = match fs::read_to_string(&p) {
+ Ok(contents) => contents,
+ Err(e) => {
+ return Err(Error::new(e, &p));
+ }
+ };
+
+ // Remove the utf-8 BOM if any
+ let contents = contents.strip_prefix('\u{feff}').unwrap_or(&contents);
+
+ let shared = Rc::clone(&self.cx.shared);
+ // Create the intermediate directories
+ let mut cur = self.dst.clone();
+ let mut root_path = String::from("../../");
+ clean_path(&shared.src_root, &p, false, |component| {
+ cur.push(component);
+ root_path.push_str("../");
+ });
+
+ shared.ensure_dir(&cur)?;
+
+ let src_fname = p.file_name().expect("source has no filename").to_os_string();
+ let mut fname = src_fname.clone();
+ fname.push(".html");
+ cur.push(&fname);
+
+ let title = format!("{} - source", src_fname.to_string_lossy());
+ let desc = format!("Source of the Rust file `{}`.", filename.prefer_remapped());
+ let page = layout::Page {
+ title: &title,
+ css_class: "source",
+ root_path: &root_path,
+ static_root_path: shared.static_root_path.as_deref(),
+ description: &desc,
+ keywords: BASIC_KEYWORDS,
+ resource_suffix: &shared.resource_suffix,
+ };
+ let v = layout::render(
+ &shared.layout,
+ &page,
+ "",
+ |buf: &mut _| {
+ let cx = &mut self.cx;
+ print_src(
+ buf,
+ contents,
+ cx.shared.edition(),
+ file_span,
+ cx,
+ &root_path,
+ None,
+ SourceContext::Standalone,
+ )
+ },
+ &shared.style_files,
+ );
+ shared.fs.write(cur, v)?;
+ self.emitted_local_sources.insert(p);
+ Ok(())
+ }
+}
+
+/// Takes a path to a source file and cleans the path to it. This canonicalizes
+/// things like ".." to components which preserve the "top down" hierarchy of a
+/// static HTML tree. Each component in the cleaned path will be passed as an
+/// argument to `f`. The very last component of the path (ie the file name) will
+/// be passed to `f` if `keep_filename` is true, and ignored otherwise.
+pub(crate) fn clean_path<F>(src_root: &Path, p: &Path, keep_filename: bool, mut f: F)
+where
+ F: FnMut(&OsStr),
+{
+ // make it relative, if possible
+ let p = p.strip_prefix(src_root).unwrap_or(p);
+
+ let mut iter = p.components().peekable();
+
+ while let Some(c) = iter.next() {
+ if !keep_filename && iter.peek().is_none() {
+ break;
+ }
+
+ match c {
+ Component::ParentDir => f("up".as_ref()),
+ Component::Normal(c) => f(c),
+ _ => continue,
+ }
+ }
+}
+
+pub(crate) enum SourceContext {
+ Standalone,
+ Embedded { offset: usize },
+}
+
+/// Wrapper struct to render the source code of a file. This will do things like
+/// adding line numbers to the left-hand side.
+pub(crate) fn print_src(
+ buf: &mut Buffer,
+ s: &str,
+ edition: Edition,
+ file_span: rustc_span::Span,
+ context: &Context<'_>,
+ root_path: &str,
+ decoration_info: Option<highlight::DecorationInfo>,
+ source_context: SourceContext,
+) {
+ let lines = s.lines().count();
+ let mut line_numbers = Buffer::empty_from(buf);
+ line_numbers.write_str("<pre class=\"line-numbers\">");
+ match source_context {
+ SourceContext::Standalone => {
+ for line in 1..=lines {
+ writeln!(line_numbers, "<span id=\"{0}\">{0}</span>", line)
+ }
+ }
+ SourceContext::Embedded { offset } => {
+ for line in 1..=lines {
+ writeln!(line_numbers, "<span>{0}</span>", line + offset)
+ }
+ }
+ }
+ line_numbers.write_str("</pre>");
+ highlight::render_with_highlighting(
+ s,
+ buf,
+ None,
+ None,
+ None,
+ edition,
+ Some(line_numbers),
+ Some(highlight::HrefContext { context, file_span, root_path }),
+ decoration_info,
+ );
+}