rfc#3662 changes under unstable flags
* All new functionality is under unstable options * Adds `--merge=shared|none|finalize` flags * Adds `--parts-out-dir=<crate specific directory>` for `--merge=none` to write cross-crate info file for a single crate * Adds `--include-parts-dir=<previously specified directory>` for `--merge=finalize` to write cross-crate info files * update tests/run-make/rustdoc-default-output/rmake.rs golden
This commit is contained in:
parent
3782251c2c
commit
2e1cba6415
@ -53,6 +53,14 @@ fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Either an input crate, markdown file, or nothing (--merge=finalize).
|
||||
pub(crate) enum InputMode {
|
||||
/// The `--merge=finalize` step does not need an input crate to rustdoc.
|
||||
NoInputMergeFinalize,
|
||||
/// A crate or markdown file.
|
||||
HasFile(Input),
|
||||
}
|
||||
|
||||
/// Configuration options for rustdoc.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Options {
|
||||
@ -286,6 +294,12 @@ pub(crate) struct RenderOptions {
|
||||
/// This field is only used for the JSON output. If it's set to true, no file will be created
|
||||
/// and content will be displayed in stdout directly.
|
||||
pub(crate) output_to_stdout: bool,
|
||||
/// Whether we should read or write rendered cross-crate info in the doc root.
|
||||
pub(crate) should_merge: ShouldMerge,
|
||||
/// Path to crate-info for external crates.
|
||||
pub(crate) include_parts_dir: Vec<PathToParts>,
|
||||
/// Where to write crate-info
|
||||
pub(crate) parts_out_dir: Option<PathToParts>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
@ -345,7 +359,7 @@ pub(crate) fn from_matches(
|
||||
early_dcx: &mut EarlyDiagCtxt,
|
||||
matches: &getopts::Matches,
|
||||
args: Vec<String>,
|
||||
) -> Option<(Input, Options, RenderOptions)> {
|
||||
) -> Option<(InputMode, Options, RenderOptions)> {
|
||||
// Check for unstable options.
|
||||
nightly_options::check_nightly_options(early_dcx, matches, &opts());
|
||||
|
||||
@ -475,15 +489,17 @@ fn println_condition(condition: Condition) {
|
||||
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);
|
||||
|
||||
let input = if describe_lints {
|
||||
"" // dummy, this won't be used
|
||||
InputMode::HasFile(make_input(early_dcx, ""))
|
||||
} else {
|
||||
match matches.free.as_slice() {
|
||||
[] if matches.opt_str("merge").as_deref() == Some("finalize") => {
|
||||
InputMode::NoInputMergeFinalize
|
||||
}
|
||||
[] => dcx.fatal("missing file operand"),
|
||||
[input] => input,
|
||||
[input] => InputMode::HasFile(make_input(early_dcx, input)),
|
||||
_ => dcx.fatal("too many file operands"),
|
||||
}
|
||||
};
|
||||
let input = make_input(early_dcx, input);
|
||||
|
||||
let externs = parse_externs(early_dcx, matches, &unstable_opts);
|
||||
let extern_html_root_urls = match parse_extern_html_roots(matches) {
|
||||
@ -491,6 +507,16 @@ fn println_condition(condition: Condition) {
|
||||
Err(err) => dcx.fatal(err),
|
||||
};
|
||||
|
||||
let parts_out_dir =
|
||||
match matches.opt_str("parts-out-dir").map(|p| PathToParts::from_flag(p)).transpose() {
|
||||
Ok(parts_out_dir) => parts_out_dir,
|
||||
Err(e) => dcx.fatal(e),
|
||||
};
|
||||
let include_parts_dir = match parse_include_parts_dir(matches) {
|
||||
Ok(include_parts_dir) => include_parts_dir,
|
||||
Err(e) => dcx.fatal(e),
|
||||
};
|
||||
|
||||
let default_settings: Vec<Vec<(String, String)>> = vec![
|
||||
matches
|
||||
.opt_str("default-theme")
|
||||
@ -732,6 +758,10 @@ fn println_condition(condition: Condition) {
|
||||
let extern_html_root_takes_precedence =
|
||||
matches.opt_present("extern-html-root-takes-precedence");
|
||||
let html_no_source = matches.opt_present("html-no-source");
|
||||
let should_merge = match parse_merge(matches) {
|
||||
Ok(result) => result,
|
||||
Err(e) => dcx.fatal(format!("--merge option error: {e}")),
|
||||
};
|
||||
|
||||
if generate_link_to_definition && (show_coverage || output_format != OutputFormat::Html) {
|
||||
dcx.struct_warn(
|
||||
@ -819,6 +849,9 @@ fn println_condition(condition: Condition) {
|
||||
no_emit_shared: false,
|
||||
html_no_source,
|
||||
output_to_stdout,
|
||||
should_merge,
|
||||
include_parts_dir,
|
||||
parts_out_dir,
|
||||
};
|
||||
Some((input, options, render_options))
|
||||
}
|
||||
@ -894,3 +927,71 @@ fn parse_extern_html_roots(
|
||||
}
|
||||
Ok(externs)
|
||||
}
|
||||
|
||||
/// Path directly to crate-info file.
|
||||
///
|
||||
/// For example, `/home/user/project/target/doc.parts/<crate>/crate-info`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct PathToParts(pub(crate) PathBuf);
|
||||
|
||||
impl PathToParts {
|
||||
fn from_flag(path: String) -> Result<PathToParts, String> {
|
||||
let mut path = PathBuf::from(path);
|
||||
// check here is for diagnostics
|
||||
if path.exists() && !path.is_dir() {
|
||||
Err(format!(
|
||||
"--parts-out-dir and --include-parts-dir expect directories, found: {}",
|
||||
path.display(),
|
||||
))
|
||||
} else {
|
||||
// if it doesn't exist, we'll create it. worry about that in write_shared
|
||||
path.push("crate-info");
|
||||
Ok(PathToParts(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reports error if --include-parts-dir / crate-info is not a file
|
||||
fn parse_include_parts_dir(m: &getopts::Matches) -> Result<Vec<PathToParts>, String> {
|
||||
let mut ret = Vec::new();
|
||||
for p in m.opt_strs("include-parts-dir") {
|
||||
let p = PathToParts::from_flag(p)?;
|
||||
// this is just for diagnostic
|
||||
if !p.0.is_file() {
|
||||
return Err(format!("--include-parts-dir expected {} to be a file", p.0.display()));
|
||||
}
|
||||
ret.push(p);
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Controls merging of cross-crate information
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ShouldMerge {
|
||||
/// Should we append to existing cci in the doc root
|
||||
pub(crate) read_rendered_cci: bool,
|
||||
/// Should we write cci to the doc root
|
||||
pub(crate) write_rendered_cci: bool,
|
||||
}
|
||||
|
||||
/// Extracts read_rendered_cci and write_rendered_cci from command line arguments, or
|
||||
/// reports an error if an invalid option was provided
|
||||
fn parse_merge(m: &getopts::Matches) -> Result<ShouldMerge, &'static str> {
|
||||
match m.opt_str("merge").as_deref() {
|
||||
// default = read-write
|
||||
None => Ok(ShouldMerge { read_rendered_cci: true, write_rendered_cci: true }),
|
||||
Some("none") if m.opt_present("include-parts-dir") => {
|
||||
Err("--include-parts-dir not allowed if --merge=none")
|
||||
}
|
||||
Some("none") => Ok(ShouldMerge { read_rendered_cci: false, write_rendered_cci: false }),
|
||||
Some("shared") if m.opt_present("parts-out-dir") || m.opt_present("include-parts-dir") => {
|
||||
Err("--parts-out-dir and --include-parts-dir not allowed if --merge=shared")
|
||||
}
|
||||
Some("shared") => Ok(ShouldMerge { read_rendered_cci: true, write_rendered_cci: true }),
|
||||
Some("finalize") if m.opt_present("parts-out-dir") => {
|
||||
Err("--parts-out-dir not allowed if --merge=finalize")
|
||||
}
|
||||
Some("finalize") => Ok(ShouldMerge { read_rendered_cci: false, write_rendered_cci: true }),
|
||||
Some(_) => Err("argument to --merge must be `none`, `shared`, or `finalize`"),
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,11 @@
|
||||
|
||||
use super::print_item::{full_path, item_path, print_item};
|
||||
use super::sidebar::{print_sidebar, sidebar_module_like, ModuleLike, Sidebar};
|
||||
use super::write_shared::write_shared;
|
||||
use super::{collect_spans_and_sources, scrape_examples_help, AllTypes, LinkFromSrc, StylePath};
|
||||
use crate::clean::types::ExternalLocation;
|
||||
use crate::clean::utils::has_doc_flag;
|
||||
use crate::clean::{self, ExternalCrate};
|
||||
use crate::config::{ModuleSorting, RenderOptions};
|
||||
use crate::config::{ModuleSorting, RenderOptions, ShouldMerge};
|
||||
use crate::docfs::{DocFS, PathError};
|
||||
use crate::error::Error;
|
||||
use crate::formats::cache::Cache;
|
||||
@ -30,6 +29,7 @@
|
||||
use crate::html::escape::Escape;
|
||||
use crate::html::format::{join_with_double_colon, Buffer};
|
||||
use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap};
|
||||
use crate::html::render::write_shared::write_shared;
|
||||
use crate::html::url_parts_builder::UrlPartsBuilder;
|
||||
use crate::html::{layout, sources, static_files};
|
||||
use crate::scrape_examples::AllCallLocations;
|
||||
@ -128,8 +128,10 @@ pub(crate) struct SharedContext<'tcx> {
|
||||
pub(crate) span_correspondence_map: FxHashMap<rustc_span::Span, LinkFromSrc>,
|
||||
/// The [`Cache`] used during rendering.
|
||||
pub(crate) cache: Cache,
|
||||
|
||||
pub(crate) call_locations: AllCallLocations,
|
||||
/// Controls whether we read / write to cci files in the doc root. Defaults read=true,
|
||||
/// write=true
|
||||
should_merge: ShouldMerge,
|
||||
}
|
||||
|
||||
impl SharedContext<'_> {
|
||||
@ -551,6 +553,7 @@ fn init(
|
||||
span_correspondence_map: matches,
|
||||
cache,
|
||||
call_locations,
|
||||
should_merge: options.should_merge,
|
||||
};
|
||||
|
||||
let dst = output;
|
||||
@ -640,92 +643,96 @@ fn after_krate(&mut self) -> Result<(), Error> {
|
||||
);
|
||||
shared.fs.write(final_file, v)?;
|
||||
|
||||
// Generating settings page.
|
||||
page.title = "Settings";
|
||||
page.description = "Settings of Rustdoc";
|
||||
page.root_path = "./";
|
||||
page.rust_logo = true;
|
||||
// if to avoid writing help, settings files to doc root unless we're on the final invocation
|
||||
if shared.should_merge.write_rendered_cci {
|
||||
// Generating settings page.
|
||||
page.title = "Settings";
|
||||
page.description = "Settings of Rustdoc";
|
||||
page.root_path = "./";
|
||||
page.rust_logo = true;
|
||||
|
||||
let sidebar = "<h2 class=\"location\">Settings</h2><div class=\"sidebar-elems\"></div>";
|
||||
let v = layout::render(
|
||||
&shared.layout,
|
||||
&page,
|
||||
sidebar,
|
||||
|buf: &mut Buffer| {
|
||||
write!(
|
||||
buf,
|
||||
"<div class=\"main-heading\">\
|
||||
<h1>Rustdoc settings</h1>\
|
||||
<span class=\"out-of-band\">\
|
||||
<a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
|
||||
Back\
|
||||
</a>\
|
||||
</span>\
|
||||
</div>\
|
||||
<noscript>\
|
||||
<section>\
|
||||
You need to enable JavaScript be able to update your settings.\
|
||||
</section>\
|
||||
</noscript>\
|
||||
<script defer src=\"{static_root_path}{settings_js}\"></script>",
|
||||
static_root_path = page.get_static_root_path(),
|
||||
settings_js = static_files::STATIC_FILES.settings_js,
|
||||
);
|
||||
// Pre-load all theme CSS files, so that switching feels seamless.
|
||||
//
|
||||
// When loading settings.html as a popover, the equivalent HTML is
|
||||
// generated in main.js.
|
||||
for file in &shared.style_files {
|
||||
if let Ok(theme) = file.basename() {
|
||||
write!(
|
||||
buf,
|
||||
"<link rel=\"preload\" href=\"{root_path}{theme}{suffix}.css\" \
|
||||
as=\"style\">",
|
||||
root_path = page.static_root_path.unwrap_or(""),
|
||||
suffix = page.resource_suffix,
|
||||
);
|
||||
let sidebar = "<h2 class=\"location\">Settings</h2><div class=\"sidebar-elems\"></div>";
|
||||
let v = layout::render(
|
||||
&shared.layout,
|
||||
&page,
|
||||
sidebar,
|
||||
|buf: &mut Buffer| {
|
||||
write!(
|
||||
buf,
|
||||
"<div class=\"main-heading\">\
|
||||
<h1>Rustdoc settings</h1>\
|
||||
<span class=\"out-of-band\">\
|
||||
<a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
|
||||
Back\
|
||||
</a>\
|
||||
</span>\
|
||||
</div>\
|
||||
<noscript>\
|
||||
<section>\
|
||||
You need to enable JavaScript be able to update your settings.\
|
||||
</section>\
|
||||
</noscript>\
|
||||
<script defer src=\"{static_root_path}{settings_js}\"></script>",
|
||||
static_root_path = page.get_static_root_path(),
|
||||
settings_js = static_files::STATIC_FILES.settings_js,
|
||||
);
|
||||
// Pre-load all theme CSS files, so that switching feels seamless.
|
||||
//
|
||||
// When loading settings.html as a popover, the equivalent HTML is
|
||||
// generated in main.js.
|
||||
for file in &shared.style_files {
|
||||
if let Ok(theme) = file.basename() {
|
||||
write!(
|
||||
buf,
|
||||
"<link rel=\"preload\" href=\"{root_path}{theme}{suffix}.css\" \
|
||||
as=\"style\">",
|
||||
root_path = page.static_root_path.unwrap_or(""),
|
||||
suffix = page.resource_suffix,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
&shared.style_files,
|
||||
);
|
||||
shared.fs.write(settings_file, v)?;
|
||||
},
|
||||
&shared.style_files,
|
||||
);
|
||||
shared.fs.write(settings_file, v)?;
|
||||
|
||||
// Generating help page.
|
||||
page.title = "Help";
|
||||
page.description = "Documentation for Rustdoc";
|
||||
page.root_path = "./";
|
||||
page.rust_logo = true;
|
||||
// Generating help page.
|
||||
page.title = "Help";
|
||||
page.description = "Documentation for Rustdoc";
|
||||
page.root_path = "./";
|
||||
page.rust_logo = true;
|
||||
|
||||
let sidebar = "<h2 class=\"location\">Help</h2><div class=\"sidebar-elems\"></div>";
|
||||
let v = layout::render(
|
||||
&shared.layout,
|
||||
&page,
|
||||
sidebar,
|
||||
|buf: &mut Buffer| {
|
||||
write!(
|
||||
buf,
|
||||
"<div class=\"main-heading\">\
|
||||
<h1>Rustdoc help</h1>\
|
||||
<span class=\"out-of-band\">\
|
||||
<a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
|
||||
Back\
|
||||
</a>\
|
||||
</span>\
|
||||
</div>\
|
||||
<noscript>\
|
||||
<section>\
|
||||
<p>You need to enable JavaScript to use keyboard commands or search.</p>\
|
||||
<p>For more information, browse the <a href=\"https://doc.rust-lang.org/rustdoc/\">rustdoc handbook</a>.</p>\
|
||||
</section>\
|
||||
</noscript>",
|
||||
)
|
||||
},
|
||||
&shared.style_files,
|
||||
);
|
||||
shared.fs.write(help_file, v)?;
|
||||
let sidebar = "<h2 class=\"location\">Help</h2><div class=\"sidebar-elems\"></div>";
|
||||
let v = layout::render(
|
||||
&shared.layout,
|
||||
&page,
|
||||
sidebar,
|
||||
|buf: &mut Buffer| {
|
||||
write!(
|
||||
buf,
|
||||
"<div class=\"main-heading\">\
|
||||
<h1>Rustdoc help</h1>\
|
||||
<span class=\"out-of-band\">\
|
||||
<a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
|
||||
Back\
|
||||
</a>\
|
||||
</span>\
|
||||
</div>\
|
||||
<noscript>\
|
||||
<section>\
|
||||
<p>You need to enable JavaScript to use keyboard commands or search.</p>\
|
||||
<p>For more information, browse the <a href=\"https://doc.rust-lang.org/rustdoc/\">rustdoc handbook</a>.</p>\
|
||||
</section>\
|
||||
</noscript>",
|
||||
)
|
||||
},
|
||||
&shared.style_files,
|
||||
);
|
||||
shared.fs.write(help_file, v)?;
|
||||
}
|
||||
|
||||
if shared.layout.scrape_examples_extension {
|
||||
// if to avoid writing files to doc root unless we're on the final invocation
|
||||
if shared.layout.scrape_examples_extension && shared.should_merge.write_rendered_cci {
|
||||
page.title = "About scraped examples";
|
||||
page.description = "How the scraped examples feature works in Rustdoc";
|
||||
let v = layout::render(
|
||||
|
@ -61,6 +61,7 @@
|
||||
|
||||
pub(crate) use self::context::*;
|
||||
pub(crate) use self::span_map::{collect_spans_and_sources, LinkFromSrc};
|
||||
pub(crate) use self::write_shared::*;
|
||||
use crate::clean::{self, ItemId, RenderedLink};
|
||||
use crate::error::Error;
|
||||
use crate::formats::cache::Cache;
|
||||
|
@ -39,7 +39,7 @@
|
||||
|
||||
use super::{collect_paths_for_type, ensure_trailing_slash, Context, RenderMode};
|
||||
use crate::clean::{Crate, Item, ItemId, ItemKind};
|
||||
use crate::config::{EmitType, RenderOptions};
|
||||
use crate::config::{EmitType, PathToParts, RenderOptions, ShouldMerge};
|
||||
use crate::docfs::PathError;
|
||||
use crate::error::Error;
|
||||
use crate::formats::cache::Cache;
|
||||
@ -50,12 +50,11 @@
|
||||
use crate::html::render::ordered_json::{EscapedJson, OrderedJson};
|
||||
use crate::html::render::search_index::{build_index, SerializedSearchIndex};
|
||||
use crate::html::render::sorted_template::{self, FileFormat, SortedTemplate};
|
||||
use crate::html::render::{AssocItemLink, ImplRenderingParameters};
|
||||
use crate::html::render::{AssocItemLink, ImplRenderingParameters, StylePath};
|
||||
use crate::html::static_files::{self, suffix_path};
|
||||
use crate::visit::DocVisitor;
|
||||
use crate::{try_err, try_none};
|
||||
|
||||
/// Write cross-crate information files, static files, invocation-specific files, etc. to disk
|
||||
pub(crate) fn write_shared(
|
||||
cx: &mut Context<'_>,
|
||||
krate: &Crate,
|
||||
@ -70,13 +69,14 @@ pub(crate) fn write_shared(
|
||||
|
||||
let SerializedSearchIndex { index, desc } =
|
||||
build_index(&krate, &mut Rc::get_mut(&mut cx.shared).unwrap().cache, tcx);
|
||||
write_search_desc(cx, &krate, &desc)?; // does not need to be merged; written unconditionally
|
||||
write_search_desc(cx, &krate, &desc)?; // does not need to be merged
|
||||
|
||||
let crate_name = krate.name(cx.tcx());
|
||||
let crate_name = crate_name.as_str(); // rand
|
||||
let crate_name_json = OrderedJson::serialize(crate_name).unwrap(); // "rand"
|
||||
let external_crates = hack_get_external_crate_names(&cx.dst, &cx.shared.resource_suffix)?;
|
||||
let info = CrateInfo {
|
||||
version: CrateInfoVersion::V1,
|
||||
src_files_js: SourcesPart::get(cx, &crate_name_json)?,
|
||||
search_index_js: SearchIndexPart::get(index, &cx.shared.resource_suffix)?,
|
||||
all_crates: AllCratesPart::get(crate_name_json.clone(), &cx.shared.resource_suffix)?,
|
||||
@ -85,47 +85,103 @@ pub(crate) fn write_shared(
|
||||
type_impl: TypeAliasPart::get(cx, krate, &crate_name_json)?,
|
||||
};
|
||||
|
||||
let crates = vec![info]; // we have info from just one crate. rest will found in out dir
|
||||
|
||||
write_static_files(cx, &opt)?;
|
||||
let dst = &cx.dst;
|
||||
if opt.emit.is_empty() || opt.emit.contains(&EmitType::InvocationSpecific) {
|
||||
if cx.include_sources {
|
||||
write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, &crates)?;
|
||||
}
|
||||
write_rendered_cci::<SearchIndexPart, _>(SearchIndexPart::blank, dst, &crates)?;
|
||||
write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, &crates)?;
|
||||
if let Some(parts_out_dir) = &opt.parts_out_dir {
|
||||
create_parents(&parts_out_dir.0)?;
|
||||
try_err!(
|
||||
fs::write(&parts_out_dir.0, serde_json::to_string(&info).unwrap()),
|
||||
&parts_out_dir.0
|
||||
);
|
||||
}
|
||||
write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, &crates)?;
|
||||
write_rendered_cci::<TypeAliasPart, _>(TypeAliasPart::blank, dst, &crates)?;
|
||||
match &opt.index_page {
|
||||
Some(index_page) if opt.enable_index_page => {
|
||||
let mut md_opts = opt.clone();
|
||||
md_opts.output = cx.dst.clone();
|
||||
md_opts.external_html = cx.shared.layout.external_html.clone();
|
||||
try_err!(
|
||||
crate::markdown::render(&index_page, md_opts, cx.shared.edition()),
|
||||
&index_page
|
||||
);
|
||||
|
||||
let mut crates = CrateInfo::read_many(&opt.include_parts_dir)?;
|
||||
crates.push(info);
|
||||
|
||||
if opt.should_merge.write_rendered_cci {
|
||||
write_not_crate_specific(
|
||||
&crates,
|
||||
&cx.dst,
|
||||
opt,
|
||||
&cx.shared.style_files,
|
||||
cx.shared.layout.css_file_extension.as_deref(),
|
||||
&cx.shared.resource_suffix,
|
||||
cx.include_sources,
|
||||
)?;
|
||||
match &opt.index_page {
|
||||
Some(index_page) if opt.enable_index_page => {
|
||||
let mut md_opts = opt.clone();
|
||||
md_opts.output = cx.dst.clone();
|
||||
md_opts.external_html = cx.shared.layout.external_html.clone();
|
||||
try_err!(
|
||||
crate::markdown::render(&index_page, md_opts, cx.shared.edition()),
|
||||
&index_page
|
||||
);
|
||||
}
|
||||
None if opt.enable_index_page => {
|
||||
write_rendered_cci::<CratesIndexPart, _>(
|
||||
|| CratesIndexPart::blank(cx),
|
||||
&cx.dst,
|
||||
&crates,
|
||||
&opt.should_merge,
|
||||
)?;
|
||||
}
|
||||
_ => {} // they don't want an index page
|
||||
}
|
||||
None if opt.enable_index_page => {
|
||||
write_rendered_cci::<CratesIndexPart, _>(|| CratesIndexPart::blank(cx), dst, &crates)?;
|
||||
}
|
||||
_ => {} // they don't want an index page
|
||||
}
|
||||
|
||||
Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes the static files, the style files, and the css extensions
|
||||
fn write_static_files(cx: &mut Context<'_>, options: &RenderOptions) -> Result<(), Error> {
|
||||
let static_dir = cx.dst.join("static.files");
|
||||
/// Writes files that are written directly to the `--out-dir`, without the prefix from the current
|
||||
/// crate. These are the rendered cross-crate files that encode info from multiple crates (e.g.
|
||||
/// search index), and the static files.
|
||||
pub(crate) fn write_not_crate_specific(
|
||||
crates: &[CrateInfo],
|
||||
dst: &Path,
|
||||
opt: &RenderOptions,
|
||||
style_files: &[StylePath],
|
||||
css_file_extension: Option<&Path>,
|
||||
resource_suffix: &str,
|
||||
include_sources: bool,
|
||||
) -> Result<(), Error> {
|
||||
write_rendered_cross_crate_info(crates, dst, opt, include_sources)?;
|
||||
write_static_files(dst, opt, style_files, css_file_extension, resource_suffix)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
cx.shared.fs.create_dir_all(&static_dir).map_err(|e| PathError::new(e, "static.files"))?;
|
||||
fn write_rendered_cross_crate_info(
|
||||
crates: &[CrateInfo],
|
||||
dst: &Path,
|
||||
opt: &RenderOptions,
|
||||
include_sources: bool,
|
||||
) -> Result<(), Error> {
|
||||
let m = &opt.should_merge;
|
||||
if opt.emit.is_empty() || opt.emit.contains(&EmitType::InvocationSpecific) {
|
||||
if include_sources {
|
||||
write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, &crates, m)?;
|
||||
}
|
||||
write_rendered_cci::<SearchIndexPart, _>(SearchIndexPart::blank, dst, &crates, m)?;
|
||||
write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, &crates, m)?;
|
||||
}
|
||||
write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, &crates, m)?;
|
||||
write_rendered_cci::<TypeAliasPart, _>(TypeAliasPart::blank, dst, &crates, m)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes the static files, the style files, and the css extensions.
|
||||
/// Have to be careful about these, because they write to the root out dir.
|
||||
fn write_static_files(
|
||||
dst: &Path,
|
||||
opt: &RenderOptions,
|
||||
style_files: &[StylePath],
|
||||
css_file_extension: Option<&Path>,
|
||||
resource_suffix: &str,
|
||||
) -> Result<(), Error> {
|
||||
let static_dir = dst.join("static.files");
|
||||
try_err!(fs::create_dir_all(&static_dir), &static_dir);
|
||||
|
||||
// Handle added third-party themes
|
||||
for entry in &cx.shared.style_files {
|
||||
for entry in style_files {
|
||||
let theme = entry.basename()?;
|
||||
let extension =
|
||||
try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
|
||||
@ -136,22 +192,24 @@ fn write_static_files(cx: &mut Context<'_>, options: &RenderOptions) -> Result<(
|
||||
}
|
||||
|
||||
let bytes = try_err!(fs::read(&entry.path), &entry.path);
|
||||
let filename = format!("{theme}{suffix}.{extension}", suffix = cx.shared.resource_suffix);
|
||||
cx.shared.fs.write(cx.dst.join(filename), bytes)?;
|
||||
let filename = format!("{theme}{resource_suffix}.{extension}");
|
||||
let dst_filename = dst.join(filename);
|
||||
try_err!(fs::write(&dst_filename, bytes), &dst_filename);
|
||||
}
|
||||
|
||||
// When the user adds their own CSS files with --extend-css, we write that as an
|
||||
// invocation-specific file (that is, with a resource suffix).
|
||||
if let Some(ref css) = cx.shared.layout.css_file_extension {
|
||||
if let Some(css) = css_file_extension {
|
||||
let buffer = try_err!(fs::read_to_string(css), css);
|
||||
let path = static_files::suffix_path("theme.css", &cx.shared.resource_suffix);
|
||||
cx.shared.fs.write(cx.dst.join(path), buffer)?;
|
||||
let path = static_files::suffix_path("theme.css", resource_suffix);
|
||||
let dst_path = dst.join(path);
|
||||
try_err!(fs::write(&dst_path, buffer), &dst_path);
|
||||
}
|
||||
|
||||
if options.emit.is_empty() || options.emit.contains(&EmitType::Toolchain) {
|
||||
if opt.emit.is_empty() || opt.emit.contains(&EmitType::Toolchain) {
|
||||
static_files::for_each(|f: &static_files::StaticFile| {
|
||||
let filename = static_dir.join(f.output_filename());
|
||||
cx.shared.fs.write(filename, f.minified())
|
||||
fs::write(&filename, f.minified()).map_err(|e| PathError::new(e, &filename))
|
||||
})?;
|
||||
}
|
||||
|
||||
@ -186,7 +244,8 @@ fn write_search_desc(
|
||||
|
||||
/// Contains pre-rendered contents to insert into the CCI template
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
struct CrateInfo {
|
||||
pub(crate) struct CrateInfo {
|
||||
version: CrateInfoVersion,
|
||||
src_files_js: PartsAndLocations<SourcesPart>,
|
||||
search_index_js: PartsAndLocations<SearchIndexPart>,
|
||||
all_crates: PartsAndLocations<AllCratesPart>,
|
||||
@ -195,6 +254,33 @@ struct CrateInfo {
|
||||
type_impl: PartsAndLocations<TypeAliasPart>,
|
||||
}
|
||||
|
||||
impl CrateInfo {
|
||||
/// Read all of the crate info from its location on the filesystem
|
||||
pub(crate) fn read_many(parts_paths: &[PathToParts]) -> Result<Vec<Self>, Error> {
|
||||
parts_paths
|
||||
.iter()
|
||||
.map(|parts_path| {
|
||||
let path = &parts_path.0;
|
||||
let parts = try_err!(fs::read(&path), &path);
|
||||
let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), &path);
|
||||
Ok::<_, Error>(parts)
|
||||
})
|
||||
.collect::<Result<Vec<CrateInfo>, Error>>()
|
||||
}
|
||||
}
|
||||
|
||||
/// Version for the format of the crate-info file.
|
||||
///
|
||||
/// This enum should only ever have one variant, representing the current version.
|
||||
/// Gives pretty good error message about expecting the current version on deserialize.
|
||||
///
|
||||
/// Must be incremented (V2, V3, etc.) upon any changes to the search index or CrateInfo,
|
||||
/// to provide better diagnostics about including an invalid file.
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
enum CrateInfoVersion {
|
||||
V1,
|
||||
}
|
||||
|
||||
/// Paths (relative to the doc root) and their pre-merge contents
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(transparent)]
|
||||
@ -900,10 +986,14 @@ fn create_parents(path: &Path) -> Result<(), Error> {
|
||||
fn read_template_or_blank<F, T: FileFormat>(
|
||||
mut make_blank: F,
|
||||
path: &Path,
|
||||
should_merge: &ShouldMerge,
|
||||
) -> Result<SortedTemplate<T>, Error>
|
||||
where
|
||||
F: FnMut() -> SortedTemplate<T>,
|
||||
{
|
||||
if !should_merge.read_rendered_cci {
|
||||
return Ok(make_blank());
|
||||
}
|
||||
match fs::read_to_string(&path) {
|
||||
Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)),
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(make_blank()),
|
||||
@ -916,6 +1006,7 @@ fn write_rendered_cci<T: CciPart, F>(
|
||||
mut make_blank: F,
|
||||
dst: &Path,
|
||||
crates_info: &[CrateInfo],
|
||||
should_merge: &ShouldMerge,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
F: FnMut() -> SortedTemplate<T::FileFormat>,
|
||||
@ -924,7 +1015,8 @@ fn write_rendered_cci<T: CciPart, F>(
|
||||
for (path, parts) in get_path_parts::<T>(dst, crates_info) {
|
||||
create_parents(&path)?;
|
||||
// read previous rendered cci from storage, append to them
|
||||
let mut template = read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path)?;
|
||||
let mut template =
|
||||
read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path, should_merge)?;
|
||||
for part in parts {
|
||||
template.append(part);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::config::ShouldMerge;
|
||||
use crate::html::render::ordered_json::{EscapedJson, OrderedJson};
|
||||
use crate::html::render::sorted_template::{Html, SortedTemplate};
|
||||
use crate::html::render::write_shared::*;
|
||||
@ -192,16 +193,17 @@ fn read_template_test() {
|
||||
let path = path.path().join("file.html");
|
||||
let make_blank = || SortedTemplate::<Html>::from_before_after("<div>", "</div>");
|
||||
|
||||
let template = read_template_or_blank(make_blank, &path).unwrap();
|
||||
let should_merge = ShouldMerge { read_rendered_cci: true, write_rendered_cci: true };
|
||||
let template = read_template_or_blank(make_blank, &path, &should_merge).unwrap();
|
||||
assert_eq!(but_last_line(&template.to_string()), "<div></div>");
|
||||
fs::write(&path, template.to_string()).unwrap();
|
||||
let mut template = read_template_or_blank(make_blank, &path).unwrap();
|
||||
let mut template = read_template_or_blank(make_blank, &path, &should_merge).unwrap();
|
||||
template.append("<img/>".to_string());
|
||||
fs::write(&path, template.to_string()).unwrap();
|
||||
let mut template = read_template_or_blank(make_blank, &path).unwrap();
|
||||
let mut template = read_template_or_blank(make_blank, &path, &should_merge).unwrap();
|
||||
template.append("<br/>".to_string());
|
||||
fs::write(&path, template.to_string()).unwrap();
|
||||
let template = read_template_or_blank(make_blank, &path).unwrap();
|
||||
let template = read_template_or_blank(make_blank, &path, &should_merge).unwrap();
|
||||
|
||||
assert_eq!(but_last_line(&template.to_string()), "<div><br/><img/></div>");
|
||||
}
|
||||
|
@ -603,6 +603,33 @@ fn opts() -> Vec<RustcOptGroup> {
|
||||
"path to function call information (for displaying examples in the documentation)",
|
||||
)
|
||||
}),
|
||||
unstable("merge", |o| {
|
||||
o.optopt(
|
||||
"",
|
||||
"merge",
|
||||
"Controls how rustdoc handles files from previously documented crates in the doc root
|
||||
none = Do not write cross-crate information to the --out-dir
|
||||
shared = Append current crate's info to files found in the --out-dir
|
||||
finalize = Write current crate's info and --include-parts-dir info to the --out-dir, overwriting conflicting files",
|
||||
"none|shared|finalize",
|
||||
)
|
||||
}),
|
||||
unstable("parts-out-dir", |o| {
|
||||
o.optopt(
|
||||
"",
|
||||
"parts-out-dir",
|
||||
"Writes trait implementations and other info for the current crate to provided path. Only use with --merge=none",
|
||||
"path/to/doc.parts/<crate-name>",
|
||||
)
|
||||
}),
|
||||
unstable("include-parts-dir", |o| {
|
||||
o.optmulti(
|
||||
"",
|
||||
"include-parts-dir",
|
||||
"Includes trait implementations and other crate info from provided path. Only use with --merge=finalize",
|
||||
"path/to/doc.parts/<crate-name>",
|
||||
)
|
||||
}),
|
||||
// deprecated / removed options
|
||||
unstable("disable-minification", |o| o.optflagmulti("", "disable-minification", "removed")),
|
||||
stable("plugin-path", |o| {
|
||||
@ -697,6 +724,32 @@ fn run_renderer<'tcx, T: formats::FormatRenderer<'tcx>>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders and writes cross-crate info files, like the search index. This function exists so that
|
||||
/// we can run rustdoc without a crate root in the `--merge=finalize` mode. Cross-crate info files
|
||||
/// discovered via `--include-parts-dir` are combined and written to the doc root.
|
||||
fn run_merge_finalize(opt: config::RenderOptions) -> Result<(), error::Error> {
|
||||
assert!(
|
||||
opt.should_merge.write_rendered_cci,
|
||||
"config.rs only allows us to return InputMode::NoInputMergeFinalize if --merge=finalize"
|
||||
);
|
||||
assert!(
|
||||
!opt.should_merge.read_rendered_cci,
|
||||
"config.rs only allows us to return InputMode::NoInputMergeFinalize if --merge=finalize"
|
||||
);
|
||||
let crates = html::render::CrateInfo::read_many(&opt.include_parts_dir)?;
|
||||
let include_sources = !opt.html_no_source;
|
||||
html::render::write_not_crate_specific(
|
||||
&crates,
|
||||
&opt.output,
|
||||
&opt,
|
||||
&opt.themes,
|
||||
opt.extension_css.as_deref(),
|
||||
&opt.resource_suffix,
|
||||
include_sources,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main_args(
|
||||
early_dcx: &mut EarlyDiagCtxt,
|
||||
at_args: &[String],
|
||||
@ -737,6 +790,17 @@ fn main_args(
|
||||
core::new_dcx(options.error_format, None, options.diagnostic_width, &options.unstable_opts);
|
||||
let dcx = dcx.handle();
|
||||
|
||||
let input = match input {
|
||||
config::InputMode::HasFile(input) => input,
|
||||
config::InputMode::NoInputMergeFinalize => {
|
||||
return wrap_return(
|
||||
dcx,
|
||||
run_merge_finalize(render_options)
|
||||
.map_err(|e| format!("could not write merged cross-crate info: {e}")),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
match (options.should_test, config::markdown_input(&input)) {
|
||||
(true, Some(_)) => return wrap_return(dcx, doctest::test_markdown(&input, options)),
|
||||
(true, None) => return doctest::run(dcx, input, options),
|
||||
|
@ -172,6 +172,23 @@ Options:
|
||||
--scrape-tests Include test code when scraping examples
|
||||
--with-examples path to function call information (for displaying examples in the documentation)
|
||||
|
||||
--merge none|shared|finalize
|
||||
Controls how rustdoc handles files from previously
|
||||
documented crates in the doc root
|
||||
none = Do not write cross-crate information to the
|
||||
--out-dir
|
||||
shared = Append current crate's info to files found in
|
||||
the --out-dir
|
||||
finalize = Write current crate's info and
|
||||
--include-parts-dir info to the --out-dir, overwriting
|
||||
conflicting files
|
||||
--parts-out-dir path/to/doc.parts/<crate-name>
|
||||
Writes trait implementations and other info for the
|
||||
current crate to provided path. Only use with
|
||||
--merge=none
|
||||
--include-parts-dir path/to/doc.parts/<crate-name>
|
||||
Includes trait implementations and other crate info
|
||||
from provided path. Only use with --merge=finalize
|
||||
--disable-minification
|
||||
removed
|
||||
--plugin-path DIR
|
||||
|
Loading…
Reference in New Issue
Block a user