2019-05-26 01:50:41 +07:00
use crate ::book ::{ Book , BookItem } ;
2019-11-18 01:03:14 +03:00
use crate ::config ::{ Config , HtmlConfig , Playpen , RustEdition } ;
2019-05-26 01:50:41 +07:00
use crate ::errors ::* ;
use crate ::renderer ::html_handlebars ::helpers ;
use crate ::renderer ::{ RenderContext , Renderer } ;
use crate ::theme ::{ self , playpen_editor , Theme } ;
use crate ::utils ;
2015-08-04 15:15:36 +02:00
2019-10-16 11:27:14 +02:00
use std ::borrow ::Cow ;
2015-08-04 15:15:36 +02:00
use std ::collections ::BTreeMap ;
2017-03-23 23:24:26 +05:30
use std ::collections ::HashMap ;
2020-05-27 02:23:36 +08:00
use std ::fs ::{ self , File } ;
2018-03-07 07:02:06 -06:00
use std ::path ::{ Path , PathBuf } ;
2015-08-04 15:15:36 +02:00
2016-08-01 14:06:08 +02:00
use handlebars ::Handlebars ;
2018-03-07 07:02:06 -06:00
use regex ::{ Captures , Regex } ;
2015-12-30 00:46:55 +01:00
2017-02-15 22:01:26 -05:00
#[ derive(Default) ]
2015-08-04 15:15:36 +02:00
pub struct HtmlHandlebars ;
2015-08-04 16:52:10 +02:00
impl HtmlHandlebars {
pub fn new ( ) -> Self {
HtmlHandlebars
}
2017-06-15 17:43:44 +08:00
2018-01-07 22:10:48 +08:00
fn render_item (
& self ,
2018-03-07 07:02:06 -06:00
item : & BookItem ,
2019-05-07 03:50:34 +07:00
mut ctx : RenderItemContext < '_ > ,
2018-01-07 22:10:48 +08:00
print_content : & mut String ,
) -> Result < ( ) > {
2017-06-17 21:15:54 +08:00
// FIXME: This should be made DRY-er and rely less on mutable state
2020-03-09 22:34:28 +01:00
let ( ch , path ) = match item {
BookItem ::Chapter ( ch ) if ! ch . is_draft_chapter ( ) = > ( ch , ch . path . as_ref ( ) . unwrap ( ) ) ,
_ = > return Ok ( ( ) ) ,
} ;
let content = ch . content . clone ( ) ;
let content = utils ::render_markdown ( & content , ctx . html_config . curly_quotes ) ;
let fixed_content = utils ::render_markdown_with_path (
& ch . content ,
ctx . html_config . curly_quotes ,
Some ( & path ) ,
) ;
print_content . push_str ( & fixed_content ) ;
// Update the context with data for this file
let ctx_path = path
. to_str ( )
2020-05-20 14:32:00 -07:00
. with_context ( | | " Could not convert path to str " ) ? ;
2020-03-09 22:34:28 +01:00
let filepath = Path ::new ( & ctx_path ) . with_extension ( " html " ) ;
// "print.html" is used for the print page.
if path = = Path ::new ( " print.md " ) {
2020-05-20 14:32:00 -07:00
bail! ( " {} is reserved for internal use " , path . display ( ) ) ;
2020-03-09 22:34:28 +01:00
} ;
2020-05-10 09:23:40 -07:00
let book_title = ctx
. data
. get ( " book_title " )
. and_then ( serde_json ::Value ::as_str )
. unwrap_or ( " " ) ;
2020-03-09 22:34:28 +01:00
2020-05-10 09:23:40 -07:00
let title = match book_title {
" " = > ch . name . clone ( ) ,
_ = > ch . name . clone ( ) + " - " + book_title ,
} ;
2017-06-15 17:43:44 +08:00
2020-03-09 22:34:28 +01:00
ctx . data . insert ( " path " . to_owned ( ) , json! ( path ) ) ;
ctx . data . insert ( " content " . to_owned ( ) , json! ( content ) ) ;
ctx . data . insert ( " chapter_title " . to_owned ( ) , json! ( ch . name ) ) ;
ctx . data . insert ( " title " . to_owned ( ) , json! ( title ) ) ;
ctx . data . insert (
" path_to_root " . to_owned ( ) ,
json! ( utils ::fs ::path_to_root ( & path ) ) ,
) ;
if let Some ( ref section ) = ch . number {
ctx . data
. insert ( " section " . to_owned ( ) , json! ( section . to_string ( ) ) ) ;
}
// Render the handlebars template with the data
debug! ( " Render template " ) ;
let rendered = ctx . handlebars . render ( " index " , & ctx . data ) ? ;
let rendered = self . post_process ( rendered , & ctx . html_config . playpen , ctx . edition ) ;
// Write to file
debug! ( " Creating {} " , filepath . display ( ) ) ;
utils ::fs ::write_file ( & ctx . destination , & filepath , rendered . as_bytes ( ) ) ? ;
if ctx . is_index {
ctx . data . insert ( " path " . to_owned ( ) , json! ( " index.md " ) ) ;
ctx . data . insert ( " path_to_root " . to_owned ( ) , json! ( " " ) ) ;
ctx . data . insert ( " is_index " . to_owned ( ) , json! ( " true " ) ) ;
let rendered_index = ctx . handlebars . render ( " index " , & ctx . data ) ? ;
let rendered_index =
self . post_process ( rendered_index , & ctx . html_config . playpen , ctx . edition ) ;
debug! ( " Creating index.html from {} " , ctx_path ) ;
utils ::fs ::write_file ( & ctx . destination , " index.html " , rendered_index . as_bytes ( ) ) ? ;
}
2017-06-15 17:43:44 +08:00
Ok ( ( ) )
}
2017-06-15 18:03:10 +08:00
2018-12-04 00:10:09 +01:00
#[ cfg_attr(feature = " cargo-clippy " , allow(clippy::let_and_return)) ]
2019-11-18 01:03:14 +03:00
fn post_process (
& self ,
rendered : String ,
playpen_config : & Playpen ,
edition : Option < RustEdition > ,
) -> String {
2018-07-11 23:33:44 +10:00
let rendered = build_header_links ( & rendered ) ;
2017-06-27 09:08:58 +02:00
let rendered = fix_code_blocks ( & rendered ) ;
2019-11-18 01:03:14 +03:00
let rendered = add_playpen_pre ( & rendered , playpen_config , edition ) ;
2017-06-15 18:03:10 +08:00
rendered
}
2018-01-07 22:10:48 +08:00
fn copy_static_files (
& self ,
destination : & Path ,
theme : & Theme ,
html_config : & HtmlConfig ,
) -> Result < ( ) > {
2019-05-26 01:50:41 +07:00
use crate ::utils ::fs ::write_file ;
2018-03-07 07:02:06 -06:00
2018-07-23 12:45:01 -05:00
write_file (
destination ,
" .nojekyll " ,
b " This file makes sure that Github Pages doesn't process mdBook's output. " ,
) ? ;
2018-05-15 12:23:08 -05:00
2018-03-07 07:02:06 -06:00
write_file ( destination , " book.js " , & theme . js ) ? ;
2018-07-25 15:51:09 -05:00
write_file ( destination , " css/general.css " , & theme . general_css ) ? ;
write_file ( destination , " css/chrome.css " , & theme . chrome_css ) ? ;
write_file ( destination , " css/print.css " , & theme . print_css ) ? ;
write_file ( destination , " css/variables.css " , & theme . variables_css ) ? ;
2018-03-07 07:02:06 -06:00
write_file ( destination , " favicon.png " , & theme . favicon ) ? ;
write_file ( destination , " highlight.css " , & theme . highlight_css ) ? ;
write_file ( destination , " tomorrow-night.css " , & theme . tomorrow_night_css ) ? ;
write_file ( destination , " ayu-highlight.css " , & theme . ayu_highlight_css ) ? ;
write_file ( destination , " highlight.js " , & theme . highlight_js ) ? ;
write_file ( destination , " clipboard.min.js " , & theme . clipboard_js ) ? ;
write_file (
2018-01-07 22:10:48 +08:00
destination ,
2018-05-15 12:18:02 -05:00
" FontAwesome/css/font-awesome.css " ,
2018-01-07 22:10:48 +08:00
theme ::FONT_AWESOME ,
) ? ;
2018-03-07 07:02:06 -06:00
write_file (
2018-01-07 22:10:48 +08:00
destination ,
2018-05-15 12:18:02 -05:00
" FontAwesome/fonts/fontawesome-webfont.eot " ,
2018-01-07 22:10:48 +08:00
theme ::FONT_AWESOME_EOT ,
) ? ;
2018-03-07 07:02:06 -06:00
write_file (
2018-01-07 22:10:48 +08:00
destination ,
2018-05-15 12:18:02 -05:00
" FontAwesome/fonts/fontawesome-webfont.svg " ,
2018-01-07 22:10:48 +08:00
theme ::FONT_AWESOME_SVG ,
) ? ;
2018-03-07 07:02:06 -06:00
write_file (
2018-01-07 22:10:48 +08:00
destination ,
2018-05-15 12:18:02 -05:00
" FontAwesome/fonts/fontawesome-webfont.ttf " ,
2018-01-07 22:10:48 +08:00
theme ::FONT_AWESOME_TTF ,
) ? ;
2018-03-07 07:02:06 -06:00
write_file (
2018-01-07 22:10:48 +08:00
destination ,
2018-05-15 12:18:02 -05:00
" FontAwesome/fonts/fontawesome-webfont.woff " ,
2018-01-07 22:10:48 +08:00
theme ::FONT_AWESOME_WOFF ,
) ? ;
2018-03-07 07:02:06 -06:00
write_file (
2018-01-07 22:10:48 +08:00
destination ,
2018-05-15 12:18:02 -05:00
" FontAwesome/fonts/fontawesome-webfont.woff2 " ,
2018-01-07 22:10:48 +08:00
theme ::FONT_AWESOME_WOFF2 ,
) ? ;
2018-03-07 07:02:06 -06:00
write_file (
2018-01-07 22:10:48 +08:00
destination ,
2018-05-15 12:18:02 -05:00
" FontAwesome/fonts/FontAwesome.ttf " ,
2018-01-07 22:10:48 +08:00
theme ::FONT_AWESOME_TTF ,
) ? ;
2020-05-19 03:09:25 -03:00
if html_config . copy_fonts {
2020-05-15 02:48:28 -03:00
write_file ( destination , " fonts/fonts.css " , theme ::fonts ::CSS ) ? ;
2020-05-19 03:17:01 -03:00
for ( file_name , contents ) in theme ::fonts ::LICENSES . iter ( ) {
write_file ( destination , file_name , contents ) ? ;
}
2020-05-15 02:48:28 -03:00
for ( file_name , contents ) in theme ::fonts ::OPEN_SANS . iter ( ) {
write_file ( destination , file_name , contents ) ? ;
}
write_file (
destination ,
theme ::fonts ::SOURCE_CODE_PRO . 0 ,
theme ::fonts ::SOURCE_CODE_PRO . 1 ,
) ? ;
}
2017-06-15 18:03:10 +08:00
2017-09-30 21:36:03 +08:00
let playpen_config = & html_config . playpen ;
2017-06-29 00:35:20 -04:00
// Ace is a very large dependency, so only load it when requested
2018-03-07 07:02:06 -06:00
if playpen_config . editable & & playpen_config . copy_js {
2017-06-29 00:35:20 -04:00
// Load the editor
2018-03-07 07:02:06 -06:00
write_file ( destination , " editor.js " , playpen_editor ::JS ) ? ;
write_file ( destination , " ace.js " , playpen_editor ::ACE_JS ) ? ;
write_file ( destination , " mode-rust.js " , playpen_editor ::MODE_RUST_JS ) ? ;
write_file ( destination , " theme-dawn.js " , playpen_editor ::THEME_DAWN_JS ) ? ;
2018-05-16 12:08:23 -05:00
write_file (
destination ,
2018-01-07 22:10:48 +08:00
" theme-tomorrow_night.js " ,
2018-03-07 07:02:06 -06:00
playpen_editor ::THEME_TOMORROW_NIGHT_JS ,
2018-01-07 22:10:48 +08:00
) ? ;
2017-06-29 00:35:20 -04:00
}
2017-06-15 18:03:10 +08:00
Ok ( ( ) )
}
/// Update the context with data for this file
2018-05-16 12:08:23 -05:00
fn configure_print_version (
& self ,
data : & mut serde_json ::Map < String , serde_json ::Value > ,
print_content : & str ,
) {
2017-09-01 08:20:27 +02:00
// Make sure that the Print chapter does not display the title from
// the last rendered chapter by removing it from its context
2017-09-07 23:19:22 +02:00
data . remove ( " title " ) ;
2017-09-13 22:17:23 +02:00
data . insert ( " is_print " . to_owned ( ) , json! ( true ) ) ;
2017-06-15 18:03:10 +08:00
data . insert ( " path " . to_owned ( ) , json! ( " print.md " ) ) ;
data . insert ( " content " . to_owned ( ) , json! ( print_content ) ) ;
2018-05-16 12:08:23 -05:00
data . insert (
" path_to_root " . to_owned ( ) ,
json! ( utils ::fs ::path_to_root ( Path ::new ( " print.md " ) ) ) ,
) ;
2017-06-15 18:03:10 +08:00
}
2020-01-24 11:01:44 +08:00
fn register_hbs_helpers ( & self , handlebars : & mut Handlebars < '_ > , html_config : & HtmlConfig ) {
2018-05-16 12:08:23 -05:00
handlebars . register_helper (
" toc " ,
Box ::new ( helpers ::toc ::RenderToc {
no_section_label : html_config . no_section_label ,
} ) ,
) ;
2017-06-15 18:03:10 +08:00
handlebars . register_helper ( " previous " , Box ::new ( helpers ::navigation ::previous ) ) ;
handlebars . register_helper ( " next " , Box ::new ( helpers ::navigation ::next ) ) ;
2018-10-13 14:44:10 +01:00
handlebars . register_helper ( " theme_option " , Box ::new ( helpers ::theme ::theme_option ) ) ;
2017-06-15 18:03:10 +08:00
}
2017-06-15 18:17:16 +08:00
2017-06-17 21:15:54 +08:00
/// Copy across any additional CSS and JavaScript files which the book
/// has been configured to use.
2018-05-16 12:08:23 -05:00
fn copy_additional_css_and_js (
& self ,
html : & HtmlConfig ,
root : & Path ,
destination : & Path ,
) -> Result < ( ) > {
2018-01-07 22:10:48 +08:00
let custom_files = html . additional_css . iter ( ) . chain ( html . additional_js . iter ( ) ) ;
2017-09-30 21:36:03 +08:00
2018-01-07 22:10:48 +08:00
debug! ( " Copying additional CSS and JS " ) ;
2017-06-17 21:15:54 +08:00
for custom_file in custom_files {
2018-01-29 23:29:09 -05:00
let input_location = root . join ( custom_file ) ;
2018-01-07 22:10:48 +08:00
let output_location = destination . join ( custom_file ) ;
2018-01-26 14:38:53 +08:00
if let Some ( parent ) = output_location . parent ( ) {
fs ::create_dir_all ( parent )
2020-05-20 14:32:00 -07:00
. with_context ( | | format! ( " Unable to create {} " , parent . display ( ) ) ) ? ;
2018-01-26 14:38:53 +08:00
}
2018-01-07 22:10:48 +08:00
debug! (
" Copying {} -> {} " ,
2018-01-29 23:29:09 -05:00
input_location . display ( ) ,
2018-01-07 22:10:48 +08:00
output_location . display ( )
) ;
2020-05-20 14:32:00 -07:00
fs ::copy ( & input_location , & output_location ) . with_context ( | | {
2018-01-07 22:10:48 +08:00
format! (
" Unable to copy {} to {} " ,
2018-01-29 23:29:09 -05:00
input_location . display ( ) ,
2018-01-07 22:10:48 +08:00
output_location . display ( )
)
} ) ? ;
2017-06-15 18:17:16 +08:00
}
Ok ( ( ) )
}
2020-05-27 02:23:36 +08:00
fn emit_redirects (
& self ,
root : & Path ,
handlebars : & Handlebars < '_ > ,
2020-05-27 03:12:57 +08:00
redirects : & HashMap < String , String > ,
2020-05-27 02:23:36 +08:00
) -> Result < ( ) > {
if redirects . is_empty ( ) {
return Ok ( ( ) ) ;
}
log ::debug! ( " Emitting redirects " ) ;
for ( original , new ) in redirects {
2020-05-27 03:12:57 +08:00
log ::debug! ( " Redirecting \" {} \" → \" {} \" " , original , new ) ;
// Note: all paths are relative to the build directory, so the
// leading slash in an absolute path means nothing (and would mess
// up `root.join(original)`).
let original = original . trim_start_matches ( " / " ) ;
2020-05-27 02:23:36 +08:00
let filename = root . join ( original ) ;
self . emit_redirect ( handlebars , & filename , new ) ? ;
}
Ok ( ( ) )
}
fn emit_redirect (
& self ,
handlebars : & Handlebars < '_ > ,
original : & Path ,
destination : & str ,
) -> Result < ( ) > {
2020-05-27 03:12:57 +08:00
if original . exists ( ) {
// sanity check to avoid accidentally overwriting a real file.
let msg = format! (
" Not redirecting \" {} \" to \" {} \" because it already exists. Are you sure it needs to be redirected? " ,
original . display ( ) ,
destination ,
) ;
return Err ( Error ::msg ( msg ) ) ;
}
2020-05-27 02:23:36 +08:00
if let Some ( parent ) = original . parent ( ) {
2020-05-27 03:12:57 +08:00
std ::fs ::create_dir_all ( parent )
. with_context ( | | format! ( " Unable to ensure \" {} \" exists " , parent . display ( ) ) ) ? ;
2020-05-27 02:23:36 +08:00
}
let ctx = json! ( {
" url " : destination ,
} ) ;
let f = File ::create ( original ) ? ;
2020-05-27 03:12:57 +08:00
handlebars
. render_to_write ( " redirect " , & ctx , f )
. with_context ( | | {
format! (
" Unable to create a redirect file at \" {} \" " ,
original . display ( )
)
} ) ? ;
2020-05-27 02:23:36 +08:00
Ok ( ( ) )
}
2015-08-04 16:52:10 +02:00
}
2018-05-16 12:06:55 -05:00
// TODO(mattico): Remove some time after the 0.1.8 release
fn maybe_wrong_theme_dir ( dir : & Path ) -> Result < bool > {
fn entry_is_maybe_book_file ( entry : fs ::DirEntry ) -> Result < bool > {
2018-05-16 12:08:23 -05:00
Ok ( entry . file_type ( ) ? . is_file ( )
& & entry . path ( ) . extension ( ) . map_or ( false , | ext | ext = = " md " ) )
2018-05-16 12:06:55 -05:00
}
if dir . is_dir ( ) {
for entry in fs ::read_dir ( dir ) ? {
if entry_is_maybe_book_file ( entry ? ) . unwrap_or ( false ) {
return Ok ( false ) ;
}
}
Ok ( true )
} else {
Ok ( false )
}
}
2015-08-04 15:15:36 +02:00
impl Renderer for HtmlHandlebars {
2018-01-07 22:10:48 +08:00
fn name ( & self ) -> & str {
" html "
}
fn render ( & self , ctx : & RenderContext ) -> Result < ( ) > {
let html_config = ctx . config . html_config ( ) . unwrap_or_default ( ) ;
let src_dir = ctx . root . join ( & ctx . config . book . src ) ;
let destination = & ctx . destination ;
let book = & ctx . book ;
2019-09-14 21:14:13 +01:00
let build_dir = ctx . root . join ( & ctx . config . build . build_dir ) ;
2017-09-30 21:36:03 +08:00
2019-07-21 04:32:28 +08:00
if destination . exists ( ) {
utils ::fs ::remove_dir_content ( destination )
2020-05-20 14:32:00 -07:00
. with_context ( | | " Unable to remove stale HTML output " ) ? ;
2019-07-21 04:32:28 +08:00
}
2019-07-21 02:37:09 +08:00
2018-01-23 01:28:37 +08:00
trace! ( " render " ) ;
2015-08-04 15:15:36 +02:00
let mut handlebars = Handlebars ::new ( ) ;
2017-09-30 21:36:03 +08:00
let theme_dir = match html_config . theme {
2017-11-18 23:21:59 +08:00
Some ( ref theme ) = > theme . to_path_buf ( ) ,
2018-05-14 14:52:29 -05:00
None = > ctx . root . join ( " theme " ) ,
2017-09-30 21:36:03 +08:00
} ;
2018-05-16 12:08:23 -05:00
if html_config . theme . is_none ( )
& & maybe_wrong_theme_dir ( & src_dir . join ( " theme " ) ) . unwrap_or ( false )
{
warn! (
" Previous versions of mdBook erroneously accepted `./src/theme` as an automatic \
theme directory "
) ;
2018-05-16 12:06:55 -05:00
warn! ( " Please move your theme files to `./theme` for them to continue being used " ) ;
}
2017-09-30 21:36:03 +08:00
let theme = theme ::Theme ::new ( theme_dir ) ;
2015-08-04 15:15:36 +02:00
2018-01-23 01:28:37 +08:00
debug! ( " Register the index handlebars template " ) ;
handlebars . register_template_string ( " index " , String ::from_utf8 ( theme . index . clone ( ) ) ? ) ? ;
2017-09-27 16:06:30 -07:00
2020-05-01 07:47:50 +02:00
debug! ( " Register the head handlebars template " ) ;
handlebars . register_partial ( " head " , String ::from_utf8 ( theme . head . clone ( ) ) ? ) ? ;
2020-05-27 02:23:36 +08:00
debug! ( " Register the redirect handlebars template " ) ;
handlebars
. register_template_string ( " redirect " , String ::from_utf8 ( theme . redirect . clone ( ) ) ? ) ? ;
2018-01-23 01:28:37 +08:00
debug! ( " Register the header handlebars template " ) ;
handlebars . register_partial ( " header " , String ::from_utf8 ( theme . header . clone ( ) ) ? ) ? ;
2015-08-04 15:15:36 +02:00
2018-01-23 01:28:37 +08:00
debug! ( " Register handlebars helpers " ) ;
2018-01-08 00:31:46 +08:00
self . register_hbs_helpers ( & mut handlebars , & html_config ) ;
2015-08-04 15:15:36 +02:00
2018-01-07 22:10:48 +08:00
let mut data = make_data ( & ctx . root , & book , & ctx . config , & html_config ) ? ;
2015-08-04 15:15:36 +02:00
2015-09-05 17:26:17 +02:00
// Print version
2017-06-15 18:39:41 +08:00
let mut print_content = String ::new ( ) ;
2015-09-05 17:26:17 +02:00
2017-09-30 22:11:47 +08:00
fs ::create_dir_all ( & destination )
2020-05-20 14:32:00 -07:00
. with_context ( | | " Unexpected error when constructing destination path " ) ? ;
2015-08-04 15:15:36 +02:00
2018-03-07 07:02:06 -06:00
let mut is_index = true ;
for item in book . iter ( ) {
2017-06-20 08:40:01 +08:00
let ctx = RenderItemContext {
handlebars : & handlebars ,
destination : destination . to_path_buf ( ) ,
data : data . clone ( ) ,
2018-12-04 00:10:09 +01:00
is_index ,
2017-09-30 21:36:03 +08:00
html_config : html_config . clone ( ) ,
2020-03-10 00:02:54 +09:00
edition : ctx . config . rust . edition ,
2017-06-20 08:40:01 +08:00
} ;
2018-05-16 12:08:23 -05:00
self . render_item ( item , ctx , & mut print_content ) ? ;
2018-03-07 07:02:06 -06:00
is_index = false ;
2015-08-04 15:15:36 +02:00
}
2020-05-13 14:45:35 +02:00
// Render 404 page
if html_config . output_404 ! = Some ( " " . to_string ( ) ) {
let default_404_location = src_dir . join ( " 404.md " ) ;
let content_404 = if let Some ( ref filename ) = html_config . input_404 {
let path = src_dir . join ( filename ) ;
std ::fs ::read_to_string ( & path ) . map_err ( | failure | {
std ::io ::Error ::new (
failure . kind ( ) ,
format! ( " Unable to open 404 input file {:?} " , & path ) ,
)
} ) ?
} else if default_404_location . exists ( ) {
std ::fs ::read_to_string ( & default_404_location ) . map_err ( | failure | {
std ::io ::Error ::new (
failure . kind ( ) ,
format! (
" Unable to open default 404 input file {:?} " ,
& default_404_location
) ,
)
} ) ?
} else {
" # 404 - Document not found \n \n Unfortunately, this URL is no longer valid, please use the navigation bar or search to continue. " . to_string ( )
} ;
let html_content_404 = utils ::render_markdown ( & content_404 , html_config . curly_quotes ) ;
let mut data_404 = data . clone ( ) ;
2020-06-07 14:14:35 +02:00
let base_url = if let Some ( site_url ) = & html_config . site_url {
site_url
} else {
warn! ( " HTML 'site-url' parameter not set, defaulting to '/'. Please configure this to ensure the 404 page work correctly, especially if your site is hosted in a subdirectory on the HTTP server. " ) ;
" / "
} ;
data_404 . insert ( " base_url " . to_owned ( ) , json! ( base_url ) ) ;
2020-05-13 14:45:35 +02:00
data_404 . insert ( " path " . to_owned ( ) , json! ( " 404.md " ) ) ;
data_404 . insert ( " content " . to_owned ( ) , json! ( html_content_404 ) ) ;
let rendered = handlebars . render ( " index " , & data_404 ) ? ;
let rendered =
self . post_process ( rendered , & html_config . playpen , ctx . config . rust . edition ) ;
let output_file = match & html_config . output_404 {
None = > " 404.html " ,
Some ( file ) = > & file ,
} ;
utils ::fs ::write_file ( & destination , output_file , rendered . as_bytes ( ) ) ? ;
debug! ( " Creating 404.html ✓ " ) ;
}
2015-09-05 17:26:17 +02:00
// Print version
2017-06-15 18:03:10 +08:00
self . configure_print_version ( & mut data , & print_content ) ;
2018-01-07 22:10:48 +08:00
if let Some ( ref title ) = ctx . config . book . title {
2017-09-30 21:36:03 +08:00
data . insert ( " title " . to_owned ( ) , json! ( title ) ) ;
}
2015-09-05 17:26:17 +02:00
2016-12-31 18:33:17 -08:00
// Render the handlebars template with the data
2018-01-23 01:28:37 +08:00
debug! ( " Render template " ) ;
2017-05-19 13:04:37 +02:00
let rendered = handlebars . render ( " index " , & data ) ? ;
2017-02-28 01:41:06 +01:00
2020-03-10 00:02:54 +09:00
let rendered = self . post_process ( rendered , & html_config . playpen , ctx . config . rust . edition ) ;
2017-09-01 16:54:57 -07:00
2018-08-02 12:43:40 -05:00
utils ::fs ::write_file ( & destination , " print.html " , rendered . as_bytes ( ) ) ? ;
2018-01-23 01:28:37 +08:00
debug! ( " Creating print.html ✓ " ) ;
2015-09-05 17:26:17 +02:00
2018-01-23 01:28:37 +08:00
debug! ( " Copy static files " ) ;
2018-01-07 22:10:48 +08:00
self . copy_static_files ( & destination , & theme , & html_config )
2020-05-20 14:32:00 -07:00
. with_context ( | | " Unable to copy across static files " ) ? ;
2018-01-29 23:29:09 -05:00
self . copy_additional_css_and_js ( & html_config , & ctx . root , & destination )
2020-05-20 14:32:00 -07:00
. with_context ( | | " Unable to copy across additional CSS and JS " ) ? ;
2017-05-20 13:56:01 +02:00
2018-03-07 07:02:06 -06:00
// Render search index
2018-07-25 12:20:48 -05:00
#[ cfg(feature = " search " ) ]
{
2018-07-24 16:15:20 -05:00
let search = html_config . search . unwrap_or_default ( ) ;
if search . enable {
super ::search ::create_files ( & search , & destination , & book ) ? ;
}
2018-06-13 13:11:25 -05:00
}
2018-03-07 07:02:06 -06:00
2020-05-27 03:12:57 +08:00
self . emit_redirects ( & ctx . destination , & handlebars , & html_config . redirect )
. context ( " Unable to emit redirects " ) ? ;
2020-05-27 02:23:36 +08:00
2019-09-14 21:14:13 +01:00
// Copy all remaining files, avoid a recursive copy from/to the book build dir
utils ::fs ::copy_files_except_ext ( & src_dir , & destination , true , Some ( & build_dir ) , & [ " md " ] ) ? ;
2017-05-18 23:52:38 +02:00
2015-08-04 15:15:36 +02:00
Ok ( ( ) )
}
}
2018-05-16 12:08:23 -05:00
fn make_data (
root : & Path ,
book : & Book ,
config : & Config ,
html_config : & HtmlConfig ,
) -> Result < serde_json ::Map < String , serde_json ::Value > > {
2018-01-23 01:28:37 +08:00
trace! ( " make_data " ) ;
2015-08-04 15:15:36 +02:00
2016-11-03 01:58:42 +01:00
let mut data = serde_json ::Map ::new ( ) ;
2019-05-30 11:53:49 +09:00
data . insert (
" language " . to_owned ( ) ,
json! ( config . book . language . clone ( ) . unwrap_or_default ( ) ) ,
) ;
2018-05-16 12:08:23 -05:00
data . insert (
" book_title " . to_owned ( ) ,
json! ( config . book . title . clone ( ) . unwrap_or_default ( ) ) ,
) ;
data . insert (
" description " . to_owned ( ) ,
json! ( config . book . description . clone ( ) . unwrap_or_default ( ) ) ,
) ;
2017-02-15 22:34:37 -05:00
data . insert ( " favicon " . to_owned ( ) , json! ( " favicon.png " ) ) ;
2018-01-07 22:10:48 +08:00
if let Some ( ref livereload ) = html_config . livereload_url {
2017-02-15 22:34:37 -05:00
data . insert ( " livereload " . to_owned ( ) , json! ( livereload ) ) ;
2016-04-02 04:46:05 +02:00
}
2015-08-04 15:15:36 +02:00
2018-10-12 19:57:59 +01:00
let default_theme = match html_config . default_theme {
2019-11-05 07:55:08 -05:00
Some ( ref theme ) = > theme . to_lowercase ( ) ,
None = > " light " . to_string ( ) ,
2018-10-12 19:57:59 +01:00
} ;
data . insert ( " default_theme " . to_owned ( ) , json! ( default_theme ) ) ;
2019-09-25 18:23:54 -04:00
let preferred_dark_theme = match html_config . preferred_dark_theme {
2019-11-05 07:55:08 -05:00
Some ( ref theme ) = > theme . to_lowercase ( ) ,
2020-04-21 10:18:44 -07:00
None = > " navy " . to_string ( ) ,
2019-09-25 18:23:54 -04:00
} ;
data . insert (
" preferred_dark_theme " . to_owned ( ) ,
json! ( preferred_dark_theme ) ,
) ;
2017-05-16 13:05:21 +08:00
// Add google analytics tag
2019-09-22 14:25:13 +02:00
if let Some ( ref ga ) = html_config . google_analytics {
2017-05-16 13:24:05 +08:00
data . insert ( " google_analytics " . to_owned ( ) , json! ( ga ) ) ;
}
2017-05-16 13:05:21 +08:00
2019-09-22 14:25:13 +02:00
if html_config . mathjax_support {
2017-06-25 00:32:26 +02:00
data . insert ( " mathjax_support " . to_owned ( ) , json! ( true ) ) ;
}
2020-05-19 03:09:25 -03:00
if html_config . copy_fonts {
data . insert ( " copy_fonts " . to_owned ( ) , json! ( true ) ) ;
2020-05-15 02:48:28 -03:00
}
2017-05-20 13:56:01 +02:00
// Add check to see if there is an additional style
2019-09-22 14:25:13 +02:00
if ! html_config . additional_css . is_empty ( ) {
2017-05-20 13:56:01 +02:00
let mut css = Vec ::new ( ) ;
2019-09-22 14:25:13 +02:00
for style in & html_config . additional_css {
2018-01-07 22:10:48 +08:00
match style . strip_prefix ( root ) {
2018-05-16 12:08:23 -05:00
Ok ( p ) = > css . push ( p . to_str ( ) . expect ( " Could not convert to str " ) ) ,
Err ( _ ) = > css . push ( style . to_str ( ) . expect ( " Could not convert to str " ) ) ,
2017-05-20 13:56:01 +02:00
}
}
data . insert ( " additional_css " . to_owned ( ) , json! ( css ) ) ;
}
2017-06-06 20:34:57 +02:00
// Add check to see if there is an additional script
2019-09-22 14:25:13 +02:00
if ! html_config . additional_js . is_empty ( ) {
2017-06-06 20:34:57 +02:00
let mut js = Vec ::new ( ) ;
2019-09-22 14:25:13 +02:00
for script in & html_config . additional_js {
2018-01-07 22:10:48 +08:00
match script . strip_prefix ( root ) {
2017-06-06 20:34:57 +02:00
Ok ( p ) = > js . push ( p . to_str ( ) . expect ( " Could not convert to str " ) ) ,
2018-09-19 20:36:32 +02:00
Err ( _ ) = > js . push ( script . to_str ( ) . expect ( " Could not convert to str " ) ) ,
2017-06-06 20:34:57 +02:00
}
}
data . insert ( " additional_js " . to_owned ( ) , json! ( js ) ) ;
}
2019-09-22 14:25:13 +02:00
if html_config . playpen . editable & & html_config . playpen . copy_js {
2018-03-07 07:02:06 -06:00
data . insert ( " playpen_js " . to_owned ( ) , json! ( true ) ) ;
2019-09-26 11:03:51 -07:00
if html_config . playpen . line_numbers {
2019-09-24 21:27:02 +08:00
data . insert ( " playpen_line_numbers " . to_owned ( ) , json! ( true ) ) ;
}
2017-06-29 00:35:20 -04:00
}
2019-10-17 21:44:54 +11:00
if html_config . playpen . copyable {
data . insert ( " playpen_copyable " . to_owned ( ) , json! ( true ) ) ;
}
2017-06-29 00:35:20 -04:00
2019-10-19 15:56:08 +08:00
data . insert ( " fold_enable " . to_owned ( ) , json! ( ( html_config . fold . enable ) ) ) ;
data . insert ( " fold_level " . to_owned ( ) , json! ( ( html_config . fold . level ) ) ) ;
2018-03-07 07:02:06 -06:00
let search = html_config . search . clone ( ) ;
if cfg! ( feature = " search " ) {
2018-06-13 13:11:25 -05:00
let search = search . unwrap_or_default ( ) ;
data . insert ( " search_enabled " . to_owned ( ) , json! ( search . enable ) ) ;
2018-07-23 12:45:01 -05:00
data . insert (
" search_js " . to_owned ( ) ,
json! ( search . enable & & search . copy_js ) ,
) ;
2018-03-07 07:02:06 -06:00
} else if search . is_some ( ) {
warn! ( " mdBook compiled without search support, ignoring `output.html.search` table " ) ;
2018-05-16 12:08:23 -05:00
warn! (
" please reinstall with `cargo install mdbook --force --features search`to use the \
search feature "
)
2018-03-07 07:02:06 -06:00
}
2018-05-16 12:08:23 -05:00
2018-10-13 12:17:33 +01:00
if let Some ( ref git_repository_url ) = html_config . git_repository_url {
2018-10-15 19:48:54 +01:00
data . insert ( " git_repository_url " . to_owned ( ) , json! ( git_repository_url ) ) ;
2018-10-13 12:17:33 +01:00
}
2019-10-19 15:56:08 +08:00
2018-10-15 19:48:54 +01:00
let git_repository_icon = match html_config . git_repository_icon {
Some ( ref git_repository_icon ) = > git_repository_icon ,
None = > " fa-github " ,
} ;
data . insert ( " git_repository_icon " . to_owned ( ) , json! ( git_repository_icon ) ) ;
2018-10-13 12:17:33 +01:00
2015-08-04 15:15:36 +02:00
let mut chapters = vec! [ ] ;
2015-09-11 20:52:55 +02:00
for item in book . iter ( ) {
// Create the data to inject in the template
2015-08-04 15:15:36 +02:00
let mut chapter = BTreeMap ::new ( ) ;
2015-09-11 20:52:55 +02:00
2015-09-16 22:46:23 -04:00
match * item {
2020-03-20 21:18:07 -05:00
BookItem ::PartTitle ( ref title ) = > {
chapter . insert ( " part " . to_owned ( ) , json! ( title ) ) ;
}
2017-11-18 20:01:50 +08:00
BookItem ::Chapter ( ref ch ) = > {
if let Some ( ref section ) = ch . number {
chapter . insert ( " section " . to_owned ( ) , json! ( section . to_string ( ) ) ) ;
}
2019-10-19 15:56:08 +08:00
chapter . insert (
" has_sub_items " . to_owned ( ) ,
json! ( ( ! ch . sub_items . is_empty ( ) ) . to_string ( ) ) ,
) ;
2017-02-15 22:34:37 -05:00
chapter . insert ( " name " . to_owned ( ) , json! ( ch . name ) ) ;
2020-02-29 17:55:45 +01:00
if let Some ( ref path ) = ch . path {
let p = path
. to_str ( )
2020-05-20 14:32:00 -07:00
. with_context ( | | " Could not convert path to str " ) ? ;
2020-02-29 17:55:45 +01:00
chapter . insert ( " path " . to_owned ( ) , json! ( p ) ) ;
}
2017-10-03 17:25:23 +05:45
}
2017-11-18 20:01:50 +08:00
BookItem ::Separator = > {
2017-02-15 22:34:37 -05:00
chapter . insert ( " spacer " . to_owned ( ) , json! ( " _spacer_ " ) ) ;
2017-10-03 17:25:23 +05:45
}
2015-08-11 22:55:51 +02:00
}
2015-08-04 15:15:36 +02:00
chapters . push ( chapter ) ;
}
2017-02-15 22:34:37 -05:00
data . insert ( " chapters " . to_owned ( ) , json! ( chapters ) ) ;
2015-08-04 15:15:36 +02:00
debug! ( " [*]: JSON constructed " ) ;
Ok ( data )
}
2017-02-16 01:39:13 -05:00
2019-06-03 21:31:15 +09:00
/// Goes through the rendered HTML, making sure all header tags have
/// an anchor respectively so people can link to sections directly.
2018-07-11 23:33:44 +10:00
fn build_header_links ( html : & str ) -> String {
2017-02-16 19:29:50 -05:00
let regex = Regex ::new ( r "<h(\d)>(.*?)</h\d>" ) . unwrap ( ) ;
2017-03-23 23:24:26 +05:30
let mut id_counter = HashMap ::new ( ) ;
2017-02-16 19:29:50 -05:00
2018-05-16 12:08:23 -05:00
regex
2019-05-07 03:50:34 +07:00
. replace_all ( html , | caps : & Captures < '_ > | {
2018-05-16 12:08:23 -05:00
let level = caps [ 1 ]
. parse ( )
. expect ( " Regex should ensure we only ever get numbers here " ) ;
2017-05-19 13:04:37 +02:00
2019-06-03 21:31:15 +09:00
insert_link_into_header ( level , & caps [ 2 ] , & mut id_counter )
2019-05-05 21:57:43 +07:00
} )
. into_owned ( )
2017-06-20 11:06:30 +08:00
}
2017-05-19 13:04:37 +02:00
2019-06-03 21:31:15 +09:00
/// Insert a sinle link into a header, making sure each link gets its own
2017-06-20 11:15:12 +08:00
/// unique ID by appending an auto-incremented number (if necessary).
2019-06-03 21:31:15 +09:00
fn insert_link_into_header (
2018-05-16 12:08:23 -05:00
level : usize ,
content : & str ,
2018-07-23 12:45:01 -05:00
id_counter : & mut HashMap < String , usize > ,
2018-05-16 12:08:23 -05:00
) -> String {
2018-03-07 07:02:06 -06:00
let raw_id = utils ::id_from_content ( content ) ;
2017-06-20 11:06:30 +08:00
2017-06-20 11:15:12 +08:00
let id_count = id_counter . entry ( raw_id . clone ( ) ) . or_insert ( 0 ) ;
2017-06-20 11:06:30 +08:00
2017-06-20 11:15:12 +08:00
let id = match * id_count {
0 = > raw_id ,
other = > format! ( " {} - {} " , raw_id , other ) ,
2017-06-20 11:06:30 +08:00
} ;
2017-06-20 11:15:12 +08:00
* id_count + = 1 ;
2017-06-20 11:06:30 +08:00
format! (
2019-06-03 21:31:15 +09:00
r ## "<h{level}><a class="header" href="#{id}" id="{id}">{text}</a></h{level}>"## ,
2017-06-20 11:06:30 +08:00
level = level ,
id = id ,
2018-07-11 23:33:44 +10:00
text = content
2017-06-20 11:06:30 +08:00
)
}
2017-05-19 13:04:37 +02:00
// The rust book uses annotations for rustdoc to test code snippets,
// like the following:
2017-02-28 01:41:06 +01:00
// ```rust,should_panic
// fn main() {
// // Code here
// }
// ```
// This function replaces all commas by spaces in the code block classes
2017-06-27 09:08:58 +02:00
fn fix_code_blocks ( html : & str ) -> String {
2017-02-28 01:41:06 +01:00
let regex = Regex ::new ( r ## "<code([^>]+)class="([^"]+)"([^>]*)>"## ) . unwrap ( ) ;
2018-05-16 12:08:23 -05:00
regex
2019-05-07 03:50:34 +07:00
. replace_all ( html , | caps : & Captures < '_ > | {
2018-05-16 12:08:23 -05:00
let before = & caps [ 1 ] ;
let classes = & caps [ 2 ] . replace ( " , " , " " ) ;
let after = & caps [ 3 ] ;
format! (
2019-05-09 11:18:28 -07:00
r # "<code{before}class="{classes}"{after}>"# ,
2017-10-03 17:25:23 +05:45
before = before ,
classes = classes ,
2018-05-16 12:08:23 -05:00
after = after
)
2019-05-05 21:57:43 +07:00
} )
. into_owned ( )
2017-02-28 01:41:06 +01:00
}
2017-03-06 12:23:15 -05:00
2019-11-18 01:03:14 +03:00
fn add_playpen_pre ( html : & str , playpen_config : & Playpen , edition : Option < RustEdition > ) -> String {
2017-03-06 13:27:25 -05:00
let regex = Regex ::new ( r ## "((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"## ) . unwrap ( ) ;
2018-05-16 12:08:23 -05:00
regex
2019-05-07 03:50:34 +07:00
. replace_all ( html , | caps : & Captures < '_ > | {
2018-05-16 12:08:23 -05:00
let text = & caps [ 1 ] ;
let classes = & caps [ 2 ] ;
let code = & caps [ 3 ] ;
2019-11-11 13:24:13 +01:00
if classes . contains ( " language-rust " ) {
if ( ! classes . contains ( " ignore " ) & & ! classes . contains ( " noplaypen " ) )
| | classes . contains ( " mdbook-runnable " )
{
2019-11-18 01:03:14 +03:00
let contains_e2015 = classes . contains ( " edition2015 " ) ;
let contains_e2018 = classes . contains ( " edition2018 " ) ;
let edition_class = if contains_e2015 | | contains_e2018 {
// the user forced edition, we should not overwrite it
" "
} else {
match edition {
Some ( RustEdition ::E2015 ) = > " edition2015 " ,
Some ( RustEdition ::E2018 ) = > " edition2018 " ,
None = > " " ,
}
} ;
2019-11-11 13:24:13 +01:00
// wrap the contents in an external pre block
format! (
2019-11-18 01:03:14 +03:00
" <pre class= \" playpen \" ><code class= \" {}{} \" >{}</code></pre> " ,
2019-11-11 13:24:13 +01:00
classes ,
2019-11-18 01:03:14 +03:00
edition_class ,
2019-10-16 11:27:14 +02:00
{
2019-11-07 02:20:10 +01:00
let content : Cow < '_ , str > = if playpen_config . editable
& & classes . contains ( " editable " )
| | text . contains ( " fn main " )
| | text . contains ( " quick_main! " )
{
code . into ( )
2019-10-16 11:27:14 +02:00
} else {
2019-11-07 02:20:10 +01:00
// we need to inject our own main
let ( attrs , code ) = partition_source ( code ) ;
format! (
2020-04-18 00:10:08 -04:00
" \n # #![allow(unused)] \n {}#fn main() {{ \n {}#}} " ,
2019-11-07 02:20:10 +01:00
attrs , code
)
. into ( )
} ;
hide_lines ( & content )
2019-10-16 11:27:14 +02:00
}
2019-11-07 02:20:10 +01:00
)
} else {
format! ( " <code class= \" {} \" > {} </code> " , classes , hide_lines ( code ) )
}
2017-05-19 13:04:37 +02:00
} else {
2018-05-16 12:08:23 -05:00
// not language-rust, so no-op
text . to_owned ( )
2017-03-06 13:27:25 -05:00
}
2019-05-05 21:57:43 +07:00
} )
. into_owned ( )
2017-03-06 12:23:15 -05:00
}
2017-03-31 17:06:03 -04:00
2019-11-07 02:20:10 +01:00
lazy_static! {
static ref BORING_LINES_REGEX : Regex = Regex ::new ( r "^(\s*)#(.?)(.*)$" ) . unwrap ( ) ;
}
fn hide_lines ( content : & str ) -> String {
let mut result = String ::with_capacity ( content . len ( ) ) ;
for line in content . lines ( ) {
if let Some ( caps ) = BORING_LINES_REGEX . captures ( line ) {
2019-11-07 02:42:02 +01:00
if & caps [ 2 ] = = " # " {
result + = & caps [ 1 ] ;
2019-11-07 02:20:10 +01:00
result + = & caps [ 2 ] ;
2019-11-07 02:42:02 +01:00
result + = & caps [ 3 ] ;
result + = " \n " ;
continue ;
} else if & caps [ 2 ] ! = " ! " & & & caps [ 2 ] ! = " [ " {
result + = " <span class= \" boring \" > " ;
result + = & caps [ 1 ] ;
if & caps [ 2 ] ! = " " {
result + = & caps [ 2 ] ;
}
result + = & caps [ 3 ] ;
result + = " \n " ;
2019-11-07 02:20:10 +01:00
result + = " </span> " ;
2019-11-07 02:42:02 +01:00
continue ;
2019-11-07 02:20:10 +01:00
}
}
2019-11-07 02:42:02 +01:00
result + = line ;
2019-11-07 02:20:10 +01:00
result + = " \n " ;
}
result
}
2017-03-31 17:06:03 -04:00
fn partition_source ( s : & str ) -> ( String , String ) {
let mut after_header = false ;
let mut before = String ::new ( ) ;
let mut after = String ::new ( ) ;
for line in s . lines ( ) {
let trimline = line . trim ( ) ;
2019-05-07 01:20:58 +07:00
let header = trimline . chars ( ) . all ( char ::is_whitespace ) | | trimline . starts_with ( " #![ " ) ;
2017-03-31 17:06:03 -04:00
if ! header | | after_header {
after_header = true ;
after . push_str ( line ) ;
after . push_str ( " \n " ) ;
} else {
before . push_str ( line ) ;
before . push_str ( " \n " ) ;
}
}
( before , after )
2017-05-19 13:04:37 +02:00
}
2017-06-20 08:40:01 +08:00
struct RenderItemContext < ' a > {
2020-01-24 11:01:44 +08:00
handlebars : & ' a Handlebars < ' a > ,
2017-06-20 08:40:01 +08:00
destination : PathBuf ,
data : serde_json ::Map < String , serde_json ::Value > ,
is_index : bool ,
2017-09-30 21:36:03 +08:00
html_config : HtmlConfig ,
2019-11-18 01:03:14 +03:00
edition : Option < RustEdition > ,
2017-06-20 08:40:01 +08:00
}
2017-06-20 10:54:32 +08:00
#[ cfg(test) ]
mod tests {
use super ::* ;
#[ test ]
fn original_build_header_links ( ) {
let inputs = vec! [
2017-09-01 16:54:57 -07:00
(
" blah blah <h1>Foo</h1> " ,
2019-06-03 21:31:15 +09:00
r ## "blah blah <h1><a class="header" href="#foo" id="foo">Foo</a></h1>"## ,
2017-09-01 16:54:57 -07:00
) ,
(
" <h1>Foo</h1> " ,
2019-06-03 21:31:15 +09:00
r ## "<h1><a class="header" href="#foo" id="foo">Foo</a></h1>"## ,
2017-09-01 16:54:57 -07:00
) ,
(
" <h3>Foo^bar</h3> " ,
2019-06-03 21:31:15 +09:00
r ## "<h3><a class="header" href="#foobar" id="foobar">Foo^bar</a></h3>"## ,
2017-09-01 16:54:57 -07:00
) ,
(
" <h4></h4> " ,
2019-06-03 21:31:15 +09:00
r ## "<h4><a class="header" href="#" id=""></a></h4>"## ,
2017-09-01 16:54:57 -07:00
) ,
(
" <h4><em>Hï</em></h4> " ,
2019-06-03 21:31:15 +09:00
r ## "<h4><a class="header" href="#hï" id="hï"><em>Hï</em></a></h4>"## ,
2017-09-01 16:54:57 -07:00
) ,
(
" <h1>Foo</h1><h3>Foo</h3> " ,
2019-06-03 21:31:15 +09:00
r ## "<h1><a class="header" href="#foo" id="foo">Foo</a></h1><h3><a class="header" href="#foo-1" id="foo-1">Foo</a></h3>"## ,
2017-09-01 16:54:57 -07:00
) ,
2017-06-20 10:54:32 +08:00
] ;
for ( src , should_be ) in inputs {
2018-07-11 23:33:44 +10:00
let got = build_header_links ( & src ) ;
2017-06-20 10:54:32 +08:00
assert_eq! ( got , should_be ) ;
}
}
2019-10-16 11:27:14 +02:00
#[ test ]
fn add_playpen ( ) {
let inputs = [
( " <code class= \" language-rust \" >x()</code> " ,
2020-04-18 00:10:08 -04:00
" <pre class= \" playpen \" ><code class= \" language-rust \" > \n <span class= \" boring \" >#![allow(unused)] \n </span><span class= \" boring \" >fn main() { \n </span>x() \n <span class= \" boring \" >} \n </span></code></pre> " ) ,
2019-10-16 11:27:14 +02:00
( " <code class= \" language-rust \" >fn main() {}</code> " ,
" <pre class= \" playpen \" ><code class= \" language-rust \" >fn main() {} \n </code></pre> " ) ,
( " <code class= \" language-rust editable \" >let s = \" foo \n # bar \n \" ;</code> " ,
" <pre class= \" playpen \" ><code class= \" language-rust editable \" >let s = \" foo \n <span class= \" boring \" > bar \n </span> \" ; \n </code></pre> " ) ,
( " <code class= \" language-rust editable \" >let s = \" foo \n ## bar \n \" ;</code> " ,
" <pre class= \" playpen \" ><code class= \" language-rust editable \" >let s = \" foo \n # bar \n \" ; \n </code></pre> " ) ,
2019-11-04 13:58:37 +01:00
( " <code class= \" language-rust editable \" >let s = \" foo \n # bar \n # \n \" ;</code> " ,
2019-11-07 02:42:02 +01:00
" <pre class= \" playpen \" ><code class= \" language-rust editable \" >let s = \" foo \n <span class= \" boring \" > bar \n </span><span class= \" boring \" > \n </span> \" ; \n </code></pre> " ) ,
( " <code class= \" language-rust ignore \" >let s = \" foo \n # bar \n \" ;</code> " ,
" <code class= \" language-rust ignore \" >let s = \" foo \n <span class= \" boring \" > bar \n </span> \" ; \n </code> " ) ,
( " <code class= \" language-rust editable \" >#![no_std] \n let s = \" foo \" ; \n #[some_attr]</code> " ,
" <pre class= \" playpen \" ><code class= \" language-rust editable \" >#![no_std] \n let s = \" foo \" ; \n #[some_attr] \n </code></pre> " ) ,
2019-10-16 11:27:14 +02:00
] ;
for ( src , should_be ) in & inputs {
let got = add_playpen_pre (
src ,
& Playpen {
editable : true ,
.. Playpen ::default ( )
} ,
2019-11-18 01:03:14 +03:00
None ,
) ;
assert_eq! ( & * got , * should_be ) ;
}
}
#[ test ]
fn add_playpen_edition2015 ( ) {
let inputs = [
( " <code class= \" language-rust \" >x()</code> " ,
2020-04-21 12:21:56 -07:00
" <pre class= \" playpen \" ><code class= \" language-rust edition2015 \" > \n <span class= \" boring \" >#![allow(unused)] \n </span><span class= \" boring \" >fn main() { \n </span>x() \n <span class= \" boring \" >} \n </span></code></pre> " ) ,
2019-11-18 01:03:14 +03:00
( " <code class= \" language-rust \" >fn main() {}</code> " ,
" <pre class= \" playpen \" ><code class= \" language-rust edition2015 \" >fn main() {} \n </code></pre> " ) ,
( " <code class= \" language-rust edition2015 \" >fn main() {}</code> " ,
" <pre class= \" playpen \" ><code class= \" language-rust edition2015 \" >fn main() {} \n </code></pre> " ) ,
( " <code class= \" language-rust edition2018 \" >fn main() {}</code> " ,
" <pre class= \" playpen \" ><code class= \" language-rust edition2018 \" >fn main() {} \n </code></pre> " ) ,
] ;
for ( src , should_be ) in & inputs {
let got = add_playpen_pre (
src ,
& Playpen {
editable : true ,
.. Playpen ::default ( )
} ,
Some ( RustEdition ::E2015 ) ,
) ;
assert_eq! ( & * got , * should_be ) ;
}
}
#[ test ]
fn add_playpen_edition2018 ( ) {
let inputs = [
( " <code class= \" language-rust \" >x()</code> " ,
2020-04-21 12:21:56 -07:00
" <pre class= \" playpen \" ><code class= \" language-rust edition2018 \" > \n <span class= \" boring \" >#![allow(unused)] \n </span><span class= \" boring \" >fn main() { \n </span>x() \n <span class= \" boring \" >} \n </span></code></pre> " ) ,
2019-11-18 01:03:14 +03:00
( " <code class= \" language-rust \" >fn main() {}</code> " ,
" <pre class= \" playpen \" ><code class= \" language-rust edition2018 \" >fn main() {} \n </code></pre> " ) ,
( " <code class= \" language-rust edition2015 \" >fn main() {}</code> " ,
" <pre class= \" playpen \" ><code class= \" language-rust edition2015 \" >fn main() {} \n </code></pre> " ) ,
( " <code class= \" language-rust edition2018 \" >fn main() {}</code> " ,
" <pre class= \" playpen \" ><code class= \" language-rust edition2018 \" >fn main() {} \n </code></pre> " ) ,
] ;
for ( src , should_be ) in & inputs {
let got = add_playpen_pre (
src ,
& Playpen {
editable : true ,
.. Playpen ::default ( )
} ,
Some ( RustEdition ::E2018 ) ,
2019-10-16 11:27:14 +02:00
) ;
assert_eq! ( & * got , * should_be ) ;
}
}
2017-06-20 11:06:30 +08:00
}