Skip to content

Rustdoc copy local img #68734

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/librustdoc/externalfiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,24 @@ impl ExternalHtml {
edition: Edition,
playground: &Option<Playground>,
) -> Option<ExternalHtml> {
let mut images_to_copy = Vec::new();
let codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());
let ih = load_external_files(in_header, diag)?;
let bc = load_external_files(before_content, diag)?;
let m_bc = load_external_files(md_before_content, diag)?;
let bc = format!(
"{}{}",
bc,
Markdown(&m_bc, &[], id_map, codes, edition, playground).to_string()
Markdown(&m_bc, &[], id_map, codes, edition, playground, &mut images_to_copy, &None)
.to_string()
);
let ac = load_external_files(after_content, diag)?;
let m_ac = load_external_files(md_after_content, diag)?;
let ac = format!(
"{}{}",
ac,
Markdown(&m_ac, &[], id_map, codes, edition, playground).to_string()
Markdown(&m_ac, &[], id_map, codes, edition, playground, &mut images_to_copy, &None)
.to_string()
);
Some(ExternalHtml { in_header: ih, before_content: bc, after_content: ac })
}
Expand Down
94 changes: 87 additions & 7 deletions src/librustdoc/html/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ use rustc_data_structures::fx::FxHashMap;
use rustc_span::edition::Edition;
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::hash_map::DefaultHasher;
use std::collections::VecDeque;
use std::default::Default;
use std::fmt::Write;
use std::hash::{Hash, Hasher};
use std::ops::Range;
use std::path::{Path, PathBuf};
use std::str;

use crate::html::highlight;
Expand All @@ -44,25 +47,33 @@ fn opts() -> Options {

/// When `to_string` is called, this struct will emit the HTML corresponding to
/// the rendered version of the contained markdown string.
pub struct Markdown<'a>(
pub struct Markdown<'a, 'b>(
pub &'a str,
/// A list of link replacements.
pub &'a [(String, String)],
/// The current list of used header IDs.
pub &'a mut IdMap,
/// Whether to allow the use of explicit error codes in doctest lang strings.
pub ErrorCodes,
/// Default edition to use when parsing doctests (to add a `fn main`).
/// Default edition to use when parsing dcotests (to add a `fn main`).
pub Edition,
pub &'a Option<Playground>,
/// images_to_copy
pub &'b mut Vec<(String, PathBuf)>,
/// static_root_path
pub &'b Option<String>,
);
/// A tuple struct like `Markdown` that renders the markdown with a table of contents.
pub struct MarkdownWithToc<'a>(
pub struct MarkdownWithToc<'a, 'b>(
pub &'a str,
pub &'a mut IdMap,
pub ErrorCodes,
pub Edition,
pub &'a Option<Playground>,
/// images_to_copy
pub &'b mut Vec<(String, PathBuf)>,
/// static_root_path
pub &'b Option<String>,
);
/// A tuple struct like `Markdown` that renders the markdown escaping HTML tags.
pub struct MarkdownHtml<'a>(
Expand Down Expand Up @@ -550,6 +561,56 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for Footnotes<'a, I> {
}
}

struct LocalImages<'a, 'b, I: Iterator<Item = Event<'a>>> {
inner: I,
images_to_copy: &'b mut Vec<(String, PathBuf)>,
static_root_path: &'b Option<String>,
}

impl<'a, 'b, I: Iterator<Item = Event<'a>>> LocalImages<'a, 'b, I> {
fn new(
iter: I,
images_to_copy: &'b mut Vec<(String, PathBuf)>,
static_root_path: &'b Option<String>,
) -> Self {
LocalImages { inner: iter, images_to_copy, static_root_path }
}
}

impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for LocalImages<'a, 'b, I> {
type Item = Event<'a>;

fn next(&mut self) -> Option<Self::Item> {
let event = self.inner.next();
if let Some(Event::Start(Tag::Image(type_, ref url, ref title))) = event {
if url.starts_with("http://") || url.starts_with("https://") {
// Not a local image, move on!
}
if let Ok(url) = Path::new(&url.clone().into_string()).canonicalize() {
let mut hasher = DefaultHasher::new();
url.hash(&mut hasher);
let hash = format!("{:x}", hasher.finish());
let static_folder_path = format!("static/{}", hash);
if self.images_to_copy.iter().find(|(h, _)| *h == hash).is_none() {
self.images_to_copy.push((hash, url));
}
return Some(match self.static_root_path {
Some(p) => {
let s = format!("../{}", Path::new(p).join(&static_folder_path).display());
Event::Start(Tag::Image(type_, CowStr::Boxed(s.into()), title.clone()))
}
None => Event::Start(Tag::Image(
type_,
CowStr::Boxed(format!("../{}", static_folder_path).into()),
title.clone(),
)),
});
}
}
event
}
}

pub fn find_testable_code<T: test::Tester>(
doc: &str,
tests: &mut T,
Expand Down Expand Up @@ -720,9 +781,18 @@ impl LangString {
}
}

