Skip to content

Commit 3b8de99

Browse files
Support case insensitive systems for file generation
1 parent aef1140 commit 3b8de99

File tree

10 files changed

+300
-41
lines changed

10 files changed

+300
-41
lines changed

src/librustdoc/config.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ crate struct RenderOptions {
265265
crate document_hidden: bool,
266266
/// If `true`, generate a JSON file in the crate folder instead of HTML redirection files.
267267
crate generate_redirect_map: bool,
268+
/// If this option is set to `true`, HTML files will be generated as it would on a
269+
/// case-insensitive file system.
270+
crate generate_case_insensitive: bool,
268271
crate unstable_features: rustc_feature::UnstableFeatures,
269272
}
270273

@@ -580,6 +583,7 @@ impl Options {
580583
let document_hidden = matches.opt_present("document-hidden-items");
581584
let run_check = matches.opt_present("check");
582585
let generate_redirect_map = matches.opt_present("generate-redirect-map");
586+
let generate_case_insensitive = matches.opt_present("generate-case-insensitive");
583587

584588
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format);
585589

@@ -638,6 +642,7 @@ impl Options {
638642
document_private,
639643
document_hidden,
640644
generate_redirect_map,
645+
generate_case_insensitive,
641646
unstable_features: rustc_feature::UnstableFeatures::from_environment(
642647
crate_name.as_deref(),
643648
),

src/librustdoc/formats/renderer.rs

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
12
use rustc_middle::ty::TyCtxt;
23
use rustc_span::{edition::Edition, Symbol};
34

5+
use std::fs;
6+
use std::io::Write;
7+
use std::path::Path;
8+
49
use crate::clean;
510
use crate::config::RenderOptions;
611
use crate::error::Error;
712
use crate::formats::cache::Cache;
13+
use crate::html::render::print_item::item_path;
814

915
/// Allows for different backends to rustdoc to be used with the `run_format()` function. Each
1016
/// backend renderer has hooks for initialization, documenting an item, entering and exiting a
@@ -26,6 +32,7 @@ crate trait FormatRenderer<'tcx>: Sized {
2632
edition: Edition,
2733
cache: Cache,
2834
tcx: TyCtxt<'tcx>,
35+
case_insensitive_conflicts: Option<FxHashSet<String>>,
2936
) -> Result<(Self, clean::Crate), Error>;
3037

3138
/// Make a new renderer to render a child of the item currently being rendered.
@@ -52,6 +59,71 @@ crate trait FormatRenderer<'tcx>: Sized {
5259
fn cache(&self) -> &Cache;
5360
}
5461

