Add DocFS layer to rustdoc
* Move fs::create_dir_all calls into DocFS to provide a clean
extension point if async extension there is needed.
* Convert callsites of create_dir_all to ensure_dir to reduce syscalls.
* Convert fs::write usage to DocFS.write
(which also removes a lot of try_err! usage for easier reading)
* Convert File::create calls to use Vec buffers and then DocFS.write
in order to consistently reduce syscalls as well, make
deferring to threads cleaner and avoid leaving dangling content if
writing to existing files....
* Convert OpenOptions usage similarly - I could find no discussion on
the use of create_new for that one output file vs all the other
files render creates, if link redirection attacks are a concern
DocFS will provide a good central point to introduce systematic
create_new usage. (fs::write/File::create is vulnerable to link
redirection attacks).
* DocFS::write defers to rayon for IO on Windows producing a modest
speedup: before this patch on my development workstation:
$ time cargo +mystg1 doc -p winapi:0.3.7
Documenting winapi v0.3.7
Finished dev [unoptimized + debuginfo] target(s) in 6m 11s
real 6m11.734s
Afterwards:
$ time cargo +mystg1 doc -p winapi:0.3.7
Compiling winapi v0.3.7
Documenting winapi v0.3.7
Finished dev [unoptimized + debuginfo] target(s) in 49.53s
real 0m49.643s
I haven't measured how much time is in the compilation logic vs in the
IO and outputting etc, but this takes it from frustating to tolerable
for me, at least for now.
This commit is contained in:
committed by
Guillaume Gomez
parent
56a12b2ad0
commit
6392bc9fcd
@@ -11,5 +11,6 @@ path = "lib.rs"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
pulldown-cmark = { version = "0.5.2", default-features = false }
|
pulldown-cmark = { version = "0.5.2", default-features = false }
|
||||||
minifier = "0.0.30"
|
minifier = "0.0.30"
|
||||||
|
rayon = { version = "0.2.0", package = "rustc-rayon" }
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
parking_lot = "0.7"
|
parking_lot = "0.7"
|
||||||
|
|||||||
77
src/librustdoc/docfs.rs
Normal file
77
src/librustdoc/docfs.rs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
//! Rustdoc's FileSystem abstraction module.
|
||||||
|
//!
|
||||||
|
//! On Windows this indirects IO into threads to work around performance issues
|
||||||
|
//! with Defender (and other similar virus scanners that do blocking operations).
|
||||||
|
//! On other platforms this is a thin shim to fs.
|
||||||
|
//!
|
||||||
|
//! Only calls needed to permit this workaround have been abstracted: thus
|
||||||
|
//! fs::read is still done directly via the fs module; if in future rustdoc
|
||||||
|
//! needs to read-after-write from a file, then it would be added to this
|
||||||
|
//! abstraction.
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
macro_rules! try_err {
|
||||||
|
($e:expr, $file:expr) => {{
|
||||||
|
match $e {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(e) => return Err(E::new(e, $file)),
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PathError {
|
||||||
|
fn new<P: AsRef<Path>>(e: io::Error, path: P) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DocFS {
|
||||||
|
sync_only: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DocFS {
|
||||||
|
pub fn new() -> DocFS {
|
||||||
|
DocFS {
|
||||||
|
sync_only: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sync_only(&mut self, sync_only: bool) {
|
||||||
|
self.sync_only = sync_only;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_dir_all<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
||||||
|
// For now, dir creation isn't a huge time consideration, do it
|
||||||
|
// synchronously, which avoids needing ordering between write() actions
|
||||||
|
// and directory creation.
|
||||||
|
fs::create_dir_all(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<P, C, E>(&self, path: P, contents: C) -> Result<(), E>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
C: AsRef<[u8]>,
|
||||||
|
E: PathError,
|
||||||
|
{
|
||||||
|
if !self.sync_only && cfg!(windows) {
|
||||||
|
// A possible future enhancement after more detailed profiling would
|
||||||
|
// be to create the file sync so errors are reported eagerly.
|
||||||
|
let contents = contents.as_ref().to_vec();
|
||||||
|
let path = path.as_ref().to_path_buf();
|
||||||
|
rayon::spawn(move ||
|
||||||
|
match fs::write(&path, &contents) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
// In principle these should get displayed at the top
|
||||||
|
// level, but just in case, send to stderr as well.
|
||||||
|
eprintln!("\"{}\": {}", path.display(), e);
|
||||||
|
panic!("\"{}\": {}", path.display(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Ok(try_err!(fs::write(&path, contents), path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,9 +35,9 @@ use std::default::Default;
|
|||||||
use std::error;
|
use std::error;
|
||||||
use std::fmt::{self, Display, Formatter, Write as FmtWrite};
|
use std::fmt::{self, Display, Formatter, Write as FmtWrite};
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fs::{self, File, OpenOptions};
|
use std::fs::{self, File};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::io::{self, BufWriter, BufReader};
|
use std::io::{self, BufReader};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::path::{PathBuf, Path, Component};
|
use std::path::{PathBuf, Path, Component};
|
||||||
use std::str;
|
use std::str;
|
||||||
@@ -61,6 +61,7 @@ use rustc_data_structures::flock;
|
|||||||
|
|
||||||
use crate::clean::{self, AttributesExt, Deprecation, GetDefId, SelfTy, Mutability};
|
use crate::clean::{self, AttributesExt, Deprecation, GetDefId, SelfTy, Mutability};
|
||||||
use crate::config::RenderOptions;
|
use crate::config::RenderOptions;
|
||||||
|
use crate::docfs::{DocFS, PathError};
|
||||||
use crate::doctree;
|
use crate::doctree;
|
||||||
use crate::fold::DocFolder;
|
use crate::fold::DocFolder;
|
||||||
use crate::html::escape::Escape;
|
use crate::html::escape::Escape;
|
||||||
@@ -89,6 +90,53 @@ impl<'a> Display for SlashChecker<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error {
|
||||||
|
pub file: PathBuf,
|
||||||
|
pub error: io::Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for Error {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
self.error.description()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "\"{}\": {}", self.file.display(), self.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathError for Error {
|
||||||
|
fn new<P: AsRef<Path>>(e: io::Error, path: P) -> Error {
|
||||||
|
Error {
|
||||||
|
file: path.as_ref().to_path_buf(),
|
||||||
|
error: e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! try_none {
|
||||||
|
($e:expr, $file:expr) => ({
|
||||||
|
use std::io;
|
||||||
|
match $e {
|
||||||
|
Some(e) => e,
|
||||||
|
None => return Err(Error::new(io::Error::new(io::ErrorKind::Other, "not found"),
|
||||||
|
$file))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! try_err {
|
||||||
|
($e:expr, $file:expr) => ({
|
||||||
|
match $e {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(e) => return Err(Error::new(e, $file)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Major driving force in all rustdoc rendering. This contains information
|
/// Major driving force in all rustdoc rendering. This contains information
|
||||||
/// about where in the tree-like hierarchy rendering is occurring and controls
|
/// about where in the tree-like hierarchy rendering is occurring and controls
|
||||||
/// how the current page is being rendered.
|
/// how the current page is being rendered.
|
||||||
@@ -156,13 +204,15 @@ struct SharedContext {
|
|||||||
pub generate_search_filter: bool,
|
pub generate_search_filter: bool,
|
||||||
/// Option disabled by default to generate files used by RLS and some other tools.
|
/// Option disabled by default to generate files used by RLS and some other tools.
|
||||||
pub generate_redirect_pages: bool,
|
pub generate_redirect_pages: bool,
|
||||||
|
/// The fs handle we are working with.
|
||||||
|
pub fs: DocFS,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedContext {
|
impl SharedContext {
|
||||||
fn ensure_dir(&self, dst: &Path) -> io::Result<()> {
|
fn ensure_dir(&self, dst: &Path) -> Result<(), Error> {
|
||||||
let mut dirs = self.created_dirs.borrow_mut();
|
let mut dirs = self.created_dirs.borrow_mut();
|
||||||
if !dirs.contains(dst) {
|
if !dirs.contains(dst) {
|
||||||
fs::create_dir_all(dst)?;
|
try_err!(self.fs.create_dir_all(dst), dst);
|
||||||
dirs.insert(dst.to_path_buf());
|
dirs.insert(dst.to_path_buf());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,53 +266,6 @@ impl Impl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Error {
|
|
||||||
pub file: PathBuf,
|
|
||||||
pub error: io::Error,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::Error for Error {
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
self.error.description()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Error {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "\"{}\": {}", self.file.display(), self.error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error {
|
|
||||||
pub fn new(e: io::Error, file: &Path) -> Error {
|
|
||||||
Error {
|
|
||||||
file: file.to_path_buf(),
|
|
||||||
error: e,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! try_none {
|
|
||||||
($e:expr, $file:expr) => ({
|
|
||||||
use std::io;
|
|
||||||
match $e {
|
|
||||||
Some(e) => e,
|
|
||||||
None => return Err(Error::new(io::Error::new(io::ErrorKind::Other, "not found"),
|
|
||||||
$file))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! try_err {
|
|
||||||
($e:expr, $file:expr) => ({
|
|
||||||
match $e {
|
|
||||||
Ok(e) => e,
|
|
||||||
Err(e) => return Err(Error::new(e, $file)),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This cache is used to store information about the `clean::Crate` being
|
/// This cache is used to store information about the `clean::Crate` being
|
||||||
/// rendered in order to provide more useful documentation. This contains
|
/// rendered in order to provide more useful documentation. This contains
|
||||||
/// information like all implementors of a trait, all traits a type implements,
|
/// information like all implementors of a trait, all traits a type implements,
|
||||||
@@ -564,6 +567,7 @@ pub fn run(mut krate: clean::Crate,
|
|||||||
static_root_path,
|
static_root_path,
|
||||||
generate_search_filter,
|
generate_search_filter,
|
||||||
generate_redirect_pages,
|
generate_redirect_pages,
|
||||||
|
fs: DocFS::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// If user passed in `--playground-url` arg, we fill in crate name here
|
// If user passed in `--playground-url` arg, we fill in crate name here
|
||||||
@@ -601,9 +605,9 @@ pub fn run(mut krate: clean::Crate,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let dst = output;
|
let dst = output;
|
||||||
try_err!(fs::create_dir_all(&dst), &dst);
|
scx.ensure_dir(&dst)?;
|
||||||
krate = render_sources(&dst, &mut scx, krate)?;
|
krate = render_sources(&dst, &mut scx, krate)?;
|
||||||
let cx = Context {
|
let mut cx = Context {
|
||||||
current: Vec::new(),
|
current: Vec::new(),
|
||||||
dst,
|
dst,
|
||||||
render_redirect_pages: false,
|
render_redirect_pages: false,
|
||||||
@@ -705,7 +709,10 @@ pub fn run(mut krate: clean::Crate,
|
|||||||
CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone());
|
CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone());
|
||||||
CURRENT_LOCATION_KEY.with(|s| s.borrow_mut().clear());
|
CURRENT_LOCATION_KEY.with(|s| s.borrow_mut().clear());
|
||||||
|
|
||||||
|
// Write shared runs within a flock; disable thread dispatching of IO temporarily.
|
||||||
|
Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true);
|
||||||
write_shared(&cx, &krate, &*cache, index, &md_opts, diag)?;
|
write_shared(&cx, &krate, &*cache, index, &md_opts, diag)?;
|
||||||
|
Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false);
|
||||||
|
|
||||||
// And finally render the whole crate's documentation
|
// And finally render the whole crate's documentation
|
||||||
cx.krate(krate)
|
cx.krate(krate)
|
||||||
@@ -797,13 +804,13 @@ fn write_shared(
|
|||||||
// Add all the static files. These may already exist, but we just
|
// Add all the static files. These may already exist, but we just
|
||||||
// overwrite them anyway to make sure that they're fresh and up-to-date.
|
// overwrite them anyway to make sure that they're fresh and up-to-date.
|
||||||
|
|
||||||
write_minify(cx.dst.join(&format!("rustdoc{}.css", cx.shared.resource_suffix)),
|
write_minify(&cx.shared.fs, cx.dst.join(&format!("rustdoc{}.css", cx.shared.resource_suffix)),
|
||||||
static_files::RUSTDOC_CSS,
|
static_files::RUSTDOC_CSS,
|
||||||
options.enable_minification)?;
|
options.enable_minification)?;
|
||||||
write_minify(cx.dst.join(&format!("settings{}.css", cx.shared.resource_suffix)),
|
write_minify(&cx.shared.fs, cx.dst.join(&format!("settings{}.css", cx.shared.resource_suffix)),
|
||||||
static_files::SETTINGS_CSS,
|
static_files::SETTINGS_CSS,
|
||||||
options.enable_minification)?;
|
options.enable_minification)?;
|
||||||
write_minify(cx.dst.join(&format!("noscript{}.css", cx.shared.resource_suffix)),
|
write_minify(&cx.shared.fs, cx.dst.join(&format!("noscript{}.css", cx.shared.resource_suffix)),
|
||||||
static_files::NOSCRIPT_CSS,
|
static_files::NOSCRIPT_CSS,
|
||||||
options.enable_minification)?;
|
options.enable_minification)?;
|
||||||
|
|
||||||
@@ -815,11 +822,13 @@ fn write_shared(
|
|||||||
let content = try_err!(fs::read(&entry), &entry);
|
let content = try_err!(fs::read(&entry), &entry);
|
||||||
let theme = try_none!(try_none!(entry.file_stem(), &entry).to_str(), &entry);
|
let theme = try_none!(try_none!(entry.file_stem(), &entry).to_str(), &entry);
|
||||||
let extension = try_none!(try_none!(entry.extension(), &entry).to_str(), &entry);
|
let extension = try_none!(try_none!(entry.extension(), &entry).to_str(), &entry);
|
||||||
write(cx.dst.join(format!("{}{}.{}", theme, cx.shared.resource_suffix, extension)),
|
cx.shared.fs.write(
|
||||||
|
cx.dst.join(format!("{}{}.{}", theme, cx.shared.resource_suffix, extension)),
|
||||||
content.as_slice())?;
|
content.as_slice())?;
|
||||||
themes.insert(theme.to_owned());
|
themes.insert(theme.to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let write = |p, c| { cx.shared.fs.write(p, c) };
|
||||||
if (*cx.shared).layout.logo.is_empty() {
|
if (*cx.shared).layout.logo.is_empty() {
|
||||||
write(cx.dst.join(&format!("rust-logo{}.png", cx.shared.resource_suffix)),
|
write(cx.dst.join(&format!("rust-logo{}.png", cx.shared.resource_suffix)),
|
||||||
static_files::RUST_LOGO)?;
|
static_files::RUST_LOGO)?;
|
||||||
@@ -834,11 +843,11 @@ fn write_shared(
|
|||||||
static_files::WHEEL_SVG)?;
|
static_files::WHEEL_SVG)?;
|
||||||
write(cx.dst.join(&format!("down-arrow{}.svg", cx.shared.resource_suffix)),
|
write(cx.dst.join(&format!("down-arrow{}.svg", cx.shared.resource_suffix)),
|
||||||
static_files::DOWN_ARROW_SVG)?;
|
static_files::DOWN_ARROW_SVG)?;
|
||||||
write_minify(cx.dst.join(&format!("light{}.css", cx.shared.resource_suffix)),
|
write_minify(&cx.shared.fs, cx.dst.join(&format!("light{}.css", cx.shared.resource_suffix)),
|
||||||
static_files::themes::LIGHT,
|
static_files::themes::LIGHT,
|
||||||
options.enable_minification)?;
|
options.enable_minification)?;
|
||||||
themes.insert("light".to_owned());
|
themes.insert("light".to_owned());
|
||||||
write_minify(cx.dst.join(&format!("dark{}.css", cx.shared.resource_suffix)),
|
write_minify(&cx.shared.fs, cx.dst.join(&format!("dark{}.css", cx.shared.resource_suffix)),
|
||||||
static_files::themes::DARK,
|
static_files::themes::DARK,
|
||||||
options.enable_minification)?;
|
options.enable_minification)?;
|
||||||
themes.insert("dark".to_owned());
|
themes.insert("dark".to_owned());
|
||||||
@@ -847,8 +856,7 @@ fn write_shared(
|
|||||||
themes.sort();
|
themes.sort();
|
||||||
// To avoid theme switch latencies as much as possible, we put everything theme related
|
// To avoid theme switch latencies as much as possible, we put everything theme related
|
||||||
// at the beginning of the html files into another js file.
|
// at the beginning of the html files into another js file.
|
||||||
write(cx.dst.join(&format!("theme{}.js", cx.shared.resource_suffix)),
|
let theme_js = format!(
|
||||||
format!(
|
|
||||||
r#"var themes = document.getElementById("theme-choices");
|
r#"var themes = document.getElementById("theme-choices");
|
||||||
var themePicker = document.getElementById("theme-picker");
|
var themePicker = document.getElementById("theme-picker");
|
||||||
|
|
||||||
@@ -891,23 +899,29 @@ themePicker.onblur = handleThemeButtonsBlur;
|
|||||||
themes.iter()
|
themes.iter()
|
||||||
.map(|s| format!("\"{}\"", s))
|
.map(|s| format!("\"{}\"", s))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(",")).as_bytes(),
|
.join(","));
|
||||||
|
write(cx.dst.join(&format!("theme{}.js", cx.shared.resource_suffix)),
|
||||||
|
theme_js.as_bytes()
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
write_minify(cx.dst.join(&format!("main{}.js", cx.shared.resource_suffix)),
|
write_minify(&cx.shared.fs, cx.dst.join(&format!("main{}.js", cx.shared.resource_suffix)),
|
||||||
static_files::MAIN_JS,
|
static_files::MAIN_JS,
|
||||||
options.enable_minification)?;
|
options.enable_minification)?;
|
||||||
write_minify(cx.dst.join(&format!("settings{}.js", cx.shared.resource_suffix)),
|
write_minify(&cx.shared.fs, cx.dst.join(&format!("settings{}.js", cx.shared.resource_suffix)),
|
||||||
static_files::SETTINGS_JS,
|
static_files::SETTINGS_JS,
|
||||||
options.enable_minification)?;
|
options.enable_minification)?;
|
||||||
if cx.shared.include_sources {
|
if cx.shared.include_sources {
|
||||||
write_minify(cx.dst.join(&format!("source-script{}.js", cx.shared.resource_suffix)),
|
write_minify(
|
||||||
|
&cx.shared.fs,
|
||||||
|
cx.dst.join(&format!("source-script{}.js", cx.shared.resource_suffix)),
|
||||||
static_files::sidebar::SOURCE_SCRIPT,
|
static_files::sidebar::SOURCE_SCRIPT,
|
||||||
options.enable_minification)?;
|
options.enable_minification)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
write_minify(cx.dst.join(&format!("storage{}.js", cx.shared.resource_suffix)),
|
write_minify(
|
||||||
|
&cx.shared.fs,
|
||||||
|
cx.dst.join(&format!("storage{}.js", cx.shared.resource_suffix)),
|
||||||
&format!("var resourcesSuffix = \"{}\";{}",
|
&format!("var resourcesSuffix = \"{}\";{}",
|
||||||
cx.shared.resource_suffix,
|
cx.shared.resource_suffix,
|
||||||
static_files::STORAGE_JS),
|
static_files::STORAGE_JS),
|
||||||
@@ -916,14 +930,14 @@ themePicker.onblur = handleThemeButtonsBlur;
|
|||||||
|
|
||||||
if let Some(ref css) = cx.shared.css_file_extension {
|
if let Some(ref css) = cx.shared.css_file_extension {
|
||||||
let out = cx.dst.join(&format!("theme{}.css", cx.shared.resource_suffix));
|
let out = cx.dst.join(&format!("theme{}.css", cx.shared.resource_suffix));
|
||||||
if !options.enable_minification {
|
|
||||||
try_err!(fs::copy(css, out), css);
|
|
||||||
} else {
|
|
||||||
let buffer = try_err!(fs::read_to_string(css), css);
|
let buffer = try_err!(fs::read_to_string(css), css);
|
||||||
write_minify(out, &buffer, options.enable_minification)?;
|
if !options.enable_minification {
|
||||||
|
cx.shared.fs.write(&out, &buffer)?;
|
||||||
|
} else {
|
||||||
|
write_minify(&cx.shared.fs, out, &buffer, options.enable_minification)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write_minify(cx.dst.join(&format!("normalize{}.css", cx.shared.resource_suffix)),
|
write_minify(&cx.shared.fs, cx.dst.join(&format!("normalize{}.css", cx.shared.resource_suffix)),
|
||||||
static_files::NORMALIZE_CSS,
|
static_files::NORMALIZE_CSS,
|
||||||
options.enable_minification)?;
|
options.enable_minification)?;
|
||||||
write(cx.dst.join("FiraSans-Regular.woff"),
|
write(cx.dst.join("FiraSans-Regular.woff"),
|
||||||
@@ -999,7 +1013,6 @@ themePicker.onblur = handleThemeButtonsBlur;
|
|||||||
let dst = cx.dst.join(&format!("aliases{}.js", cx.shared.resource_suffix));
|
let dst = cx.dst.join(&format!("aliases{}.js", cx.shared.resource_suffix));
|
||||||
{
|
{
|
||||||
let (mut all_aliases, _, _) = try_err!(collect(&dst, &krate.name, "ALIASES", false), &dst);
|
let (mut all_aliases, _, _) = try_err!(collect(&dst, &krate.name, "ALIASES", false), &dst);
|
||||||
let mut w = try_err!(File::create(&dst), &dst);
|
|
||||||
let mut output = String::with_capacity(100);
|
let mut output = String::with_capacity(100);
|
||||||
for (alias, items) in &cache.aliases {
|
for (alias, items) in &cache.aliases {
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
@@ -1014,10 +1027,12 @@ themePicker.onblur = handleThemeButtonsBlur;
|
|||||||
}
|
}
|
||||||
all_aliases.push(format!("ALIASES[\"{}\"] = {{{}}};", krate.name, output));
|
all_aliases.push(format!("ALIASES[\"{}\"] = {{{}}};", krate.name, output));
|
||||||
all_aliases.sort();
|
all_aliases.sort();
|
||||||
try_err!(writeln!(&mut w, "var ALIASES = {{}};"), &dst);
|
let mut v = Vec::new();
|
||||||
|
try_err!(writeln!(&mut v, "var ALIASES = {{}};"), &dst);
|
||||||
for aliases in &all_aliases {
|
for aliases in &all_aliases {
|
||||||
try_err!(writeln!(&mut w, "{}", aliases), &dst);
|
try_err!(writeln!(&mut v, "{}", aliases), &dst);
|
||||||
}
|
}
|
||||||
|
cx.shared.fs.write(&dst, &v)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
@@ -1101,11 +1116,12 @@ themePicker.onblur = handleThemeButtonsBlur;
|
|||||||
&krate.name,
|
&krate.name,
|
||||||
hierarchy.to_json_string()));
|
hierarchy.to_json_string()));
|
||||||
all_sources.sort();
|
all_sources.sort();
|
||||||
let mut w = try_err!(File::create(&dst), &dst);
|
let mut v = Vec::new();
|
||||||
try_err!(writeln!(&mut w,
|
try_err!(writeln!(&mut v,
|
||||||
"var N = null;var sourcesIndex = {{}};\n{}\ncreateSourceSidebar();",
|
"var N = null;var sourcesIndex = {{}};\n{}\ncreateSourceSidebar();",
|
||||||
all_sources.join("\n")),
|
all_sources.join("\n")),
|
||||||
&dst);
|
&dst);
|
||||||
|
cx.shared.fs.write(&dst, &v)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the search index
|
// Update the search index
|
||||||
@@ -1119,14 +1135,17 @@ themePicker.onblur = handleThemeButtonsBlur;
|
|||||||
// Sort the indexes by crate so the file will be generated identically even
|
// Sort the indexes by crate so the file will be generated identically even
|
||||||
// with rustdoc running in parallel.
|
// with rustdoc running in parallel.
|
||||||
all_indexes.sort();
|
all_indexes.sort();
|
||||||
let mut w = try_err!(File::create(&dst), &dst);
|
{
|
||||||
try_err!(writeln!(&mut w, "var N=null,E=\"\",T=\"t\",U=\"u\",searchIndex={{}};"), &dst);
|
let mut v = Vec::new();
|
||||||
try_err!(write_minify_replacer(&mut w,
|
try_err!(writeln!(&mut v, "var N=null,E=\"\",T=\"t\",U=\"u\",searchIndex={{}};"), &dst);
|
||||||
|
try_err!(write_minify_replacer(
|
||||||
|
&mut v,
|
||||||
&format!("{}\n{}", variables.join(""), all_indexes.join("\n")),
|
&format!("{}\n{}", variables.join(""), all_indexes.join("\n")),
|
||||||
options.enable_minification),
|
options.enable_minification),
|
||||||
&dst);
|
&dst);
|
||||||
try_err!(write!(&mut w, "initSearch(searchIndex);addSearchOptions(searchIndex);"), &dst);
|
try_err!(write!(&mut v, "initSearch(searchIndex);addSearchOptions(searchIndex);"), &dst);
|
||||||
|
cx.shared.fs.write(&dst, &v)?;
|
||||||
|
}
|
||||||
if options.enable_index_page {
|
if options.enable_index_page {
|
||||||
if let Some(index_page) = options.index_page.clone() {
|
if let Some(index_page) = options.index_page.clone() {
|
||||||
let mut md_opts = options.clone();
|
let mut md_opts = options.clone();
|
||||||
@@ -1136,7 +1155,6 @@ themePicker.onblur = handleThemeButtonsBlur;
|
|||||||
crate::markdown::render(index_page, md_opts, diag, cx.edition);
|
crate::markdown::render(index_page, md_opts, diag, cx.edition);
|
||||||
} else {
|
} else {
|
||||||
let dst = cx.dst.join("index.html");
|
let dst = cx.dst.join("index.html");
|
||||||
let mut w = BufWriter::new(try_err!(File::create(&dst), &dst));
|
|
||||||
let page = layout::Page {
|
let page = layout::Page {
|
||||||
title: "Index of crates",
|
title: "Index of crates",
|
||||||
css_class: "mod",
|
css_class: "mod",
|
||||||
@@ -1163,12 +1181,13 @@ themePicker.onblur = handleThemeButtonsBlur;
|
|||||||
SlashChecker(s), s)
|
SlashChecker(s), s)
|
||||||
})
|
})
|
||||||
.collect::<String>());
|
.collect::<String>());
|
||||||
try_err!(layout::render(&mut w, &cx.shared.layout,
|
let mut v = Vec::new();
|
||||||
|
try_err!(layout::render(&mut v, &cx.shared.layout,
|
||||||
&page, &(""), &content,
|
&page, &(""), &content,
|
||||||
cx.shared.css_file_extension.is_some(),
|
cx.shared.css_file_extension.is_some(),
|
||||||
&cx.shared.themes,
|
&cx.shared.themes,
|
||||||
cx.shared.generate_search_filter), &dst);
|
cx.shared.generate_search_filter), &dst);
|
||||||
try_err!(w.flush(), &dst);
|
cx.shared.fs.write(&dst, &v)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1220,7 +1239,7 @@ themePicker.onblur = handleThemeButtonsBlur;
|
|||||||
for part in &remote_path[..remote_path.len() - 1] {
|
for part in &remote_path[..remote_path.len() - 1] {
|
||||||
mydst.push(part);
|
mydst.push(part);
|
||||||
}
|
}
|
||||||
try_err!(fs::create_dir_all(&mydst), &mydst);
|
cx.shared.ensure_dir(&mydst)?;
|
||||||
mydst.push(&format!("{}.{}.js",
|
mydst.push(&format!("{}.{}.js",
|
||||||
remote_item_type.css_class(),
|
remote_item_type.css_class(),
|
||||||
remote_path[remote_path.len() - 1]));
|
remote_path[remote_path.len() - 1]));
|
||||||
@@ -1233,19 +1252,20 @@ themePicker.onblur = handleThemeButtonsBlur;
|
|||||||
// identically even with rustdoc running in parallel.
|
// identically even with rustdoc running in parallel.
|
||||||
all_implementors.sort();
|
all_implementors.sort();
|
||||||
|
|
||||||
let mut f = try_err!(File::create(&mydst), &mydst);
|
let mut v = Vec::new();
|
||||||
try_err!(writeln!(&mut f, "(function() {{var implementors = {{}};"), &mydst);
|
try_err!(writeln!(&mut v, "(function() {{var implementors = {{}};"), &mydst);
|
||||||
for implementor in &all_implementors {
|
for implementor in &all_implementors {
|
||||||
try_err!(writeln!(&mut f, "{}", *implementor), &mydst);
|
try_err!(writeln!(&mut v, "{}", *implementor), &mydst);
|
||||||
}
|
}
|
||||||
try_err!(writeln!(&mut f, "{}", r"
|
try_err!(writeln!(&mut v, "{}", r"
|
||||||
if (window.register_implementors) {
|
if (window.register_implementors) {
|
||||||
window.register_implementors(implementors);
|
window.register_implementors(implementors);
|
||||||
} else {
|
} else {
|
||||||
window.pending_implementors = implementors;
|
window.pending_implementors = implementors;
|
||||||
}
|
}
|
||||||
"), &mydst);
|
"), &mydst);
|
||||||
try_err!(writeln!(&mut f, r"}})()"), &mydst);
|
try_err!(writeln!(&mut v, r"}})()"), &mydst);
|
||||||
|
cx.shared.fs.write(&mydst, &v)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1254,7 +1274,7 @@ fn render_sources(dst: &Path, scx: &mut SharedContext,
|
|||||||
krate: clean::Crate) -> Result<clean::Crate, Error> {
|
krate: clean::Crate) -> Result<clean::Crate, Error> {
|
||||||
info!("emitting source files");
|
info!("emitting source files");
|
||||||
let dst = dst.join("src").join(&krate.name);
|
let dst = dst.join("src").join(&krate.name);
|
||||||
try_err!(fs::create_dir_all(&dst), &dst);
|
scx.ensure_dir(&dst)?;
|
||||||
let mut folder = SourceCollector {
|
let mut folder = SourceCollector {
|
||||||
dst,
|
dst,
|
||||||
scx,
|
scx,
|
||||||
@@ -1262,22 +1282,17 @@ fn render_sources(dst: &Path, scx: &mut SharedContext,
|
|||||||
Ok(folder.fold_crate(krate))
|
Ok(folder.fold_crate(krate))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes the entire contents of a string to a destination, not attempting to
|
fn write_minify(fs:&DocFS, dst: PathBuf, contents: &str, enable_minification: bool
|
||||||
/// catch any errors.
|
) -> Result<(), Error> {
|
||||||
fn write(dst: PathBuf, contents: &[u8]) -> Result<(), Error> {
|
|
||||||
Ok(try_err!(fs::write(&dst, contents), &dst))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_minify(dst: PathBuf, contents: &str, enable_minification: bool) -> Result<(), Error> {
|
|
||||||
if enable_minification {
|
if enable_minification {
|
||||||
if dst.extension() == Some(&OsStr::new("css")) {
|
if dst.extension() == Some(&OsStr::new("css")) {
|
||||||
let res = try_none!(minifier::css::minify(contents).ok(), &dst);
|
let res = try_none!(minifier::css::minify(contents).ok(), &dst);
|
||||||
write(dst, res.as_bytes())
|
fs.write(dst, res.as_bytes())
|
||||||
} else {
|
} else {
|
||||||
write(dst, minifier::js::minify(contents).as_bytes())
|
fs.write(dst, minifier::js::minify(contents).as_bytes())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
write(dst, contents.as_bytes())
|
fs.write(dst, contents.as_bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1439,7 +1454,7 @@ impl<'a> DocFolder for SourceCollector<'a> {
|
|||||||
|
|
||||||
impl<'a> SourceCollector<'a> {
|
impl<'a> SourceCollector<'a> {
|
||||||
/// Renders the given filename into its corresponding HTML source file.
|
/// Renders the given filename into its corresponding HTML source file.
|
||||||
fn emit_source(&mut self, filename: &FileName) -> io::Result<()> {
|
fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> {
|
||||||
let p = match *filename {
|
let p = match *filename {
|
||||||
FileName::Real(ref file) => file,
|
FileName::Real(ref file) => file,
|
||||||
_ => return Ok(()),
|
_ => return Ok(()),
|
||||||
@@ -1449,7 +1464,7 @@ impl<'a> SourceCollector<'a> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let contents = fs::read_to_string(&p)?;
|
let contents = try_err!(fs::read_to_string(&p), &p);
|
||||||
|
|
||||||
// Remove the utf-8 BOM if any
|
// Remove the utf-8 BOM if any
|
||||||
let contents = if contents.starts_with("\u{feff}") {
|
let contents = if contents.starts_with("\u{feff}") {
|
||||||
@@ -1468,7 +1483,7 @@ impl<'a> SourceCollector<'a> {
|
|||||||
href.push_str(&component.to_string_lossy());
|
href.push_str(&component.to_string_lossy());
|
||||||
href.push('/');
|
href.push('/');
|
||||||
});
|
});
|
||||||
fs::create_dir_all(&cur)?;
|
self.scx.ensure_dir(&cur)?;
|
||||||
let mut fname = p.file_name()
|
let mut fname = p.file_name()
|
||||||
.expect("source has no filename")
|
.expect("source has no filename")
|
||||||
.to_os_string();
|
.to_os_string();
|
||||||
@@ -1476,7 +1491,7 @@ impl<'a> SourceCollector<'a> {
|
|||||||
cur.push(&fname);
|
cur.push(&fname);
|
||||||
href.push_str(&fname.to_string_lossy());
|
href.push_str(&fname.to_string_lossy());
|
||||||
|
|
||||||
let mut w = BufWriter::new(File::create(&cur)?);
|
let mut v = Vec::new();
|
||||||
let title = format!("{} -- source", cur.file_name().expect("failed to get file name")
|
let title = format!("{} -- source", cur.file_name().expect("failed to get file name")
|
||||||
.to_string_lossy());
|
.to_string_lossy());
|
||||||
let desc = format!("Source to the Rust file `{}`.", filename);
|
let desc = format!("Source to the Rust file `{}`.", filename);
|
||||||
@@ -1491,12 +1506,12 @@ impl<'a> SourceCollector<'a> {
|
|||||||
extra_scripts: &[&format!("source-files{}", self.scx.resource_suffix)],
|
extra_scripts: &[&format!("source-files{}", self.scx.resource_suffix)],
|
||||||
static_extra_scripts: &[&format!("source-script{}", self.scx.resource_suffix)],
|
static_extra_scripts: &[&format!("source-script{}", self.scx.resource_suffix)],
|
||||||
};
|
};
|
||||||
layout::render(&mut w, &self.scx.layout,
|
try_err!(layout::render(&mut v, &self.scx.layout,
|
||||||
&page, &(""), &Source(contents),
|
&page, &(""), &Source(contents),
|
||||||
self.scx.css_file_extension.is_some(),
|
self.scx.css_file_extension.is_some(),
|
||||||
&self.scx.themes,
|
&self.scx.themes,
|
||||||
self.scx.generate_search_filter)?;
|
self.scx.generate_search_filter), &cur);
|
||||||
w.flush()?;
|
self.scx.fs.write(&cur, &v)?;
|
||||||
self.scx.local_sources.insert(p.clone(), href);
|
self.scx.local_sources.insert(p.clone(), href);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -2073,7 +2088,6 @@ impl Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut w = BufWriter::new(try_err!(File::create(&final_file), &final_file));
|
|
||||||
let mut root_path = self.dst.to_str().expect("invalid path").to_owned();
|
let mut root_path = self.dst.to_str().expect("invalid path").to_owned();
|
||||||
if !root_path.ends_with('/') {
|
if !root_path.ends_with('/') {
|
||||||
root_path.push('/');
|
root_path.push('/');
|
||||||
@@ -2099,12 +2113,14 @@ impl Context {
|
|||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
try_err!(layout::render(&mut w, &self.shared.layout,
|
let mut v = Vec::new();
|
||||||
|
try_err!(layout::render(&mut v, &self.shared.layout,
|
||||||
&page, &sidebar, &all,
|
&page, &sidebar, &all,
|
||||||
self.shared.css_file_extension.is_some(),
|
self.shared.css_file_extension.is_some(),
|
||||||
&self.shared.themes,
|
&self.shared.themes,
|
||||||
self.shared.generate_search_filter),
|
self.shared.generate_search_filter),
|
||||||
&final_file);
|
&final_file);
|
||||||
|
self.shared.fs.write(&final_file, &v)?;
|
||||||
|
|
||||||
// Generating settings page.
|
// Generating settings page.
|
||||||
let settings = Settings::new(self.shared.static_root_path.deref().unwrap_or("./"),
|
let settings = Settings::new(self.shared.static_root_path.deref().unwrap_or("./"),
|
||||||
@@ -2113,17 +2129,18 @@ impl Context {
|
|||||||
page.description = "Settings of Rustdoc";
|
page.description = "Settings of Rustdoc";
|
||||||
page.root_path = "./";
|
page.root_path = "./";
|
||||||
|
|
||||||
let mut w = BufWriter::new(try_err!(File::create(&settings_file), &settings_file));
|
|
||||||
let mut themes = self.shared.themes.clone();
|
let mut themes = self.shared.themes.clone();
|
||||||
let sidebar = "<p class='location'>Settings</p><div class='sidebar-elems'></div>";
|
let sidebar = "<p class='location'>Settings</p><div class='sidebar-elems'></div>";
|
||||||
themes.push(PathBuf::from("settings.css"));
|
themes.push(PathBuf::from("settings.css"));
|
||||||
let layout = self.shared.layout.clone();
|
let layout = self.shared.layout.clone();
|
||||||
try_err!(layout::render(&mut w, &layout,
|
let mut v = Vec::new();
|
||||||
|
try_err!(layout::render(&mut v, &layout,
|
||||||
&page, &sidebar, &settings,
|
&page, &sidebar, &settings,
|
||||||
self.shared.css_file_extension.is_some(),
|
self.shared.css_file_extension.is_some(),
|
||||||
&themes,
|
&themes,
|
||||||
self.shared.generate_search_filter),
|
self.shared.generate_search_filter),
|
||||||
&settings_file);
|
&settings_file);
|
||||||
|
self.shared.fs.write(&settings_file, &v)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -2223,6 +2240,7 @@ impl Context {
|
|||||||
// recurse into the items of the module as well.
|
// recurse into the items of the module as well.
|
||||||
let name = item.name.as_ref().unwrap().to_string();
|
let name = item.name.as_ref().unwrap().to_string();
|
||||||
let mut item = Some(item);
|
let mut item = Some(item);
|
||||||
|
let scx = self.shared.clone();
|
||||||
self.recurse(name, |this| {
|
self.recurse(name, |this| {
|
||||||
let item = item.take().unwrap();
|
let item = item.take().unwrap();
|
||||||
|
|
||||||
@@ -2230,9 +2248,9 @@ impl Context {
|
|||||||
this.render_item(&mut buf, &item, false).unwrap();
|
this.render_item(&mut buf, &item, false).unwrap();
|
||||||
// buf will be empty if the module is stripped and there is no redirect for it
|
// buf will be empty if the module is stripped and there is no redirect for it
|
||||||
if !buf.is_empty() {
|
if !buf.is_empty() {
|
||||||
try_err!(this.shared.ensure_dir(&this.dst), &this.dst);
|
this.shared.ensure_dir(&this.dst)?;
|
||||||
let joint_dst = this.dst.join("index.html");
|
let joint_dst = this.dst.join("index.html");
|
||||||
try_err!(fs::write(&joint_dst, buf), &joint_dst);
|
scx.fs.write(&joint_dst, buf)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let m = match item.inner {
|
let m = match item.inner {
|
||||||
@@ -2245,9 +2263,10 @@ impl Context {
|
|||||||
if !this.render_redirect_pages {
|
if !this.render_redirect_pages {
|
||||||
let items = this.build_sidebar_items(&m);
|
let items = this.build_sidebar_items(&m);
|
||||||
let js_dst = this.dst.join("sidebar-items.js");
|
let js_dst = this.dst.join("sidebar-items.js");
|
||||||
let mut js_out = BufWriter::new(try_err!(File::create(&js_dst), &js_dst));
|
let mut v = Vec::new();
|
||||||
try_err!(write!(&mut js_out, "initSidebarItems({});",
|
try_err!(write!(&mut v, "initSidebarItems({});",
|
||||||
as_json(&items)), &js_dst);
|
as_json(&items)), &js_dst);
|
||||||
|
scx.fs.write(&js_dst, &v)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for item in m.items {
|
for item in m.items {
|
||||||
@@ -2264,9 +2283,9 @@ impl Context {
|
|||||||
let name = item.name.as_ref().unwrap();
|
let name = item.name.as_ref().unwrap();
|
||||||
let item_type = item.type_();
|
let item_type = item.type_();
|
||||||
let file_name = &item_path(item_type, name);
|
let file_name = &item_path(item_type, name);
|
||||||
try_err!(self.shared.ensure_dir(&self.dst), &self.dst);
|
self.shared.ensure_dir(&self.dst)?;
|
||||||
let joint_dst = self.dst.join(file_name);
|
let joint_dst = self.dst.join(file_name);
|
||||||
try_err!(fs::write(&joint_dst, buf), &joint_dst);
|
self.shared.fs.write(&joint_dst, buf)?;
|
||||||
|
|
||||||
if !self.render_redirect_pages {
|
if !self.render_redirect_pages {
|
||||||
all.append(full_path(self, &item), &item_type);
|
all.append(full_path(self, &item), &item_type);
|
||||||
@@ -2276,21 +2295,18 @@ impl Context {
|
|||||||
// URL for the page.
|
// URL for the page.
|
||||||
let redir_name = format!("{}.{}.html", name, item_type.name_space());
|
let redir_name = format!("{}.{}.html", name, item_type.name_space());
|
||||||
let redir_dst = self.dst.join(redir_name);
|
let redir_dst = self.dst.join(redir_name);
|
||||||
if let Ok(redirect_out) = OpenOptions::new().create_new(true)
|
let mut v = Vec::new();
|
||||||
.write(true)
|
try_err!(layout::redirect(&mut v, file_name), &redir_dst);
|
||||||
.open(&redir_dst) {
|
self.shared.fs.write(&redir_dst, &v)?;
|
||||||
let mut redirect_out = BufWriter::new(redirect_out);
|
|
||||||
try_err!(layout::redirect(&mut redirect_out, file_name), &redir_dst);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// If the item is a macro, redirect from the old macro URL (with !)
|
// If the item is a macro, redirect from the old macro URL (with !)
|
||||||
// to the new one (without).
|
// to the new one (without).
|
||||||
if item_type == ItemType::Macro {
|
if item_type == ItemType::Macro {
|
||||||
let redir_name = format!("{}.{}!.html", item_type, name);
|
let redir_name = format!("{}.{}!.html", item_type, name);
|
||||||
let redir_dst = self.dst.join(redir_name);
|
let redir_dst = self.dst.join(redir_name);
|
||||||
let redirect_out = try_err!(File::create(&redir_dst), &redir_dst);
|
let mut v = Vec::new();
|
||||||
let mut redirect_out = BufWriter::new(redirect_out);
|
try_err!(layout::redirect(&mut v, file_name), &redir_dst);
|
||||||
try_err!(layout::redirect(&mut redirect_out, file_name), &redir_dst);
|
self.shared.fs.write(&redir_dst, &v)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ mod externalfiles;
|
|||||||
mod clean;
|
mod clean;
|
||||||
mod config;
|
mod config;
|
||||||
mod core;
|
mod core;
|
||||||
|
mod docfs;
|
||||||
mod doctree;
|
mod doctree;
|
||||||
mod fold;
|
mod fold;
|
||||||
pub mod html {
|
pub mod html {
|
||||||
|
|||||||
Reference in New Issue
Block a user