impl Markdown<'_> {
impl Markdown<'_, '_> {
pub fn to_string(self) -> String {
let Markdown(md, links, mut ids, codes, edition, playground) = self;
let Markdown(
md,
links,
mut ids,
codes,
edition,
playground,
images_to_copy,
static_root_path,
) = self;

// This is actually common enough to special-case
if md.is_empty() {
Expand All @@ -742,6 +812,7 @@ impl Markdown<'_> {

let p = HeadingLinks::new(p, None, &mut ids);
let p = LinkReplacer::new(p, links);
let p = LocalImages::new(p, images_to_copy, static_root_path);
let p = CodeBlocks::new(p, codes, edition, playground);
let p = Footnotes::new(p);
html::push_html(&mut s, p);
Expand All @@ -750,9 +821,17 @@ impl Markdown<'_> {
}
}

impl MarkdownWithToc<'_> {
impl MarkdownWithToc<'_, '_> {
pub fn to_string(self) -> String {
let MarkdownWithToc(md, mut ids, codes, edition, playground) = self;
let MarkdownWithToc(
md,
mut ids,
codes,
edition,
playground,
images_to_copy,
static_root_path,
) = self;

let p = Parser::new_ext(md, opts());

Expand All @@ -762,6 +841,7 @@ impl MarkdownWithToc<'_> {

{
let p = HeadingLinks::new(p, Some(&mut toc), &mut ids);
let p = LocalImages::new(p, images_to_copy, static_root_path);
let p = CodeBlocks::new(p, codes, edition, playground);
let p = Footnotes::new(p);
html::push_html(&mut s, p);
Expand Down
20 changes: 17 additions & 3 deletions src/librustdoc/html/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ use crate::html::item_type::ItemType;
use crate::html::markdown::{self, ErrorCodes, IdMap, Markdown, MarkdownHtml, MarkdownSummaryLine};
use crate::html::sources;
use crate::html::{highlight, layout, static_files};
use crate::markdown::generate_static_images;

use minifier;

Expand Down Expand Up @@ -203,6 +204,10 @@ crate struct SharedContext {
pub edition: Edition,
pub codes: ErrorCodes,
playground: Option<markdown::Playground>,
/// Local images to move into the static folder.
///
/// The tuple contains the hash as first argument and the image original path.
pub images_to_copy: RefCell<Vec<(String, PathBuf)>>,
}

impl Context {
Expand Down Expand Up @@ -482,9 +487,10 @@ pub fn run(
edition,
codes: ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build()),
playground,
images_to_copy: RefCell::new(Vec::new()),
};

let dst = output;
let dst = output.clone();
scx.ensure_dir(&dst)?;
krate = sources::render(&dst, &mut scx, krate)?;
let (new_crate, index, cache) =
Expand Down Expand Up @@ -1349,6 +1355,8 @@ impl Context {
);
self.shared.fs.write(&settings_file, v.as_bytes())?;

generate_static_images(&self.dst, &*self.shared.images_to_copy.borrow());

Ok(())
}

Expand Down Expand Up @@ -1801,6 +1809,7 @@ fn render_markdown(
is_hidden: bool,
) {
let mut ids = cx.id_map.borrow_mut();
let mut images_to_copy = cx.shared.images_to_copy.borrow_mut();
write!(
w,
"<div class='docblock{}'>{}{}</div>",
Expand All @@ -1812,7 +1821,9 @@ fn render_markdown(
&mut ids,
cx.shared.codes,
cx.shared.edition,
&cx.shared.playground
&cx.shared.playground,
&mut images_to_copy,
&cx.shared.static_root_path,
)
.to_string()
)
Expand Down Expand Up @@ -3660,6 +3671,7 @@ fn render_impl(
write!(w, "</h3>");
if let Some(ref dox) = cx.shared.maybe_collapsed_doc_value(&i.impl_item) {
let mut ids = cx.id_map.borrow_mut();
let mut images_to_copy = cx.shared.images_to_copy.borrow_mut();
write!(
w,
"<div class='docblock'>{}</div>",
Expand All @@ -3669,7 +3681,9 @@ fn render_impl(
&mut ids,
cx.shared.codes,
cx.shared.edition,
&cx.shared.playground
&cx.shared.playground,
&mut images_to_copy,
&cx.shared.static_root_path,
)
.to_string()
);
Expand Down
41 changes: 36 additions & 5 deletions src/librustdoc/markdown.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::fs::File;
use std::fs::{self, File};
use std::io::prelude::*;
use std::path::PathBuf;
use std::path::{Path, PathBuf};

use rustc_feature::UnstableFeatures;
use rustc_span::edition::Edition;
Expand Down Expand Up @@ -76,10 +76,21 @@ pub fn render(

let mut ids = IdMap::new();
let error_codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());
let mut images_to_copy = Vec::new();
let text = if !options.markdown_no_toc {
MarkdownWithToc(text, &mut ids, error_codes, edition, &playground).to_string()
MarkdownWithToc(
text,
&mut ids,
error_codes,
edition,
&playground,
&mut images_to_copy,
&None,
)
.to_string()
} else {
Markdown(text, &[], &mut ids, error_codes, edition, &playground).to_string()
Markdown(text, &[], &mut ids, error_codes, edition, &playground, &mut images_to_copy, &None)
.to_string()
};

let err = write!(
Expand Down Expand Up @@ -122,7 +133,27 @@ pub fn render(
diag.struct_err(&format!("cannot write to `{}`: {}", output.display(), e)).emit();
6
}
Ok(_) => 0,
Ok(_) => {
generate_static_images(&output, &images_to_copy);
0
}
}
}

pub fn generate_static_images<P: AsRef<Path>>(target_dir: P, images_to_copy: &[(String, PathBuf)]) {
if images_to_copy.is_empty() {
return;
}
let target_dir = target_dir.as_ref().join("static");
let _ = fs::create_dir(&target_dir);
for (hash, image_to_copy) in images_to_copy {
if fs::copy(image_to_copy, target_dir.join(hash)).is_err() {
eprintln!(
"Couldn't copy `{}` into `{}`...",
image_to_copy.display(),
target_dir.display()
);
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/test/rustdoc/copy-local-img.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#![crate_name = "foo"]

// @has static/8a40d4987fbb905
// @has foo/struct.Enum.html
// @has - '//img[@src="../static/8a40d4987fbb905"]' ''
Comment on lines +3 to +5
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fear these hashes won't be consistent between systems now that the filename is canonicalised. Is there a cut-down test which will still work here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, didn't think about that, the hash will fail indeed. Great catch! I can add something to count files in the given folder and add a "start with" thing. Unless you have another idea?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One option could indeed be that. Another -- hash the file as part of the deduplication effort, but then simply number them as you add them to the static/ dir?


/// Image test!
///
/// ![osef](src/test/rustdoc/copy-local-img.rs)
pub struct Enum;