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