@@ -63,14 +63,16 @@ impl HtmlHandlebars {
63
63
debug ! ( "[*]: Render template" ) ;
64
64
let rendered = ctx. handlebars . render ( "index" , & ctx. data ) ?;
65
65
66
- let filename = Path :: new ( & ch. path ) . with_extension ( "html" ) ;
66
+ let filepath = Path :: new ( & ch. path ) . with_extension ( "html" ) ;
67
67
let rendered = self . post_process ( rendered,
68
- filename. file_name ( ) . unwrap ( ) . to_str ( ) . unwrap_or ( "" ) ,
69
- ctx. book . get_html_config ( ) . get_playpen_config ( ) ) ;
68
+ & normalize_path ( filepath. to_str ( )
69
+ . ok_or ( Error :: from ( format ! ( "Bad file name: {}" , filepath. display( ) ) ) ) ?) ,
70
+ ctx. book . get_html_config ( ) . get_playpen_config ( )
71
+ ) ;
70
72
71
73
// Write to file
72
- info ! ( "[*] Creating {:?} ✓" , filename . display( ) ) ;
73
- ctx. book . write_file ( filename , & rendered. into_bytes ( ) ) ?;
74
+ info ! ( "[*] Creating {:?} ✓" , filepath . display( ) ) ;
75
+ ctx. book . write_file ( filepath , & rendered. into_bytes ( ) ) ?;
74
76
75
77
if ctx. is_index {
76
78
self . render_index ( ctx. book , ch, & ctx. destination ) ?;
@@ -111,9 +113,9 @@ impl HtmlHandlebars {
111
113
Ok ( ( ) )
112
114
}
113
115
114
- fn post_process ( & self , rendered : String , filename : & str , playpen_config : & PlaypenConfig ) -> String {
115
- let rendered = build_header_links ( & rendered, filename ) ;
116
- let rendered = fix_anchor_links ( & rendered, filename ) ;
116
+ fn post_process ( & self , rendered : String , filepath : & str , playpen_config : & PlaypenConfig ) -> String {
117
+ let rendered = build_header_links ( & rendered, & filepath ) ;
118
+ let rendered = fix_anchor_links ( & rendered, & filepath ) ;
117
119
let rendered = fix_code_blocks ( & rendered) ;
118
120
let rendered = add_playpen_pre ( & rendered, playpen_config) ;
119
121
@@ -182,7 +184,7 @@ impl HtmlHandlebars {
182
184
Ok ( ( ) )
183
185
}
184
186
185
- /// Helper function to write a file to the build directory, normalizing
187
+ /// Helper function to write a file to the build directory, normalizing
186
188
/// the path to be relative to the book root.
187
189
fn write_custom_file ( & self , custom_file : & Path , book : & MDBook ) -> Result < ( ) > {
188
190
let mut data = Vec :: new ( ) ;
@@ -284,7 +286,7 @@ impl Renderer for HtmlHandlebars {
284
286
285
287
let rendered = self . post_process ( rendered, "print.html" ,
286
288
book. get_html_config ( ) . get_playpen_config ( ) ) ;
287
-
289
+
288
290
book. write_file (
289
291
Path :: new ( "print" ) . with_extension ( "html" ) ,
290
292
& rendered. into_bytes ( ) ,
@@ -412,7 +414,7 @@ fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>
412
414
413
415
/// Goes through the rendered HTML, making sure all header tags are wrapped in
414
416
/// an anchor so people can link to sections directly.
415
- fn build_header_links ( html : & str , filename : & str ) -> String {
417
+ fn build_header_links ( html : & str , filepath : & str ) -> String {
416
418
let regex = Regex :: new ( r"<h(\d)>(.*?)</h\d>" ) . unwrap ( ) ;
417
419
let mut id_counter = HashMap :: new ( ) ;
418
420
@@ -422,14 +424,14 @@ fn build_header_links(html: &str, filename: &str) -> String {
422
424
"Regex should ensure we only ever get numbers here" ,
423
425
) ;
424
426
425
- wrap_header_with_link ( level, & caps[ 2 ] , & mut id_counter, filename )
427
+ wrap_header_with_link ( level, & caps[ 2 ] , & mut id_counter, filepath )
426
428
} )
427
429
. into_owned ( )
428
430
}
429
431
430
432
/// Wraps a single header tag with a link, making sure each tag gets its own
431
433
/// unique ID by appending an auto-incremented number (if necessary).
432
- fn wrap_header_with_link ( level : usize , content : & str , id_counter : & mut HashMap < String , usize > , filename : & str )
434
+ fn wrap_header_with_link ( level : usize , content : & str , id_counter : & mut HashMap < String , usize > , filepath : & str )
433
435
-> String {
434
436
let raw_id = id_from_content ( content) ;
435
437
@@ -443,11 +445,11 @@ fn wrap_header_with_link(level: usize, content: &str, id_counter: &mut HashMap<S
443
445
* id_count += 1 ;
444
446
445
447
format ! (
446
- r#"<a class="header" href="{filename }#{id}" id="{id}"><h{level}>{text}</h{level}></a>"# ,
448
+ r## "<a class="header" href="{filepath }#{id}" id="{id}"><h{level}>{text}</h{level}></a>"# # ,
447
449
level = level,
448
450
id = id,
449
451
text = content,
450
- filename = filename
452
+ filepath = filepath
451
453
)
452
454
}
453
455
@@ -457,7 +459,7 @@ fn id_from_content(content: &str) -> String {
457
459
let mut content = content. to_string ( ) ;
458
460
459
461
// Skip any tags or html-encoded stuff
460
- let repl_sub = vec ! [
462
+ const REPL_SUB : & [ & str ] = & [
461
463
"<em>" ,
462
464
"</em>" ,
463
465
"<code>" ,
@@ -470,27 +472,17 @@ fn id_from_content(content: &str) -> String {
470
472
"'" ,
471
473
""" ,
472
474
] ;
473
- for sub in repl_sub {
475
+ for sub in REPL_SUB {
474
476
content = content. replace ( sub, "" ) ;
475
477
}
476
478
477
- let mut id = String :: new ( ) ;
478
-
479
- for c in content. chars ( ) {
480
- if c. is_alphanumeric ( ) || c == '-' || c == '_' {
481
- id. push ( c. to_ascii_lowercase ( ) ) ;
482
- } else if c. is_whitespace ( ) {
483
- id. push ( c) ;
484
- }
485
- }
486
-
487
- id
479
+ normalize_id ( & content)
488
480
}
489
481
490
482
// anchors to the same page (href="#anchor") do not work because of
491
483
// <base href="../"> pointing to the root folder. This function *fixes*
492
484
// that in a very inelegant way
493
- fn fix_anchor_links ( html : & str , filename : & str ) -> String {
485
+ fn fix_anchor_links ( html : & str , filepath : & str ) -> String {
494
486
let regex = Regex :: new ( r##"<a([^>]+)href="#([^"]+)"([^>]*)>"## ) . unwrap ( ) ;
495
487
regex
496
488
. replace_all ( html, |caps : & Captures | {
@@ -499,9 +491,9 @@ fn fix_anchor_links(html: &str, filename: &str) -> String {
499
491
let after = & caps[ 3 ] ;
500
492
501
493
format ! (
502
- "<a{before}href=\" {filename }#{anchor}\" {after}>" ,
494
+ "<a{before}href=\" {filepath }#{anchor}\" {after}>" ,
503
495
before = before,
504
- filename = filename ,
496
+ filepath = filepath ,
505
497
anchor = anchor,
506
498
after = after
507
499
)
@@ -592,6 +584,26 @@ struct RenderItemContext<'a> {
592
584
is_index : bool ,
593
585
}
594
586
587
+ pub fn normalize_path ( path : & str ) -> String {
588
+ use std:: path:: is_separator;
589
+ path. chars ( )
590
+ . map ( |ch| if is_separator ( ch) { '/' } else { ch } )
591
+ . collect :: < String > ( )
592
+ }
593
+
594
+ pub fn normalize_id ( content : & str ) -> String {
595
+ content. chars ( )
596
+ . filter_map ( |ch|
597
+ if ch. is_alphanumeric ( ) || ch == '_' {
598
+ Some ( ch. to_ascii_lowercase ( ) )
599
+ } else if ch. is_whitespace ( ) {
600
+ Some ( '-' )
601
+ } else {
602
+ None
603
+ }
604
+ )
605
+ . collect :: < String > ( )
606
+ }
595
607
596
608
597
609
#[ cfg( test) ]
@@ -601,17 +613,39 @@ mod tests {
601
613
#[ test]
602
614
fn original_build_header_links ( ) {
603
615
let inputs = vec ! [
604
- ( "blah blah <h1>Foo</h1>" , r#"blah blah <a class="header" href="bar.rs#foo" id="foo"><h1>Foo</h1></a>"# ) ,
605
- ( "<h1>Foo</h1>" , r#"<a class="header" href="bar.rs#foo" id="foo"><h1>Foo</h1></a>"# ) ,
606
- ( "<h3>Foo^bar</h3>" , r#"<a class="header" href="bar.rs#foobar" id="foobar"><h3>Foo^bar</h3></a>"# ) ,
607
- ( "<h4></h4>" , r#"<a class="header" href="bar.rs#" id=""><h4></h4></a>"# ) ,
608
- ( "<h4><em>Hï</em></h4>" , r#"<a class="header" href="bar.rs#hï" id="hï"><h4><em>Hï</em></h4></a>"# ) ,
609
- ( "<h1>Foo</h1><h3>Foo</h3>" ,
610
- r#"<a class="header" href="bar.rs#foo" id="foo"><h1>Foo</h1></a><a class="header" href="bar.rs#foo-1" id="foo-1"><h3>Foo</h3></a>"# ) ,
616
+ (
617
+ "blah blah <h1>Foo</h1>" ,
618
+ r##"blah blah <a class="header" href="./some_chapter/some_section.html#foo" id="foo"><h1>Foo</h1></a>"## ,
619
+ ) ,
620
+ (
621
+ "<h1>Foo</h1>" ,
622
+ r##"<a class="header" href="./some_chapter/some_section.html#foo" id="foo"><h1>Foo</h1></a>"## ,
623
+ ) ,
624
+ (
625
+ "<h3>Foo^bar</h3>" ,
626
+ r##"<a class="header" href="./some_chapter/some_section.html#foobar" id="foobar"><h3>Foo^bar</h3></a>"## ,
627
+ ) ,
628
+ (
629
+ "<h4></h4>" ,
630
+ r##"<a class="header" href="./some_chapter/some_section.html#" id=""><h4></h4></a>"##
631
+ ) ,
632
+ (
633
+ "<h4><em>Hï</em></h4>" ,
634
+ r##"<a class="header" href="./some_chapter/some_section.html#hï" id="hï"><h4><em>Hï</em></h4></a>"##
635
+ ) ,
636
+ (
637
+ "<h1>Foo</h1><h3>Foo</h3>" ,
638
+ r##"<a class="header" href="./some_chapter/some_section.html#foo" id="foo"><h1>Foo</h1></a><a class="header" href="./some_chapter/some_section.html#foo-1" id="foo-1"><h3>Foo</h3></a>"##
639
+ ) ,
611
640
] ;
612
641
613
642
for ( src, should_be) in inputs {
614
- let got = build_header_links ( src, "bar.rs" ) ;
643
+ let filepath = "./some_chapter/some_section.html" ;
644
+ let got = build_header_links ( & src, filepath) ;
645
+ assert_eq ! ( got, should_be) ;
646
+
647
+ // This is redundant for most cases
648
+ let got = fix_anchor_links ( & got, filepath) ;
615
649
assert_eq ! ( got, should_be) ;
616
650
}
617
651
}
0 commit comments