{
if item.def_id.is_local() {
@@ -2399,7 +2253,7 @@ impl<'a> Item<'a> {
(_, _, Unknown) => return None,
};
- clean_srcpath(&src_root, file, false, |component| {
+ sources::clean_path(&src_root, file, false, |component| {
path.push_str(&component.to_string_lossy());
path.push('/');
});
@@ -5048,27 +4902,6 @@ fn sidebar_foreign_type(fmt: &mut fmt::Formatter<'_>, it: &clean::Item) -> fmt::
Ok(())
}
-impl<'a> fmt::Display for Source<'a> {
- fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
- let Source(s) = *self;
- let lines = s.lines().count();
- let mut cols = 0;
- let mut tmp = lines;
- while tmp > 0 {
- cols += 1;
- tmp /= 10;
- }
- write!(fmt, "")?;
- for i in 1..=lines {
- write!(fmt, "{0:1$}\n", i, cols)?;
- }
- write!(fmt, "")?;
- write!(fmt, "{}",
- highlight::render_with_highlighting(s, None, None, None))?;
- Ok(())
- }
-}
-
fn item_macro(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item,
t: &clean::Macro) -> fmt::Result {
wrap_into_docblock(w, |w| {
@@ -5125,7 +4958,7 @@ fn item_keyword(w: &mut fmt::Formatter<'_>, cx: &Context,
document(w, cx, it)
}
-const BASIC_KEYWORDS: &'static str = "rust, rustlang, rust-lang";
+crate const BASIC_KEYWORDS: &'static str = "rust, rustlang, rust-lang";
fn make_item_keywords(it: &clean::Item) -> String {
format!("{}, {}", BASIC_KEYWORDS, it.name.as_ref().unwrap())
diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs
new file mode 100644
index 000000000000..c1f1f59d9149
--- /dev/null
+++ b/src/librustdoc/html/sources.rs
@@ -0,0 +1,187 @@
+use crate::clean;
+use crate::docfs::PathError;
+use crate::fold::DocFolder;
+use crate::html::layout;
+use crate::html::render::{Error, SharedContext, BASIC_KEYWORDS};
+use crate::html::highlight;
+use std::ffi::OsStr;
+use std::fs;
+use std::path::{Component, Path, PathBuf};
+use std::fmt;
+use syntax::source_map::FileName;
+
+crate fn render(dst: &Path, scx: &mut SharedContext,
+ krate: clean::Crate) -> Result {
+ info!("emitting source files");
+ let dst = dst.join("src").join(&krate.name);
+ scx.ensure_dir(&dst)?;
+ let mut folder = SourceCollector {
+ dst,
+ scx,
+ };
+ Ok(folder.fold_crate(krate))
+}
+
+/// Helper struct to render all source code to HTML pages
+struct SourceCollector<'a> {
+ scx: &'a mut SharedContext,
+
+ /// Root destination to place all HTML output into
+ dst: PathBuf,
+}
+
+impl<'a> DocFolder for SourceCollector<'a> {
+ fn fold_item(&mut self, item: clean::Item) -> Option {
+ // 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 self.scx.include_sources
+ // skip all invalid or macro spans
+ && item.source.filename.is_real()
+ // skip non-local items
+ && item.def_id.is_local() {
+
+ // 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.scx
+ .include_sources = match self.emit_source(&item.source.filename) {
+ Ok(()) => true,
+ Err(e) => {
+ println!("warning: source code was requested to be rendered, \
+ but processing `{}` had an error: {}",
+ item.source.filename, e);
+ println!(" skipping rendering of source code");
+ false
+ }
+ };
+ }
+ self.fold_item_recur(item)
+ }
+}
+
+impl<'a> SourceCollector<'a> {
+ /// Renders the given filename into its corresponding HTML source file.
+ fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> {
+ let p = match *filename {
+ FileName::Real(ref file) => file,
+ _ => return Ok(()),
+ };
+ if self.scx.local_sources.contains_key(&**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 = if contents.starts_with("\u{feff}") {
+ &contents[3..]
+ } else {
+ &contents[..]
+ };
+
+ // Create the intermediate directories
+ let mut cur = self.dst.clone();
+ let mut root_path = String::from("../../");
+ let mut href = String::new();
+ clean_path(&self.scx.src_root, &p, false, |component| {
+ cur.push(component);
+ root_path.push_str("../");
+ href.push_str(&component.to_string_lossy());
+ href.push('/');
+ });
+ self.scx.ensure_dir(&cur)?;
+ let mut fname = p.file_name()
+ .expect("source has no filename")
+ .to_os_string();
+ fname.push(".html");
+ cur.push(&fname);
+ href.push_str(&fname.to_string_lossy());
+
+ let mut v = Vec::new();
+ let title = format!("{} -- source", cur.file_name().expect("failed to get file name")
+ .to_string_lossy());
+ let desc = format!("Source to the Rust file `{}`.", filename);
+ let page = layout::Page {
+ title: &title,
+ css_class: "source",
+ root_path: &root_path,
+ static_root_path: self.scx.static_root_path.as_deref(),
+ description: &desc,
+ keywords: BASIC_KEYWORDS,
+ resource_suffix: &self.scx.resource_suffix,
+ extra_scripts: &[&format!("source-files{}", self.scx.resource_suffix)],
+ static_extra_scripts: &[&format!("source-script{}", self.scx.resource_suffix)],
+ };
+ let result = layout::render(&mut v, &self.scx.layout,
+ &page, &(""), &Source(contents),
+ self.scx.css_file_extension.is_some(),
+ &self.scx.themes,
+ self.scx.generate_search_filter);
+ if let Err(e) = result {
+ return Err(Error::new(e, &cur));
+ }
+ self.scx.fs.write(&cur, &v)?;
+ self.scx.local_sources.insert(p.clone(), href);
+ 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 fn clean_path(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,
+ }
+ }
+}
+
+/// Wrapper struct to render the source code of a file. This will do things like
+/// adding line numbers to the left-hand side.
+struct Source<'a>(&'a str);
+
+impl<'a> fmt::Display for Source<'a> {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let Source(s) = *self;
+ let lines = s.lines().count();
+ let mut cols = 0;
+ let mut tmp = lines;
+ while tmp > 0 {
+ cols += 1;
+ tmp /= 10;
+ }
+ write!(fmt, "")?;
+ for i in 1..=lines {
+ write!(fmt, "{0:1$}\n", i, cols)?;
+ }
+ write!(fmt, "")?;
+ write!(fmt, "{}",
+ highlight::render_with_highlighting(s, None, None, None))?;
+ Ok(())
+ }
+}
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index e30b35937db9..968578f957c5 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -67,6 +67,7 @@ pub mod html {
crate mod render;
crate mod static_files;
crate mod toc;
+ crate mod sources;
}
mod markdown;
mod passes;