2 use crate::docfs
::PathError
;
3 use crate::error
::Error
;
4 use crate::html
::format
::Buffer
;
5 use crate::html
::highlight
;
6 use crate::html
::layout
;
7 use crate::html
::render
::{Context, BASIC_KEYWORDS}
;
8 use crate::visit
::DocVisitor
;
9 use rustc_data_structures
::fx
::{FxHashMap, FxHashSet}
;
10 use rustc_hir
::def_id
::LOCAL_CRATE
;
11 use rustc_middle
::ty
::TyCtxt
;
12 use rustc_session
::Session
;
13 use rustc_span
::edition
::Edition
;
14 use rustc_span
::source_map
::FileName
;
17 use std
::path
::{Component, Path, PathBuf}
;
19 crate fn render(cx
: &mut Context
<'_
>, krate
: &clean
::Crate
) -> Result
<(), Error
> {
20 info
!("emitting source files");
22 let dst
= cx
.dst
.join("src").join(krate
.name(cx
.tcx()).as_str());
23 cx
.shared
.ensure_dir(&dst
)?
;
25 let mut collector
= SourceCollector { dst, cx, emitted_local_sources: FxHashSet::default() }
;
26 collector
.visit_crate(krate
);
30 crate fn collect_local_sources
<'tcx
>(
34 ) -> FxHashMap
<PathBuf
, String
> {
35 let mut lsc
= LocalSourcesCollector { tcx, local_sources: FxHashMap::default(), src_root }
;
36 lsc
.visit_crate(krate
);
40 struct LocalSourcesCollector
<'a
, 'tcx
> {
42 local_sources
: FxHashMap
<PathBuf
, String
>,
46 fn is_real_and_local(span
: clean
::Span
, sess
: &Session
) -> bool
{
47 span
.cnum(sess
) == LOCAL_CRATE
&& span
.filename(sess
).is_real()
50 impl LocalSourcesCollector
<'_
, '_
> {
51 fn add_local_source(&mut self, item
: &clean
::Item
) {
52 let sess
= self.tcx
.sess
;
53 let span
= item
.span(self.tcx
);
54 // skip all synthetic "files"
55 if !is_real_and_local(span
, sess
) {
58 let filename
= span
.filename(sess
);
59 let p
= if let FileName
::Real(file
) = filename
{
60 match file
.into_local_path() {
67 if self.local_sources
.contains_key(&*p
) {
68 // We've already emitted this source
72 let mut href
= String
::new();
73 clean_path(self.src_root
, &p
, false, |component
| {
74 href
.push_str(&component
.to_string_lossy());
78 let mut src_fname
= p
.file_name().expect("source has no filename").to_os_string();
79 src_fname
.push(".html");
80 href
.push_str(&src_fname
.to_string_lossy());
81 self.local_sources
.insert(p
, href
);
85 impl DocVisitor
for LocalSourcesCollector
<'_
, '_
> {
86 fn visit_item(&mut self, item
: &clean
::Item
) {
87 self.add_local_source(item
);
89 self.visit_item_recur(item
)
93 /// Helper struct to render all source code to HTML pages
94 struct SourceCollector
<'a
, 'tcx
> {
95 cx
: &'a
mut Context
<'tcx
>,
97 /// Root destination to place all HTML output into
99 emitted_local_sources
: FxHashSet
<PathBuf
>,
102 impl DocVisitor
for SourceCollector
<'_
, '_
> {
103 fn visit_item(&mut self, item
: &clean
::Item
) {
104 if !self.cx
.include_sources
{
108 let tcx
= self.cx
.tcx();
109 let span
= item
.span(tcx
);
112 // If we're not rendering sources, there's nothing to do.
113 // If we're including source files, and we haven't seen this file yet,
114 // then we need to render it out to the filesystem.
115 if is_real_and_local(span
, sess
) {
116 let filename
= span
.filename(sess
);
117 let span
= span
.inner();
118 let pos
= sess
.source_map().lookup_source_file(span
.lo());
119 let file_span
= span
.with_lo(pos
.start_pos
).with_hi(pos
.end_pos
);
120 // If it turns out that we couldn't read this file, then we probably
121 // can't read any of the files (generating html output from json or
122 // something like that), so just don't include sources for the
123 // entire crate. The other option is maintaining this mapping on a
124 // per-file basis, but that's probably not worth it...
125 self.cx
.include_sources
= match self.emit_source(&filename
, file_span
) {
128 self.cx
.shared
.tcx
.sess
.span_err(
131 "failed to render source code for `{}`: {}",
132 filename
.prefer_local(),
141 self.visit_item_recur(item
)
145 impl SourceCollector
<'_
, '_
> {
146 /// Renders the given filename into its corresponding HTML source file.
150 file_span
: rustc_span
::Span
,
151 ) -> Result
<(), Error
> {
152 let p
= match *filename
{
153 FileName
::Real(ref file
) => {
154 if let Some(local_path
) = file
.local_path() {
155 local_path
.to_path_buf()
157 unreachable
!("only the current crate should have sources emitted");
162 if self.emitted_local_sources
.contains(&*p
) {
163 // We've already emitted this source
167 let contents
= match fs
::read_to_string(&p
) {
168 Ok(contents
) => contents
,
170 return Err(Error
::new(e
, &p
));
174 // Remove the utf-8 BOM if any
175 let contents
= contents
.strip_prefix('
\u{feff}'
).unwrap_or(&contents
);
177 // Create the intermediate directories
178 let mut cur
= self.dst
.clone();
179 let mut root_path
= String
::from("../../");
180 clean_path(&self.cx
.shared
.src_root
, &p
, false, |component
| {
182 root_path
.push_str("../");
185 self.cx
.shared
.ensure_dir(&cur
)?
;
187 let src_fname
= p
.file_name().expect("source has no filename").to_os_string();
188 let mut fname
= src_fname
.clone();
192 let title
= format
!("{} - source", src_fname
.to_string_lossy());
193 let desc
= format
!("Source of the Rust file `{}`.", filename
.prefer_remapped());
194 let page
= layout
::Page
{
197 root_path
: &root_path
,
198 static_root_path
: self.cx
.shared
.static_root_path
.as_deref(),
200 keywords
: BASIC_KEYWORDS
,
201 resource_suffix
: &self.cx
.shared
.resource_suffix
,
202 extra_scripts
: &[&format
!("source-files{}", self.cx
.shared
.resource_suffix
)],
203 static_extra_scripts
: &[&format
!("source-script{}", self.cx
.shared
.resource_suffix
)],
205 let v
= layout
::render(
206 &self.cx
.shared
.layout
,
213 self.cx
.shared
.edition(),
218 SourceContext
::Standalone
,
221 &self.cx
.shared
.style_files
,
223 self.cx
.shared
.fs
.write(cur
, v
)?
;
224 self.emitted_local_sources
.insert(p
);
229 /// Takes a path to a source file and cleans the path to it. This canonicalizes
230 /// things like ".." to components which preserve the "top down" hierarchy of a
231 /// static HTML tree. Each component in the cleaned path will be passed as an
232 /// argument to `f`. The very last component of the path (ie the file name) will
233 /// be passed to `f` if `keep_filename` is true, and ignored otherwise.
234 crate fn clean_path
<F
>(src_root
: &Path
, p
: &Path
, keep_filename
: bool
, mut f
: F
)
238 // make it relative, if possible
239 let p
= p
.strip_prefix(src_root
).unwrap_or(p
);
241 let mut iter
= p
.components().peekable();
243 while let Some(c
) = iter
.next() {
244 if !keep_filename
&& iter
.peek().is_none() {
249 Component
::ParentDir
=> f("up".as_ref()),
250 Component
::Normal(c
) => f(c
),
256 crate enum SourceContext
{
258 Embedded { offset: usize }
,
261 /// Wrapper struct to render the source code of a file. This will do things like
262 /// adding line numbers to the left-hand side.
267 file_span
: rustc_span
::Span
,
268 context
: &Context
<'_
>,
270 decoration_info
: Option
<highlight
::DecorationInfo
>,
271 source_context
: SourceContext
,
273 let lines
= s
.lines().count();
274 let mut line_numbers
= Buffer
::empty_from(buf
);
275 line_numbers
.write_str("<pre class=\"line-numbers\">");
276 match source_context
{
277 SourceContext
::Standalone
=> {
278 for line
in 1..=lines
{
279 writeln
!(line_numbers
, "<span id=\"{0}\">{0}</span>", line
)
282 SourceContext
::Embedded { offset }
=> {
283 for line
in 1..=lines
{
284 writeln
!(line_numbers
, "<span>{0}</span>", line
+ offset
)
288 line_numbers
.write_str("</pre>");
289 highlight
::render_with_highlighting(
297 Some(highlight
::ContextInfo { context, file_span, root_path }
),