about summary refs log tree commit diff
path: root/src/librustdoc/html/sources.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc/html/sources.rs')
-rw-r--r--src/librustdoc/html/sources.rs187
1 files changed, 187 insertions, 0 deletions
diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs
new file mode 100644
index 00000000000..c1f1f59d914
--- /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<clean::Crate, Error> {
+    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<clean::Item> {
+        // 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<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,
+        }
+    }
+}
+
+/// 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, "<pre class=\"line-numbers\">")?;
+        for i in 1..=lines {
+            write!(fmt, "<span id=\"{0}\">{0:1$}</span>\n", i, cols)?;
+        }
+        write!(fmt, "</pre>")?;
+        write!(fmt, "{}",
+               highlight::render_with_highlighting(s, None, None, None))?;
+        Ok(())
+    }
+}