]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/sources.rs
Rollup merge of #64136 - crgl:doc-from-parser-lhs, r=Centril
[rust.git] / src / librustdoc / html / sources.rs
1 use crate::clean;
2 use crate::docfs::PathError;
3 use crate::fold::DocFolder;
4 use crate::html::layout;
5 use crate::html::render::{Error, SharedContext, BASIC_KEYWORDS};
6 use crate::html::highlight;
7 use crate::html::format::Buffer;
8 use std::ffi::OsStr;
9 use std::fs;
10 use std::path::{Component, Path, PathBuf};
11 use syntax::source_map::FileName;
12
13 crate fn render(dst: &Path, scx: &mut SharedContext,
14                   krate: clean::Crate) -> Result<clean::Crate, Error> {
15     info!("emitting source files");
16     let dst = dst.join("src").join(&krate.name);
17     scx.ensure_dir(&dst)?;
18     let mut folder = SourceCollector {
19         dst,
20         scx,
21     };
22     Ok(folder.fold_crate(krate))
23 }
24
25 /// Helper struct to render all source code to HTML pages
26 struct SourceCollector<'a> {
27     scx: &'a mut SharedContext,
28
29     /// Root destination to place all HTML output into
30     dst: PathBuf,
31 }
32
33 impl<'a> DocFolder for SourceCollector<'a> {
34     fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
35         // If we're including source files, and we haven't seen this file yet,
36         // then we need to render it out to the filesystem.
37         if self.scx.include_sources
38             // skip all invalid or macro spans
39             && item.source.filename.is_real()
40             // skip non-local items
41             && item.def_id.is_local() {
42
43             // If it turns out that we couldn't read this file, then we probably
44             // can't read any of the files (generating html output from json or
45             // something like that), so just don't include sources for the
46             // entire crate. The other option is maintaining this mapping on a
47             // per-file basis, but that's probably not worth it...
48             self.scx
49                 .include_sources = match self.emit_source(&item.source.filename) {
50                 Ok(()) => true,
51                 Err(e) => {
52                     println!("warning: source code was requested to be rendered, \
53                               but processing `{}` had an error: {}",
54                              item.source.filename, e);
55                     println!("         skipping rendering of source code");
56                     false
57                 }
58             };
59         }
60         self.fold_item_recur(item)
61     }
62 }
63
64 impl<'a> SourceCollector<'a> {
65     /// Renders the given filename into its corresponding HTML source file.
66     fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> {
67         let p = match *filename {
68             FileName::Real(ref file) => file,
69             _ => return Ok(()),
70         };
71         if self.scx.local_sources.contains_key(&**p) {
72             // We've already emitted this source
73             return Ok(());
74         }
75
76         let contents = match fs::read_to_string(&p) {
77             Ok(contents) => contents,
78             Err(e) => {
79                 return Err(Error::new(e, &p));
80             }
81         };
82
83         // Remove the utf-8 BOM if any
84         let contents = if contents.starts_with("\u{feff}") {
85             &contents[3..]
86         } else {
87             &contents[..]
88         };
89
90         // Create the intermediate directories
91         let mut cur = self.dst.clone();
92         let mut root_path = String::from("../../");
93         let mut href = String::new();
94         clean_path(&self.scx.src_root, &p, false, |component| {
95             cur.push(component);
96             root_path.push_str("../");
97             href.push_str(&component.to_string_lossy());
98             href.push('/');
99         });
100         self.scx.ensure_dir(&cur)?;
101         let mut fname = p.file_name()
102                          .expect("source has no filename")
103                          .to_os_string();
104         fname.push(".html");
105         cur.push(&fname);
106         href.push_str(&fname.to_string_lossy());
107
108         let title = format!("{} -- source", cur.file_name().expect("failed to get file name")
109                                                .to_string_lossy());
110         let desc = format!("Source to the Rust file `{}`.", filename);
111         let page = layout::Page {
112             title: &title,
113             css_class: "source",
114             root_path: &root_path,
115             static_root_path: self.scx.static_root_path.as_deref(),
116             description: &desc,
117             keywords: BASIC_KEYWORDS,
118             resource_suffix: &self.scx.resource_suffix,
119             extra_scripts: &[&format!("source-files{}", self.scx.resource_suffix)],
120             static_extra_scripts: &[&format!("source-script{}", self.scx.resource_suffix)],
121         };
122         let v = layout::render(&self.scx.layout,
123                        &page, "", |buf: &mut _| print_src(buf, &contents),
124                        &self.scx.themes);
125         self.scx.fs.write(&cur, v.as_bytes())?;
126         self.scx.local_sources.insert(p.clone(), href);
127         Ok(())
128     }
129 }
130
131 /// Takes a path to a source file and cleans the path to it. This canonicalizes
132 /// things like ".." to components which preserve the "top down" hierarchy of a
133 /// static HTML tree. Each component in the cleaned path will be passed as an
134 /// argument to `f`. The very last component of the path (ie the file name) will
135 /// be passed to `f` if `keep_filename` is true, and ignored otherwise.
136 pub fn clean_path<F>(src_root: &Path, p: &Path, keep_filename: bool, mut f: F)
137 where
138     F: FnMut(&OsStr),
139 {
140     // make it relative, if possible
141     let p = p.strip_prefix(src_root).unwrap_or(p);
142
143     let mut iter = p.components().peekable();
144
145     while let Some(c) = iter.next() {
146         if !keep_filename && iter.peek().is_none() {
147             break;
148         }
149
150         match c {
151             Component::ParentDir => f("up".as_ref()),
152             Component::Normal(c) => f(c),
153             _ => continue,
154         }
155     }
156 }
157
158 /// Wrapper struct to render the source code of a file. This will do things like
159 /// adding line numbers to the left-hand side.
160 fn print_src(buf: &mut Buffer, s: &str) {
161     let lines = s.lines().count();
162     let mut cols = 0;
163     let mut tmp = lines;
164     while tmp > 0 {
165         cols += 1;
166         tmp /= 10;
167     }
168     write!(buf, "<pre class=\"line-numbers\">");
169     for i in 1..=lines {
170         write!(buf, "<span id=\"{0}\">{0:1$}</span>\n", i, cols);
171     }
172     write!(buf, "</pre>");
173     write!(buf, "{}",
174             highlight::render_with_highlighting(s, None, None, None));
175 }