-
Notifications
You must be signed in to change notification settings - Fork 13.2k
/
Copy pathsources.rs
176 lines (162 loc) · 6.11 KB
/
sources.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
use crate::clean;
use crate::docfs::PathError;
use crate::fold::DocFolder;
use crate::html::format::Buffer;
use crate::html::highlight;
use crate::html::layout;
use crate::html::render::{Error, SharedContext, BASIC_KEYWORDS};
use rustc_span::source_map::FileName;
use std::ffi::OsStr;
use std::fs;
use std::path::{Component, Path, PathBuf};
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 synthetic "files"
&& 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 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 v = layout::render(
&self.scx.layout,
&page,
"",
|buf: &mut _| print_src(buf, &contents),
&self.scx.themes,
);
self.scx.fs.write(&cur, v.as_bytes())?;
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.
fn print_src(buf: &mut Buffer, s: &str) {
let lines = s.lines().count();
let mut cols = 0;
let mut tmp = lines;
while tmp > 0 {
cols += 1;
tmp /= 10;
}
write!(buf, "<pre class=\"line-numbers\">");
for i in 1..=lines {
write!(buf, "<span id=\"{0}\">{0:1$}</span>\n", i, cols);
}
write!(buf, "</pre>");
write!(buf, "{}", highlight::render_with_highlighting(s, None, None, None));
}