62+
fn handle_module(dst: &Path, item: &clean::Item, paths_map: &mut FxHashMap<String, usize>) {
63+
// modules are special because they add a namespace. We also need to
64+
// recurse into the items of the module as well.
65+
let name = item.name.as_ref().unwrap().to_string();
66+
if name.is_empty() {
67+
panic!("Unexpected module with empty name");
68+
}
69+
let module = match *item.kind {
70+
clean::StrippedItem(box clean::ModuleItem(ref m)) | clean::ModuleItem(ref m) => m,
71+
_ => unreachable!(),
72+
};
73+
let mod_path = dst.join(&name);
74+
for it in &module.items {
75+
if it.is_mod() {
76+
handle_module(&mod_path, it, paths_map);
77+
} else if it.name.is_some() && !it.is_extern_crate() {
78+
let name = it.name.as_ref().unwrap();
79+
let item_type = it.type_();
80+
let file_name = &item_path(item_type, &name.as_str());
81+
let insensitive_path = mod_path.join(file_name).display().to_string();
82+
83+
let entry = paths_map.entry(insensitive_path.to_lowercase()).or_insert(0);
84+
*entry += 1;
85+
}
86+
}
87+
}
88+
89+
fn build_case_insensitive_map(
90+
krate: &clean::Crate,
91+
options: &RenderOptions,
92+
) -> Option<FxHashSet<String>> {
93+
let mut paths_map: FxHashMap<String, usize> = FxHashMap::default();
94+
95+
handle_module(&options.output, &krate.module, &mut paths_map);
96+
Some(paths_map.into_iter().filter(|(_, count)| *count > 1).map(|(path, _)| path).collect())
97+
}
98+
99+
fn check_if_case_insensitive(dst: &Path) -> bool {
100+
fn create_and_write(dst: &Path, content: &str) {
101+
if let Ok(mut f) = fs::OpenOptions::new().write(true).create(true).truncate(true).open(dst)
102+
{
103+
// Ignoring potential errors.
104+
let _ = f.write(content.as_bytes());
105+
}
106+
}
107+
fn compare_content(dst: &Path, content: &str) -> bool {
108+
fs::read_to_string(dst).unwrap_or_else(|_| String::new()).as_str() == content
109+
}
110+
111+
let path1 = dst.join("___a.tmp");
112+
let content1 = "a";
113+
let path2 = dst.join("___A.tmp");
114+
let content2 = "A";
115+
116+
create_and_write(&path1, content1);
117+
create_and_write(&path1, content2);
118+
119+
let res = compare_content(&path1, content1) && compare_content(&path2, content2);
120+
// We ignore the errors when removing the files.
121+
let _ = fs::remove_file(&path1);
122+
let _ = fs::remove_file(&path2);
123+
124+
res
125+
}
126+
55127
/// Main method for rendering a crate.
56128
crate fn run_format<'tcx, T: FormatRenderer<'tcx>>(
57129
krate: clean::Crate,
@@ -63,9 +135,16 @@ crate fn run_format<'tcx, T: FormatRenderer<'tcx>>(
63135
) -> Result<(), Error> {
64136
let prof = &tcx.sess.prof;
65137

138+
let case_insensitive_conflicts =
139+
if options.generate_case_insensitive || check_if_case_insensitive(&options.output) {
140+
build_case_insensitive_map(&krate, &options)
141+
} else {
142+
None
143+
};
144+
66145
let (mut format_renderer, krate) = prof
67146
.extra_verbose_generic_activity("create_renderer", T::descr())
68-
.run(|| T::init(krate, options, edition, cache, tcx))?;
147+
.run(|| T::init(krate, options, edition, cache, tcx, case_insensitive_conflicts))?;
69148

70149
// Render the crate documentation
71150
let crate_name = krate.name;

src/librustdoc/html/layout.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,42 @@ crate fn render<T: Print, S: Print>(
222222
)
223223
}
224224

225+
/// Since this is a "conflict file" on case insensitive file system, it'll be loaded by JS instead
226+
/// of being loaded directly. Which is why we need to modify its output a bit.
227+
crate fn conflict_layout<T: Print, S>(page: &Page<'_>, sidebar: S, t: T) -> String
228+
where
229+
S: FnOnce(&mut Buffer) -> Option<String>,
230+
{
231+
let content = Buffer::html().to_display(t).to_string();
232+
let mut sidebar_buf = Buffer::html();
233+
let script_src = sidebar(&mut sidebar_buf);
234+
format!(
235+
"\
236+
document.getElementById('main').innerHTML = \"{content}\";\
237+
document.getElementsByClassName('sidebar')[0].innerHTML += \"{sidebar}\";\
238+
document.title = \"{title}\";\
239+
window.initSidebarVars();\
240+
rustdocInit();{script}",
241+
content = content.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n"),
242+
title = page.title.replace("\\", "\\\\").replace("\"", "\\\""),
243+
sidebar = sidebar_buf
244+
.into_inner()
245+
.replace("\\", "\\\\")
246+
.replace("\"", "\\\"")
247+
.replace("\n", "\\n"),
248+
script = if let Some(script_src) = script_src {
249+
format!(
250+
"var script = document.createElement('script');\
251+
script.src = {:?};\
252+
document.body.appendChild(script);",
253+
script_src,
254+
)
255+
} else {
256+
String::new()
257+
},
258+
)
259+
}
260+
225261
crate fn redirect(url: &str) -> String {
226262
// <script> triggers a redirect before refresh, so this is fine.
227263
format!(

0 commit comments

Comments
 (0)