@@ -23,10 +23,13 @@ use rustc_data_structures::fx::FxHashMap;
23
23
use rustc_span:: edition:: Edition ;
24
24
use std:: borrow:: Cow ;
25
25
use std:: cell:: RefCell ;
26
+ use std:: collections:: hash_map:: DefaultHasher ;
26
27
use std:: collections:: VecDeque ;
27
28
use std:: default:: Default ;
28
29
use std:: fmt:: Write ;
30
+ use std:: hash:: { Hash , Hasher } ;
29
31
use std:: ops:: Range ;
32
+ use std:: path:: { Path , PathBuf } ;
30
33
use std:: str;
31
34
32
35
use crate :: html:: highlight;
@@ -44,25 +47,33 @@ fn opts() -> Options {
44
47
45
48
/// When `to_string` is called, this struct will emit the HTML corresponding to
46
49
/// the rendered version of the contained markdown string.
47
- pub struct Markdown < ' a > (
50
+ pub struct Markdown < ' a , ' b > (
48
51
pub & ' a str ,
49
52
/// A list of link replacements.
50
53
pub & ' a [ ( String , String ) ] ,
51
54
/// The current list of used header IDs.
52
55
pub & ' a mut IdMap ,
53
56
/// Whether to allow the use of explicit error codes in doctest lang strings.
54
57
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`).
56
59
pub Edition ,
57
60
pub & ' a Option < Playground > ,
61
+ /// images_to_copy
62
+ pub & ' b mut Vec < ( String , PathBuf ) > ,
63
+ /// static_root_path
64
+ pub & ' b Option < String > ,
58
65
) ;
59
66
/// 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 > (
61
68
pub & ' a str ,
62
69
pub & ' a mut IdMap ,
63
70
pub ErrorCodes ,
64
71
pub Edition ,
65
72
pub & ' a Option < Playground > ,
73
+ /// images_to_copy
74
+ pub & ' b mut Vec < ( String , PathBuf ) > ,
75
+ /// static_root_path
76
+ pub & ' b Option < String > ,
66
77
) ;
67
78
/// A tuple struct like `Markdown` that renders the markdown escaping HTML tags.
68
79
pub struct MarkdownHtml < ' a > (
@@ -550,6 +561,56 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for Footnotes<'a, I> {
550
561
}
551
562
}
552
563
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
+
553
614
pub fn find_testable_code < T : test:: Tester > (
554
615
doc : & str ,
555
616
tests : & mut T ,
@@ -720,9 +781,18 @@ impl LangString {
720
781
}
721
782
}
722
783
723
- impl Markdown < ' _ > {
784
+ impl Markdown < ' _ , ' _ > {
724
785
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 ;
726
796
727
797
// This is actually common enough to special-case
728
798
if md. is_empty ( ) {
@@ -742,6 +812,7 @@ impl Markdown<'_> {
742
812
743
813
let p = HeadingLinks :: new ( p, None , & mut ids) ;
744
814
let p = LinkReplacer :: new ( p, links) ;
815
+ let p = LocalImages :: new ( p, images_to_copy, static_root_path) ;
745
816
let p = CodeBlocks :: new ( p, codes, edition, playground) ;
746
817
let p = Footnotes :: new ( p) ;
747
818
html:: push_html ( & mut s, p) ;
@@ -750,9 +821,17 @@ impl Markdown<'_> {
750
821
}
751
822
}
752
823
753
- impl MarkdownWithToc < ' _ > {
824
+ impl MarkdownWithToc < ' _ , ' _ > {
754
825
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 ;
756
835
757
836
let p = Parser :: new_ext ( md, opts ( ) ) ;
758
837
@@ -762,6 +841,7 @@ impl MarkdownWithToc<'_> {
762
841
763
842
{
764
843
let p = HeadingLinks :: new ( p, Some ( & mut toc) , & mut ids) ;
844
+ let p = LocalImages :: new ( p, images_to_copy, static_root_path) ;
765
845
let p = CodeBlocks :: new ( p, codes, edition, playground) ;
766
846
let p = Footnotes :: new ( p) ;
767
847
html:: push_html ( & mut s, p) ;
0 commit comments