Skip to content

Commit

Permalink
Support case insensitive systems for file generation
Browse files Browse the repository at this point in the history
  • Loading branch information
GuillaumeGomez committed Mar 28, 2021
1 parent aef1140 commit 3b8de99
Show file tree
Hide file tree
Showing 10 changed files with 300 additions and 41 deletions.
5 changes: 5 additions & 0 deletions src/librustdoc/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,9 @@ crate struct RenderOptions {
crate document_hidden: bool,
/// If `true`, generate a JSON file in the crate folder instead of HTML redirection files.
crate generate_redirect_map: bool,
/// If this option is set to `true`, HTML files will be generated as it would on a
/// case-insensitive file system.
crate generate_case_insensitive: bool,
crate unstable_features: rustc_feature::UnstableFeatures,
}

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

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

Expand Down Expand Up @@ -638,6 +642,7 @@ impl Options {
document_private,
document_hidden,
generate_redirect_map,
generate_case_insensitive,
unstable_features: rustc_feature::UnstableFeatures::from_environment(
crate_name.as_deref(),
),
Expand Down
81 changes: 80 additions & 1 deletion src/librustdoc/formats/renderer.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_middle::ty::TyCtxt;
use rustc_span::{edition::Edition, Symbol};

use std::fs;
use std::io::Write;
use std::path::Path;

use crate::clean;
use crate::config::RenderOptions;
use crate::error::Error;
use crate::formats::cache::Cache;
use crate::html::render::print_item::item_path;

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

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

fn handle_module(dst: &Path, item: &clean::Item, paths_map: &mut FxHashMap<String, usize>) {
// modules are special because they add a namespace. We also need to
// recurse into the items of the module as well.
let name = item.name.as_ref().unwrap().to_string();
if name.is_empty() {
panic!("Unexpected module with empty name");
}
let module = match *item.kind {
clean::StrippedItem(box clean::ModuleItem(ref m)) | clean::ModuleItem(ref m) => m,
_ => unreachable!(),
};
let mod_path = dst.join(&name);
for it in &module.items {
if it.is_mod() {
handle_module(&mod_path, it, paths_map);
} else if it.name.is_some() && !it.is_extern_crate() {
let name = it.name.as_ref().unwrap();
let item_type = it.type_();
let file_name = &item_path(item_type, &name.as_str());
let insensitive_path = mod_path.join(file_name).display().to_string();

let entry = paths_map.entry(insensitive_path.to_lowercase()).or_insert(0);
*entry += 1;
}
}
}

fn build_case_insensitive_map(
krate: &clean::Crate,
options: &RenderOptions,
) -> Option<FxHashSet<String>> {
let mut paths_map: FxHashMap<String, usize> = FxHashMap::default();

handle_module(&options.output, &krate.module, &mut paths_map);
Some(paths_map.into_iter().filter(|(_, count)| *count > 1).map(|(path, _)| path).collect())
}

fn check_if_case_insensitive(dst: &Path) -> bool {
fn create_and_write(dst: &Path, content: &str) {
if let Ok(mut f) = fs::OpenOptions::new().write(true).create(true).truncate(true).open(dst)
{
// Ignoring potential errors.
let _ = f.write(content.as_bytes());
}
}
fn compare_content(dst: &Path, content: &str) -> bool {
fs::read_to_string(dst).unwrap_or_else(|_| String::new()).as_str() == content
}

let path1 = dst.join("___a.tmp");
let content1 = "a";
let path2 = dst.join("___A.tmp");
let content2 = "A";

create_and_write(&path1, content1);
create_and_write(&path1, content2);

let res = compare_content(&path1, content1) && compare_content(&path2, content2);
// We ignore the errors when removing the files.
let _ = fs::remove_file(&path1);
let _ = fs::remove_file(&path2);

res
}

/// Main method for rendering a crate.
crate fn run_format<'tcx, T: FormatRenderer<'tcx>>(
krate: clean::Crate,
Expand All @@ -63,9 +135,16 @@ crate fn run_format<'tcx, T: FormatRenderer<'tcx>>(
) -> Result<(), Error> {
let prof = &tcx.sess.prof;

let case_insensitive_conflicts =
if options.generate_case_insensitive || check_if_case_insensitive(&options.output) {
build_case_insensitive_map(&krate, &options)
} else {
None
};

let (mut format_renderer, krate) = prof
.extra_verbose_generic_activity("create_renderer", T::descr())
.run(|| T::init(krate, options, edition, cache, tcx))?;
.run(|| T::init(krate, options, edition, cache, tcx, case_insensitive_conflicts))?;

// Render the crate documentation
let crate_name = krate.name;
Expand Down
36 changes: 36 additions & 0 deletions src/librustdoc/html/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,42 @@ crate fn render<T: Print, S: Print>(
)
}

/// Since this is a "conflict file" on case insensitive file system, it'll be loaded by JS instead
/// of being loaded directly. Which is why we need to modify its output a bit.
crate fn conflict_layout<T: Print, S>(page: &Page<'_>, sidebar: S, t: T) -> String
where
S: FnOnce(&mut Buffer) -> Option<String>,
{
let content = Buffer::html().to_display(t).to_string();
let mut sidebar_buf = Buffer::html();
let script_src = sidebar(&mut sidebar_buf);
format!(
"\
document.getElementById('main').innerHTML = \"{content}\";\
document.getElementsByClassName('sidebar')[0].innerHTML += \"{sidebar}\";\
document.title = \"{title}\";\
window.initSidebarVars();\
rustdocInit();{script}",
content = content.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n"),
title = page.title.replace("\\", "\\\\").replace("\"", "\\\""),
sidebar = sidebar_buf
.into_inner()
.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n"),
script = if let Some(script_src) = script_src {
format!(
"var script = document.createElement('script');\
script.src = {:?};\
document.body.appendChild(script);",
script_src,
)
} else {
String::new()
},
)
}

crate fn redirect(url: &str) -> String {
// <script> triggers a redirect before refresh, so this is fine.
format!(
Expand Down
Loading

0 comments on commit 3b8de99

Please sign in to comment.