run rustfmt on the repository #398(Updated) (#438)

rustfmt the repository #398
This commit is contained in:
Pratik Karki 2017-10-03 17:25:23 +05:45 committed by Michał Budzyński
parent b45e5e4420
commit 382fc4139b
32 changed files with 732 additions and 683 deletions

View File

@ -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");
} }
} }

View File

@ -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"

View File

@ -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

View File

@ -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`

View File

@ -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());

View File

@ -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) {{
@ -70,7 +91,8 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
"#, "#,
public_address, public_address,
ws_port, ws_port,
RELOAD_COMMAND)); RELOAD_COMMAND
));
book.build()?; book.build()?;

View File

@ -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()?;

View File

@ -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);
}, }
} }
} }
} }

View File

@ -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);

View File

@ -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())
let relative = destination .expect("Could not strip the root prefix, path is not \
.strip_prefix(self.config.get_root()) relative to root")
.expect("Could not strip the root prefix, path is not relative to root")
.to_str() .to_str()
.expect("Could not convert to &str"); .expect("Could not convert to &str");
debug!("[*]: {:?} does not exist, trying to create .gitignore", gitignore); 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,11 +286,9 @@ 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())
} }
@ -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 {

View File

@ -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);
} }
@ -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

View File

@ -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 {

View File

@ -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)
} }

View File

@ -1,4 +1,4 @@
use std::path::{PathBuf, Path}; use std::path::{Path, PathBuf};
use super::tomlconfig::TomlPlaypenConfig; use super::tomlconfig::TomlPlaypenConfig;
@ -28,7 +28,10 @@ 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 {

View File

@ -53,8 +53,7 @@ pub struct TomlPlaypenConfig {
/// ``` /// ```
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)
} }

View File

@ -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;

View File

@ -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(
ErrorKind::Other,
"Your summary.md is messed up\n\n "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(
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 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;
} }
}, }
_ => {}, _ => {}
} }
} }

View File

@ -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,14 +50,15 @@ 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(),
@ -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}}",
}); });
} }

View File

@ -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,
item: &BookItem,
mut ctx: RenderItemContext,
print_content: &mut String)
-> Result<()> { -> 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,13 +50,18 @@ 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;
} }
@ -62,20 +69,20 @@ impl HtmlHandlebars {
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,
"print.html",
book.get_html_config().get_playpen_config()); 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
.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"))
) }
},
} }
} }
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
.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"))
) }
},
} }
} }
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,11 +412,9 @@ 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)
}) })
@ -444,7 +423,10 @@ fn build_header_links(html: &str, filepath: &str) -> String {
/// 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,
content: &str,
id_counter: &mut HashMap<String, usize>,
filepath: &str)
-> String { -> String {
let raw_id = id_from_content(content); let raw_id = id_from_content(content);
@ -472,8 +454,7 @@ 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>",
@ -483,8 +464,7 @@ fn id_from_content(content: &str) -> String {
"&gt;", "&gt;",
"&amp;", "&amp;",
"&#39;", "&#39;",
"&quot;", "&quot;"];
];
for sub in REPL_SUB { for sub in REPL_SUB {
content = content.replace(sub, ""); content = content.replace(sub, "");
} }
@ -500,19 +480,16 @@ 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,39 +505,46 @@ 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,
classes = classes,
after = after)
}) })
.into_owned() .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")) ||
classes.contains("mdbook-runnable")
{
// wrap the contents in an external pre block // wrap the contents in an external pre block
if playpen_config.is_editable() && if playpen_config.is_editable() && classes.contains("editable") ||
classes.contains("editable") || text.contains("fn main") || text.contains("quick_main!") { text.contains("fn main") || text.contains("quick_main!")
{
format!("<pre class=\"playpen\">{}</pre>", text) format!("<pre class=\"playpen\">{}</pre>", text)
} else { } else {
// we need to inject our own main // we need to inject our own main
let (attrs, code) = partition_source(code); let (attrs, code) = partition_source(code);
format!("<pre class=\"playpen\"><code class=\"{}\">\n# #![allow(unused_variables)]\n\ format!("<pre class=\"playpen\"><code class=\"{}\">\n# \
#![allow(unused_variables)]\n\
{}#fn main() {{\n\ {}#fn main() {{\n\
{}\ {}\
#}}</code></pre>", #}}</code></pre>",
classes, attrs, code) classes,
attrs,
code)
} }
} else { } else {
// not language-rust, so no-op // not language-rust, so no-op
@ -609,15 +593,13 @@ 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");
} }
} }

