Skip to content

Commit d23042d

Browse files
Copy local resources into doc generated folder
1 parent a9985cf commit d23042d

File tree

5 files changed

+179
-1
lines changed

5 files changed

+179
-1
lines changed

src/librustdoc/formats/cache.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::mem;
2+
use std::path::PathBuf;
23

34
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
45
use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, DefIdSet};
@@ -121,6 +122,8 @@ pub(crate) struct Cache {
121122
pub(crate) intra_doc_links: FxHashMap<ItemId, Vec<clean::ItemLink>>,
122123
/// Cfg that have been hidden via #![doc(cfg_hide(...))]
123124
pub(crate) hidden_cfg: FxHashSet<clean::cfg::Cfg>,
125+
/// Local resources that are copied into the rustdoc output directory.
126+
pub(crate) local_resources: LocalResources,
124127
}
125128

126129
/// This struct is used to wrap the `cache` and `tcx` in order to run `DocFolder`.
@@ -516,6 +519,33 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
516519
}
517520
}
518521

522+
#[derive(Default)]
523+
pub(crate) struct LocalResources {
524+
/// The key is the original location of the resource. The value is the new name.
525+
pub(crate) resources_to_copy: FxHashMap<PathBuf, String>,
526+
/// This will be used when generating the HTML, once everything is generated, we copy these
527+
/// files into the static folder.
528+
///
529+
/// The key is the depth and the value is hashmap where the key is the path of the resource in
530+
/// the markdown and the value is the new path to the resources in the rustdoc output folder.
531+
pub(crate) resources_correspondance: FxHashMap<usize, FxHashMap<String, String>>,
532+
pub(crate) total_entries: usize,
533+
}
534+
535+
impl LocalResources {
536+
pub(crate) fn add_entry_at_depth(&mut self, depth: usize, key: String, value: String) {
537+
if self
538+
.resources_correspondance
539+
.entry(depth)
540+
.or_insert_with(FxHashMap::default)
541+
.insert(key, value)
542+
.is_none()
543+
{
544+
self.total_entries += 1;
545+
}
546+
}
547+
}
548+
519549
pub(crate) struct OrphanImplItem {
520550
pub(crate) parent: DefId,
521551
pub(crate) item: clean::Item,

src/librustdoc/html/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ mod url_parts_builder;
1313

1414
#[cfg(test)]
1515
mod tests;
16+
17+
pub(crate) const LOCAL_RESOURCES_FOLDER_NAME: &str = "local_resources";

src/librustdoc/html/render/context.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ pub(crate) struct SharedContext<'tcx> {
129129
pub(crate) call_locations: AllCallLocations,
130130
}
131131

