]>
Commit | Line | Data |
---|---|---|
e1599b0c XL |
1 | use crate::clean; |
2 | use crate::docfs::PathError; | |
3dfed10e | 3 | use crate::error::Error; |
dfeec247 XL |
4 | use crate::html::format::Buffer; |
5 | use crate::html::highlight; | |
e1599b0c | 6 | use crate::html::layout; |
94222f64 | 7 | use crate::html::render::{Context, BASIC_KEYWORDS}; |
3c0e092e | 8 | use crate::visit::DocVisitor; |
923072b8 | 9 | |
94222f64 | 10 | use rustc_data_structures::fx::{FxHashMap, FxHashSet}; |
ba9703b0 | 11 | use rustc_hir::def_id::LOCAL_CRATE; |
94222f64 | 12 | use rustc_middle::ty::TyCtxt; |
fc512014 | 13 | use rustc_session::Session; |
dfeec247 | 14 | use rustc_span::source_map::FileName; |
923072b8 | 15 | |
e1599b0c XL |
16 | use std::ffi::OsStr; |
17 | use std::fs; | |
18 | use std::path::{Component, Path, PathBuf}; | |
923072b8 | 19 | use std::rc::Rc; |
e1599b0c | 20 | |
923072b8 | 21 | pub(crate) fn render(cx: &mut Context<'_>, krate: &clean::Crate) -> Result<(), Error> { |
e1599b0c | 22 | info!("emitting source files"); |
3c0e092e | 23 | |
a2a8927a | 24 | let dst = cx.dst.join("src").join(krate.name(cx.tcx()).as_str()); |
94222f64 | 25 | cx.shared.ensure_dir(&dst)?; |
3c0e092e XL |
26 | |
27 | let mut collector = SourceCollector { dst, cx, emitted_local_sources: FxHashSet::default() }; | |
28 | collector.visit_crate(krate); | |
29 | Ok(()) | |
e1599b0c XL |
30 | } |
31 | ||
923072b8 | 32 | pub(crate) fn collect_local_sources<'tcx>( |
94222f64 XL |
33 | tcx: TyCtxt<'tcx>, |
34 | src_root: &Path, | |
3c0e092e XL |
35 | krate: &clean::Crate, |
36 | ) -> FxHashMap<PathBuf, String> { | |
94222f64 | 37 | let mut lsc = LocalSourcesCollector { tcx, local_sources: FxHashMap::default(), src_root }; |
3c0e092e XL |
38 | lsc.visit_crate(krate); |
39 | lsc.local_sources | |
94222f64 XL |
40 | } |
41 | ||
42 | struct LocalSourcesCollector<'a, 'tcx> { | |
43 | tcx: TyCtxt<'tcx>, | |
44 | local_sources: FxHashMap<PathBuf, String>, | |
45 | src_root: &'a Path, | |
46 | } | |
47 | ||
48 | fn is_real_and_local(span: clean::Span, sess: &Session) -> bool { | |
3c0e092e | 49 | span.cnum(sess) == LOCAL_CRATE && span.filename(sess).is_real() |
94222f64 XL |
50 | } |
51 | ||
52 | impl LocalSourcesCollector<'_, '_> { | |
53 | fn add_local_source(&mut self, item: &clean::Item) { | |
54 | let sess = self.tcx.sess; | |
55 | let span = item.span(self.tcx); | |
f2b60f7d | 56 | let Some(span) = span else { return }; |
94222f64 XL |
57 | // skip all synthetic "files" |
58 | if !is_real_and_local(span, sess) { | |
59 | return; | |
60 | } | |
61 | let filename = span.filename(sess); | |
3c0e092e XL |
62 | let p = if let FileName::Real(file) = filename { |
63 | match file.into_local_path() { | |
64 | Some(p) => p, | |
65 | None => return, | |
66 | } | |
67 | } else { | |
68 | return; | |
94222f64 XL |
69 | }; |
70 | if self.local_sources.contains_key(&*p) { | |
71 | // We've already emitted this source | |
72 | return; | |
73 | } | |
74 | ||
75 | let mut href = String::new(); | |
3c0e092e | 76 | clean_path(self.src_root, &p, false, |component| { |
94222f64 XL |
77 | href.push_str(&component.to_string_lossy()); |
78 | href.push('/'); | |
79 | }); | |
80 | ||
81 | let mut src_fname = p.file_name().expect("source has no filename").to_os_string(); | |
82 | src_fname.push(".html"); | |
83 | href.push_str(&src_fname.to_string_lossy()); | |
84 | self.local_sources.insert(p, href); | |
85 | } | |
86 | } | |
87 | ||
3c0e092e XL |
88 | impl DocVisitor for LocalSourcesCollector<'_, '_> { |
89 | fn visit_item(&mut self, item: &clean::Item) { | |
90 | self.add_local_source(item); | |
94222f64 | 91 | |
3c0e092e | 92 | self.visit_item_recur(item) |
94222f64 XL |
93 | } |
94 | } | |
95 | ||
e1599b0c | 96 | /// Helper struct to render all source code to HTML pages |
fc512014 | 97 | struct SourceCollector<'a, 'tcx> { |
94222f64 | 98 | cx: &'a mut Context<'tcx>, |
e1599b0c XL |
99 | |
100 | /// Root destination to place all HTML output into | |
101 | dst: PathBuf, | |
94222f64 | 102 | emitted_local_sources: FxHashSet<PathBuf>, |
e1599b0c XL |
103 | } |
104 | ||
3c0e092e XL |
105 | impl DocVisitor for SourceCollector<'_, '_> { |
106 | fn visit_item(&mut self, item: &clean::Item) { | |
107 | if !self.cx.include_sources { | |
108 | return; | |
109 | } | |
110 | ||
94222f64 XL |
111 | let tcx = self.cx.tcx(); |
112 | let span = item.span(tcx); | |
f2b60f7d | 113 | let Some(span) = span else { return }; |
94222f64 XL |
114 | let sess = tcx.sess; |
115 | ||
fc512014 | 116 | // If we're not rendering sources, there's nothing to do. |
e1599b0c XL |
117 | // If we're including source files, and we haven't seen this file yet, |
118 | // then we need to render it out to the filesystem. | |
3c0e092e | 119 | if is_real_and_local(span, sess) { |
94222f64 XL |
120 | let filename = span.filename(sess); |
121 | let span = span.inner(); | |
122 | let pos = sess.source_map().lookup_source_file(span.lo()); | |
123 | let file_span = span.with_lo(pos.start_pos).with_hi(pos.end_pos); | |
e1599b0c XL |
124 | // If it turns out that we couldn't read this file, then we probably |
125 | // can't read any of the files (generating html output from json or | |
126 | // something like that), so just don't include sources for the | |
127 | // entire crate. The other option is maintaining this mapping on a | |
128 | // per-file basis, but that's probably not worth it... | |
94222f64 | 129 | self.cx.include_sources = match self.emit_source(&filename, file_span) { |
e1599b0c XL |
130 | Ok(()) => true, |
131 | Err(e) => { | |
94222f64 XL |
132 | self.cx.shared.tcx.sess.span_err( |
133 | span, | |
17df50a5 XL |
134 | &format!( |
135 | "failed to render source code for `{}`: {}", | |
136 | filename.prefer_local(), | |
94222f64 | 137 | e, |
17df50a5 | 138 | ), |
dfeec247 | 139 | ); |
e1599b0c XL |
140 | false |
141 | } | |
142 | }; | |
143 | } | |
3c0e092e XL |
144 | |
145 | self.visit_item_recur(item) | |
e1599b0c XL |
146 | } |
147 | } | |
148 | ||
a2a8927a | 149 | impl SourceCollector<'_, '_> { |
e1599b0c | 150 | /// Renders the given filename into its corresponding HTML source file. |
94222f64 XL |
151 | fn emit_source( |
152 | &mut self, | |
153 | filename: &FileName, | |
154 | file_span: rustc_span::Span, | |
155 | ) -> Result<(), Error> { | |
e1599b0c | 156 | let p = match *filename { |
17df50a5 XL |
157 | FileName::Real(ref file) => { |
158 | if let Some(local_path) = file.local_path() { | |
159 | local_path.to_path_buf() | |
160 | } else { | |
161 | unreachable!("only the current crate should have sources emitted"); | |
162 | } | |
163 | } | |
e1599b0c XL |
164 | _ => return Ok(()), |
165 | }; | |
94222f64 | 166 | if self.emitted_local_sources.contains(&*p) { |
e1599b0c XL |
167 | // We've already emitted this source |
168 | return Ok(()); | |
169 | } | |
170 | ||
5869c6ff | 171 | let contents = match fs::read_to_string(&p) { |
e1599b0c XL |
172 | Ok(contents) => contents, |
173 | Err(e) => { | |
174 | return Err(Error::new(e, &p)); | |
175 | } | |
176 | }; | |
177 | ||
178 | // Remove the utf-8 BOM if any | |
3c0e092e | 179 | let contents = contents.strip_prefix('\u{feff}').unwrap_or(&contents); |
e1599b0c | 180 | |
923072b8 | 181 | let shared = Rc::clone(&self.cx.shared); |
e1599b0c XL |
182 | // Create the intermediate directories |
183 | let mut cur = self.dst.clone(); | |
184 | let mut root_path = String::from("../../"); | |
923072b8 | 185 | clean_path(&shared.src_root, &p, false, |component| { |
e1599b0c XL |
186 | cur.push(component); |
187 | root_path.push_str("../"); | |
e1599b0c | 188 | }); |
94222f64 | 189 | |
923072b8 | 190 | shared.ensure_dir(&cur)?; |
29967ef6 XL |
191 | |
192 | let src_fname = p.file_name().expect("source has no filename").to_os_string(); | |
193 | let mut fname = src_fname.clone(); | |
e1599b0c XL |
194 | fname.push(".html"); |
195 | cur.push(&fname); | |
e1599b0c | 196 | |
29967ef6 | 197 | let title = format!("{} - source", src_fname.to_string_lossy()); |
17df50a5 | 198 | let desc = format!("Source of the Rust file `{}`.", filename.prefer_remapped()); |
e1599b0c XL |
199 | let page = layout::Page { |
200 | title: &title, | |
201 | css_class: "source", | |
202 | root_path: &root_path, | |
923072b8 | 203 | static_root_path: shared.static_root_path.as_deref(), |
e1599b0c XL |
204 | description: &desc, |
205 | keywords: BASIC_KEYWORDS, | |
923072b8 | 206 | resource_suffix: &shared.resource_suffix, |
e1599b0c | 207 | }; |
dfeec247 | 208 | let v = layout::render( |
923072b8 | 209 | &shared.layout, |
dfeec247 XL |
210 | &page, |
211 | "", | |
94222f64 | 212 | |buf: &mut _| { |
923072b8 | 213 | let cx = &mut self.cx; |
3c0e092e XL |
214 | print_src( |
215 | buf, | |
216 | contents, | |
3c0e092e | 217 | file_span, |
923072b8 | 218 | cx, |
3c0e092e | 219 | &root_path, |
f2b60f7d | 220 | highlight::DecorationInfo::default(), |
3c0e092e XL |
221 | SourceContext::Standalone, |
222 | ) | |
94222f64 | 223 | }, |
923072b8 | 224 | &shared.style_files, |
dfeec247 | 225 | ); |
923072b8 | 226 | shared.fs.write(cur, v)?; |
94222f64 | 227 | self.emitted_local_sources.insert(p); |
e1599b0c XL |
228 | Ok(()) |
229 | } | |
230 | } | |
231 | ||
232 | /// Takes a path to a source file and cleans the path to it. This canonicalizes | |
233 | /// things like ".." to components which preserve the "top down" hierarchy of a | |
234 | /// static HTML tree. Each component in the cleaned path will be passed as an | |
235 | /// argument to `f`. The very last component of the path (ie the file name) will | |
236 | /// be passed to `f` if `keep_filename` is true, and ignored otherwise. | |
923072b8 | 237 | pub(crate) fn clean_path<F>(src_root: &Path, p: &Path, keep_filename: bool, mut f: F) |
e1599b0c XL |
238 | where |
239 | F: FnMut(&OsStr), | |
240 | { | |
241 | // make it relative, if possible | |
242 | let p = p.strip_prefix(src_root).unwrap_or(p); | |
243 | ||
244 | let mut iter = p.components().peekable(); | |
245 | ||
246 | while let Some(c) = iter.next() { | |
247 | if !keep_filename && iter.peek().is_none() { | |
248 | break; | |
249 | } | |
250 | ||
251 | match c { | |
252 | Component::ParentDir => f("up".as_ref()), | |
253 | Component::Normal(c) => f(c), | |
254 | _ => continue, | |
255 | } | |
256 | } | |
257 | } | |
258 | ||
923072b8 | 259 | pub(crate) enum SourceContext { |
3c0e092e XL |
260 | Standalone, |
261 | Embedded { offset: usize }, | |
262 | } | |
263 | ||
e1599b0c XL |
264 | /// Wrapper struct to render the source code of a file. This will do things like |
265 | /// adding line numbers to the left-hand side. | |
923072b8 | 266 | pub(crate) fn print_src( |
94222f64 XL |
267 | buf: &mut Buffer, |
268 | s: &str, | |
94222f64 XL |
269 | file_span: rustc_span::Span, |
270 | context: &Context<'_>, | |
271 | root_path: &str, | |
f2b60f7d | 272 | decoration_info: highlight::DecorationInfo, |
3c0e092e | 273 | source_context: SourceContext, |
94222f64 | 274 | ) { |
e1599b0c | 275 | let lines = s.lines().count(); |
17df50a5 | 276 | let mut line_numbers = Buffer::empty_from(buf); |
17df50a5 | 277 | line_numbers.write_str("<pre class=\"line-numbers\">"); |
a2a8927a XL |
278 | match source_context { |
279 | SourceContext::Standalone => { | |
280 | for line in 1..=lines { | |
5e7ed085 | 281 | writeln!(line_numbers, "<span id=\"{0}\">{0}</span>", line) |
3c0e092e | 282 | } |
a2a8927a XL |
283 | } |
284 | SourceContext::Embedded { offset } => { | |
285 | for line in 1..=lines { | |
5e7ed085 | 286 | writeln!(line_numbers, "<span>{0}</span>", line + offset) |
3c0e092e XL |
287 | } |
288 | } | |
e1599b0c | 289 | } |
17df50a5 | 290 | line_numbers.write_str("</pre>"); |
f2b60f7d FG |
291 | let current_href = &context |
292 | .href_from_span(clean::Span::new(file_span), false) | |
293 | .expect("only local crates should have sources emitted"); | |
294 | highlight::render_source_with_highlighting( | |
94222f64 XL |
295 | s, |
296 | buf, | |
f2b60f7d FG |
297 | line_numbers, |
298 | highlight::HrefContext { context, file_span, root_path, current_href }, | |
3c0e092e | 299 | decoration_info, |
94222f64 | 300 | ); |
e1599b0c | 301 | } |