View File

@ -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,14 +11,14 @@ 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()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", ""); .replace("\"", "");
let mut previous: Option<BTreeMap<String, String>> = None; let mut previous: Option<BTreeMap<String, String>> = None;
@ -26,21 +26,21 @@ pub fn previous(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(
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 == &current { if path == &current {
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(|| {
RenderError::new("No title found for chapter in \
JSON data")
})
.and_then(|n| { .and_then(|n| {
previous_chapter.insert("title".to_owned(), json!(n)); previous_chapter.insert("title".to_owned(), json!(n));
Ok(()) Ok(())
@ -48,12 +48,18 @@ pub fn previous(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(
// 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(|| {
RenderError::new("No path found for chapter in \
JSON data")
})
.and_then(|p| { .and_then(|p| {
Path::new(p) Path::new(p).with_extension("html")
.with_extension("html") .to_str()
.to_str().ok_or_else(|| RenderError::new("Link could not be converted to str")) .ok_or_else(|| {
RenderError::new("Link could not be \
converted to str")
})
.and_then(|p| { .and_then(|p| {
previous_chapter previous_chapter
.insert("link".to_owned(), json!(p.replace("\\", "/"))); .insert("link".to_owned(), json!(p.replace("\\", "/")));
@ -75,9 +81,8 @@ pub fn previous(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(
} else { } else {
previous = Some(item.clone()); previous = Some(item.clone());
} }
}, }
_ => continue, _ => continue,
} }
} }
@ -91,13 +96,13 @@ 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()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", ""); .replace("\"", "");
let mut previous: Option<BTreeMap<String, String>> = None; let mut previous: Option<BTreeMap<String, String>> = None;
@ -105,32 +110,35 @@ pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), R
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 == &current { if previous_path == &current {
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(|| {
RenderError::new("Link could not converted \
to str")
})
.and_then(|l| { .and_then(|l| {
debug!("[*]: Inserting link: {:?}", l); debug!("[*]: Inserting link: {:?}", l);
// Hack for windows who tends to use `\` as separator instead of `/` // Hack for windows who tends to use `\` as separator instead of `/`
@ -141,7 +149,8 @@ pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), R
debug!("[*]: Render template"); debug!("[*]: Render template");
// Render template // Render template
_h.template().ok_or_else(|| RenderError::new("Error with the handlebars template")) _h.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(&next_chapter)?); let mut local_rc = rc.with_context(Context::wraps(&next_chapter)?);
t.render(r, &mut local_rc) t.render(r, &mut local_rc)
@ -151,7 +160,7 @@ pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), R
} }
previous = Some(item.clone()); previous = Some(item.clone());
}, }
_ => continue, _ => continue,
} }

View File

@ -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,17 +11,16 @@ 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()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", ""); .replace("\"", "");
rc.writer.write_all(b"<ul class=\"chapter\">")?; rc.writer.write_all(b"<ul class=\"chapter\">")?;
@ -29,11 +28,9 @@ impl HelperDef for RenderToc {
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>")?;

View File

@ -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,8 +64,7 @@ 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),
@ -70,8 +74,7 @@ impl Theme {
(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() {

View File

@ -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"), &mut editor.theme_tomorrow_night_js), (src.join("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() {

View File

@ -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();
@ -60,7 +60,7 @@ pub fn path_to_root<P: Into<PathBuf>>(path: P) -> String {
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,20 +147,17 @@ 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...")))?;
} }
@ -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")
} }
} }
} }

View File

@ -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,12 +76,23 @@ 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 { '“' } else { '”' }, if preceded_by_whitespace {
''
} else {
''
}
}
'"' => {
if preceded_by_whitespace {
'“'
} else {
'”'
}
}
_ => original_char, _ => original_char,
}; };
@ -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);
@ -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]

View File

@ -13,8 +13,7 @@ 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);
@ -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);
} }

View File

@ -63,18 +63,15 @@ 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();
} }

View File

@ -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);
} }
} }

View File

@ -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);
} }
} }

View 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());

View File

@ -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());

View File

@ -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")]);
} }