132+
pub(crate) fn root_path(depth: usize) -> String {
133+
"../".repeat(depth)
134+
}
135+
132136
impl SharedContext<'_> {
133137
pub(crate) fn ensure_dir(&self, dst: &Path) -> Result<(), Error> {
134138
let mut dirs = self.created_dirs.borrow_mut();
@@ -165,7 +169,7 @@ impl<'tcx> Context<'tcx> {
165169
/// String representation of how to get back to the root path of the 'doc/'
166170
/// folder in terms of a relative URL.
167171
pub(super) fn root_path(&self) -> String {
168-
"../".repeat(self.current.len())
172+
root_path(self.current.len())
169173
}
170174

171175
fn render_item(&mut self, it: &clean::Item, is_module: bool) -> String {
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//! This file will go through all doc comments and retrieve local resources to then store them
2+
//! in the rustdoc output directory.
3+
4+
use pulldown_cmark::{Event, Parser, Tag};
5+
6+
use rustc_span::def_id::LOCAL_CRATE;
7+
use rustc_span::FileName;
8+
9+
use crate::clean::{Crate, Item};
10+
use crate::core::DocContext;
11+
use crate::html::markdown::main_body_opts;
12+
use crate::html::render::root_path;
13+
use crate::html::LOCAL_RESOURCES_FOLDER_NAME;
14+
use crate::passes::Pass;
15+
use crate::visit::DocVisitor;
16+
17+
use std::path::{Path, PathBuf};
18+
19+
pub(crate) const COLLECT_LOCAL_RESOURCES: Pass = Pass {
20+
name: "collect-local-resources",
21+
run: collect_local_resources,
22+
description: "resolves intra-doc links",
23+
};
24+
25+
fn span_file_path(cx: &DocContext<'_>, item: &Item) -> Option<PathBuf> {
26+
item.span(cx.tcx).and_then(|span| match span.filename(cx.sess()) {
27+
FileName::Real(ref path) => Some(path.local_path_if_available().into()),
28+
_ => None,
29+
})
30+
}
31+
32+
struct ResourcesCollector<'a, 'tcx> {
33+
cx: &'a mut DocContext<'tcx>,
34+
/// The depth is used to know how many "../" needs to be generated to get the original file
35+
/// path.
36+
depth: usize,
37+
}
38+
39+
fn collect_local_resources(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
40+
let mut collector = ResourcesCollector { cx, depth: 1 };
41+
collector.visit_crate(&krate);
42+
krate
43+
}
44+
45+
impl<'a, 'tcx> ResourcesCollector<'a, 'tcx> {
46+
pub fn handle_event(
47+
&mut self,
48+
event: Event<'_>,
49+
current_path: &mut Option<PathBuf>,
50+
item: &Item,
51+
) {
52+
if let Event::Start(Tag::Image(_, ref ori_path, _)) = event &&
53+
!ori_path.starts_with("http://") &&
54+
!ori_path.starts_with("https://")
55+
{
56+
let ori_path = ori_path.to_string();
57+
if self.cx.cache.local_resources.resources_correspondance
58+
.get(&self.depth)
59+
.and_then(|entry| entry.get(&ori_path))
60+
.is_some()
61+
{
62+
// We already have this entry so nothing to be done!
63+
return;
64+
}
65+
if current_path.is_none() {
66+
*current_path = span_file_path(self.cx, item);
67+
}
68+
let Some(current_path) = current_path else { return };
69+
70+
let path = match current_path.parent()
71+
.unwrap_or_else(|| Path::new("."))
72+
.join(&ori_path)
73+
.canonicalize()
74+
{
75+
Ok(p) => p,
76+
Err(_) => {
77+
self.cx.tcx.sess.struct_span_err(
78+
item.attr_span(self.cx.tcx),
79+
&format!("`{ori_path}`: No such file"),
80+
).emit();
81+
return;
82+
}
83+
};
84+
85+
if !path.is_file() {
86+
self.cx.tcx.sess.struct_span_err(
87+
item.attr_span(self.cx.tcx),
88+
&format!("`{ori_path}`: No such file (expanded into `{}`)", path.display()),
89+
).emit();
90+
return;
91+
}
92+
93+
// We now enter the file into the `resources_to_copy` in case it's not already in
94+
// and then generate a path the file that we store into `resources_correspondance`
95+
// with the `add_entry_at_depth` method.
96+
let current_nb = self.cx.cache.local_resources.resources_to_copy.len();
97+
let file_name = self.cx.cache.local_resources.resources_to_copy
98+
.entry(path.clone())
99+
.or_insert_with(|| {
100+
let extension = path.extension();
101+
let (extension, dot) = match extension.and_then(|e| e.to_str()) {
102+
Some(e) => (e, "."),
103+
None => ("", ""),
104+
};
105+
format!(
106+
"{current_nb}{}{dot}{extension}",
107+
self.cx.render_options.resource_suffix,
108+
)
109+
});
110+
let file = format!(
111+
"{}{LOCAL_RESOURCES_FOLDER_NAME}/{}/{file_name}",
112+
root_path(self.depth),
113+
self.cx.tcx.crate_name(LOCAL_CRATE).as_str(),
114+
);
115+
self.cx.cache.local_resources.add_entry_at_depth(self.depth, ori_path, file);
116+
}
117+
}
118+
}
119+
120+
impl<'a, 'tcx> DocVisitor for ResourcesCollector<'a, 'tcx> {
121+
fn visit_item(&mut self, item: &Item) {
122+
if let Some(md) = item.collapsed_doc_value() {
123+
let mut current_path = None;
124+
for event in Parser::new_ext(&md, main_body_opts()).into_iter() {
125+
self.handle_event(event, &mut current_path, item);
126+
}
127+
}
128+
129+
if item.is_mod() && !item.is_crate() {
130+
self.depth += 1;
131+
self.visit_item_recur(item);
132+
self.depth -= 1;
133+
} else {
134+
self.visit_item_recur(item)
135+
}
136+
}
137+
}

src/librustdoc/passes/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ pub(crate) use self::propagate_doc_cfg::PROPAGATE_DOC_CFG;
2727
pub(crate) mod collect_intra_doc_links;
2828
pub(crate) use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS;
2929

30+
pub(crate) mod collect_local_resources;
31+
pub(crate) use self::collect_local_resources::COLLECT_LOCAL_RESOURCES;
32+
3033
mod check_doc_test_visibility;
3134
pub(crate) use self::check_doc_test_visibility::CHECK_DOC_TEST_VISIBILITY;
3235

@@ -77,6 +80,7 @@ pub(crate) const PASSES: &[Pass] = &[
7780
PROPAGATE_DOC_CFG,
7881
COLLECT_INTRA_DOC_LINKS,
7982
COLLECT_TRAIT_IMPLS,
83+
COLLECT_LOCAL_RESOURCES,
8084
CALCULATE_DOC_COVERAGE,
8185
RUN_LINTS,
8286
];
@@ -89,6 +93,7 @@ pub(crate) const DEFAULT_PASSES: &[ConditionalPass] = &[
8993
ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate),
9094
ConditionalPass::new(STRIP_PRIV_IMPORTS, WhenDocumentPrivate),
9195
ConditionalPass::always(COLLECT_INTRA_DOC_LINKS),
96+
ConditionalPass::always(COLLECT_LOCAL_RESOURCES),
9297
ConditionalPass::always(PROPAGATE_DOC_CFG),
9398
ConditionalPass::always(RUN_LINTS),
9499
];

0 commit comments

Comments
 (0)