run rustfmt on the repository #398(Updated) (#438)
rustfmt the repository #398
This commit is contained in:
parent
b45e5e4420
commit
382fc4139b
38
build.rs
38
build.rs
|
@ -32,26 +32,23 @@ error_chain!{
|
||||||
}
|
}
|
||||||
|
|
||||||
fn program_exists(program: &str) -> Result<()> {
|
fn program_exists(program: &str) -> Result<()> {
|
||||||
execs::cmd(program)
|
execs::cmd(program).arg("-v")
|
||||||
.arg("-v")
|
.output()
|
||||||
.output()
|
.chain_err(|| format!("Please install '{}'!", program))?;
|
||||||
.chain_err(|| format!("Please install '{}'!", program))?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn npm_package_exists(package: &str) -> Result<()> {
|
fn npm_package_exists(package: &str) -> Result<()> {
|
||||||
let status = execs::cmd("npm")
|
let status = execs::cmd("npm").args(&["list", "-g"])
|
||||||
.args(&["list", "-g"])
|
.arg(package)
|
||||||
.arg(package)
|
.output();
|
||||||
.output();
|
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
Ok(ref out) if out.status.success() => Ok(()),
|
Ok(ref out) if out.status.success() => Ok(()),
|
||||||
_ => {
|
_ => {
|
||||||
bail!("Missing npm package '{0}' \
|
bail!("Missing npm package '{0}' install with: 'npm -g install {0}'",
|
||||||
install with: 'npm -g install {0}'",
|
|
||||||
package)
|
package)
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +56,7 @@ pub enum Resource<'a> {
|
||||||
Program(&'a str),
|
Program(&'a str),
|
||||||
Package(&'a str),
|
Package(&'a str),
|
||||||
}
|
}
|
||||||
use Resource::{Program, Package};
|
use Resource::{Package, Program};
|
||||||
|
|
||||||
impl<'a> Resource<'a> {
|
impl<'a> Resource<'a> {
|
||||||
pub fn exists(&self) -> Result<()> {
|
pub fn exists(&self) -> Result<()> {
|
||||||
|
@ -71,7 +68,6 @@ impl<'a> Resource<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run() -> Result<()> {
|
fn run() -> Result<()> {
|
||||||
|
|
||||||
if let Ok(_) = env::var("CARGO_FEATURE_REGENERATE_CSS") {
|
if let Ok(_) = env::var("CARGO_FEATURE_REGENERATE_CSS") {
|
||||||
// Check dependencies
|
// Check dependencies
|
||||||
Program("npm").exists()?;
|
Program("npm").exists()?;
|
||||||
|
@ -85,14 +81,14 @@ fn run() -> Result<()> {
|
||||||
let theme_dir = Path::new(&manifest_dir).join("src/theme/");
|
let theme_dir = Path::new(&manifest_dir).join("src/theme/");
|
||||||
let stylus_dir = theme_dir.join("stylus/book.styl");
|
let stylus_dir = theme_dir.join("stylus/book.styl");
|
||||||
|
|
||||||
if !execs::cmd("stylus")
|
if !execs::cmd("stylus").arg(stylus_dir)
|
||||||
.arg(stylus_dir)
|
.arg("--out")
|
||||||
.arg("--out")
|
.arg(theme_dir)
|
||||||
.arg(theme_dir)
|
.arg("--use")
|
||||||
.arg("--use")
|
.arg("nib")
|
||||||
.arg("nib")
|
.status()?
|
||||||
.status()?
|
.success()
|
||||||
.success() {
|
{
|
||||||
bail!("Stylus encoutered an error");
|
bail!("Stylus encoutered an error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
21
rustfmt.toml
21
rustfmt.toml
|
@ -1,16 +1,7 @@
|
||||||
write_mode = "Overwrite"
|
array_layout = "Visual"
|
||||||
|
chain_indent = "Visual"
|
||||||
|
fn_args_layout = "Visual"
|
||||||
|
fn_call_style = "Visual"
|
||||||
|
format_strings = true
|
||||||
|
generics_indent = "Visual"
|
||||||
|
|
||||||
max_width = 120
|
|
||||||
ideal_width = 120
|
|
||||||
fn_call_width = 100
|
|
||||||
|
|
||||||
fn_args_density = "Compressed"
|
|
||||||
|
|
||||||
enum_trailing_comma = true
|
|
||||||
match_block_trailing_comma = true
|
|
||||||
struct_trailing_comma = "Always"
|
|
||||||
wrap_comments = true
|
|
||||||
use_try_shorthand = true
|
|
||||||
|
|
||||||
report_todo = "Always"
|
|
||||||
report_fixme = "Always"
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use clap::{ArgMatches, SubCommand, App};
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
use mdbook::MDBook;
|
use mdbook::MDBook;
|
||||||
use mdbook::errors::Result;
|
use mdbook::errors::Result;
|
||||||
use {get_book_dir, open};
|
use {get_book_dir, open};
|
||||||
|
@ -8,10 +8,21 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||||
SubCommand::with_name("build")
|
SubCommand::with_name("build")
|
||||||
.about("Build the book from the markdown files")
|
.about("Build the book from the markdown files")
|
||||||
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
|
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
|
||||||
.arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'")
|
.arg_from_usage(
|
||||||
.arg_from_usage("--no-create 'Will not create non-existent files linked from SUMMARY.md'")
|
"-d, --dest-dir=[dest-dir] 'The output directory for your \
|
||||||
.arg_from_usage("--curly-quotes 'Convert straight quotes to curly quotes, except for those that occur in code blocks and code spans'")
|
book{n}(Defaults to ./book when omitted)'",
|
||||||
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'")
|
)
|
||||||
|
.arg_from_usage(
|
||||||
|
"--no-create 'Will not create non-existent files linked from SUMMARY.md'",
|
||||||
|
)
|
||||||
|
.arg_from_usage(
|
||||||
|
"--curly-quotes 'Convert straight quotes to curly quotes, except for those \
|
||||||
|
that occur in code blocks and code spans'",
|
||||||
|
)
|
||||||
|
.arg_from_usage(
|
||||||
|
"[dir] 'A directory for your book{n}(Defaults to Current Directory \
|
||||||
|
when omitted)'",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build command implementation
|
// Build command implementation
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use clap::{ArgMatches, SubCommand, App};
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
use mdbook::MDBook;
|
use mdbook::MDBook;
|
||||||
use mdbook::errors::Result;
|
use mdbook::errors::Result;
|
||||||
use get_book_dir;
|
use get_book_dir;
|
||||||
|
@ -10,14 +10,14 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||||
SubCommand::with_name("init")
|
SubCommand::with_name("init")
|
||||||
.about("Create boilerplate structure and files in the directory")
|
.about("Create boilerplate structure and files in the directory")
|
||||||
// the {n} denotes a newline which will properly aligned in all help messages
|
// the {n} denotes a newline which will properly aligned in all help messages
|
||||||
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'")
|
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory \
|
||||||
|
when omitted)'")
|
||||||
.arg_from_usage("--theme 'Copies the default theme into your source folder'")
|
.arg_from_usage("--theme 'Copies the default theme into your source folder'")
|
||||||
.arg_from_usage("--force 'skip confirmation prompts'")
|
.arg_from_usage("--force 'skip confirmation prompts'")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init command implementation
|
// Init command implementation
|
||||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||||
|
|
||||||
let book_dir = get_book_dir(args);
|
let book_dir = get_book_dir(args);
|
||||||
let mut book = MDBook::new(&book_dir);
|
let mut book = MDBook::new(&book_dir);
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||||
|
|
||||||
// If flag `--theme` is present, copy theme to src
|
// If flag `--theme` is present, copy theme to src
|
||||||
if args.is_present("theme") {
|
if args.is_present("theme") {
|
||||||
|
|
||||||
// Skip this if `--force` is present
|
// Skip this if `--force` is present
|
||||||
if !args.is_present("force") {
|
if !args.is_present("force") {
|
||||||
// Print warning
|
// Print warning
|
||||||
|
@ -45,7 +44,6 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||||
// Call the function that copies the theme
|
// Call the function that copies the theme
|
||||||
book.copy_theme()?;
|
book.copy_theme()?;
|
||||||
println!("\nTheme copied.");
|
println!("\nTheme copied.");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Because of `src/book/mdbook.rs#L37-L39`, `dest` will always start with `root`
|
// Because of `src/book/mdbook.rs#L37-L39`, `dest` will always start with `root`
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
extern crate mdbook;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
extern crate log;
|
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
|
extern crate log;
|
||||||
|
extern crate mdbook;
|
||||||
extern crate open;
|
extern crate open;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use clap::{App, ArgMatches, AppSettings};
|
use clap::{App, AppSettings, ArgMatches};
|
||||||
use log::{LogRecord, LogLevelFilter};
|
use log::{LogLevelFilter, LogRecord};
|
||||||
use env_logger::LogBuilder;
|
use env_logger::LogBuilder;
|
||||||
|
|
||||||
pub mod build;
|
pub mod build;
|
||||||
|
@ -33,7 +33,10 @@ fn main() {
|
||||||
// Get the version from our Cargo.toml using clap's crate_version!() macro
|
// Get the version from our Cargo.toml using clap's crate_version!() macro
|
||||||
.version(concat!("v",crate_version!()))
|
.version(concat!("v",crate_version!()))
|
||||||
.setting(AppSettings::SubcommandRequired)
|
.setting(AppSettings::SubcommandRequired)
|
||||||
.after_help("For more information about a specific command, try `mdbook <command> --help`\nSource code for mdbook available at: https://github.com/azerupi/mdBook")
|
.after_help("For more information about a specific command, \
|
||||||
|
try `mdbook <command> --help`\n\
|
||||||
|
Source code for mdbook available \
|
||||||
|
at: https://github.com/azerupi/mdBook")
|
||||||
.subcommand(init::make_subcommand())
|
.subcommand(init::make_subcommand())
|
||||||
.subcommand(build::make_subcommand())
|
.subcommand(build::make_subcommand())
|
||||||
.subcommand(test::make_subcommand());
|
.subcommand(test::make_subcommand());
|
||||||
|
@ -71,7 +74,7 @@ fn init_logger() {
|
||||||
builder.format(format).filter(None, LogLevelFilter::Info);
|
builder.format(format).filter(None, LogLevelFilter::Info);
|
||||||
|
|
||||||
if let Ok(var) = env::var("RUST_LOG") {
|
if let Ok(var) = env::var("RUST_LOG") {
|
||||||
builder.parse(&var);
|
builder.parse(&var);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.init().unwrap();
|
builder.init().unwrap();
|
||||||
|
|
|
@ -4,8 +4,9 @@ extern crate ws;
|
||||||
|
|
||||||
use std;
|
use std;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use self::iron::{Iron, AfterMiddleware, IronResult, IronError, Request, Response, status, Set, Chain};
|
use self::iron::{status, AfterMiddleware, Chain, Iron, IronError, IronResult, Request, Response,
|
||||||
use clap::{ArgMatches, SubCommand, App};
|
Set};
|
||||||
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
use mdbook::MDBook;
|
use mdbook::MDBook;
|
||||||
use mdbook::errors::Result;
|
use mdbook::errors::Result;
|
||||||
use {get_book_dir, open};
|
use {get_book_dir, open};
|
||||||
|
@ -17,14 +18,33 @@ struct ErrorRecover;
|
||||||
// Create clap subcommand arguments
|
// Create clap subcommand arguments
|
||||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||||
SubCommand::with_name("serve")
|
SubCommand::with_name("serve")
|
||||||
.about("Serve the book at http://localhost:3000. Rebuild and reload on change.")
|
.about(
|
||||||
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'")
|
"Serve the book at http://localhost:3000. Rebuild and reload on change.",
|
||||||
.arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'")
|
)
|
||||||
.arg_from_usage("--curly-quotes 'Convert straight quotes to curly quotes, except for those that occur in code blocks and code spans'")
|
.arg_from_usage(
|
||||||
|
"[dir] 'A directory for your book{n}(Defaults to \
|
||||||
|
Current Directory when omitted)'",
|
||||||
|
)
|
||||||
|
.arg_from_usage(
|
||||||
|
"-d, --dest-dir=[dest-dir] 'The output directory for \
|
||||||
|
your book{n}(Defaults to ./book when omitted)'",
|
||||||
|
)
|
||||||
|
.arg_from_usage(
|
||||||
|
"--curly-quotes 'Convert straight quotes to curly quotes, except \
|
||||||
|
for those that occur in code blocks and code spans'",
|
||||||
|
)
|
||||||
.arg_from_usage("-p, --port=[port] 'Use another port{n}(Defaults to 3000)'")
|
.arg_from_usage("-p, --port=[port] 'Use another port{n}(Defaults to 3000)'")
|
||||||
.arg_from_usage("-w, --websocket-port=[ws-port] 'Use another port for the websocket connection (livereload){n}(Defaults to 3001)'")
|
.arg_from_usage(
|
||||||
.arg_from_usage("-i, --interface=[interface] 'Interface to listen on{n}(Defaults to localhost)'")
|
"-w, --websocket-port=[ws-port] 'Use another port for the \
|
||||||
.arg_from_usage("-a, --address=[address] 'Address that the browser can reach the websocket server from{n}(Defaults to the interface address)'")
|
websocket connection (livereload){n}(Defaults to 3001)'",
|
||||||
|
)
|
||||||
|
.arg_from_usage(
|
||||||
|
"-i, --interface=[interface] 'Interface to listen on{n}(Defaults to localhost)'",
|
||||||
|
)
|
||||||
|
.arg_from_usage(
|
||||||
|
"-a, --address=[address] 'Address that the browser can reach the \
|
||||||
|
websocket server from{n}(Defaults to the interface address)'",
|
||||||
|
)
|
||||||
.arg_from_usage("-o, --open 'Open the book server in a web browser'")
|
.arg_from_usage("-o, --open 'Open the book server in a web browser'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +73,8 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||||
let address = format!("{}:{}", interface, port);
|
let address = format!("{}:{}", interface, port);
|
||||||
let ws_address = format!("{}:{}", interface, ws_port);
|
let ws_address = format!("{}:{}", interface, ws_port);
|
||||||
|
|
||||||
book.set_livereload(format!(r#"
|
book.set_livereload(format!(
|
||||||
|
r#"
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var socket = new WebSocket("ws://{}:{}");
|
var socket = new WebSocket("ws://{}:{}");
|
||||||
socket.onmessage = function (event) {{
|
socket.onmessage = function (event) {{
|
||||||
|
@ -68,9 +89,10 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||||
}}
|
}}
|
||||||
</script>
|
</script>
|
||||||
"#,
|
"#,
|
||||||
public_address,
|
public_address,
|
||||||
ws_port,
|
ws_port,
|
||||||
RELOAD_COMMAND));
|
RELOAD_COMMAND
|
||||||
|
));
|
||||||
|
|
||||||
book.build()?;
|
book.build()?;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use clap::{ArgMatches, SubCommand, App};
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
use mdbook::MDBook;
|
use mdbook::MDBook;
|
||||||
use mdbook::errors::Result;
|
use mdbook::errors::Result;
|
||||||
use get_book_dir;
|
use get_book_dir;
|
||||||
|
@ -7,12 +7,16 @@ use get_book_dir;
|
||||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||||
SubCommand::with_name("test")
|
SubCommand::with_name("test")
|
||||||
.about("Test that code samples compile")
|
.about("Test that code samples compile")
|
||||||
.arg_from_usage("-L, --library-path [DIR]... 'directory to add to crate search path'")
|
.arg_from_usage(
|
||||||
|
"-L, --library-path [DIR]... 'directory to add to crate search path'",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// test command implementation
|
// test command implementation
|
||||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||||
let library_paths: Vec<&str> = args.values_of("library-path").map(|v| v.collect()).unwrap_or_default();
|
let library_paths: Vec<&str> = args.values_of("library-path")
|
||||||
|
.map(|v| v.collect())
|
||||||
|
.unwrap_or_default();
|
||||||
let book_dir = get_book_dir(args);
|
let book_dir = get_book_dir(args);
|
||||||
let mut book = MDBook::new(&book_dir).read_config()?;
|
let mut book = MDBook::new(&book_dir).read_config()?;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::path::Path;
|
||||||
use self::notify::Watcher;
|
use self::notify::Watcher;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
use clap::{ArgMatches, SubCommand, App};
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
use mdbook::MDBook;
|
use mdbook::MDBook;
|
||||||
use mdbook::errors::Result;
|
use mdbook::errors::Result;
|
||||||
use {get_book_dir, open};
|
use {get_book_dir, open};
|
||||||
|
@ -14,9 +14,18 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||||
SubCommand::with_name("watch")
|
SubCommand::with_name("watch")
|
||||||
.about("Watch the files for changes")
|
.about("Watch the files for changes")
|
||||||
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
|
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
|
||||||
.arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'")
|
.arg_from_usage(
|
||||||
.arg_from_usage("--curly-quotes 'Convert straight quotes to curly quotes, except for those that occur in code blocks and code spans'")
|
"-d, --dest-dir=[dest-dir] 'The output directory for \
|
||||||
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'")
|
your book{n}(Defaults to ./book when omitted)'",
|
||||||
|
)
|
||||||
|
.arg_from_usage(
|
||||||
|
"--curly-quotes 'Convert straight quotes to curly quotes, except \
|
||||||
|
for those that occur in code blocks and code spans'",
|
||||||
|
)
|
||||||
|
.arg_from_usage(
|
||||||
|
"[dir] 'A directory for your book{n}(Defaults to \
|
||||||
|
Current Directory when omitted)'",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch command implementation
|
// Watch command implementation
|
||||||
|
@ -51,7 +60,8 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||||
|
|
||||||
// Calls the closure when a book source file is changed. This is blocking!
|
// Calls the closure when a book source file is changed. This is blocking!
|
||||||
pub fn trigger_on_change<F>(book: &mut MDBook, closure: F) -> ()
|
pub fn trigger_on_change<F>(book: &mut MDBook, closure: F) -> ()
|
||||||
where F: Fn(&Path, &mut MDBook) -> ()
|
where
|
||||||
|
F: Fn(&Path, &mut MDBook) -> (),
|
||||||
{
|
{
|
||||||
use self::notify::RecursiveMode::*;
|
use self::notify::RecursiveMode::*;
|
||||||
use self::notify::DebouncedEvent::*;
|
use self::notify::DebouncedEvent::*;
|
||||||
|
@ -64,7 +74,7 @@ pub fn trigger_on_change<F>(book: &mut MDBook, closure: F) -> ()
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error while trying to watch the files:\n\n\t{:?}", e);
|
println!("Error while trying to watch the files:\n\n\t{:?}", e);
|
||||||
::std::process::exit(0);
|
::std::process::exit(0);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add the source directory to the watcher
|
// Add the source directory to the watcher
|
||||||
|
@ -74,19 +84,20 @@ pub fn trigger_on_change<F>(book: &mut MDBook, closure: F) -> ()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add the theme directory to the watcher
|
// Add the theme directory to the watcher
|
||||||
watcher.watch(book.get_theme_path(), Recursive).unwrap_or_default();
|
watcher.watch(book.get_theme_path(), Recursive)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
|
||||||
// Add the book.{json,toml} file to the watcher if it exists, because it's not
|
// Add the book.{json,toml} file to the watcher if it exists, because it's not
|
||||||
// located in the source directory
|
// located in the source directory
|
||||||
if watcher
|
if watcher.watch(book.get_root().join("book.json"), NonRecursive)
|
||||||
.watch(book.get_root().join("book.json"), NonRecursive)
|
.is_err()
|
||||||
.is_err() {
|
{
|
||||||
// do nothing if book.json is not found
|
// do nothing if book.json is not found
|
||||||
}
|
}
|
||||||
if watcher
|
if watcher.watch(book.get_root().join("book.toml"), NonRecursive)
|
||||||
.watch(book.get_root().join("book.toml"), NonRecursive)
|
.is_err()
|
||||||
.is_err() {
|
{
|
||||||
// do nothing if book.toml is not found
|
// do nothing if book.toml is not found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,18 +107,15 @@ pub fn trigger_on_change<F>(book: &mut MDBook, closure: F) -> ()
|
||||||
match rx.recv() {
|
match rx.recv() {
|
||||||
Ok(event) => {
|
Ok(event) => {
|
||||||
match event {
|
match event {
|
||||||
Create(path) |
|
Create(path) | Write(path) | Remove(path) | Rename(_, path) => {
|
||||||
Write(path) |
|
|
||||||
Remove(path) |
|
|
||||||
Rename(_, path) => {
|
|
||||||
closure(&path, book);
|
closure(&path, book);
|
||||||
},
|
}
|
||||||
_ => {},
|
_ => {}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("An error occured: {:?}", e);
|
println!("An error occured: {:?}", e);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ pub struct BookItems<'a> {
|
||||||
|
|
||||||
impl Chapter {
|
impl Chapter {
|
||||||
pub fn new(name: String, path: PathBuf) -> Self {
|
pub fn new(name: String, path: PathBuf) -> Self {
|
||||||
|
|
||||||
Chapter {
|
Chapter {
|
||||||
name: name,
|
name: name,
|
||||||
path: path,
|
path: path,
|
||||||
|
@ -39,7 +38,8 @@ impl Chapter {
|
||||||
|
|
||||||
impl Serialize for Chapter {
|
impl Serialize for Chapter {
|
||||||
fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
|
||||||
where S: Serializer
|
where
|
||||||
|
S: Serializer,
|
||||||
{
|
{
|
||||||
let mut struct_ = serializer.serialize_struct("Chapter", 2)?;
|
let mut struct_ = serializer.serialize_struct("Chapter", 2)?;
|
||||||
struct_.serialize_field("name", &self.name)?;
|
struct_.serialize_field("name", &self.name)?;
|
||||||
|
@ -63,21 +63,20 @@ impl<'a> Iterator for BookItems<'a> {
|
||||||
Some((parent_items, parent_idx)) => {
|
Some((parent_items, parent_idx)) => {
|
||||||
self.items = parent_items;
|
self.items = parent_items;
|
||||||
self.current_index = parent_idx + 1;
|
self.current_index = parent_idx + 1;
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let cur = &self.items[self.current_index];
|
let cur = &self.items[self.current_index];
|
||||||
|
|
||||||
match *cur {
|
match *cur {
|
||||||
BookItem::Chapter(_, ref ch) |
|
BookItem::Chapter(_, ref ch) | BookItem::Affix(ref ch) => {
|
||||||
BookItem::Affix(ref ch) => {
|
|
||||||
self.stack.push((self.items, self.current_index));
|
self.stack.push((self.items, self.current_index));
|
||||||
self.items = &ch.sub_items[..];
|
self.items = &ch.sub_items[..];
|
||||||
self.current_index = 0;
|
self.current_index = 0;
|
||||||
},
|
}
|
||||||
BookItem::Spacer => {
|
BookItem::Spacer => {
|
||||||
self.current_index += 1;
|
self.current_index += 1;
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Some(cur);
|
return Some(cur);
|
||||||
|
|
113
src/book/mod.rs
113
src/book/mod.rs
|
@ -8,8 +8,8 @@ use std::io::{Read, Write};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use tempdir::TempDir;
|
use tempdir::TempDir;
|
||||||
|
|
||||||
use {theme, parse, utils};
|
use {parse, theme, utils};
|
||||||
use renderer::{Renderer, HtmlHandlebars};
|
use renderer::{HtmlHandlebars, Renderer};
|
||||||
use preprocess;
|
use preprocess;
|
||||||
use errors::*;
|
use errors::*;
|
||||||
|
|
||||||
|
@ -60,7 +60,6 @@ impl MDBook {
|
||||||
/// [`set_dest()`](#method.set_dest)
|
/// [`set_dest()`](#method.set_dest)
|
||||||
|
|
||||||
pub fn new<P: Into<PathBuf>>(root: P) -> MDBook {
|
pub fn new<P: Into<PathBuf>>(root: P) -> MDBook {
|
||||||
|
|
||||||
let root = root.into();
|
let root = root.into();
|
||||||
if !root.exists() || !root.is_dir() {
|
if !root.exists() || !root.is_dir() {
|
||||||
warn!("{:?} No directory with that name", root);
|
warn!("{:?} No directory with that name", root);
|
||||||
|
@ -130,7 +129,6 @@ impl MDBook {
|
||||||
/// `chapter_1.md` to the source directory.
|
/// `chapter_1.md` to the source directory.
|
||||||
|
|
||||||
pub fn init(&mut self) -> Result<()> {
|
pub fn init(&mut self) -> Result<()> {
|
||||||
|
|
||||||
debug!("[fn]: init");
|
debug!("[fn]: init");
|
||||||
|
|
||||||
if !self.config.get_root().exists() {
|
if !self.config.get_root().exists() {
|
||||||
|
@ -139,24 +137,25 @@ impl MDBook {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
if !self.get_destination().exists() {
|
if !self.get_destination().exists() {
|
||||||
debug!("[*]: {:?} does not exist, trying to create directory", self.get_destination());
|
debug!("[*]: {:?} does not exist, trying to create directory",
|
||||||
|
self.get_destination());
|
||||||
fs::create_dir_all(self.get_destination())?;
|
fs::create_dir_all(self.get_destination())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if !self.config.get_source().exists() {
|
if !self.config.get_source().exists() {
|
||||||
debug!("[*]: {:?} does not exist, trying to create directory", self.config.get_source());
|
debug!("[*]: {:?} does not exist, trying to create directory",
|
||||||
|
self.config.get_source());
|
||||||
fs::create_dir_all(self.config.get_source())?;
|
fs::create_dir_all(self.config.get_source())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let summary = self.config.get_source().join("SUMMARY.md");
|
let summary = self.config.get_source().join("SUMMARY.md");
|
||||||
|
|
||||||
if !summary.exists() {
|
if !summary.exists() {
|
||||||
|
|
||||||
// Summary does not exist, create it
|
// Summary does not exist, create it
|
||||||
debug!("[*]: {:?} does not exist, trying to create SUMMARY.md", &summary);
|
debug!("[*]: {:?} does not exist, trying to create SUMMARY.md",
|
||||||
|
&summary);
|
||||||
let mut f = File::create(&summary)?;
|
let mut f = File::create(&summary)?;
|
||||||
|
|
||||||
debug!("[*]: Writing to SUMMARY.md");
|
debug!("[*]: Writing to SUMMARY.md");
|
||||||
|
@ -175,16 +174,15 @@ impl MDBook {
|
||||||
debug!("[*]: item: {:?}", item);
|
debug!("[*]: item: {:?}", item);
|
||||||
let ch = match *item {
|
let ch = match *item {
|
||||||
BookItem::Spacer => continue,
|
BookItem::Spacer => continue,
|
||||||
BookItem::Chapter(_, ref ch) |
|
BookItem::Chapter(_, ref ch) | BookItem::Affix(ref ch) => ch,
|
||||||
BookItem::Affix(ref ch) => ch,
|
|
||||||
};
|
};
|
||||||
if !ch.path.as_os_str().is_empty() {
|
if !ch.path.as_os_str().is_empty() {
|
||||||
let path = self.config.get_source().join(&ch.path);
|
let path = self.config.get_source().join(&ch.path);
|
||||||
|
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
if !self.create_missing {
|
if !self.create_missing {
|
||||||
return Err(format!("'{}' referenced from SUMMARY.md does not exist.", path.to_string_lossy())
|
return Err(format!("'{}' referenced from SUMMARY.md does not exist.",
|
||||||
.into());
|
path.to_string_lossy()).into());
|
||||||
}
|
}
|
||||||
debug!("[*]: {:?} does not exist, trying to create file", path);
|
debug!("[*]: {:?} does not exist, trying to create file", path);
|
||||||
::std::fs::create_dir_all(path.parent().unwrap())?;
|
::std::fs::create_dir_all(path.parent().unwrap())?;
|
||||||
|
@ -203,21 +201,22 @@ impl MDBook {
|
||||||
pub fn create_gitignore(&self) {
|
pub fn create_gitignore(&self) {
|
||||||
let gitignore = self.get_gitignore();
|
let gitignore = self.get_gitignore();
|
||||||
|
|
||||||
let destination = self.config.get_html_config()
|
let destination = self.config.get_html_config().get_destination();
|
||||||
.get_destination();
|
|
||||||
|
|
||||||
// Check that the gitignore does not extist and that the destination path begins with the root path
|
// Check that the gitignore does not extist and
|
||||||
|
// that the destination path begins with the root path
|
||||||
// We assume tha if it does begin with the root path it is contained within. This assumption
|
// We assume tha if it does begin with the root path it is contained within. This assumption
|
||||||
// will not hold true for paths containing double dots to go back up e.g. `root/../destination`
|
// will not hold true for paths containing double dots to go back up
|
||||||
|
// e.g. `root/../destination`
|
||||||
if !gitignore.exists() && destination.starts_with(self.config.get_root()) {
|
if !gitignore.exists() && destination.starts_with(self.config.get_root()) {
|
||||||
|
let relative = destination.strip_prefix(self.config.get_root())
|
||||||
|
.expect("Could not strip the root prefix, path is not \
|
||||||
|
relative to root")
|
||||||
|
.to_str()
|
||||||
|
.expect("Could not convert to &str");
|
||||||
|
|
||||||
let relative = destination
|
debug!("[*]: {:?} does not exist, trying to create .gitignore",
|
||||||
.strip_prefix(self.config.get_root())
|
gitignore);
|
||||||
.expect("Could not strip the root prefix, path is not relative to root")
|
|
||||||
.to_str()
|
|
||||||
.expect("Could not convert to &str");
|
|
||||||
|
|
||||||
debug!("[*]: {:?} does not exist, trying to create .gitignore", gitignore);
|
|
||||||
|
|
||||||
let mut f = File::create(&gitignore).expect("Could not create file.");
|
let mut f = File::create(&gitignore).expect("Could not create file.");
|
||||||
|
|
||||||
|
@ -254,7 +253,8 @@ impl MDBook {
|
||||||
|
|
||||||
let themedir = self.config.get_html_config().get_theme();
|
let themedir = self.config.get_html_config().get_theme();
|
||||||
if !themedir.exists() {
|
if !themedir.exists() {
|
||||||
debug!("[*]: {:?} does not exist, trying to create directory", themedir);
|
debug!("[*]: {:?} does not exist, trying to create directory",
|
||||||
|
themedir);
|
||||||
fs::create_dir(&themedir)?;
|
fs::create_dir(&themedir)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,12 +286,10 @@ impl MDBook {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_file<P: AsRef<Path>>(&self, filename: P, content: &[u8]) -> Result<()> {
|
pub fn write_file<P: AsRef<Path>>(&self, filename: P, content: &[u8]) -> Result<()> {
|
||||||
let path = self.get_destination()
|
let path = self.get_destination().join(filename);
|
||||||
.join(filename);
|
|
||||||
|
|
||||||
utils::fs::create_file(&path)?
|
utils::fs::create_file(&path)?.write_all(content)
|
||||||
.write_all(content)
|
.map_err(|e| e.into())
|
||||||
.map_err(|e| e.into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses the `book.json` file (if it exists) to extract
|
/// Parses the `book.json` file (if it exists) to extract
|
||||||
|
@ -300,7 +298,6 @@ impl MDBook {
|
||||||
/// The root directory is the one specified when creating a new `MDBook`
|
/// The root directory is the one specified when creating a new `MDBook`
|
||||||
|
|
||||||
pub fn read_config(mut self) -> Result<Self> {
|
pub fn read_config(mut self) -> Result<Self> {
|
||||||
|
|
||||||
let toml = self.get_root().join("book.toml");
|
let toml = self.get_root().join("book.toml");
|
||||||
let json = self.get_root().join("book.json");
|
let json = self.get_root().join("book.json");
|
||||||
|
|
||||||
|
@ -362,14 +359,11 @@ impl MDBook {
|
||||||
.collect();
|
.collect();
|
||||||
let temp_dir = TempDir::new("mdbook")?;
|
let temp_dir = TempDir::new("mdbook")?;
|
||||||
for item in self.iter() {
|
for item in self.iter() {
|
||||||
|
|
||||||
if let BookItem::Chapter(_, ref ch) = *item {
|
if let BookItem::Chapter(_, ref ch) = *item {
|
||||||
if !ch.path.as_os_str().is_empty() {
|
if !ch.path.as_os_str().is_empty() {
|
||||||
|
|
||||||
let path = self.get_source().join(&ch.path);
|
let path = self.get_source().join(&ch.path);
|
||||||
let base = path.parent().ok_or_else(
|
let base = path.parent()
|
||||||
|| String::from("Invalid bookitem path!"),
|
.ok_or_else(|| String::from("Invalid bookitem path!"))?;
|
||||||
)?;
|
|
||||||
let content = utils::fs::file_to_string(&path)?;
|
let content = utils::fs::file_to_string(&path)?;
|
||||||
// Parse and expand links
|
// Parse and expand links
|
||||||
let content = preprocess::links::replace_all(&content, base)?;
|
let content = preprocess::links::replace_all(&content, base)?;
|
||||||
|
@ -380,10 +374,14 @@ impl MDBook {
|
||||||
let mut tmpf = utils::fs::create_file(&path)?;
|
let mut tmpf = utils::fs::create_file(&path)?;
|
||||||
tmpf.write_all(content.as_bytes())?;
|
tmpf.write_all(content.as_bytes())?;
|
||||||
|
|
||||||
let output = Command::new("rustdoc").arg(&path).arg("--test").args(&library_args).output()?;
|
let output = Command::new("rustdoc").arg(&path)
|
||||||
|
.arg("--test")
|
||||||
|
.args(&library_args)
|
||||||
|
.output()?;
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
bail!(ErrorKind::Subprocess("Rustdoc returned an error".to_string(), output));
|
bail!(ErrorKind::Subprocess("Rustdoc returned an error".to_string(),
|
||||||
|
output));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -398,15 +396,15 @@ impl MDBook {
|
||||||
|
|
||||||
pub fn with_destination<T: Into<PathBuf>>(mut self, destination: T) -> Self {
|
pub fn with_destination<T: Into<PathBuf>>(mut self, destination: T) -> Self {
|
||||||
let root = self.config.get_root().to_owned();
|
let root = self.config.get_root().to_owned();
|
||||||
self.config.get_mut_html_config()
|
self.config
|
||||||
|
.get_mut_html_config()
|
||||||
.set_destination(&root, &destination.into());
|
.set_destination(&root, &destination.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn get_destination(&self) -> &Path {
|
pub fn get_destination(&self) -> &Path {
|
||||||
self.config.get_html_config()
|
self.config.get_html_config().get_destination()
|
||||||
.get_destination()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_source<T: Into<PathBuf>>(mut self, source: T) -> Self {
|
pub fn with_source<T: Into<PathBuf>>(mut self, source: T) -> Self {
|
||||||
|
@ -452,61 +450,56 @@ impl MDBook {
|
||||||
|
|
||||||
pub fn with_theme_path<T: Into<PathBuf>>(mut self, theme_path: T) -> Self {
|
pub fn with_theme_path<T: Into<PathBuf>>(mut self, theme_path: T) -> Self {
|
||||||
let root = self.config.get_root().to_owned();
|
let root = self.config.get_root().to_owned();
|
||||||
self.config.get_mut_html_config()
|
self.config
|
||||||
|
.get_mut_html_config()
|
||||||
.set_theme(&root, &theme_path.into());
|
.set_theme(&root, &theme_path.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_theme_path(&self) -> &Path {
|
pub fn get_theme_path(&self) -> &Path {
|
||||||
self.config.get_html_config()
|
self.config.get_html_config().get_theme()
|
||||||
.get_theme()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_curly_quotes(mut self, curly_quotes: bool) -> Self {
|
pub fn with_curly_quotes(mut self, curly_quotes: bool) -> Self {
|
||||||
self.config.get_mut_html_config()
|
self.config
|
||||||
|
.get_mut_html_config()
|
||||||
.set_curly_quotes(curly_quotes);
|
.set_curly_quotes(curly_quotes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_curly_quotes(&self) -> bool {
|
pub fn get_curly_quotes(&self) -> bool {
|
||||||
self.config.get_html_config()
|
self.config.get_html_config().get_curly_quotes()
|
||||||
.get_curly_quotes()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_mathjax_support(mut self, mathjax_support: bool) -> Self {
|
pub fn with_mathjax_support(mut self, mathjax_support: bool) -> Self {
|
||||||
self.config.get_mut_html_config()
|
self.config
|
||||||
|
.get_mut_html_config()
|
||||||
.set_mathjax_support(mathjax_support);
|
.set_mathjax_support(mathjax_support);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mathjax_support(&self) -> bool {
|
pub fn get_mathjax_support(&self) -> bool {
|
||||||
self.config.get_html_config()
|
self.config.get_html_config().get_mathjax_support()
|
||||||
.get_mathjax_support()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_google_analytics_id(&self) -> Option<String> {
|
pub fn get_google_analytics_id(&self) -> Option<String> {
|
||||||
self.config.get_html_config()
|
self.config.get_html_config().get_google_analytics_id()
|
||||||
.get_google_analytics_id()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_additional_js(&self) -> bool {
|
pub fn has_additional_js(&self) -> bool {
|
||||||
self.config.get_html_config()
|
self.config.get_html_config().has_additional_js()
|
||||||
.has_additional_js()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_additional_js(&self) -> &[PathBuf] {
|
pub fn get_additional_js(&self) -> &[PathBuf] {
|
||||||
self.config.get_html_config()
|
self.config.get_html_config().get_additional_js()
|
||||||
.get_additional_js()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_additional_css(&self) -> bool {
|
pub fn has_additional_css(&self) -> bool {
|
||||||
self.config.get_html_config()
|
self.config.get_html_config().has_additional_css()
|
||||||
.has_additional_css()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_additional_css(&self) -> &[PathBuf] {
|
pub fn get_additional_css(&self) -> &[PathBuf] {
|
||||||
self.config.get_html_config()
|
self.config.get_html_config().get_additional_css()
|
||||||
.get_additional_css()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_html_config(&self) -> &HtmlConfig {
|
pub fn get_html_config(&self) -> &HtmlConfig {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::path::{PathBuf, Path};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use super::HtmlConfig;
|
use super::HtmlConfig;
|
||||||
use super::tomlconfig::TomlConfig;
|
use super::tomlconfig::TomlConfig;
|
||||||
|
@ -33,7 +33,8 @@ impl BookConfig {
|
||||||
///
|
///
|
||||||
/// assert_eq!(config.get_root(), &root);
|
/// assert_eq!(config.get_root(), &root);
|
||||||
/// assert_eq!(config.get_source(), PathBuf::from("directory/to/my/book/src"));
|
/// assert_eq!(config.get_source(), PathBuf::from("directory/to/my/book/src"));
|
||||||
/// assert_eq!(config.get_html_config(), &HtmlConfig::new(PathBuf::from("directory/to/my/book")));
|
/// assert_eq!(config.get_html_config(),
|
||||||
|
/// &HtmlConfig::new(PathBuf::from("directory/to/my/book")));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new<T: Into<PathBuf>>(root: T) -> Self {
|
pub fn new<T: Into<PathBuf>>(root: T) -> Self {
|
||||||
let root: PathBuf = root.into();
|
let root: PathBuf = root.into();
|
||||||
|
@ -86,7 +87,6 @@ impl BookConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fill_from_tomlconfig(&mut self, tomlconfig: TomlConfig) -> &mut Self {
|
pub fn fill_from_tomlconfig(&mut self, tomlconfig: TomlConfig) -> &mut Self {
|
||||||
|
|
||||||
if let Some(s) = tomlconfig.source {
|
if let Some(s) = tomlconfig.source {
|
||||||
self.set_source(s);
|
self.set_source(s);
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,7 @@ impl BookConfig {
|
||||||
self.get_mut_html_config()
|
self.get_mut_html_config()
|
||||||
.fill_from_tomlconfig(root, tomlhtmlconfig);
|
.fill_from_tomlconfig(root, tomlhtmlconfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +128,6 @@ impl BookConfig {
|
||||||
/// The JSON configuration file is **deprecated** and should not be used anymore.
|
/// The JSON configuration file is **deprecated** and should not be used anymore.
|
||||||
/// Please, migrate to the TOML configuration file.
|
/// Please, migrate to the TOML configuration file.
|
||||||
pub fn fill_from_jsonconfig(&mut self, jsonconfig: JsonConfig) -> &mut Self {
|
pub fn fill_from_jsonconfig(&mut self, jsonconfig: JsonConfig) -> &mut Self {
|
||||||
|
|
||||||
if let Some(s) = jsonconfig.src {
|
if let Some(s) = jsonconfig.src {
|
||||||
self.set_source(s);
|
self.set_source(s);
|
||||||
}
|
}
|
||||||
|
@ -147,14 +146,12 @@ impl BookConfig {
|
||||||
|
|
||||||
if let Some(d) = jsonconfig.dest {
|
if let Some(d) = jsonconfig.dest {
|
||||||
let root = self.get_root().to_owned();
|
let root = self.get_root().to_owned();
|
||||||
self.get_mut_html_config()
|
self.get_mut_html_config().set_destination(&root, &d);
|
||||||
.set_destination(&root, &d);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(d) = jsonconfig.theme_path {
|
if let Some(d) = jsonconfig.theme_path {
|
||||||
let root = self.get_root().to_owned();
|
let root = self.get_root().to_owned();
|
||||||
self.get_mut_html_config()
|
self.get_mut_html_config().set_theme(&root, &d);
|
||||||
.set_theme(&root, &d);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::path::{PathBuf, Path};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use super::tomlconfig::TomlHtmlConfig;
|
use super::tomlconfig::TomlHtmlConfig;
|
||||||
use super::playpenconfig::PlaypenConfig;
|
use super::playpenconfig::PlaypenConfig;
|
||||||
|
@ -16,7 +16,8 @@ pub struct HtmlConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HtmlConfig {
|
impl HtmlConfig {
|
||||||
/// Creates a new `HtmlConfig` struct containing the configuration parameters for the HTML renderer.
|
/// Creates a new `HtmlConfig` struct containing
|
||||||
|
/// the configuration parameters for the HTML renderer.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use std::path::PathBuf;
|
/// # use std::path::PathBuf;
|
||||||
|
@ -42,7 +43,10 @@ impl HtmlConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fill_from_tomlconfig<T: Into<PathBuf>>(&mut self, root: T, tomlconfig: TomlHtmlConfig) -> &mut Self {
|
pub fn fill_from_tomlconfig<T: Into<PathBuf>>(&mut self,
|
||||||
|
root: T,
|
||||||
|
tomlconfig: TomlHtmlConfig)
|
||||||
|
-> &mut Self {
|
||||||
let root = root.into();
|
let root = root.into();
|
||||||
|
|
||||||
if let Some(d) = tomlconfig.destination {
|
if let Some(d) = tomlconfig.destination {
|
||||||
|
|
|
@ -34,8 +34,7 @@ pub struct JsonConfig {
|
||||||
/// ```
|
/// ```
|
||||||
impl JsonConfig {
|
impl JsonConfig {
|
||||||
pub fn from_json(input: &str) -> Result<Self> {
|
pub fn from_json(input: &str) -> Result<Self> {
|
||||||
let config: JsonConfig = serde_json::from_str(input)
|
let config: JsonConfig = serde_json::from_str(input).chain_err(|| "Could not parse JSON")?;
|
||||||
.chain_err(|| "Could not parse JSON")?;
|
|
||||||
|
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::path::{PathBuf, Path};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use super::tomlconfig::TomlPlaypenConfig;
|
use super::tomlconfig::TomlPlaypenConfig;
|
||||||
|
|
||||||
|
@ -28,9 +28,12 @@ impl PlaypenConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fill_from_tomlconfig<T: Into<PathBuf>>(&mut self, root: T, tomlplaypenconfig: TomlPlaypenConfig) -> &mut Self {
|
pub fn fill_from_tomlconfig<T: Into<PathBuf>>(&mut self,
|
||||||
|
root: T,
|
||||||
|
tomlplaypenconfig: TomlPlaypenConfig)
|
||||||
|
-> &mut Self {
|
||||||
let root = root.into();
|
let root = root.into();
|
||||||
|
|
||||||
if let Some(editor) = tomlplaypenconfig.editor {
|
if let Some(editor) = tomlplaypenconfig.editor {
|
||||||
if editor.is_relative() {
|
if editor.is_relative() {
|
||||||
self.editor = root.join(editor);
|
self.editor = root.join(editor);
|
||||||
|
|
|
@ -46,16 +46,15 @@ pub struct TomlPlaypenConfig {
|
||||||
/// let toml = r#"title="Some title"
|
/// let toml = r#"title="Some title"
|
||||||
/// [output.html]
|
/// [output.html]
|
||||||
/// destination = "htmlbook" "#;
|
/// destination = "htmlbook" "#;
|
||||||
///
|
///
|
||||||
/// let config = TomlConfig::from_toml(&toml).expect("Should parse correctly");
|
/// let config = TomlConfig::from_toml(&toml).expect("Should parse correctly");
|
||||||
/// assert_eq!(config.title, Some(String::from("Some title")));
|
/// assert_eq!(config.title, Some(String::from("Some title")));
|
||||||
/// assert_eq!(config.output.unwrap().html.unwrap().destination, Some(PathBuf::from("htmlbook")));
|
/// assert_eq!(config.output.unwrap().html.unwrap().destination, Some(PathBuf::from("htmlbook")));
|
||||||
/// ```
|
/// ```
|
||||||
impl TomlConfig {
|
impl TomlConfig {
|
||||||
pub fn from_toml(input: &str) -> Result<Self> {
|
pub fn from_toml(input: &str) -> Result<Self> {
|
||||||
let config: TomlConfig = toml::from_str(input)
|
let config: TomlConfig = toml::from_str(input).chain_err(|| "Could not parse TOML")?;
|
||||||
.chain_err(|| "Could not parse TOML")?;
|
|
||||||
|
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
//! **mdBook** is similar to Gitbook but implemented in Rust.
|
//! **mdBook** is similar to Gitbook but implemented in Rust.
|
||||||
//! It offers a command line interface, but can also be used as a regular crate.
|
//! It offers a command line interface, but can also be used as a regular crate.
|
||||||
//!
|
//!
|
||||||
//! This is the API doc, but you can find a [less "low-level" documentation here](../index.html) that
|
//! This is the API doc, but you can find a [less "low-level" documentation here](../index.html)
|
||||||
//! contains information about the command line tool, format, structure etc.
|
//! that contains information about the command line tool, format, structure etc.
|
||||||
//! It is also rendered with mdBook to showcase the features and default theme.
|
//! It is also rendered with mdBook to showcase the features and default theme.
|
||||||
//!
|
//!
|
||||||
//! Some reasons why you would want to use the crate (over the cli):
|
//! Some reasons why you would want to use the crate (over the cli):
|
||||||
|
@ -80,9 +80,9 @@ extern crate lazy_static;
|
||||||
extern crate log;
|
extern crate log;
|
||||||
extern crate pulldown_cmark;
|
extern crate pulldown_cmark;
|
||||||
extern crate regex;
|
extern crate regex;
|
||||||
|
extern crate serde;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
extern crate serde;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
extern crate tempdir;
|
extern crate tempdir;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Result, Error, ErrorKind};
|
use std::io::{Error, ErrorKind, Read, Result};
|
||||||
use book::bookitem::{BookItem, Chapter};
|
use book::bookitem::{BookItem, Chapter};
|
||||||
|
|
||||||
pub fn construct_bookitems(path: &PathBuf) -> Result<Vec<BookItem>> {
|
pub fn construct_bookitems(path: &PathBuf) -> Result<Vec<BookItem>> {
|
||||||
|
@ -14,7 +14,10 @@ pub fn construct_bookitems(path: &PathBuf) -> Result<Vec<BookItem>> {
|
||||||
Ok(top_items)
|
Ok(top_items)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32>) -> Result<Vec<BookItem>> {
|
fn parse_level(summary: &mut Vec<&str>,
|
||||||
|
current_level: i32,
|
||||||
|
mut section: Vec<i32>)
|
||||||
|
-> Result<Vec<BookItem>> {
|
||||||
debug!("[fn]: parse_level");
|
debug!("[fn]: parse_level");
|
||||||
let mut items: Vec<BookItem> = vec![];
|
let mut items: Vec<BookItem> = vec![];
|
||||||
|
|
||||||
|
@ -36,9 +39,9 @@ fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32
|
||||||
// Level can not be root level !!
|
// Level can not be root level !!
|
||||||
// Add a sub-number to section
|
// Add a sub-number to section
|
||||||
section.push(0);
|
section.push(0);
|
||||||
let last = items
|
let last = items.pop().expect(
|
||||||
.pop()
|
"There should be at least one item since this can't be the root level",
|
||||||
.expect("There should be at least one item since this can't be the root level");
|
);
|
||||||
|
|
||||||
if let BookItem::Chapter(ref s, ref ch) = last {
|
if let BookItem::Chapter(ref s, ref ch) = last {
|
||||||
let mut ch = ch.clone();
|
let mut ch = ch.clone();
|
||||||
|
@ -49,52 +52,57 @@ fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32
|
||||||
section.pop();
|
section.pop();
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::new(ErrorKind::Other,
|
return Err(Error::new(
|
||||||
"Your summary.md is messed up\n\n
|
ErrorKind::Other,
|
||||||
|
"Your summary.md is messed up\n\n
|
||||||
Prefix, \
|
Prefix, \
|
||||||
Suffix and Spacer elements can only exist on the root level.\n
|
Suffix and Spacer elements can only exist \
|
||||||
|
on the root level.\n
|
||||||
\
|
\
|
||||||
Prefix elements can only exist before any chapter and there can be \
|
Prefix elements can only exist before \
|
||||||
no chapters after suffix elements."));
|
any chapter and there can be \
|
||||||
|
no chapters after suffix elements.",
|
||||||
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// level and current_level are the same, parse the line
|
// level and current_level are the same, parse the line
|
||||||
item = if let Some(parsed_item) = parse_line(summary[0]) {
|
item = if let Some(parsed_item) = parse_line(summary[0]) {
|
||||||
|
|
||||||
// Eliminate possible errors and set section to -1 after suffix
|
// Eliminate possible errors and set section to -1 after suffix
|
||||||
match parsed_item {
|
match parsed_item {
|
||||||
// error if level != 0 and BookItem is != Chapter
|
// error if level != 0 and BookItem is != Chapter
|
||||||
BookItem::Affix(_) |
|
BookItem::Affix(_) | BookItem::Spacer if level > 0 => {
|
||||||
BookItem::Spacer if level > 0 => {
|
return Err(Error::new(
|
||||||
return Err(Error::new(ErrorKind::Other,
|
ErrorKind::Other,
|
||||||
"Your summary.md is messed up\n\n
|
"Your summary.md is messed up\n\n
|
||||||
\
|
\
|
||||||
Prefix, Suffix and Spacer elements can only exist on the \
|
Prefix, Suffix and Spacer elements \
|
||||||
root level.\n
|
can only exist on the root level.\n
|
||||||
Prefix \
|
Prefix \
|
||||||
elements can only exist before any chapter and there can be \
|
elements can only exist before any chapter and \
|
||||||
no chapters after suffix elements."))
|
there can be no chapters after suffix elements.",
|
||||||
},
|
))
|
||||||
|
}
|
||||||
|
|
||||||
// error if BookItem == Chapter and section == -1
|
// error if BookItem == Chapter and section == -1
|
||||||
BookItem::Chapter(_, _) if section[0] == -1 => {
|
BookItem::Chapter(_, _) if section[0] == -1 => {
|
||||||
return Err(Error::new(ErrorKind::Other,
|
return Err(Error::new(
|
||||||
"Your summary.md is messed up\n\n
|
ErrorKind::Other,
|
||||||
|
"Your summary.md is messed up\n\n
|
||||||
\
|
\
|
||||||
Prefix, Suffix and Spacer elements can only exist on the \
|
Prefix, Suffix and Spacer elements can only \
|
||||||
root level.\n
|
exist on the root level.\n
|
||||||
Prefix \
|
Prefix \
|
||||||
elements can only exist before any chapter and there can be \
|
elements can only exist before any chapter and \
|
||||||
no chapters after suffix elements."))
|
there can be no chapters after suffix elements.",
|
||||||
},
|
))
|
||||||
|
}
|
||||||
|
|
||||||
// Set section = -1 after suffix
|
// Set section = -1 after suffix
|
||||||
BookItem::Affix(_) if section[0] > 0 => {
|
BookItem::Affix(_) if section[0] > 0 => {
|
||||||
section[0] = -1;
|
section[0] = -1;
|
||||||
},
|
}
|
||||||
|
|
||||||
_ => {},
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
match parsed_item {
|
match parsed_item {
|
||||||
|
@ -102,14 +110,12 @@ fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32
|
||||||
// Increment section
|
// Increment section
|
||||||
let len = section.len() - 1;
|
let len = section.len() - 1;
|
||||||
section[len] += 1;
|
section[len] += 1;
|
||||||
let s = section
|
let s = section.iter()
|
||||||
.iter()
|
.fold("".to_owned(), |s, i| s + &i.to_string() + ".");
|
||||||
.fold("".to_owned(), |s, i| s + &i.to_string() + ".");
|
|
||||||
BookItem::Chapter(s, ch)
|
BookItem::Chapter(s, ch)
|
||||||
},
|
}
|
||||||
_ => parsed_item,
|
_ => parsed_item,
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// If parse_line does not return Some(_) continue...
|
// If parse_line does not return Some(_) continue...
|
||||||
summary.remove(0);
|
summary.remove(0);
|
||||||
|
@ -146,8 +152,10 @@ fn level(line: &str, spaces_in_tab: i32) -> Result<i32> {
|
||||||
if spaces > 0 {
|
if spaces > 0 {
|
||||||
debug!("[SUMMARY.md]:");
|
debug!("[SUMMARY.md]:");
|
||||||
debug!("\t[line]: {}", line);
|
debug!("\t[line]: {}", line);
|
||||||
debug!("[*]: There is an indentation error on this line. Indentation should be {} spaces", spaces_in_tab);
|
debug!("[*]: There is an indentation error on this line. Indentation should be {} spaces",
|
||||||
return Err(Error::new(ErrorKind::Other, format!("Indentation error on line:\n\n{}", line)));
|
spaces_in_tab);
|
||||||
|
return Err(Error::new(ErrorKind::Other,
|
||||||
|
format!("Indentation error on line:\n\n{}", line)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(level)
|
Ok(level)
|
||||||
|
@ -177,7 +185,7 @@ fn parse_line(l: &str) -> Option<BookItem> {
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
// Non-list element
|
// Non-list element
|
||||||
'[' => {
|
'[' => {
|
||||||
debug!("[*]: Line is a link element");
|
debug!("[*]: Line is a link element");
|
||||||
|
@ -187,8 +195,8 @@ fn parse_line(l: &str) -> Option<BookItem> {
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => {},
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,6 @@ struct Link<'a> {
|
||||||
|
|
||||||
impl<'a> Link<'a> {
|
impl<'a> Link<'a> {
|
||||||
fn from_capture(cap: Captures<'a>) -> Option<Link<'a>> {
|
fn from_capture(cap: Captures<'a>) -> Option<Link<'a>> {
|
||||||
|
|
||||||
let link_type = match (cap.get(0), cap.get(1), cap.get(2)) {
|
let link_type = match (cap.get(0), cap.get(1), cap.get(2)) {
|
||||||
(_, Some(typ), Some(rest)) => {
|
(_, Some(typ), Some(rest)) => {
|
||||||
let mut path_props = rest.as_str().split_whitespace();
|
let mut path_props = rest.as_str().split_whitespace();
|
||||||
|
@ -51,21 +50,22 @@ impl<'a> Link<'a> {
|
||||||
("playpen", Some(pth)) => Some(LinkType::Playpen(pth, props)),
|
("playpen", Some(pth)) => Some(LinkType::Playpen(pth, props)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
(Some(mat), None, None) if mat.as_str().starts_with(ESCAPE_CHAR) => Some(LinkType::Escaped),
|
(Some(mat), None, None) if mat.as_str().starts_with(ESCAPE_CHAR) => {
|
||||||
|
Some(LinkType::Escaped)
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
link_type.and_then(|lnk| {
|
link_type.and_then(|lnk| {
|
||||||
cap.get(0)
|
cap.get(0).map(|mat| {
|
||||||
.map(|mat| {
|
Link {
|
||||||
Link {
|
start_index: mat.start(),
|
||||||
start_index: mat.start(),
|
end_index: mat.end(),
|
||||||
end_index: mat.end(),
|
link: lnk,
|
||||||
link: lnk,
|
link_text: mat.as_str(),
|
||||||
link_text: mat.as_str(),
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,14 +75,22 @@ impl<'a> Link<'a> {
|
||||||
// omit the escape char
|
// omit the escape char
|
||||||
LinkType::Escaped => Ok((&self.link_text[1..]).to_owned()),
|
LinkType::Escaped => Ok((&self.link_text[1..]).to_owned()),
|
||||||
LinkType::Include(ref pat) => {
|
LinkType::Include(ref pat) => {
|
||||||
file_to_string(base.join(pat)).chain_err(|| format!("Could not read file for link {}", self.link_text))
|
file_to_string(base.join(pat)).chain_err(|| {
|
||||||
},
|
format!("Could not read file for \
|
||||||
|
link {}",
|
||||||
|
self.link_text)
|
||||||
|
})
|
||||||
|
}
|
||||||
LinkType::Playpen(ref pat, ref attrs) => {
|
LinkType::Playpen(ref pat, ref attrs) => {
|
||||||
let contents = file_to_string(base.join(pat))
|
let contents = file_to_string(base.join(pat)).chain_err(|| {
|
||||||
.chain_err(|| format!("Could not read file for link {}", self.link_text))?;
|
format!("Could not \
|
||||||
|
read file \
|
||||||
|
for link {}",
|
||||||
|
self.link_text)
|
||||||
|
})?;
|
||||||
let ftype = if !attrs.is_empty() { "rust," } else { "rust" };
|
let ftype = if !attrs.is_empty() { "rust," } else { "rust" };
|
||||||
Ok(format!("```{}{}\n{}\n```\n", ftype, attrs.join(","), contents))
|
Ok(format!("```{}{}\n{}\n```\n", ftype, attrs.join(","), contents))
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,7 +198,8 @@ fn test_find_links_escaped_link() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_find_playpens_with_properties() {
|
fn test_find_playpens_with_properties() {
|
||||||
let s = "Some random text with escaped playpen {{#playpen file.rs editable }} and some more\n text {{#playpen my.rs editable no_run should_panic}} ...";
|
let s = "Some random text with escaped playpen {{#playpen file.rs editable }} and some more\n \
|
||||||
|
text {{#playpen my.rs editable no_run should_panic}} ...";
|
||||||
|
|
||||||
let res = find_links(s).collect::<Vec<_>>();
|
let res = find_links(s).collect::<Vec<_>>();
|
||||||
println!("\nOUTPUT: {:?}\n", res);
|
println!("\nOUTPUT: {:?}\n", res);
|
||||||
|
@ -202,16 +211,19 @@ fn test_find_playpens_with_properties() {
|
||||||
link_text: "{{#playpen file.rs editable }}",
|
link_text: "{{#playpen file.rs editable }}",
|
||||||
},
|
},
|
||||||
Link {
|
Link {
|
||||||
start_index: 90,
|
start_index: 89,
|
||||||
end_index: 137,
|
end_index: 136,
|
||||||
link: LinkType::Playpen(PathBuf::from("my.rs"), vec!["editable", "no_run", "should_panic"]),
|
link: LinkType::Playpen(PathBuf::from("my.rs"),
|
||||||
|
vec!["editable", "no_run", "should_panic"]),
|
||||||
link_text: "{{#playpen my.rs editable no_run should_panic}}",
|
link_text: "{{#playpen my.rs editable no_run should_panic}}",
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_find_all_link_types() {
|
fn test_find_all_link_types() {
|
||||||
let s = "Some random text with escaped playpen {{#include file.rs}} and \\{{#contents are insignifficant in escaped link}} some more\n text {{#playpen my.rs editable no_run should_panic}} ...";
|
let s = "Some random text with escaped playpen {{#include file.rs}} and \\{{#contents are \
|
||||||
|
insignifficant in escaped link}} some more\n text {{#playpen my.rs editable no_run \
|
||||||
|
should_panic}} ...";
|
||||||
|
|
||||||
let res = find_links(s).collect::<Vec<_>>();
|
let res = find_links(s).collect::<Vec<_>>();
|
||||||
println!("\nOUTPUT: {:?}\n", res);
|
println!("\nOUTPUT: {:?}\n", res);
|
||||||
|
@ -234,7 +246,8 @@ fn test_find_all_link_types() {
|
||||||
Link {
|
Link {
|
||||||
start_index: 130,
|
start_index: 130,
|
||||||
end_index: 177,
|
end_index: 177,
|
||||||
link: LinkType::Playpen(PathBuf::from("my.rs"), vec!["editable", "no_run", "should_panic"]),
|
link: LinkType::Playpen(PathBuf::from("my.rs"),
|
||||||
|
vec!["editable", "no_run", "should_panic"]),
|
||||||
link_text: "{{#playpen my.rs editable no_run should_panic}}",
|
link_text: "{{#playpen my.rs editable no_run should_panic}}",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@ use renderer::Renderer;
|
||||||
use book::MDBook;
|
use book::MDBook;
|
||||||
use book::bookitem::{BookItem, Chapter};
|
use book::bookitem::{BookItem, Chapter};
|
||||||
use config::PlaypenConfig;
|
use config::PlaypenConfig;
|
||||||
use {utils, theme};
|
use {theme, utils};
|
||||||
use theme::{Theme, playpen_editor};
|
use theme::{playpen_editor, Theme};
|
||||||
use errors::*;
|
use errors::*;
|
||||||
use regex::{Regex, Captures};
|
use regex::{Captures, Regex};
|
||||||
|
|
||||||
use std::ascii::AsciiExt;
|
use std::ascii::AsciiExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -28,18 +28,20 @@ impl HtmlHandlebars {
|
||||||
HtmlHandlebars
|
HtmlHandlebars
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_item(&self, item: &BookItem, mut ctx: RenderItemContext, print_content: &mut String)
|
fn render_item(&self,
|
||||||
-> Result<()> {
|
item: &BookItem,
|
||||||
|
mut ctx: RenderItemContext,
|
||||||
|
print_content: &mut String)
|
||||||
|
-> Result<()> {
|
||||||
// FIXME: This should be made DRY-er and rely less on mutable state
|
// FIXME: This should be made DRY-er and rely less on mutable state
|
||||||
match *item {
|
match *item {
|
||||||
BookItem::Chapter(_, ref ch) |
|
BookItem::Chapter(_, ref ch) | BookItem::Affix(ref ch)
|
||||||
BookItem::Affix(ref ch) if !ch.path.as_os_str().is_empty() => {
|
if !ch.path.as_os_str().is_empty() =>
|
||||||
|
{
|
||||||
let path = ctx.book.get_source().join(&ch.path);
|
let path = ctx.book.get_source().join(&ch.path);
|
||||||
let content = utils::fs::file_to_string(&path)?;
|
let content = utils::fs::file_to_string(&path)?;
|
||||||
let base = path.parent().ok_or_else(
|
let base = path.parent()
|
||||||
|| String::from("Invalid bookitem path!"),
|
.ok_or_else(|| String::from("Invalid bookitem path!"))?;
|
||||||
)?;
|
|
||||||
|
|
||||||
// Parse and expand links
|
// Parse and expand links
|
||||||
let content = preprocess::links::replace_all(&content, base)?;
|
let content = preprocess::links::replace_all(&content, base)?;
|
||||||
|
@ -48,34 +50,39 @@ impl HtmlHandlebars {
|
||||||
|
|
||||||
// Update the context with data for this file
|
// Update the context with data for this file
|
||||||
let path = ch.path.to_str().ok_or_else(|| {
|
let path = ch.path.to_str().ok_or_else(|| {
|
||||||
io::Error::new(io::ErrorKind::Other, "Could not convert path to str")
|
io::Error::new(io::ErrorKind::Other,
|
||||||
})?;
|
"Could not convert path \
|
||||||
|
to str")
|
||||||
|
})?;
|
||||||
|
|
||||||
// Non-lexical lifetimes needed :'(
|
// Non-lexical lifetimes needed :'(
|
||||||
let title: String;
|
let title: String;
|
||||||
{
|
{
|
||||||
let book_title = ctx.data.get("book_title").and_then(serde_json::Value::as_str).unwrap_or("");
|
let book_title = ctx.data
|
||||||
|
.get("book_title")
|
||||||
|
.and_then(serde_json::Value::as_str)
|
||||||
|
.unwrap_or("");
|
||||||
title = ch.name.clone() + " - " + book_title;
|
title = ch.name.clone() + " - " + book_title;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.data.insert("path".to_owned(), json!(path));
|
ctx.data.insert("path".to_owned(), json!(path));
|
||||||
ctx.data.insert("content".to_owned(), json!(content));
|
ctx.data.insert("content".to_owned(), json!(content));
|
||||||
ctx.data.insert("chapter_title".to_owned(), json!(ch.name));
|
ctx.data.insert("chapter_title".to_owned(), json!(ch.name));
|
||||||
ctx.data.insert("title".to_owned(), json!(title));
|
ctx.data.insert("title".to_owned(), json!(title));
|
||||||
ctx.data.insert(
|
ctx.data.insert("path_to_root".to_owned(),
|
||||||
"path_to_root".to_owned(),
|
json!(utils::fs::path_to_root(&ch.path)));
|
||||||
json!(utils::fs::path_to_root(&ch.path)),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Render the handlebars template with the data
|
// Render the handlebars template with the data
|
||||||
debug!("[*]: Render template");
|
debug!("[*]: Render template");
|
||||||
let rendered = ctx.handlebars.render("index", &ctx.data)?;
|
let rendered = ctx.handlebars.render("index", &ctx.data)?;
|
||||||
|
|
||||||
let filepath = Path::new(&ch.path).with_extension("html");
|
let filepath = Path::new(&ch.path).with_extension("html");
|
||||||
let rendered = self.post_process(rendered,
|
let rendered = self.post_process(
|
||||||
&normalize_path(filepath.to_str()
|
rendered,
|
||||||
.ok_or(Error::from(format!("Bad file name: {}", filepath.display())))?),
|
&normalize_path(filepath.to_str().ok_or(Error::from(
|
||||||
ctx.book.get_html_config().get_playpen_config()
|
format!("Bad file name: {}", filepath.display()),
|
||||||
|
))?),
|
||||||
|
ctx.book.get_html_config().get_playpen_config(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Write to file
|
// Write to file
|
||||||
|
@ -85,8 +92,8 @@ impl HtmlHandlebars {
|
||||||
if ctx.is_index {
|
if ctx.is_index {
|
||||||
self.render_index(ctx.book, ch, &ctx.destination)?;
|
self.render_index(ctx.book, ch, &ctx.destination)?;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => {},
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -104,24 +111,24 @@ impl HtmlHandlebars {
|
||||||
// This could cause a problem when someone displays
|
// This could cause a problem when someone displays
|
||||||
// code containing <base href=...>
|
// code containing <base href=...>
|
||||||
// on the front page, however this case should be very very rare...
|
// on the front page, however this case should be very very rare...
|
||||||
content = content
|
content = content.lines()
|
||||||
.lines()
|
.filter(|line| !line.contains("<base href="))
|
||||||
.filter(|line| !line.contains("<base href="))
|
.collect::<Vec<&str>>()
|
||||||
.collect::<Vec<&str>>()
|
.join("\n");
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
book.write_file("index.html", content.as_bytes())?;
|
book.write_file("index.html", content.as_bytes())?;
|
||||||
|
|
||||||
info!(
|
info!("[*] Creating index.html from {:?} ✓",
|
||||||
"[*] Creating index.html from {:?} ✓",
|
book.get_destination().join(&ch.path.with_extension("html")));
|
||||||
book.get_destination()
|
|
||||||
.join(&ch.path.with_extension("html"))
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn post_process(&self, rendered: String, filepath: &str, playpen_config: &PlaypenConfig) -> String {
|
fn post_process(&self,
|
||||||
|
rendered: String,
|
||||||
|
filepath: &str,
|
||||||
|
playpen_config: &PlaypenConfig)
|
||||||
|
-> String {
|
||||||
let rendered = build_header_links(&rendered, &filepath);
|
let rendered = build_header_links(&rendered, &filepath);
|
||||||
let rendered = fix_anchor_links(&rendered, &filepath);
|
let rendered = fix_anchor_links(&rendered, &filepath);
|
||||||
let rendered = fix_code_blocks(&rendered);
|
let rendered = fix_code_blocks(&rendered);
|
||||||
|
@ -136,45 +143,24 @@ impl HtmlHandlebars {
|
||||||
book.write_file("favicon.png", &theme.favicon)?;
|
book.write_file("favicon.png", &theme.favicon)?;
|
||||||
book.write_file("jquery.js", &theme.jquery)?;
|
book.write_file("jquery.js", &theme.jquery)?;
|
||||||
book.write_file("highlight.css", &theme.highlight_css)?;
|
book.write_file("highlight.css", &theme.highlight_css)?;
|
||||||
book.write_file(
|
book.write_file("tomorrow-night.css", &theme.tomorrow_night_css)?;
|
||||||
"tomorrow-night.css",
|
book.write_file("ayu-highlight.css", &theme.ayu_highlight_css)?;
|
||||||
&theme.tomorrow_night_css,
|
|
||||||
)?;
|
|
||||||
book.write_file(
|
|
||||||
"ayu-highlight.css",
|
|
||||||
&theme.ayu_highlight_css,
|
|
||||||
)?;
|
|
||||||
book.write_file("highlight.js", &theme.highlight_js)?;
|
book.write_file("highlight.js", &theme.highlight_js)?;
|
||||||
book.write_file("clipboard.min.js", &theme.clipboard_js)?;
|
book.write_file("clipboard.min.js", &theme.clipboard_js)?;
|
||||||
book.write_file("store.js", &theme.store_js)?;
|
book.write_file("store.js", &theme.store_js)?;
|
||||||
book.write_file(
|
book.write_file("_FontAwesome/css/font-awesome.css", theme::FONT_AWESOME)?;
|
||||||
"_FontAwesome/css/font-awesome.css",
|
book.write_file("_FontAwesome/fonts/fontawesome-webfont.eot",
|
||||||
theme::FONT_AWESOME,
|
theme::FONT_AWESOME_EOT)?;
|
||||||
)?;
|
book.write_file("_FontAwesome/fonts/fontawesome-webfont.svg",
|
||||||
book.write_file(
|
theme::FONT_AWESOME_SVG)?;
|
||||||
"_FontAwesome/fonts/fontawesome-webfont.eot",
|
book.write_file("_FontAwesome/fonts/fontawesome-webfont.ttf",
|
||||||
theme::FONT_AWESOME_EOT,
|
theme::FONT_AWESOME_TTF)?;
|
||||||
)?;
|
book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff",
|
||||||
book.write_file(
|
theme::FONT_AWESOME_WOFF)?;
|
||||||
"_FontAwesome/fonts/fontawesome-webfont.svg",
|
book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff2",
|
||||||
theme::FONT_AWESOME_SVG,
|
theme::FONT_AWESOME_WOFF2)?;
|
||||||
)?;
|
book.write_file("_FontAwesome/fonts/FontAwesome.ttf",
|
||||||
book.write_file(
|
theme::FONT_AWESOME_TTF)?;
|
||||||
"_FontAwesome/fonts/fontawesome-webfont.ttf",
|
|
||||||
theme::FONT_AWESOME_TTF,
|
|
||||||
)?;
|
|
||||||
book.write_file(
|
|
||||||
"_FontAwesome/fonts/fontawesome-webfont.woff",
|
|
||||||
theme::FONT_AWESOME_WOFF,
|
|
||||||
)?;
|
|
||||||
book.write_file(
|
|
||||||
"_FontAwesome/fonts/fontawesome-webfont.woff2",
|
|
||||||
theme::FONT_AWESOME_WOFF2,
|
|
||||||
)?;
|
|
||||||
book.write_file(
|
|
||||||
"_FontAwesome/fonts/FontAwesome.ttf",
|
|
||||||
theme::FONT_AWESOME_TTF,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let playpen_config = book.get_html_config().get_playpen_config();
|
let playpen_config = book.get_html_config().get_playpen_config();
|
||||||
|
|
||||||
|
@ -202,12 +188,11 @@ impl HtmlHandlebars {
|
||||||
let name = match custom_file.strip_prefix(book.get_root()) {
|
let name = match custom_file.strip_prefix(book.get_root()) {
|
||||||
Ok(p) => p.to_str().expect("Could not convert to str"),
|
Ok(p) => p.to_str().expect("Could not convert to str"),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
custom_file
|
custom_file.file_name()
|
||||||
.file_name()
|
.expect("File has a file name")
|
||||||
.expect("File has a file name")
|
.to_str()
|
||||||
.to_str()
|
.expect("Could not convert to str")
|
||||||
.expect("Could not convert to str")
|
}
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
book.write_file(name, &data)?;
|
book.write_file(name, &data)?;
|
||||||
|
@ -216,14 +201,17 @@ impl HtmlHandlebars {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the context with data for this file
|
/// Update the context with data for this file
|
||||||
fn configure_print_version(&self, data: &mut serde_json::Map<String, serde_json::Value>, print_content: &str) {
|
fn configure_print_version(&self,
|
||||||
|
data: &mut serde_json::Map<String, serde_json::Value>,
|
||||||
|
print_content: &str) {
|
||||||
// Make sure that the Print chapter does not display the title from
|
// Make sure that the Print chapter does not display the title from
|
||||||
// the last rendered chapter by removing it from its context
|
// the last rendered chapter by removing it from its context
|
||||||
data.remove("title");
|
data.remove("title");
|
||||||
data.insert("is_print".to_owned(), json!(true));
|
data.insert("is_print".to_owned(), json!(true));
|
||||||
data.insert("path".to_owned(), json!("print.md"));
|
data.insert("path".to_owned(), json!("print.md"));
|
||||||
data.insert("content".to_owned(), json!(print_content));
|
data.insert("content".to_owned(), json!(print_content));
|
||||||
data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(Path::new("print.md"))));
|
data.insert("path_to_root".to_owned(),
|
||||||
|
json!(utils::fs::path_to_root(Path::new("print.md"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_hbs_helpers(&self, handlebars: &mut Handlebars) {
|
fn register_hbs_helpers(&self, handlebars: &mut Handlebars) {
|
||||||
|
@ -235,10 +223,9 @@ impl HtmlHandlebars {
|
||||||
/// Copy across any additional CSS and JavaScript files which the book
|
/// Copy across any additional CSS and JavaScript files which the book
|
||||||
/// has been configured to use.
|
/// has been configured to use.
|
||||||
fn copy_additional_css_and_js(&self, book: &MDBook) -> Result<()> {
|
fn copy_additional_css_and_js(&self, book: &MDBook) -> Result<()> {
|
||||||
let custom_files = book.get_additional_css().iter().chain(
|
let custom_files = book.get_additional_css()
|
||||||
book.get_additional_js()
|
.iter()
|
||||||
.iter(),
|
.chain(book.get_additional_js().iter());
|
||||||
);
|
|
||||||
|
|
||||||
for custom_file in custom_files {
|
for custom_file in custom_files {
|
||||||
self.write_custom_file(custom_file, book)?;
|
self.write_custom_file(custom_file, book)?;
|
||||||
|
@ -257,10 +244,7 @@ impl Renderer for HtmlHandlebars {
|
||||||
let theme = theme::Theme::new(book.get_theme_path());
|
let theme = theme::Theme::new(book.get_theme_path());
|
||||||
|
|
||||||
debug!("[*]: Register handlebars template");
|
debug!("[*]: Register handlebars template");
|
||||||
handlebars.register_template_string(
|
handlebars.register_template_string("index", String::from_utf8(theme.index.clone())?)?;
|
||||||
"index",
|
|
||||||
String::from_utf8(theme.index.clone())?,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
debug!("[*]: Register handlebars helpers");
|
debug!("[*]: Register handlebars helpers");
|
||||||
self.register_hbs_helpers(&mut handlebars);
|
self.register_hbs_helpers(&mut handlebars);
|
||||||
|
@ -297,13 +281,12 @@ impl Renderer for HtmlHandlebars {
|
||||||
|
|
||||||
let rendered = handlebars.render("index", &data)?;
|
let rendered = handlebars.render("index", &data)?;
|
||||||
|
|
||||||
let rendered = self.post_process(rendered, "print.html",
|
let rendered = self.post_process(rendered,
|
||||||
book.get_html_config().get_playpen_config());
|
"print.html",
|
||||||
|
book.get_html_config().get_playpen_config());
|
||||||
|
|
||||||
book.write_file(
|
book.write_file(Path::new("print").with_extension("html"),
|
||||||
Path::new("print").with_extension("html"),
|
&rendered.into_bytes())?;
|
||||||
&rendered.into_bytes(),
|
|
||||||
)?;
|
|
||||||
info!("[*] Creating print.html ✓");
|
info!("[*] Creating print.html ✓");
|
||||||
|
|
||||||
// Copy static files (js, css, images, ...)
|
// Copy static files (js, css, images, ...)
|
||||||
|
@ -346,14 +329,11 @@ fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>
|
||||||
match style.strip_prefix(book.get_root()) {
|
match style.strip_prefix(book.get_root()) {
|
||||||
Ok(p) => css.push(p.to_str().expect("Could not convert to str")),
|
Ok(p) => css.push(p.to_str().expect("Could not convert to str")),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
css.push(
|
css.push(style.file_name()
|
||||||
style
|
.expect("File has a file name")
|
||||||
.file_name()
|
.to_str()
|
||||||
.expect("File has a file name")
|
.expect("Could not convert to str"))
|
||||||
.to_str()
|
}
|
||||||
.expect("Could not convert to str"),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data.insert("additional_css".to_owned(), json!(css));
|
data.insert("additional_css".to_owned(), json!(css));
|
||||||
|
@ -366,14 +346,11 @@ fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>
|
||||||
match script.strip_prefix(book.get_root()) {
|
match script.strip_prefix(book.get_root()) {
|
||||||
Ok(p) => js.push(p.to_str().expect("Could not convert to str")),
|
Ok(p) => js.push(p.to_str().expect("Could not convert to str")),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
js.push(
|
js.push(script.file_name()
|
||||||
script
|
.expect("File has a file name")
|
||||||
.file_name()
|
.to_str()
|
||||||
.expect("File has a file name")
|
.expect("Could not convert to str"))
|
||||||
.to_str()
|
}
|
||||||
.expect("Could not convert to str"),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data.insert("additional_js".to_owned(), json!(js));
|
data.insert("additional_js".to_owned(), json!(js));
|
||||||
|
@ -385,7 +362,8 @@ fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>
|
||||||
data.insert("ace_js".to_owned(), json!("ace.js"));
|
data.insert("ace_js".to_owned(), json!("ace.js"));
|
||||||
data.insert("mode_rust_js".to_owned(), json!("mode-rust.js"));
|
data.insert("mode_rust_js".to_owned(), json!("mode-rust.js"));
|
||||||
data.insert("theme_dawn_js".to_owned(), json!("theme-dawn.js"));
|
data.insert("theme_dawn_js".to_owned(), json!("theme-dawn.js"));
|
||||||
data.insert("theme_tomorrow_night_js".to_owned(), json!("theme-tomorrow_night.js"));
|
data.insert("theme_tomorrow_night_js".to_owned(),
|
||||||
|
json!("theme-tomorrow_night.js"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut chapters = vec![];
|
let mut chapters = vec![];
|
||||||
|
@ -398,22 +376,25 @@ fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>
|
||||||
BookItem::Affix(ref ch) => {
|
BookItem::Affix(ref ch) => {
|
||||||
chapter.insert("name".to_owned(), json!(ch.name));
|
chapter.insert("name".to_owned(), json!(ch.name));
|
||||||
let path = ch.path.to_str().ok_or_else(|| {
|
let path = ch.path.to_str().ok_or_else(|| {
|
||||||
io::Error::new(io::ErrorKind::Other, "Could not convert path to str")
|
io::Error::new(io::ErrorKind::Other,
|
||||||
})?;
|
"Could not convert path \
|
||||||
|
to str")
|
||||||
|
})?;
|
||||||
chapter.insert("path".to_owned(), json!(path));
|
chapter.insert("path".to_owned(), json!(path));
|
||||||
},
|
}
|
||||||
BookItem::Chapter(ref s, ref ch) => {
|
BookItem::Chapter(ref s, ref ch) => {
|
||||||
chapter.insert("section".to_owned(), json!(s));
|
chapter.insert("section".to_owned(), json!(s));
|
||||||
chapter.insert("name".to_owned(), json!(ch.name));
|
chapter.insert("name".to_owned(), json!(ch.name));
|
||||||
let path = ch.path.to_str().ok_or_else(|| {
|
let path = ch.path.to_str().ok_or_else(|| {
|
||||||
io::Error::new(io::ErrorKind::Other, "Could not convert path to str")
|
io::Error::new(io::ErrorKind::Other,
|
||||||
})?;
|
"Could not convert path \
|
||||||
|
to str")
|
||||||
|
})?;
|
||||||
chapter.insert("path".to_owned(), json!(path));
|
chapter.insert("path".to_owned(), json!(path));
|
||||||
},
|
}
|
||||||
BookItem::Spacer => {
|
BookItem::Spacer => {
|
||||||
chapter.insert("spacer".to_owned(), json!("_spacer_"));
|
chapter.insert("spacer".to_owned(), json!("_spacer_"));
|
||||||
},
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chapters.push(chapter);
|
chapters.push(chapter);
|
||||||
|
@ -431,21 +412,22 @@ fn build_header_links(html: &str, filepath: &str) -> String {
|
||||||
let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();
|
let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();
|
||||||
let mut id_counter = HashMap::new();
|
let mut id_counter = HashMap::new();
|
||||||
|
|
||||||
regex
|
regex.replace_all(html, |caps: &Captures| {
|
||||||
.replace_all(html, |caps: &Captures| {
|
let level = caps[1].parse()
|
||||||
let level = caps[1].parse().expect(
|
.expect("Regex should ensure we only ever get numbers here");
|
||||||
"Regex should ensure we only ever get numbers here",
|
|
||||||
);
|
|
||||||
|
|
||||||
wrap_header_with_link(level, &caps[2], &mut id_counter, filepath)
|
wrap_header_with_link(level, &caps[2], &mut id_counter, filepath)
|
||||||
})
|
})
|
||||||
.into_owned()
|
.into_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wraps a single header tag with a link, making sure each tag gets its own
|
/// Wraps a single header tag with a link, making sure each tag gets its own
|
||||||
/// unique ID by appending an auto-incremented number (if necessary).
|
/// unique ID by appending an auto-incremented number (if necessary).
|
||||||
fn wrap_header_with_link(level: usize, content: &str, id_counter: &mut HashMap<String, usize>, filepath: &str)
|
fn wrap_header_with_link(level: usize,
|
||||||
-> String {
|
content: &str,
|
||||||
|
id_counter: &mut HashMap<String, usize>,
|
||||||
|
filepath: &str)
|
||||||
|
-> String {
|
||||||
let raw_id = id_from_content(content);
|
let raw_id = id_from_content(content);
|
||||||
|
|
||||||
let id_count = id_counter.entry(raw_id.clone()).or_insert(0);
|
let id_count = id_counter.entry(raw_id.clone()).or_insert(0);
|
||||||
|
@ -466,25 +448,23 @@ fn wrap_header_with_link(level: usize, content: &str, id_counter: &mut HashMap<S
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate an id for use with anchors which is derived from a "normalised"
|
/// Generate an id for use with anchors which is derived from a "normalised"
|
||||||
/// string.
|
/// string.
|
||||||
fn id_from_content(content: &str) -> String {
|
fn id_from_content(content: &str) -> String {
|
||||||
let mut content = content.to_string();
|
let mut content = content.to_string();
|
||||||
|
|
||||||
// Skip any tags or html-encoded stuff
|
// Skip any tags or html-encoded stuff
|
||||||
const REPL_SUB: &[&str] = &[
|
const REPL_SUB: &[&str] = &["<em>",
|
||||||
"<em>",
|
"</em>",
|
||||||
"</em>",
|
"<code>",
|
||||||
"<code>",
|
"</code>",
|
||||||
"</code>",
|
"<strong>",
|
||||||
"<strong>",
|
"</strong>",
|
||||||
"</strong>",
|
"<",
|
||||||
"<",
|
">",
|
||||||
">",
|
"&",
|
||||||
"&",
|
"'",
|
||||||
"'",
|
"""];
|
||||||
""",
|
|
||||||
];
|
|
||||||
for sub in REPL_SUB {
|
for sub in REPL_SUB {
|
||||||
content = content.replace(sub, "");
|
content = content.replace(sub, "");
|
||||||
}
|
}
|
||||||
|
@ -500,21 +480,18 @@ fn id_from_content(content: &str) -> String {
|
||||||
// that in a very inelegant way
|
// that in a very inelegant way
|
||||||
fn fix_anchor_links(html: &str, filepath: &str) -> String {
|
fn fix_anchor_links(html: &str, filepath: &str) -> String {
|
||||||
let regex = Regex::new(r##"<a([^>]+)href="#([^"]+)"([^>]*)>"##).unwrap();
|
let regex = Regex::new(r##"<a([^>]+)href="#([^"]+)"([^>]*)>"##).unwrap();
|
||||||
regex
|
regex.replace_all(html, |caps: &Captures| {
|
||||||
.replace_all(html, |caps: &Captures| {
|
let before = &caps[1];
|
||||||
let before = &caps[1];
|
let anchor = &caps[2];
|
||||||
let anchor = &caps[2];
|
let after = &caps[3];
|
||||||
let after = &caps[3];
|
|
||||||
|
|
||||||
format!(
|
format!("<a{before}href=\"{filepath}#{anchor}\"{after}>",
|
||||||
"<a{before}href=\"{filepath}#{anchor}\"{after}>",
|
|
||||||
before = before,
|
before = before,
|
||||||
filepath = filepath,
|
filepath = filepath,
|
||||||
anchor = anchor,
|
anchor = anchor,
|
||||||
after = after
|
after = after)
|
||||||
)
|
})
|
||||||
})
|
.into_owned()
|
||||||
.into_owned()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -528,46 +505,53 @@ fn fix_anchor_links(html: &str, filepath: &str) -> String {
|
||||||
// This function replaces all commas by spaces in the code block classes
|
// This function replaces all commas by spaces in the code block classes
|
||||||
fn fix_code_blocks(html: &str) -> String {
|
fn fix_code_blocks(html: &str) -> String {
|
||||||
let regex = Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap();
|
let regex = Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap();
|
||||||
regex
|
regex.replace_all(html, |caps: &Captures| {
|
||||||
.replace_all(html, |caps: &Captures| {
|
let before = &caps[1];
|
||||||
let before = &caps[1];
|
let classes = &caps[2].replace(",", " ");
|
||||||
let classes = &caps[2].replace(",", " ");
|
let after = &caps[3];
|
||||||
let after = &caps[3];
|
|
||||||
|
|
||||||
format!(r#"<code{before}class="{classes}"{after}>"#, before = before, classes = classes, after = after)
|
format!(r#"<code{before}class="{classes}"{after}>"#,
|
||||||
})
|
before = before,
|
||||||
.into_owned()
|
classes = classes,
|
||||||
|
after = after)
|
||||||
|
})
|
||||||
|
.into_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_playpen_pre(html: &str, playpen_config: &PlaypenConfig) -> String {
|
fn add_playpen_pre(html: &str, playpen_config: &PlaypenConfig) -> String {
|
||||||
let regex = Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap();
|
let regex = Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap();
|
||||||
regex
|
regex.replace_all(html, |caps: &Captures| {
|
||||||
.replace_all(html, |caps: &Captures| {
|
let text = &caps[1];
|
||||||
let text = &caps[1];
|
let classes = &caps[2];
|
||||||
let classes = &caps[2];
|
let code = &caps[3];
|
||||||
let code = &caps[3];
|
|
||||||
|
|
||||||
if (classes.contains("language-rust") && !classes.contains("ignore")) || classes.contains("mdbook-runnable") {
|
if (classes.contains("language-rust") && !classes.contains("ignore")) ||
|
||||||
// wrap the contents in an external pre block
|
classes.contains("mdbook-runnable")
|
||||||
if playpen_config.is_editable() &&
|
{
|
||||||
classes.contains("editable") || text.contains("fn main") || text.contains("quick_main!") {
|
// wrap the contents in an external pre block
|
||||||
format!("<pre class=\"playpen\">{}</pre>", text)
|
if playpen_config.is_editable() && classes.contains("editable") ||
|
||||||
} else {
|
text.contains("fn main") || text.contains("quick_main!")
|
||||||
// we need to inject our own main
|
{
|
||||||
let (attrs, code) = partition_source(code);
|
format!("<pre class=\"playpen\">{}</pre>", text)
|
||||||
|
|
||||||
format!("<pre class=\"playpen\"><code class=\"{}\">\n# #![allow(unused_variables)]\n\
|
|
||||||
{}#fn main() {{\n\
|
|
||||||
{}\
|
|
||||||
#}}</code></pre>",
|
|
||||||
classes, attrs, code)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// not language-rust, so no-op
|
// we need to inject our own main
|
||||||
text.to_owned()
|
let (attrs, code) = partition_source(code);
|
||||||
|
|
||||||
|
format!("<pre class=\"playpen\"><code class=\"{}\">\n# \
|
||||||
|
#![allow(unused_variables)]\n\
|
||||||
|
{}#fn main() {{\n\
|
||||||
|
{}\
|
||||||
|
#}}</code></pre>",
|
||||||
|
classes,
|
||||||
|
attrs,
|
||||||
|
code)
|
||||||
}
|
}
|
||||||
})
|
} else {
|
||||||
.into_owned()
|
// not language-rust, so no-op
|
||||||
|
text.to_owned()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn partition_source(s: &str) -> (String, String) {
|
fn partition_source(s: &str) -> (String, String) {
|
||||||
|
@ -609,16 +593,14 @@ pub fn normalize_path(path: &str) -> String {
|
||||||
|
|
||||||
pub fn normalize_id(content: &str) -> String {
|
pub fn normalize_id(content: &str) -> String {
|
||||||
content.chars()
|
content.chars()
|
||||||
.filter_map(|ch|
|
.filter_map(|ch| if ch.is_alphanumeric() || ch == '_' || ch == '-' {
|
||||||
if ch.is_alphanumeric() || ch == '_' || ch == '-' {
|
Some(ch.to_ascii_lowercase())
|
||||||
Some(ch.to_ascii_lowercase())
|
} else if ch.is_whitespace() {
|
||||||
} else if ch.is_whitespace() {
|
Some('-')
|
||||||
Some('-')
|
} else {
|
||||||
} else {
|
None
|
||||||
None
|
})
|
||||||
}
|
.collect::<String>()
|
||||||
)
|
|
||||||
.collect::<String>()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -643,15 +625,15 @@ mod tests {
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"<h4></h4>",
|
"<h4></h4>",
|
||||||
r##"<a class="header" href="./some_chapter/some_section.html#" id=""><h4></h4></a>"##
|
r##"<a class="header" href="./some_chapter/some_section.html#" id=""><h4></h4></a>"##,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"<h4><em>Hï</em></h4>",
|
"<h4><em>Hï</em></h4>",
|
||||||
r##"<a class="header" href="./some_chapter/some_section.html#hï" id="hï"><h4><em>Hï</em></h4></a>"##
|
r##"<a class="header" href="./some_chapter/some_section.html#hï" id="hï"><h4><em>Hï</em></h4></a>"##,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"<h1>Foo</h1><h3>Foo</h3>",
|
"<h1>Foo</h1><h3>Foo</h3>",
|
||||||
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>"##
|
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>"##,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -668,7 +650,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn anchor_generation() {
|
fn anchor_generation() {
|
||||||
assert_eq!(id_from_content("## `--passes`: add more rustdoc passes"), "--passes-add-more-rustdoc-passes");
|
assert_eq!(id_from_content("## `--passes`: add more rustdoc passes"),
|
||||||
assert_eq!(id_from_content("## Method-call expressions"), "method-call-expressions");
|
"--passes-add-more-rustdoc-passes");
|
||||||
|
assert_eq!(id_from_content("## Method-call expressions"),
|
||||||
|
"method-call-expressions");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::path::Path;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use handlebars::{Handlebars, RenderError, RenderContext, Helper, Renderable, Context};
|
use handlebars::{Context, Handlebars, Helper, RenderContext, RenderError, Renderable};
|
||||||
|
|
||||||
|
|
||||||
// Handlebars helper for navigation
|
// Handlebars helper for navigation
|
||||||
|
@ -11,73 +11,78 @@ pub fn previous(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(
|
||||||
debug!("[fn]: previous (handlebars helper)");
|
debug!("[fn]: previous (handlebars helper)");
|
||||||
|
|
||||||
debug!("[*]: Get data from context");
|
debug!("[*]: Get data from context");
|
||||||
let chapters = rc.evaluate_absolute("chapters")
|
let chapters = rc.evaluate_absolute("chapters").and_then(|c| {
|
||||||
.and_then(|c| {
|
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
|
||||||
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
|
.map_err(|_| RenderError::new("Could not decode the JSON data"))
|
||||||
.map_err(|_| RenderError::new("Could not decode the JSON data"))
|
})?;
|
||||||
})?;
|
|
||||||
|
|
||||||
let current = rc.evaluate_absolute("path")?
|
let current = rc.evaluate_absolute("path")?
|
||||||
.as_str().ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
.as_str()
|
||||||
.replace("\"", "");
|
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
||||||
|
.replace("\"", "");
|
||||||
|
|
||||||
let mut previous: Option<BTreeMap<String, String>> = None;
|
let mut previous: Option<BTreeMap<String, String>> = None;
|
||||||
|
|
||||||
debug!("[*]: Search for current Chapter");
|
debug!("[*]: Search for current Chapter");
|
||||||
// Search for current chapter and return previous entry
|
// Search for current chapter and return previous entry
|
||||||
for item in chapters {
|
for item in chapters {
|
||||||
|
|
||||||
match item.get("path") {
|
match item.get("path") {
|
||||||
Some(path) if !path.is_empty() => {
|
Some(path) if !path.is_empty() => {
|
||||||
if path == ¤t {
|
if path == ¤t {
|
||||||
|
|
||||||
debug!("[*]: Found current chapter");
|
debug!("[*]: Found current chapter");
|
||||||
if let Some(previous) = previous {
|
if let Some(previous) = previous {
|
||||||
|
|
||||||
debug!("[*]: Creating BTreeMap to inject in context");
|
debug!("[*]: Creating BTreeMap to inject in context");
|
||||||
// Create new BTreeMap to extend the context: 'title' and 'link'
|
// Create new BTreeMap to extend the context: 'title' and 'link'
|
||||||
let mut previous_chapter = BTreeMap::new();
|
let mut previous_chapter = BTreeMap::new();
|
||||||
|
|
||||||
// Chapter title
|
// Chapter title
|
||||||
previous
|
previous.get("name")
|
||||||
.get("name").ok_or_else(|| RenderError::new("No title found for chapter in JSON data"))
|
.ok_or_else(|| {
|
||||||
.and_then(|n| {
|
RenderError::new("No title found for chapter in \
|
||||||
previous_chapter.insert("title".to_owned(), json!(n));
|
JSON data")
|
||||||
Ok(())
|
})
|
||||||
})?;
|
.and_then(|n| {
|
||||||
|
previous_chapter.insert("title".to_owned(), json!(n));
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
|
||||||
// Chapter link
|
// Chapter link
|
||||||
previous
|
previous.get("path")
|
||||||
.get("path").ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))
|
.ok_or_else(|| {
|
||||||
.and_then(|p| {
|
RenderError::new("No path found for chapter in \
|
||||||
Path::new(p)
|
JSON data")
|
||||||
.with_extension("html")
|
})
|
||||||
.to_str().ok_or_else(|| RenderError::new("Link could not be converted to str"))
|
.and_then(|p| {
|
||||||
.and_then(|p| {
|
Path::new(p).with_extension("html")
|
||||||
previous_chapter
|
.to_str()
|
||||||
.insert("link".to_owned(), json!(p.replace("\\", "/")));
|
.ok_or_else(|| {
|
||||||
Ok(())
|
RenderError::new("Link could not be \
|
||||||
})
|
converted to str")
|
||||||
})?;
|
})
|
||||||
|
.and_then(|p| {
|
||||||
|
previous_chapter
|
||||||
|
.insert("link".to_owned(), json!(p.replace("\\", "/")));
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
|
||||||
debug!("[*]: Render template");
|
debug!("[*]: Render template");
|
||||||
// Render template
|
// Render template
|
||||||
_h.template()
|
_h.template()
|
||||||
.ok_or_else(|| RenderError::new("Error with the handlebars template"))
|
.ok_or_else(|| RenderError::new("Error with the handlebars template"))
|
||||||
.and_then(|t| {
|
.and_then(|t| {
|
||||||
let mut local_rc = rc.with_context(Context::wraps(&previous_chapter)?);
|
let mut local_rc = rc.with_context(Context::wraps(&previous_chapter)?);
|
||||||
t.render(r, &mut local_rc)
|
t.render(r, &mut local_rc)
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
previous = Some(item.clone());
|
previous = Some(item.clone());
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => continue,
|
_ => continue,
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,67 +96,71 @@ pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), R
|
||||||
debug!("[fn]: next (handlebars helper)");
|
debug!("[fn]: next (handlebars helper)");
|
||||||
|
|
||||||
debug!("[*]: Get data from context");
|
debug!("[*]: Get data from context");
|
||||||
let chapters = rc.evaluate_absolute("chapters")
|
let chapters = rc.evaluate_absolute("chapters").and_then(|c| {
|
||||||
.and_then(|c| {
|
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
|
||||||
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
|
.map_err(|_| RenderError::new("Could not decode the JSON data"))
|
||||||
.map_err(|_| RenderError::new("Could not decode the JSON data"))
|
})?;
|
||||||
})?;
|
|
||||||
let current = rc.evaluate_absolute("path")?
|
let current = rc.evaluate_absolute("path")?
|
||||||
.as_str().ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
.as_str()
|
||||||
.replace("\"", "");
|
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
||||||
|
.replace("\"", "");
|
||||||
|
|
||||||
let mut previous: Option<BTreeMap<String, String>> = None;
|
let mut previous: Option<BTreeMap<String, String>> = None;
|
||||||
|
|
||||||
debug!("[*]: Search for current Chapter");
|
debug!("[*]: Search for current Chapter");
|
||||||
// Search for current chapter and return previous entry
|
// Search for current chapter and return previous entry
|
||||||
for item in chapters {
|
for item in chapters {
|
||||||
|
|
||||||
match item.get("path") {
|
match item.get("path") {
|
||||||
|
|
||||||
Some(path) if !path.is_empty() => {
|
Some(path) if !path.is_empty() => {
|
||||||
|
|
||||||
if let Some(previous) = previous {
|
if let Some(previous) = previous {
|
||||||
|
let previous_path = previous.get("path").ok_or_else(|| {
|
||||||
let previous_path = previous
|
RenderError::new("No path found for chapter in JSON data")
|
||||||
.get("path").ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))?;
|
})?;
|
||||||
|
|
||||||
if previous_path == ¤t {
|
if previous_path == ¤t {
|
||||||
|
|
||||||
debug!("[*]: Found current chapter");
|
debug!("[*]: Found current chapter");
|
||||||
debug!("[*]: Creating BTreeMap to inject in context");
|
debug!("[*]: Creating BTreeMap to inject in context");
|
||||||
// Create new BTreeMap to extend the context: 'title' and 'link'
|
// Create new BTreeMap to extend the context: 'title' and 'link'
|
||||||
let mut next_chapter = BTreeMap::new();
|
let mut next_chapter = BTreeMap::new();
|
||||||
|
|
||||||
item.get("name").ok_or_else(|| RenderError::new("No title found for chapter in JSON data"))
|
item.get("name")
|
||||||
|
.ok_or_else(|| {
|
||||||
|
RenderError::new("No title found for chapter in JSON \
|
||||||
|
data")
|
||||||
|
})
|
||||||
.and_then(|n| {
|
.and_then(|n| {
|
||||||
next_chapter.insert("title".to_owned(), json!(n));
|
next_chapter.insert("title".to_owned(), json!(n));
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Path::new(path)
|
Path::new(path).with_extension("html")
|
||||||
.with_extension("html")
|
.to_str()
|
||||||
.to_str().ok_or_else(|| RenderError::new("Link could not converted to str"))
|
.ok_or_else(|| {
|
||||||
.and_then(|l| {
|
RenderError::new("Link could not converted \
|
||||||
debug!("[*]: Inserting link: {:?}", l);
|
to str")
|
||||||
// Hack for windows who tends to use `\` as separator instead of `/`
|
})
|
||||||
next_chapter.insert("link".to_owned(), json!(l.replace("\\", "/")));
|
.and_then(|l| {
|
||||||
Ok(())
|
debug!("[*]: Inserting link: {:?}", l);
|
||||||
})?;
|
// Hack for windows who tends to use `\` as separator instead of `/`
|
||||||
|
next_chapter.insert("link".to_owned(), json!(l.replace("\\", "/")));
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
debug!("[*]: Render template");
|
debug!("[*]: Render template");
|
||||||
|
|
||||||
// Render template
|
// Render template
|
||||||
_h.template().ok_or_else(|| RenderError::new("Error with the handlebars template"))
|
_h.template()
|
||||||
.and_then(|t| {
|
.ok_or_else(|| RenderError::new("Error with the handlebars template"))
|
||||||
let mut local_rc = rc.with_context(Context::wraps(&next_chapter)?);
|
.and_then(|t| {
|
||||||
t.render(r, &mut local_rc)
|
let mut local_rc = rc.with_context(Context::wraps(&next_chapter)?);
|
||||||
})?;
|
t.render(r, &mut local_rc)
|
||||||
|
})?;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
previous = Some(item.clone());
|
previous = Some(item.clone());
|
||||||
},
|
}
|
||||||
|
|
||||||
_ => continue,
|
_ => continue,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ use std::path::Path;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use handlebars::{Handlebars, HelperDef, RenderError, RenderContext, Helper};
|
use handlebars::{Handlebars, Helper, HelperDef, RenderContext, RenderError};
|
||||||
use pulldown_cmark::{Parser, html, Event, Tag};
|
use pulldown_cmark::{html, Event, Parser, Tag};
|
||||||
|
|
||||||
// Handlebars helper to construct TOC
|
// Handlebars helper to construct TOC
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -11,29 +11,26 @@ pub struct RenderToc;
|
||||||
|
|
||||||
impl HelperDef for RenderToc {
|
impl HelperDef for RenderToc {
|
||||||
fn call(&self, _h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
|
fn call(&self, _h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
|
||||||
|
|
||||||
// get value from context data
|
// get value from context data
|
||||||
// rc.get_path() is current json parent path, you should always use it like this
|
// rc.get_path() is current json parent path, you should always use it like this
|
||||||
// param is the key of value you want to display
|
// param is the key of value you want to display
|
||||||
let chapters = rc.evaluate_absolute("chapters")
|
let chapters = rc.evaluate_absolute("chapters").and_then(|c| {
|
||||||
.and_then(|c| {
|
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
|
||||||
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
|
.map_err(|_| RenderError::new("Could not decode the JSON data"))
|
||||||
.map_err(|_| RenderError::new("Could not decode the JSON data"))
|
})?;
|
||||||
})?;
|
|
||||||
let current = rc.evaluate_absolute("path")?
|
let current = rc.evaluate_absolute("path")?
|
||||||
.as_str().ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
.as_str()
|
||||||
.replace("\"", "");
|
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
||||||
|
.replace("\"", "");
|
||||||
|
|
||||||
rc.writer.write_all(b"<ul class=\"chapter\">")?;
|
rc.writer.write_all(b"<ul class=\"chapter\">")?;
|
||||||
|
|
||||||
let mut current_level = 1;
|
let mut current_level = 1;
|
||||||
|
|
||||||
for item in chapters {
|
for item in chapters {
|
||||||
|
|
||||||
// Spacer
|
// Spacer
|
||||||
if item.get("spacer").is_some() {
|
if item.get("spacer").is_some() {
|
||||||
rc.writer
|
rc.writer.write_all(b"<li class=\"spacer\"></li>")?;
|
||||||
.write_all(b"<li class=\"spacer\"></li>")?;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +123,6 @@ impl HelperDef for RenderToc {
|
||||||
}
|
}
|
||||||
|
|
||||||
rc.writer.write_all(b"</li>")?;
|
rc.writer.write_all(b"</li>")?;
|
||||||
|
|
||||||
}
|
}
|
||||||
while current_level > 1 {
|
while current_level > 1 {
|
||||||
rc.writer.write_all(b"</ul>")?;
|
rc.writer.write_all(b"</ul>")?;
|
||||||
|
|
|
@ -18,11 +18,16 @@ pub static JQUERY: &'static [u8] = include_bytes!("jquery.js");
|
||||||
pub static CLIPBOARD_JS: &'static [u8] = include_bytes!("clipboard.min.js");
|
pub static CLIPBOARD_JS: &'static [u8] = include_bytes!("clipboard.min.js");
|
||||||
pub static STORE_JS: &'static [u8] = include_bytes!("store.js");
|
pub static STORE_JS: &'static [u8] = include_bytes!("store.js");
|
||||||
pub static FONT_AWESOME: &'static [u8] = include_bytes!("_FontAwesome/css/font-awesome.min.css");
|
pub static FONT_AWESOME: &'static [u8] = include_bytes!("_FontAwesome/css/font-awesome.min.css");
|
||||||
pub static FONT_AWESOME_EOT: &'static [u8] = include_bytes!("_FontAwesome/fonts/fontawesome-webfont.eot");
|
pub static FONT_AWESOME_EOT: &'static [u8] =
|
||||||
pub static FONT_AWESOME_SVG: &'static [u8] = include_bytes!("_FontAwesome/fonts/fontawesome-webfont.svg");
|
include_bytes!("_FontAwesome/fonts/fontawesome-webfont.eot");
|
||||||
pub static FONT_AWESOME_TTF: &'static [u8] = include_bytes!("_FontAwesome/fonts/fontawesome-webfont.ttf");
|
pub static FONT_AWESOME_SVG: &'static [u8] =
|
||||||
pub static FONT_AWESOME_WOFF: &'static [u8] = include_bytes!("_FontAwesome/fonts/fontawesome-webfont.woff");
|
include_bytes!("_FontAwesome/fonts/fontawesome-webfont.svg");
|
||||||
pub static FONT_AWESOME_WOFF2: &'static [u8] = include_bytes!("_FontAwesome/fonts/fontawesome-webfont.woff2");
|
pub static FONT_AWESOME_TTF: &'static [u8] =
|
||||||
|
include_bytes!("_FontAwesome/fonts/fontawesome-webfont.ttf");
|
||||||
|
pub static FONT_AWESOME_WOFF: &'static [u8] =
|
||||||
|
include_bytes!("_FontAwesome/fonts/fontawesome-webfont.woff");
|
||||||
|
pub static FONT_AWESOME_WOFF2: &'static [u8] =
|
||||||
|
include_bytes!("_FontAwesome/fonts/fontawesome-webfont.woff2");
|
||||||
pub static FONT_AWESOME_OTF: &'static [u8] = include_bytes!("_FontAwesome/fonts/FontAwesome.otf");
|
pub static FONT_AWESOME_OTF: &'static [u8] = include_bytes!("_FontAwesome/fonts/FontAwesome.otf");
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,19 +64,17 @@ impl Theme {
|
||||||
|
|
||||||
// Check for individual files, if they exist copy them across
|
// Check for individual files, if they exist copy them across
|
||||||
{
|
{
|
||||||
let files = vec![
|
let files = vec![(theme_dir.join("index.hbs"), &mut theme.index),
|
||||||
(theme_dir.join("index.hbs"), &mut theme.index),
|
(theme_dir.join("book.js"), &mut theme.js),
|
||||||
(theme_dir.join("book.js"), &mut theme.js),
|
(theme_dir.join("book.css"), &mut theme.css),
|
||||||
(theme_dir.join("book.css"), &mut theme.css),
|
(theme_dir.join("favicon.png"), &mut theme.favicon),
|
||||||
(theme_dir.join("favicon.png"), &mut theme.favicon),
|
(theme_dir.join("highlight.js"), &mut theme.highlight_js),
|
||||||
(theme_dir.join("highlight.js"), &mut theme.highlight_js),
|
(theme_dir.join("clipboard.min.js"), &mut theme.clipboard_js),
|
||||||
(theme_dir.join("clipboard.min.js"), &mut theme.clipboard_js),
|
(theme_dir.join("store.js"), &mut theme.store_js),
|
||||||
(theme_dir.join("store.js"), &mut theme.store_js),
|
(theme_dir.join("highlight.css"), &mut theme.highlight_css),
|
||||||
(theme_dir.join("highlight.css"), &mut theme.highlight_css),
|
(theme_dir.join("tomorrow-night.css"), &mut theme.tomorrow_night_css),
|
||||||
(theme_dir.join("tomorrow-night.css"), &mut theme.tomorrow_night_css),
|
(theme_dir.join("ayu-highlight.css"), &mut theme.ayu_highlight_css),
|
||||||
(theme_dir.join("ayu-highlight.css"), &mut theme.ayu_highlight_css),
|
(theme_dir.join("jquery.js"), &mut theme.jquery)];
|
||||||
(theme_dir.join("jquery.js"), &mut theme.jquery),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (filename, dest) in files {
|
for (filename, dest) in files {
|
||||||
if !filename.exists() {
|
if !filename.exists() {
|
||||||
|
|
|
@ -47,13 +47,12 @@ impl PlaypenEditor {
|
||||||
|
|
||||||
// Check for individual files if they exist
|
// Check for individual files if they exist
|
||||||
{
|
{
|
||||||
let files = vec![
|
let files = vec![(src.join("editor.js"), &mut editor.js),
|
||||||
(src.join("editor.js"), &mut editor.js),
|
(src.join("ace.js"), &mut editor.ace_js),
|
||||||
(src.join("ace.js"), &mut editor.ace_js),
|
(src.join("mode-rust.js"), &mut editor.mode_rust_js),
|
||||||
(src.join("mode-rust.js"), &mut editor.mode_rust_js),
|
(src.join("theme-dawn.js"), &mut editor.theme_dawn_js),
|
||||||
(src.join("theme-dawn.js"), &mut editor.theme_dawn_js),
|
(src.join("theme-tomorrow_night.js"),
|
||||||
(src.join("theme-tomorrow_night.js"), &mut editor.theme_tomorrow_night_js),
|
&mut editor.theme_tomorrow_night_js)];
|
||||||
];
|
|
||||||
|
|
||||||
for (filename, dest) in files {
|
for (filename, dest) in files {
|
||||||
if !filename.exists() {
|
if !filename.exists() {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::path::{Path, PathBuf, Component};
|
use std::path::{Component, Path, PathBuf};
|
||||||
use errors::*;
|
use errors::*;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
|
@ -11,7 +11,7 @@ pub fn file_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("[*]: Failed to open {:?}", path);
|
debug!("[*]: Failed to open {:?}", path);
|
||||||
bail!(e);
|
bail!(e);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut content = String::new();
|
let mut content = String::new();
|
||||||
|
@ -56,14 +56,14 @@ pub fn path_to_root<P: Into<PathBuf>>(path: P) -> String {
|
||||||
.expect("")
|
.expect("")
|
||||||
.components()
|
.components()
|
||||||
.fold(String::new(), |mut s, c| {
|
.fold(String::new(), |mut s, c| {
|
||||||
match c {
|
match c {
|
||||||
Component::Normal(_) => s.push_str("../"),
|
Component::Normal(_) => s.push_str("../"),
|
||||||
_ => {
|
_ => {
|
||||||
debug!("[*]: Other path component... {:?}", c);
|
debug!("[*]: Other path component... {:?}", c);
|
||||||
},
|
|
||||||
}
|
}
|
||||||
s
|
}
|
||||||
})
|
s
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -107,7 +107,10 @@ pub fn remove_dir_content(dir: &Path) -> Result<()> {
|
||||||
/// Copies all files of a directory to another one except the files
|
/// Copies all files of a directory to another one except the files
|
||||||
/// with the extensions given in the `ext_blacklist` array
|
/// with the extensions given in the `ext_blacklist` array
|
||||||
|
|
||||||
pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blacklist: &[&str])
|
pub fn copy_files_except_ext(from: &Path,
|
||||||
|
to: &Path,
|
||||||
|
recursive: bool,
|
||||||
|
ext_blacklist: &[&str])
|
||||||
-> Result<()> {
|
-> Result<()> {
|
||||||
debug!("[fn] copy_files_except_ext");
|
debug!("[fn] copy_files_except_ext");
|
||||||
// Check that from and to are different
|
// Check that from and to are different
|
||||||
|
@ -132,9 +135,11 @@ pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blackl
|
||||||
fs::create_dir(&to.join(entry.file_name()))?;
|
fs::create_dir(&to.join(entry.file_name()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
copy_files_except_ext(&from.join(entry.file_name()), &to.join(entry.file_name()), true, ext_blacklist)?;
|
copy_files_except_ext(&from.join(entry.file_name()),
|
||||||
|
&to.join(entry.file_name()),
|
||||||
|
true,
|
||||||
|
ext_blacklist)?;
|
||||||
} else if metadata.is_file() {
|
} else if metadata.is_file() {
|
||||||
|
|
||||||
// Check if it is in the blacklist
|
// Check if it is in the blacklist
|
||||||
if let Some(ext) = entry.path().extension() {
|
if let Some(ext) = entry.path().extension() {
|
||||||
if ext_blacklist.contains(&ext.to_str().unwrap()) {
|
if ext_blacklist.contains(&ext.to_str().unwrap()) {
|
||||||
|
@ -142,22 +147,19 @@ pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blackl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug!("[*] creating path for file: {:?}",
|
debug!("[*] creating path for file: {:?}",
|
||||||
&to.join(entry
|
&to.join(entry.path()
|
||||||
.path()
|
.file_name()
|
||||||
.file_name()
|
.expect("a file should have a file name...")));
|
||||||
.expect("a file should have a file name...")));
|
|
||||||
|
|
||||||
info!("[*] Copying file: {:?}\n to {:?}",
|
info!("[*] Copying file: {:?}\n to {:?}",
|
||||||
entry.path(),
|
entry.path(),
|
||||||
&to.join(entry
|
&to.join(entry.path()
|
||||||
.path()
|
.file_name()
|
||||||
.file_name()
|
.expect("a file should have a file name...")));
|
||||||
.expect("a file should have a file name...")));
|
|
||||||
fs::copy(entry.path(),
|
fs::copy(entry.path(),
|
||||||
&to.join(entry
|
&to.join(entry.path()
|
||||||
.path()
|
.file_name()
|
||||||
.file_name()
|
.expect("a file should have a file name...")))?;
|
||||||
.expect("a file should have a file name...")))?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -216,7 +218,7 @@ mod tests {
|
||||||
|
|
||||||
match copy_files_except_ext(&tmp.path(), &tmp.path().join("output"), true, &["md"]) {
|
match copy_files_except_ext(&tmp.path(), &tmp.path().join("output"), true, &["md"]) {
|
||||||
Err(e) => panic!("Error while executing the function:\n{:?}", e),
|
Err(e) => panic!("Error while executing the function:\n{:?}", e),
|
||||||
Ok(_) => {},
|
Ok(_) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the correct files where created
|
// Check if the correct files where created
|
||||||
|
@ -235,6 +237,5 @@ mod tests {
|
||||||
if !(&tmp.path().join("output/sub_dir_exists/file.txt")).exists() {
|
if !(&tmp.path().join("output/sub_dir_exists/file.txt")).exists() {
|
||||||
panic!("output/sub_dir/file.png should exist")
|
panic!("output/sub_dir/file.png should exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
|
|
||||||
use pulldown_cmark::{Parser, Event, Tag, html, Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES};
|
use pulldown_cmark::{html, Event, Options, Parser, Tag, OPTION_ENABLE_FOOTNOTES,
|
||||||
|
OPTION_ENABLE_TABLES};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,7 +18,8 @@ pub fn render_markdown(text: &str, curly_quotes: bool) -> String {
|
||||||
|
|
||||||
let p = Parser::new_ext(text, opts);
|
let p = Parser::new_ext(text, opts);
|
||||||
let mut converter = EventQuoteConverter::new(curly_quotes);
|
let mut converter = EventQuoteConverter::new(curly_quotes);
|
||||||
let events = p.map(clean_codeblock_headers).map(|event| converter.convert(event));
|
let events = p.map(clean_codeblock_headers)
|
||||||
|
.map(|event| converter.convert(event));
|
||||||
|
|
||||||
html::push_html(&mut s, events);
|
html::push_html(&mut s, events);
|
||||||
s
|
s
|
||||||
|
@ -30,7 +32,10 @@ struct EventQuoteConverter {
|
||||||
|
|
||||||
impl EventQuoteConverter {
|
impl EventQuoteConverter {
|
||||||
fn new(enabled: bool) -> Self {
|
fn new(enabled: bool) -> Self {
|
||||||
EventQuoteConverter { enabled: enabled, convert_text: true }
|
EventQuoteConverter {
|
||||||
|
enabled: enabled,
|
||||||
|
convert_text: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert<'a>(&mut self, event: Event<'a>) -> Event<'a> {
|
fn convert<'a>(&mut self, event: Event<'a>) -> Event<'a> {
|
||||||
|
@ -39,17 +44,17 @@ impl EventQuoteConverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::Start(Tag::CodeBlock(_)) |
|
Event::Start(Tag::CodeBlock(_)) | Event::Start(Tag::Code) => {
|
||||||
Event::Start(Tag::Code) => {
|
|
||||||
self.convert_text = false;
|
self.convert_text = false;
|
||||||
event
|
event
|
||||||
},
|
}
|
||||||
Event::End(Tag::CodeBlock(_)) |
|
Event::End(Tag::CodeBlock(_)) | Event::End(Tag::Code) => {
|
||||||
Event::End(Tag::Code) => {
|
|
||||||
self.convert_text = true;
|
self.convert_text = true;
|
||||||
event
|
event
|
||||||
},
|
}
|
||||||
Event::Text(ref text) if self.convert_text => Event::Text(Cow::from(convert_quotes_to_curly(text))),
|
Event::Text(ref text) if self.convert_text => {
|
||||||
|
Event::Text(Cow::from(convert_quotes_to_curly(text)))
|
||||||
|
}
|
||||||
_ => event,
|
_ => event,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,13 +63,10 @@ impl EventQuoteConverter {
|
||||||
fn clean_codeblock_headers(event: Event) -> Event {
|
fn clean_codeblock_headers(event: Event) -> Event {
|
||||||
match event {
|
match event {
|
||||||
Event::Start(Tag::CodeBlock(ref info)) => {
|
Event::Start(Tag::CodeBlock(ref info)) => {
|
||||||
let info: String = info
|
let info: String = info.chars().filter(|ch| !ch.is_whitespace()).collect();
|
||||||
.chars()
|
|
||||||
.filter(|ch| !ch.is_whitespace())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Event::Start(Tag::CodeBlock(Cow::from(info)))
|
Event::Start(Tag::CodeBlock(Cow::from(info)))
|
||||||
},
|
}
|
||||||
_ => event,
|
_ => event,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,20 +76,31 @@ fn convert_quotes_to_curly(original_text: &str) -> String {
|
||||||
// We'll consider the start to be "whitespace".
|
// We'll consider the start to be "whitespace".
|
||||||
let mut preceded_by_whitespace = true;
|
let mut preceded_by_whitespace = true;
|
||||||
|
|
||||||
original_text
|
original_text.chars()
|
||||||
.chars()
|
.map(|original_char| {
|
||||||
.map(|original_char| {
|
let converted_char = match original_char {
|
||||||
let converted_char = match original_char {
|
'\'' => {
|
||||||
'\'' => if preceded_by_whitespace { '‘' } else { '’' },
|
if preceded_by_whitespace {
|
||||||
'"' => if preceded_by_whitespace { '“' } else { '”' },
|
'‘'
|
||||||
_ => original_char,
|
} else {
|
||||||
};
|
'’'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'"' => {
|
||||||
|
if preceded_by_whitespace {
|
||||||
|
'“'
|
||||||
|
} else {
|
||||||
|
'”'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => original_char,
|
||||||
|
};
|
||||||
|
|
||||||
preceded_by_whitespace = original_char.is_whitespace();
|
preceded_by_whitespace = original_char.is_whitespace();
|
||||||
|
|
||||||
converted_char
|
converted_char
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -146,7 +159,8 @@ more text with spaces
|
||||||
```
|
```
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let expected = r#"<pre><code class="language-rust,no_run,should_panic,property_3"></code></pre>
|
let expected =
|
||||||
|
r#"<pre><code class="language-rust,no_run,should_panic,property_3"></code></pre>
|
||||||
"#;
|
"#;
|
||||||
assert_eq!(render_markdown(input, false), expected);
|
assert_eq!(render_markdown(input, false), expected);
|
||||||
assert_eq!(render_markdown(input, true), expected);
|
assert_eq!(render_markdown(input, true), expected);
|
||||||
|
@ -159,7 +173,8 @@ more text with spaces
|
||||||
```
|
```
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let expected = r#"<pre><code class="language-rust,no_run,,,should_panic,,property_3"></code></pre>
|
let expected =
|
||||||
|
r#"<pre><code class="language-rust,no_run,,,should_panic,,property_3"></code></pre>
|
||||||
"#;
|
"#;
|
||||||
assert_eq!(render_markdown(input, false), expected);
|
assert_eq!(render_markdown(input, false), expected);
|
||||||
assert_eq!(render_markdown(input, true), expected);
|
assert_eq!(render_markdown(input, true), expected);
|
||||||
|
@ -168,7 +183,7 @@ more text with spaces
|
||||||
#[test]
|
#[test]
|
||||||
fn rust_code_block_without_properties_has_proper_html_class() {
|
fn rust_code_block_without_properties_has_proper_html_class() {
|
||||||
let input = r#"
|
let input = r#"
|
||||||
```rust
|
```rust
|
||||||
```
|
```
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
@ -183,7 +198,6 @@ more text with spaces
|
||||||
"#;
|
"#;
|
||||||
assert_eq!(render_markdown(input, false), expected);
|
assert_eq!(render_markdown(input, false), expected);
|
||||||
assert_eq!(render_markdown(input, true), expected);
|
assert_eq!(render_markdown(input, true), expected);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,12 +206,14 @@ more text with spaces
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_converts_single_quotes() {
|
fn it_converts_single_quotes() {
|
||||||
assert_eq!(convert_quotes_to_curly("'one', 'two'"), "‘one’, ‘two’");
|
assert_eq!(convert_quotes_to_curly("'one', 'two'"),
|
||||||
|
"‘one’, ‘two’");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_converts_double_quotes() {
|
fn it_converts_double_quotes() {
|
||||||
assert_eq!(convert_quotes_to_curly(r#""one", "two""#), "“one”, “two”");
|
assert_eq!(convert_quotes_to_curly(r#""one", "two""#),
|
||||||
|
"“one”, “two”");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -13,10 +13,9 @@ use tempdir::TempDir;
|
||||||
fn do_not_overwrite_unspecified_config_values() {
|
fn do_not_overwrite_unspecified_config_values() {
|
||||||
let dir = TempDir::new("mdbook").expect("Could not create a temp dir");
|
let dir = TempDir::new("mdbook").expect("Could not create a temp dir");
|
||||||
|
|
||||||
let book = MDBook::new(dir.path())
|
let book = MDBook::new(dir.path()).with_source("bar")
|
||||||
.with_source("bar")
|
.with_destination("baz")
|
||||||
.with_destination("baz")
|
.with_mathjax_support(true);
|
||||||
.with_mathjax_support(true);
|
|
||||||
|
|
||||||
assert_eq!(book.get_root(), dir.path());
|
assert_eq!(book.get_root(), dir.path());
|
||||||
assert_eq!(book.get_source(), dir.path().join("bar"));
|
assert_eq!(book.get_source(), dir.path().join("bar"));
|
||||||
|
@ -33,7 +32,8 @@ fn do_not_overwrite_unspecified_config_values() {
|
||||||
// Try with a partial config file
|
// Try with a partial config file
|
||||||
let file_path = dir.path().join("book.toml");
|
let file_path = dir.path().join("book.toml");
|
||||||
let mut f = File::create(file_path).expect("Could not create config file");
|
let mut f = File::create(file_path).expect("Could not create config file");
|
||||||
f.write_all(br#"source = "barbaz""#).expect("Could not write to config file");
|
f.write_all(br#"source = "barbaz""#)
|
||||||
|
.expect("Could not write to config file");
|
||||||
f.sync_all().expect("Could not sync the file");
|
f.sync_all().expect("Could not sync the file");
|
||||||
|
|
||||||
let book = book.read_config().expect("Error reading the config file");
|
let book = book.read_config().expect("Error reading the config file");
|
||||||
|
@ -43,4 +43,3 @@ fn do_not_overwrite_unspecified_config_values() {
|
||||||
assert_eq!(book.get_destination(), dir.path().join("baz"));
|
assert_eq!(book.get_destination(), dir.path().join("baz"));
|
||||||
assert_eq!(book.get_mathjax_support(), true);
|
assert_eq!(book.get_mathjax_support(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,20 +63,17 @@ impl DummyBook {
|
||||||
let to_substitute = if self.passing_test { "true" } else { "false" };
|
let to_substitute = if self.passing_test { "true" } else { "false" };
|
||||||
let nested_text = NESTED.replace("$TEST_STATUS", to_substitute);
|
let nested_text = NESTED.replace("$TEST_STATUS", to_substitute);
|
||||||
|
|
||||||
let inputs = vec![
|
let inputs = vec![(src.join("SUMMARY.md"), SUMMARY_MD),
|
||||||
(src.join("SUMMARY.md"), SUMMARY_MD),
|
(src.join("intro.md"), INTRO),
|
||||||
(src.join("intro.md"), INTRO),
|
(first.join("index.md"), FIRST),
|
||||||
(first.join("index.md"), FIRST),
|
(first.join("nested.md"), &nested_text),
|
||||||
(first.join("nested.md"), &nested_text),
|
(src.join("second.md"), SECOND),
|
||||||
(src.join("second.md"), SECOND),
|
(src.join("conclusion.md"), CONCLUSION)];
|
||||||
(src.join("conclusion.md"), CONCLUSION),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (path, content) in inputs {
|
for (path, content) in inputs {
|
||||||
File::create(path)
|
File::create(path).unwrap()
|
||||||
.unwrap()
|
.write_all(content.as_bytes())
|
||||||
.write_all(content.as_bytes())
|
.unwrap();
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
temp
|
temp
|
||||||
|
|
|
@ -13,12 +13,15 @@ pub fn assert_contains_strings<P: AsRef<Path>>(filename: P, strings: &[&str]) {
|
||||||
let filename = filename.as_ref();
|
let filename = filename.as_ref();
|
||||||
|
|
||||||
let mut content = String::new();
|
let mut content = String::new();
|
||||||
File::open(&filename)
|
File::open(&filename).expect("Couldn't open the provided file")
|
||||||
.expect("Couldn't open the provided file")
|
.read_to_string(&mut content)
|
||||||
.read_to_string(&mut content)
|
.expect("Couldn't read the file's contents");
|
||||||
.expect("Couldn't read the file's contents");
|
|
||||||
|
|
||||||
for s in strings {
|
for s in strings {
|
||||||
assert!(content.contains(s), "Searching for {:?} in {}\n\n{}", s, filename.display(), content);
|
assert!(content.contains(s),
|
||||||
|
"Searching for {:?} in {}\n\n{}",
|
||||||
|
s,
|
||||||
|
filename.display(),
|
||||||
|
content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,16 +32,19 @@ fn run_mdbook_init_with_custom_book_and_src_locations() {
|
||||||
|
|
||||||
let temp = TempDir::new("mdbook").unwrap();
|
let temp = TempDir::new("mdbook").unwrap();
|
||||||
for file in &created_files {
|
for file in &created_files {
|
||||||
assert!(!temp.path().join(file).exists(), "{} shouldn't exist yet!", file);
|
assert!(!temp.path().join(file).exists(),
|
||||||
|
"{} shouldn't exist yet!",
|
||||||
|
file);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut md = MDBook::new(temp.path())
|
let mut md = MDBook::new(temp.path()).with_source("in")
|
||||||
.with_source("in")
|
.with_destination("out");
|
||||||
.with_destination("out");
|
|
||||||
|
|
||||||
md.init().unwrap();
|
md.init().unwrap();
|
||||||
|
|
||||||
for file in &created_files {
|
for file in &created_files {
|
||||||
assert!(temp.path().join(file).exists(), "{} should have been created by `mdbook init`", file);
|
assert!(temp.path().join(file).exists(),
|
||||||
|
"{} should have been created by `mdbook init`",
|
||||||
|
file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,13 +37,11 @@ fn make_sure_bottom_level_files_contain_links_to_chapters() {
|
||||||
md.build().unwrap();
|
md.build().unwrap();
|
||||||
|
|
||||||
let dest = temp.path().join("book");
|
let dest = temp.path().join("book");
|
||||||
let links = vec![
|
let links = vec![r#"href="intro.html""#,
|
||||||
r#"href="intro.html""#,
|
r#"href="./first/index.html""#,
|
||||||
r#"href="./first/index.html""#,
|
r#"href="./first/nested.html""#,
|
||||||
r#"href="./first/nested.html""#,
|
r#"href="./second.html""#,
|
||||||
r#"href="./second.html""#,
|
r#"href="./conclusion.html""#];
|
||||||
r#"href="./conclusion.html""#,
|
|
||||||
];
|
|
||||||
|
|
||||||
let files_in_bottom_dir = vec!["index.html", "intro.html", "second.html", "conclusion.html"];
|
let files_in_bottom_dir = vec!["index.html", "intro.html", "second.html", "conclusion.html"];
|
||||||
|
|
||||||
|
@ -59,14 +57,12 @@ fn check_correct_cross_links_in_nested_dir() {
|
||||||
md.build().unwrap();
|
md.build().unwrap();
|
||||||
|
|
||||||
let first = temp.path().join("book").join("first");
|
let first = temp.path().join("book").join("first");
|
||||||
let links = vec![
|
let links = vec![r#"<base href="../">"#,
|
||||||
r#"<base href="../">"#,
|
r#"href="intro.html""#,
|
||||||
r#"href="intro.html""#,
|
r#"href="./first/index.html""#,
|
||||||
r#"href="./first/index.html""#,
|
r#"href="./first/nested.html""#,
|
||||||
r#"href="./first/nested.html""#,
|
r#"href="./second.html""#,
|
||||||
r#"href="./second.html""#,
|
r#"href="./conclusion.html""#];
|
||||||
r#"href="./conclusion.html""#,
|
|
||||||
];
|
|
||||||
|
|
||||||
let files_in_nested_dir = vec!["index.html", "nested.html"];
|
let files_in_nested_dir = vec!["index.html", "nested.html"];
|
||||||
|
|
||||||
|
@ -74,19 +70,11 @@ fn check_correct_cross_links_in_nested_dir() {
|
||||||
assert_contains_strings(first.join(filename), &links);
|
assert_contains_strings(first.join(filename), &links);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_contains_strings(
|
assert_contains_strings(first.join("index.html"),
|
||||||
first.join("index.html"),
|
&[r##"href="./first/index.html#some-section" id="some-section""##]);
|
||||||
&[
|
|
||||||
r##"href="./first/index.html#some-section" id="some-section""##
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_contains_strings(
|
assert_contains_strings(first.join("nested.html"),
|
||||||
first.join("nested.html"),
|
&[r##"href="./first/nested.html#some-section" id="some-section""##]);
|
||||||
&[
|
|
||||||
r##"href="./first/nested.html#some-section" id="some-section""##
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -106,13 +94,11 @@ fn rendered_code_has_playpen_stuff() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn chapter_content_appears_in_rendered_document() {
|
fn chapter_content_appears_in_rendered_document() {
|
||||||
let content = vec![
|
let content = vec![("index.html", "Here's some interesting text"),
|
||||||
("index.html", "Here's some interesting text"),
|
("second.html", "Second Chapter"),
|
||||||
("second.html", "Second Chapter"),
|
("first/nested.html", "testable code"),
|
||||||
("first/nested.html", "testable code"),
|
("first/index.html", "more text"),
|
||||||
("first/index.html", "more text"),
|
("conclusion.html", "Conclusion")];
|
||||||
("conclusion.html", "Conclusion"),
|
|
||||||
];
|
|
||||||
|
|
||||||
let temp = DummyBook::default().build();
|
let temp = DummyBook::default().build();
|
||||||
let mut md = MDBook::new(temp.path());
|
let mut md = MDBook::new(temp.path());
|
||||||
|
|
|
@ -9,9 +9,7 @@ use mdbook::MDBook;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mdbook_can_correctly_test_a_passing_book() {
|
fn mdbook_can_correctly_test_a_passing_book() {
|
||||||
let temp = DummyBook::default()
|
let temp = DummyBook::default().with_passing_test(true).build();
|
||||||
.with_passing_test(true)
|
|
||||||
.build();
|
|
||||||
let mut md = MDBook::new(temp.path());
|
let mut md = MDBook::new(temp.path());
|
||||||
|
|
||||||
assert!(md.test(vec![]).is_ok());
|
assert!(md.test(vec![]).is_ok());
|
||||||
|
@ -19,9 +17,7 @@ fn mdbook_can_correctly_test_a_passing_book() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mdbook_detects_book_with_failing_tests() {
|
fn mdbook_detects_book_with_failing_tests() {
|
||||||
let temp = DummyBook::default()
|
let temp = DummyBook::default().with_passing_test(false).build();
|
||||||
.with_passing_test(false)
|
|
||||||
.build();
|
|
||||||
let mut md: MDBook = MDBook::new(temp.path());
|
let mut md: MDBook = MDBook::new(temp.path());
|
||||||
|
|
||||||
assert!(md.test(vec![]).is_err());
|
assert!(md.test(vec![]).is_err());
|
||||||
|
|
|
@ -56,7 +56,8 @@ fn from_toml_authors() {
|
||||||
let parsed = TomlConfig::from_toml(toml).expect("This should parse");
|
let parsed = TomlConfig::from_toml(toml).expect("This should parse");
|
||||||
let config = BookConfig::from_tomlconfig("root", parsed);
|
let config = BookConfig::from_tomlconfig("root", parsed);
|
||||||
|
|
||||||
assert_eq!(config.get_authors(), &[String::from("John Doe"), String::from("Jane Doe")]);
|
assert_eq!(config.get_authors(),
|
||||||
|
&[String::from("John Doe"), String::from("Jane Doe")]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that the default `playpen` config is correct in the TOML config
|
// Tests that the default `playpen` config is correct in the TOML config
|
||||||
|
@ -69,7 +70,8 @@ fn from_toml_playpen_default() {
|
||||||
|
|
||||||
let playpenconfig = config.get_html_config().get_playpen_config();
|
let playpenconfig = config.get_html_config().get_playpen_config();
|
||||||
|
|
||||||
assert_eq!(playpenconfig.get_editor(), PathBuf::from("root/theme/editor"));
|
assert_eq!(playpenconfig.get_editor(),
|
||||||
|
PathBuf::from("root/theme/editor"));
|
||||||
assert_eq!(playpenconfig.is_editable(), false);
|
assert_eq!(playpenconfig.is_editable(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +86,8 @@ fn from_toml_playpen_editor() {
|
||||||
|
|
||||||
let playpenconfig = config.get_html_config().get_playpen_config();
|
let playpenconfig = config.get_html_config().get_playpen_config();
|
||||||
|
|
||||||
assert_eq!(playpenconfig.get_editor(), PathBuf::from("root/theme/editordir"));
|
assert_eq!(playpenconfig.get_editor(),
|
||||||
|
PathBuf::from("root/theme/editordir"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that the `playpen.editable` key is correctly parsed in the TOML config
|
// Tests that the `playpen.editable` key is correctly parsed in the TOML config
|
||||||
|
@ -168,7 +171,9 @@ fn from_toml_output_html_google_analytics() {
|
||||||
|
|
||||||
let htmlconfig = config.get_html_config();
|
let htmlconfig = config.get_html_config();
|
||||||
|
|
||||||
assert_eq!(htmlconfig.get_google_analytics_id().expect("the google-analytics key was provided"), String::from("123456"));
|
assert_eq!(htmlconfig.get_google_analytics_id()
|
||||||
|
.expect("the google-analytics key was provided"),
|
||||||
|
String::from("123456"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that the `output.html.additional-css` key is correctly parsed in the TOML config
|
// Tests that the `output.html.additional-css` key is correctly parsed in the TOML config
|
||||||
|
@ -182,7 +187,9 @@ fn from_toml_output_html_additional_stylesheet() {
|
||||||
|
|
||||||
let htmlconfig = config.get_html_config();
|
let htmlconfig = config.get_html_config();
|
||||||
|
|
||||||
assert_eq!(htmlconfig.get_additional_css(), &[PathBuf::from("root/custom.css"), PathBuf::from("root/two/custom.css")]);
|
assert_eq!(htmlconfig.get_additional_css(),
|
||||||
|
&[PathBuf::from("root/custom.css"),
|
||||||
|
PathBuf::from("root/two/custom.css")]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that the `output.html.additional-js` key is correctly parsed in the TOML config
|
// Tests that the `output.html.additional-js` key is correctly parsed in the TOML config
|
||||||
|
@ -196,5 +203,7 @@ fn from_toml_output_html_additional_scripts() {
|
||||||
|
|
||||||
let htmlconfig = config.get_html_config();
|
let htmlconfig = config.get_html_config();
|
||||||
|
|
||||||
assert_eq!(htmlconfig.get_additional_js(), &[PathBuf::from("root/custom.js"), PathBuf::from("root/two/custom.js")]);
|
assert_eq!(htmlconfig.get_additional_js(),
|
||||||
|
&[PathBuf::from("root/custom.js"),
|
||||||
|
PathBuf::from("root/two/custom.js")]);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue