@@ -23,10 +23,13 @@ use rustc_data_structures::fx::FxHashMap;
2323use rustc_span:: edition:: Edition ;
2424use std:: borrow:: Cow ;
2525use std:: cell:: RefCell ;
26+ use std:: collections:: hash_map:: DefaultHasher ;
2627use std:: collections:: VecDeque ;
2728use std:: default:: Default ;
2829use std:: fmt:: Write ;
30+ use std:: hash:: { Hash , Hasher } ;
2931use std:: ops:: Range ;
32+ use std:: path:: { Path , PathBuf } ;
3033use std:: str;
3134
3235use crate :: html:: highlight;
@@ -44,25 +47,33 @@ fn opts() -> Options {
4447
4548/// When `to_string` is called, this struct will emit the HTML corresponding to
4649/// the rendered version of the contained markdown string.
47- pub struct Markdown < ' a > (
50+ pub struct Markdown < ' a , ' b > (
4851 pub & ' a str ,
4952 /// A list of link replacements.
5053 pub & ' a [ ( String , String ) ] ,
5154 /// The current list of used header IDs.
5255 pub & ' a mut IdMap ,
5356 /// Whether to allow the use of explicit error codes in doctest lang strings.
5457 pub ErrorCodes ,
55- /// Default edition to use when parsing doctests (to add a `fn main`).
58+ /// Default edition to use when parsing dcotests (to add a `fn main`).
5659 pub Edition ,
5760 pub & ' a Option < Playground > ,
61+ /// images_to_copy
62+ pub & ' b mut Vec < ( String , PathBuf ) > ,
63+ /// static_root_path
64+ pub & ' b Option < String > ,
5865) ;
5966/// A tuple struct like `Markdown` that renders the markdown with a table of contents.
60- pub struct MarkdownWithToc < ' a > (
67+ pub struct MarkdownWithToc < ' a , ' b > (
6168 pub & ' a str ,
6269 pub & ' a mut IdMap ,
6370 pub ErrorCodes ,
6471 pub Edition ,
6572 pub & ' a Option < Playground > ,
73+ /// images_to_copy
74+ pub & ' b mut Vec < ( String , PathBuf ) > ,
75+ /// static_root_path
76+ pub & ' b Option < String > ,
6677) ;
6778/// A tuple struct like `Markdown` that renders the markdown escaping HTML tags.
6879pub struct MarkdownHtml < ' a > (
@@ -550,6 +561,56 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for Footnotes<'a, I> {
550561 }
551562}
552563
564+ struct LocalImages < ' a , ' b , I : Iterator < Item = Event < ' a > > > {
565+ inner : I ,
566+ images_to_copy : & ' b mut Vec < ( String , PathBuf ) > ,
567+ static_root_path : & ' b Option < String > ,
568+ }
569+
570+ impl < ' a , ' b , I : Iterator < Item = Event < ' a > > > LocalImages < ' a , ' b , I > {
571+ fn new (
572+ iter : I ,
573+ images_to_copy : & ' b mut Vec < ( String , PathBuf ) > ,
574+ static_root_path : & ' b Option < String > ,
575+ ) -> Self {
576+ LocalImages { inner : iter, images_to_copy, static_root_path }
577+ }
578+ }
579+
580+ impl < ' a , ' b , I : Iterator < Item = Event < ' a > > > Iterator for LocalImages < ' a , ' b , I > {
581+ type Item = Event < ' a > ;
582+
583+ fn next ( & mut self ) -> Option < Self :: Item > {
584+ let event = self . inner . next ( ) ;
585+ if let Some ( Event :: Start ( Tag :: Image ( type_, ref url, ref title) ) ) = event {
586+ if url. starts_with ( "http://" ) || url. starts_with ( "https://" ) {
587+ // Not a local image, move on!
588+ }
589+ if let Ok ( url) = Path :: new ( & url. clone ( ) . into_string ( ) ) . canonicalize ( ) {
590+ let mut hasher = DefaultHasher :: new ( ) ;
591+ url. hash ( & mut hasher) ;
592+ let hash = format ! ( "{:x}" , hasher. finish( ) ) ;
593+ let static_folder_path = format ! ( "static/{}" , hash) ;
594+ if self . images_to_copy . iter ( ) . find ( |( h, _) | * h == hash) . is_none ( ) {
595+ self . images_to_copy . push ( ( hash, url) ) ;
596+ }
597+ return Some ( match self . static_root_path {
598+ Some ( p) => {
599+ let s = format ! ( "../{}" , Path :: new( p) . join( & static_folder_path) . display( ) ) ;
600+ Event :: Start ( Tag :: Image ( type_, CowStr :: Boxed ( s. into ( ) ) , title. clone ( ) ) )
601+ }
602+ None => Event :: Start ( Tag :: Image (
603+ type_,
604+ CowStr :: Boxed ( format ! ( "../{}" , static_folder_path) . into ( ) ) ,
605+ title. clone ( ) ,
606+ ) ) ,
607+ } ) ;
608+ }
609+ }
610+ event
611+ }
612+ }
613+
553614pub fn find_testable_code < T : test:: Tester > (
554615 doc : & str ,
555616 tests : & mut T ,
@@ -720,9 +781,18 @@ impl LangString {
720781 }
721782}
722783
723- impl Markdown < ' _ > {
784+ impl Markdown < ' _ , ' _ > {
724785 pub fn to_string ( self ) -> String {
725- let Markdown ( md, links, mut ids, codes, edition, playground) = self ;
786+ let Markdown (
787+ md,
788+ links,
789+ mut ids,
790+ codes,
791+ edition,
792+ playground,
793+ images_to_copy,
794+ static_root_path,
795+ ) = self ;
726796
727797 // This is actually common enough to special-case
728798 if md. is_empty ( ) {
@@ -742,6 +812,7 @@ impl Markdown<'_> {
742812
743813 let p = HeadingLinks :: new ( p, None , & mut ids) ;
744814 let p = LinkReplacer :: new ( p, links) ;
815+ let p = LocalImages :: new ( p, images_to_copy, static_root_path) ;
745816 let p = CodeBlocks :: new ( p, codes, edition, playground) ;
746817 let p = Footnotes :: new ( p) ;
747818 html:: push_html ( & mut s, p) ;
@@ -750,9 +821,17 @@ impl Markdown<'_> {
750821 }
751822}
752823
753- impl MarkdownWithToc < ' _ > {
824+ impl MarkdownWithToc < ' _ , ' _ > {
754825 pub fn to_string ( self ) -> String {
755- let MarkdownWithToc ( md, mut ids, codes, edition, playground) = self ;
826+ let MarkdownWithToc (
827+ md,
828+ mut ids,
829+ codes,
830+ edition,
831+ playground,
832+ images_to_copy,
833+ static_root_path,
834+ ) = self ;
756835
757836 let p = Parser :: new_ext ( md, opts ( ) ) ;
758837
@@ -762,6 +841,7 @@ impl MarkdownWithToc<'_> {
762841
763842 {
764843 let p = HeadingLinks :: new ( p, Some ( & mut toc) , & mut ids) ;
844+ let p = LocalImages :: new ( p, images_to_copy, static_root_path) ;
765845 let p = CodeBlocks :: new ( p, codes, edition, playground) ;
766846 let p = Footnotes :: new ( p) ;
767847 html:: push_html ( & mut s, p) ;
0 commit comments