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<()> {
execs::cmd(program)
.arg("-v")
.output()
.chain_err(|| format!("Please install '{}'!", program))?;
execs::cmd(program).arg("-v")
.output()
.chain_err(|| format!("Please install '{}'!", program))?;
Ok(())
}
fn npm_package_exists(package: &str) -> Result<()> {
let status = execs::cmd("npm")
.args(&["list", "-g"])
.arg(package)
.output();
let status = execs::cmd("npm").args(&["list", "-g"])
.arg(package)
.output();
match status {
Ok(ref out) if out.status.success() => Ok(()),
_ => {
bail!("Missing npm package '{0}' \
install with: 'npm -g install {0}'",
bail!("Missing npm package '{0}' install with: 'npm -g install {0}'",
package)
},
}
}
}
@ -59,7 +56,7 @@ pub enum Resource<'a> {
Program(&'a str),
Package(&'a str),
}
use Resource::{Program, Package};
use Resource::{Package, Program};
impl<'a> Resource<'a> {
pub fn exists(&self) -> Result<()> {
@ -71,7 +68,6 @@ impl<'a> Resource<'a> {
}
fn run() -> Result<()> {
if let Ok(_) = env::var("CARGO_FEATURE_REGENERATE_CSS") {
// Check dependencies
Program("npm").exists()?;
@ -85,14 +81,14 @@ fn run() -> Result<()> {
let theme_dir = Path::new(&manifest_dir).join("src/theme/");
let stylus_dir = theme_dir.join("stylus/book.styl");
if !execs::cmd("stylus")
.arg(stylus_dir)
.arg("--out")
.arg(theme_dir)
.arg("--use")
.arg("nib")
.status()?
.success() {
if !execs::cmd("stylus").arg(stylus_dir)
.arg("--out")
.arg(theme_dir)
.arg("--use")
.arg("nib")
.status()?
.success()
{
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::errors::Result;
use {get_book_dir, open};
@ -8,10 +8,21 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("build")
.about("Build the book from the markdown files")
.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("--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)'")
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'The output directory for your \
book{n}(Defaults to ./book 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

View File

@ -1,6 +1,6 @@
use std::io;
use std::io::Write;
use clap::{ArgMatches, SubCommand, App};
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use mdbook::errors::Result;
use get_book_dir;
@ -10,14 +10,14 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("init")
.about("Create boilerplate structure and files in the directory")
// 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("--force 'skip confirmation prompts'")
}
// Init command implementation
pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args);
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 args.is_present("theme") {
// Skip this if `--force` is present
if !args.is_present("force") {
// Print warning
@ -45,7 +44,6 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
// Call the function that copies the theme
book.copy_theme()?;
println!("\nTheme copied.");
}
// 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]
extern crate clap;
extern crate log;
extern crate env_logger;
extern crate log;
extern crate mdbook;
extern crate open;
use std::env;
use std::ffi::OsStr;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use clap::{App, ArgMatches, AppSettings};
use log::{LogRecord, LogLevelFilter};
use clap::{App, AppSettings, ArgMatches};
use log::{LogLevelFilter, LogRecord};
use env_logger::LogBuilder;
pub mod build;
@ -33,7 +33,10 @@ fn main() {
// Get the version from our Cargo.toml using clap's crate_version!() macro
.version(concat!("v",crate_version!()))
.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(build::make_subcommand())
.subcommand(test::make_subcommand());
@ -71,7 +74,7 @@ fn init_logger() {
builder.format(format).filter(None, LogLevelFilter::Info);
if let Ok(var) = env::var("RUST_LOG") {
builder.parse(&var);
builder.parse(&var);
}
builder.init().unwrap();

View File

@ -4,8 +4,9 @@ extern crate ws;
use std;
use std::path::Path;
use self::iron::{Iron, AfterMiddleware, IronResult, IronError, Request, Response, status, Set, Chain};
use clap::{ArgMatches, SubCommand, App};
use self::iron::{status, AfterMiddleware, Chain, Iron, IronError, IronResult, Request, Response,
Set};
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use mdbook::errors::Result;
use {get_book_dir, open};
@ -17,14 +18,33 @@ struct ErrorRecover;
// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("serve")
.about("Serve the book at http://localhost:3000. Rebuild and reload on change.")
.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'")
.about(
"Serve the book at http://localhost:3000. Rebuild and reload on change.",
)
.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("-w, --websocket-port=[ws-port] 'Use another port for the 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(
"-w, --websocket-port=[ws-port] 'Use another port for the \
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'")
}
@ -53,7 +73,8 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
let address = format!("{}:{}", interface, port);
let ws_address = format!("{}:{}", interface, ws_port);
book.set_livereload(format!(r#"
book.set_livereload(format!(
r#"
<script type="text/javascript">
var socket = new WebSocket("ws://{}:{}");
socket.onmessage = function (event) {{
@ -68,9 +89,10 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
}}
</script>
"#,
public_address,
ws_port,
RELOAD_COMMAND));
public_address,
ws_port,
RELOAD_COMMAND
));
book.build()?;

View File

@ -1,4 +1,4 @@
use clap::{ArgMatches, SubCommand, App};
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use mdbook::errors::Result;
use get_book_dir;
@ -7,12 +7,16 @@ use get_book_dir;
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("test")
.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
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 mut book = MDBook::new(&book_dir).read_config()?;

View File

@ -4,7 +4,7 @@ use std::path::Path;
use self::notify::Watcher;
use std::time::Duration;
use std::sync::mpsc::channel;
use clap::{ArgMatches, SubCommand, App};
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use mdbook::errors::Result;
use {get_book_dir, open};
@ -14,9 +14,18 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("watch")
.about("Watch the files for changes")
.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("--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(
"[dir] 'A directory for your book{n}(Defaults to \
Current Directory when omitted)'",
)
}
// 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!
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::DebouncedEvent::*;
@ -64,7 +74,7 @@ pub fn trigger_on_change<F>(book: &mut MDBook, closure: F) -> ()
Err(e) => {
println!("Error while trying to watch the files:\n\n\t{:?}", e);
::std::process::exit(0);
},
}
};
// 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
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
// located in the source directory
if watcher
.watch(book.get_root().join("book.json"), NonRecursive)
.is_err() {
if watcher.watch(book.get_root().join("book.json"), NonRecursive)
.is_err()
{
// do nothing if book.json is not found
}
if watcher
.watch(book.get_root().join("book.toml"), NonRecursive)
.is_err() {
if watcher.watch(book.get_root().join("book.toml"), NonRecursive)
.is_err()
{
// 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() {
Ok(event) => {
match event {
Create(path) |
Write(path) |
Remove(path) |
Rename(_, path) => {
Create(path) | Write(path) | Remove(path) | Rename(_, path) => {
closure(&path, book);
},
_ => {},
}
_ => {}
}
},
}
Err(e) => {
println!("An error occured: {:?}", e);
},
}
}
}
}

View File

@ -27,7 +27,6 @@ pub struct BookItems<'a> {
impl Chapter {
pub fn new(name: String, path: PathBuf) -> Self {
Chapter {
name: name,
path: path,
@ -39,7 +38,8 @@ impl Chapter {
impl Serialize for Chapter {
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)?;
struct_.serialize_field("name", &self.name)?;
@ -63,21 +63,20 @@ impl<'a> Iterator for BookItems<'a> {
Some((parent_items, parent_idx)) => {
self.items = parent_items;
self.current_index = parent_idx + 1;
},
}
}
} else {
let cur = &self.items[self.current_index];
match *cur {
BookItem::Chapter(_, ref ch) |
BookItem::Affix(ref ch) => {
BookItem::Chapter(_, ref ch) | BookItem::Affix(ref ch) => {
self.stack.push((self.items, self.current_index));
self.items = &ch.sub_items[..];
self.current_index = 0;
},
}
BookItem::Spacer => {
self.current_index += 1;
},
}
}
return Some(cur);

View File

@ -8,8 +8,8 @@ use std::io::{Read, Write};
use std::process::Command;
use tempdir::TempDir;
use {theme, parse, utils};
use renderer::{Renderer, HtmlHandlebars};
use {parse, theme, utils};
use renderer::{HtmlHandlebars, Renderer};
use preprocess;
use errors::*;
@ -60,7 +60,6 @@ impl MDBook {
/// [`set_dest()`](#method.set_dest)
pub fn new<P: Into<PathBuf>>(root: P) -> MDBook {
let root = root.into();
if !root.exists() || !root.is_dir() {
warn!("{:?} No directory with that name", root);
@ -130,7 +129,6 @@ impl MDBook {
/// `chapter_1.md` to the source directory.
pub fn init(&mut self) -> Result<()> {
debug!("[fn]: init");
if !self.config.get_root().exists() {
@ -139,24 +137,25 @@ impl MDBook {
}
{
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())?;
}
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())?;
}
let summary = self.config.get_source().join("SUMMARY.md");
if !summary.exists() {
// 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)?;
debug!("[*]: Writing to SUMMARY.md");
@ -175,16 +174,15 @@ impl MDBook {
debug!("[*]: item: {:?}", item);
let ch = match *item {
BookItem::Spacer => continue,
BookItem::Chapter(_, ref ch) |
BookItem::Affix(ref ch) => ch,
BookItem::Chapter(_, ref ch) | BookItem::Affix(ref ch) => ch,
};
if !ch.path.as_os_str().is_empty() {
let path = self.config.get_source().join(&ch.path);
if !path.exists() {
if !self.create_missing {
return Err(format!("'{}' referenced from SUMMARY.md does not exist.", path.to_string_lossy())
.into());
return Err(format!("'{}' referenced from SUMMARY.md does not exist.",
path.to_string_lossy()).into());
}
debug!("[*]: {:?} does not exist, trying to create file", path);
::std::fs::create_dir_all(path.parent().unwrap())?;
@ -203,21 +201,22 @@ impl MDBook {
pub fn create_gitignore(&self) {
let gitignore = self.get_gitignore();
let destination = self.config.get_html_config()
.get_destination();
let destination = self.config.get_html_config().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
// 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()) {
let relative = destination.strip_prefix(self.config.get_root())
.expect("Could not strip the root prefix, path is not \
relative to root")
.to_str()
.expect("Could not convert to &str");
let relative = destination
.strip_prefix(self.config.get_root())
.expect("Could not strip the root prefix, path is not relative to root")
.to_str()
.expect("Could not convert to &str");
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.");
@ -254,7 +253,8 @@ impl MDBook {
let themedir = self.config.get_html_config().get_theme();
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)?;
}
@ -286,12 +286,10 @@ impl MDBook {
}
pub fn write_file<P: AsRef<Path>>(&self, filename: P, content: &[u8]) -> Result<()> {
let path = self.get_destination()
.join(filename);
let path = self.get_destination().join(filename);
utils::fs::create_file(&path)?
.write_all(content)
.map_err(|e| e.into())
utils::fs::create_file(&path)?.write_all(content)
.map_err(|e| e.into())
}
/// Parses the `book.json` file (if it exists) to extract
@ -300,7 +298,6 @@ impl MDBook {
/// The root directory is the one specified when creating a new `MDBook`
pub fn read_config(mut self) -> Result<Self> {
let toml = self.get_root().join("book.toml");
let json = self.get_root().join("book.json");
@ -362,14 +359,11 @@ impl MDBook {
.collect();
let temp_dir = TempDir::new("mdbook")?;
for item in self.iter() {
if let BookItem::Chapter(_, ref ch) = *item {
if !ch.path.as_os_str().is_empty() {
let path = self.get_source().join(&ch.path);
let base = path.parent().ok_or_else(
|| String::from("Invalid bookitem path!"),
)?;
let base = path.parent()
.ok_or_else(|| String::from("Invalid bookitem path!"))?;
let content = utils::fs::file_to_string(&path)?;
// Parse and expand links
let content = preprocess::links::replace_all(&content, base)?;
@ -380,10 +374,14 @@ impl MDBook {
let mut tmpf = utils::fs::create_file(&path)?;
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() {
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 {
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());
self
}
pub fn get_destination(&self) -> &Path {
self.config.get_html_config()
.get_destination()
self.config.get_html_config().get_destination()
}
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 {
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());
self
}
pub fn get_theme_path(&self) -> &Path {
self.config.get_html_config()
.get_theme()
self.config.get_html_config().get_theme()
}
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);
self
}
pub fn get_curly_quotes(&self) -> bool {
self.config.get_html_config()
.get_curly_quotes()
self.config.get_html_config().get_curly_quotes()
}
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);
self
}
pub fn get_mathjax_support(&self) -> bool {
self.config.get_html_config()
.get_mathjax_support()
self.config.get_html_config().get_mathjax_support()
}
pub fn get_google_analytics_id(&self) -> Option<String> {
self.config.get_html_config()
.get_google_analytics_id()
self.config.get_html_config().get_google_analytics_id()
}
pub fn has_additional_js(&self) -> bool {
self.config.get_html_config()
.has_additional_js()
self.config.get_html_config().has_additional_js()
}
pub fn get_additional_js(&self) -> &[PathBuf] {
self.config.get_html_config()
.get_additional_js()
self.config.get_html_config().get_additional_js()
}
pub fn has_additional_css(&self) -> bool {
self.config.get_html_config()
.has_additional_css()
self.config.get_html_config().has_additional_css()
}
pub fn get_additional_css(&self) -> &[PathBuf] {
self.config.get_html_config()
.get_additional_css()
self.config.get_html_config().get_additional_css()
}
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::tomlconfig::TomlConfig;
@ -33,7 +33,8 @@ impl BookConfig {
///
/// assert_eq!(config.get_root(), &root);
/// 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 {
let root: PathBuf = root.into();
@ -86,7 +87,6 @@ impl BookConfig {
}
pub fn fill_from_tomlconfig(&mut self, tomlconfig: TomlConfig) -> &mut Self {
if let Some(s) = tomlconfig.source {
self.set_source(s);
}
@ -112,7 +112,7 @@ impl BookConfig {
self.get_mut_html_config()
.fill_from_tomlconfig(root, tomlhtmlconfig);
}
self
}
@ -128,7 +128,6 @@ impl BookConfig {
/// The JSON configuration file is **deprecated** and should not be used anymore.
/// Please, migrate to the TOML configuration file.
pub fn fill_from_jsonconfig(&mut self, jsonconfig: JsonConfig) -> &mut Self {
if let Some(s) = jsonconfig.src {
self.set_source(s);
}
@ -147,14 +146,12 @@ impl BookConfig {
if let Some(d) = jsonconfig.dest {
let root = self.get_root().to_owned();
self.get_mut_html_config()
.set_destination(&root, &d);
self.get_mut_html_config().set_destination(&root, &d);
}
if let Some(d) = jsonconfig.theme_path {
let root = self.get_root().to_owned();
self.get_mut_html_config()
.set_theme(&root, &d);
self.get_mut_html_config().set_theme(&root, &d);
}
self

View File

@ -1,4 +1,4 @@
use std::path::{PathBuf, Path};
use std::path::{Path, PathBuf};
use super::tomlconfig::TomlHtmlConfig;
use super::playpenconfig::PlaypenConfig;
@ -16,7 +16,8 @@ pub struct 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;
@ -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();
if let Some(d) = tomlconfig.destination {

View File

@ -34,8 +34,7 @@ pub struct JsonConfig {
/// ```
impl JsonConfig {
pub fn from_json(input: &str) -> Result<Self> {
let config: JsonConfig = serde_json::from_str(input)
.chain_err(|| "Could not parse JSON")?;
let config: JsonConfig = serde_json::from_str(input).chain_err(|| "Could not parse JSON")?;
Ok(config)
}

View File

@ -1,4 +1,4 @@
use std::path::{PathBuf, Path};
use std::path::{Path, PathBuf};
use super::tomlconfig::TomlPlaypenConfig;
@ -28,9 +28,12 @@ impl PlaypenConfig {
}
}
pub fn fill_from_tomlconfig<T: Into<PathBuf>>(&mut self, root: T, tomlplaypenconfig: TomlPlaypenConfig) -> &mut Self {
pub fn fill_from_tomlconfig<T: Into<PathBuf>>(&mut self,
root: T,
tomlplaypenconfig: TomlPlaypenConfig)
-> &mut Self {
let root = root.into();
if let Some(editor) = tomlplaypenconfig.editor {
if editor.is_relative() {
self.editor = root.join(editor);

View File

@ -46,16 +46,15 @@ pub struct TomlPlaypenConfig {
/// let toml = r#"title="Some title"
/// [output.html]
/// destination = "htmlbook" "#;
///
///
/// let config = TomlConfig::from_toml(&toml).expect("Should parse correctly");
/// assert_eq!(config.title, Some(String::from("Some title")));
/// assert_eq!(config.output.unwrap().html.unwrap().destination, Some(PathBuf::from("htmlbook")));
/// ```
impl TomlConfig {
pub fn from_toml(input: &str) -> Result<Self> {
let config: TomlConfig = toml::from_str(input)
.chain_err(|| "Could not parse TOML")?;
let config: TomlConfig = toml::from_str(input).chain_err(|| "Could not parse TOML")?;
Ok(config)
}
}

View File

@ -3,8 +3,8 @@
//! **mdBook** is similar to Gitbook but implemented in Rust.
//! 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
//! contains information about the command line tool, format, structure etc.
//! This is the API doc, but you can find a [less "low-level" documentation here](../index.html)
//! that contains information about the command line tool, format, structure etc.
//! 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):
@ -80,9 +80,9 @@ extern crate lazy_static;
extern crate log;
extern crate pulldown_cmark;
extern crate regex;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde;
#[macro_use]
extern crate serde_json;
extern crate tempdir;

View File

@ -1,6 +1,6 @@
use std::path::PathBuf;
use std::fs::File;
use std::io::{Read, Result, Error, ErrorKind};
use std::io::{Error, ErrorKind, Read, Result};
use book::bookitem::{BookItem, Chapter};
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)
}
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");
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 !!
// Add a sub-number to section
section.push(0);
let last = items
.pop()
.expect("There should be at least one item since this can't be the root level");
let last = items.pop().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 {
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();
continue;
} else {
return Err(Error::new(ErrorKind::Other,
"Your summary.md is messed up\n\n
return Err(Error::new(
ErrorKind::Other,
"Your summary.md is messed up\n\n
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 \
no chapters after suffix elements."));
Prefix elements can only exist before \
any chapter and there can be \
no chapters after suffix elements.",
));
};
} else {
// level and current_level are the same, parse the line
item = if let Some(parsed_item) = parse_line(summary[0]) {
// Eliminate possible errors and set section to -1 after suffix
match parsed_item {
// error if level != 0 and BookItem is != Chapter
BookItem::Affix(_) |
BookItem::Spacer if level > 0 => {
return Err(Error::new(ErrorKind::Other,
"Your summary.md is messed up\n\n
BookItem::Affix(_) | BookItem::Spacer if level > 0 => {
return Err(Error::new(
ErrorKind::Other,
"Your summary.md is messed up\n\n
\
Prefix, Suffix and Spacer elements can only exist on the \
root level.\n
Prefix, Suffix and Spacer elements \
can only exist on the root level.\n
Prefix \
elements can only exist before any chapter and there can be \
no chapters after suffix elements."))
},
elements can only exist before any chapter and \
there can be no chapters after suffix elements.",
))
}
// error if BookItem == Chapter and section == -1
BookItem::Chapter(_, _) if section[0] == -1 => {
return Err(Error::new(ErrorKind::Other,
"Your summary.md is messed up\n\n
return Err(Error::new(
ErrorKind::Other,
"Your summary.md is messed up\n\n
\
Prefix, Suffix and Spacer elements can only exist on the \
root level.\n
Prefix, Suffix and Spacer elements can only \
exist on the root level.\n
Prefix \
elements can only exist before any chapter and there can be \
no chapters after suffix elements."))
},
elements can only exist before any chapter and \
there can be no chapters after suffix elements.",
))
}
// Set section = -1 after suffix
BookItem::Affix(_) if section[0] > 0 => {
section[0] = -1;
},
}
_ => {},
_ => {}
}
match parsed_item {
@ -102,14 +110,12 @@ fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32
// Increment section
let len = section.len() - 1;
section[len] += 1;
let s = section
.iter()
.fold("".to_owned(), |s, i| s + &i.to_string() + ".");
let s = section.iter()
.fold("".to_owned(), |s, i| s + &i.to_string() + ".");
BookItem::Chapter(s, ch)
},
}
_ => parsed_item,
}
} else {
// If parse_line does not return Some(_) continue...
summary.remove(0);
@ -146,8 +152,10 @@ fn level(line: &str, spaces_in_tab: i32) -> Result<i32> {
if spaces > 0 {
debug!("[SUMMARY.md]:");
debug!("\t[line]: {}", line);
debug!("[*]: There is an indentation error on this line. Indentation should be {} spaces", spaces_in_tab);
return Err(Error::new(ErrorKind::Other, format!("Indentation error on line:\n\n{}", line)));
debug!("[*]: There is an indentation error on this line. Indentation should be {} spaces",
spaces_in_tab);
return Err(Error::new(ErrorKind::Other,
format!("Indentation error on line:\n\n{}", line)));
}
Ok(level)
@ -177,7 +185,7 @@ fn parse_line(l: &str) -> Option<BookItem> {
} else {
return None;
}
},
}
// Non-list element
'[' => {
debug!("[*]: Line is a link element");
@ -187,8 +195,8 @@ fn parse_line(l: &str) -> Option<BookItem> {
} else {
return None;
}
},
_ => {},
}
_ => {}
}
}

View File

@ -39,7 +39,6 @@ struct Link<'a> {
impl<'a> Link<'a> {
fn from_capture(cap: Captures<'a>) -> Option<Link<'a>> {
let link_type = match (cap.get(0), cap.get(1), cap.get(2)) {
(_, Some(typ), Some(rest)) => {
let mut path_props = rest.as_str().split_whitespace();
@ -51,21 +50,22 @@ impl<'a> Link<'a> {
("playpen", Some(pth)) => Some(LinkType::Playpen(pth, props)),
_ => 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,
};
link_type.and_then(|lnk| {
cap.get(0)
.map(|mat| {
Link {
start_index: mat.start(),
end_index: mat.end(),
link: lnk,
link_text: mat.as_str(),
}
})
cap.get(0).map(|mat| {
Link {
start_index: mat.start(),
end_index: mat.end(),
link: lnk,
link_text: mat.as_str(),
}
})
})
}
@ -75,14 +75,22 @@ impl<'a> Link<'a> {
// omit the escape char
LinkType::Escaped => Ok((&self.link_text[1..]).to_owned()),
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) => {
let contents = file_to_string(base.join(pat))
.chain_err(|| format!("Could not read file for link {}", self.link_text))?;
let contents = file_to_string(base.join(pat)).chain_err(|| {
format!("Could not \
read file \
for link {}",
self.link_text)
})?;
let ftype = if !attrs.is_empty() { "rust," } else { "rust" };
Ok(format!("```{}{}\n{}\n```\n", ftype, attrs.join(","), contents))
},
}
}
}
}
@ -190,7 +198,8 @@ fn test_find_links_escaped_link() {
#[test]
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<_>>();
println!("\nOUTPUT: {:?}\n", res);
@ -202,16 +211,19 @@ fn test_find_playpens_with_properties() {
link_text: "{{#playpen file.rs editable }}",
},
Link {
start_index: 90,
end_index: 137,
link: LinkType::Playpen(PathBuf::from("my.rs"), vec!["editable", "no_run", "should_panic"]),
start_index: 89,
end_index: 136,
link: LinkType::Playpen(PathBuf::from("my.rs"),
vec!["editable", "no_run", "should_panic"]),
link_text: "{{#playpen my.rs editable no_run should_panic}}",
}]);
}
#[test]
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<_>>();
println!("\nOUTPUT: {:?}\n", res);
@ -234,7 +246,8 @@ fn test_find_all_link_types() {
Link {
start_index: 130,
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}}",
});
}

View File

@ -4,10 +4,10 @@ use renderer::Renderer;
use book::MDBook;
use book::bookitem::{BookItem, Chapter};
use config::PlaypenConfig;
use {utils, theme};
use theme::{Theme, playpen_editor};
use {theme, utils};
use theme::{playpen_editor, Theme};
use errors::*;
use regex::{Regex, Captures};
use regex::{Captures, Regex};
use std::ascii::AsciiExt;
use std::path::{Path, PathBuf};
@ -28,18 +28,20 @@ impl HtmlHandlebars {
HtmlHandlebars
}
fn render_item(&self, item: &BookItem, mut ctx: RenderItemContext, print_content: &mut String)
-> Result<()> {
fn render_item(&self,
item: &BookItem,
mut ctx: RenderItemContext,
print_content: &mut String)
-> Result<()> {
// FIXME: This should be made DRY-er and rely less on mutable state
match *item {
BookItem::Chapter(_, ref ch) |
BookItem::Affix(ref ch) if !ch.path.as_os_str().is_empty() => {
BookItem::Chapter(_, ref ch) | BookItem::Affix(ref ch)
if !ch.path.as_os_str().is_empty() =>
{
let path = ctx.book.get_source().join(&ch.path);
let content = utils::fs::file_to_string(&path)?;
let base = path.parent().ok_or_else(
|| String::from("Invalid bookitem path!"),
)?;
let base = path.parent()
.ok_or_else(|| String::from("Invalid bookitem path!"))?;
// Parse and expand links
let content = preprocess::links::replace_all(&content, base)?;
@ -48,34 +50,39 @@ impl HtmlHandlebars {
// Update the context with data for this file
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 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;
}
ctx.data.insert("path".to_owned(), json!(path));
ctx.data.insert("content".to_owned(), json!(content));
ctx.data.insert("chapter_title".to_owned(), json!(ch.name));
ctx.data.insert("title".to_owned(), json!(title));
ctx.data.insert(
"path_to_root".to_owned(),
json!(utils::fs::path_to_root(&ch.path)),
);
ctx.data.insert("path_to_root".to_owned(),
json!(utils::fs::path_to_root(&ch.path)));
// Render the handlebars template with the data
debug!("[*]: Render template");
let rendered = ctx.handlebars.render("index", &ctx.data)?;
let filepath = Path::new(&ch.path).with_extension("html");
let rendered = self.post_process(rendered,
&normalize_path(filepath.to_str()
.ok_or(Error::from(format!("Bad file name: {}", filepath.display())))?),
ctx.book.get_html_config().get_playpen_config()
let rendered = self.post_process(
rendered,
&normalize_path(filepath.to_str().ok_or(Error::from(
format!("Bad file name: {}", filepath.display()),
))?),
ctx.book.get_html_config().get_playpen_config(),
);
// Write to file
@ -85,8 +92,8 @@ impl HtmlHandlebars {
if ctx.is_index {
self.render_index(ctx.book, ch, &ctx.destination)?;
}
},
_ => {},
}
_ => {}
}
Ok(())
@ -104,24 +111,24 @@ impl HtmlHandlebars {
// This could cause a problem when someone displays
// code containing <base href=...>
// on the front page, however this case should be very very rare...
content = content
.lines()
.filter(|line| !line.contains("<base href="))
.collect::<Vec<&str>>()
.join("\n");
content = content.lines()
.filter(|line| !line.contains("<base href="))
.collect::<Vec<&str>>()
.join("\n");
book.write_file("index.html", content.as_bytes())?;
info!(
"[*] Creating index.html from {:?} ✓",
book.get_destination()
.join(&ch.path.with_extension("html"))
);
info!("[*] Creating index.html from {:?} ✓",
book.get_destination().join(&ch.path.with_extension("html")));
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 = fix_anchor_links(&rendered, &filepath);
let rendered = fix_code_blocks(&rendered);
@ -136,45 +143,24 @@ impl HtmlHandlebars {
book.write_file("favicon.png", &theme.favicon)?;
book.write_file("jquery.js", &theme.jquery)?;
book.write_file("highlight.css", &theme.highlight_css)?;
book.write_file(
"tomorrow-night.css",
&theme.tomorrow_night_css,
)?;
book.write_file(
"ayu-highlight.css",
&theme.ayu_highlight_css,
)?;
book.write_file("tomorrow-night.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("clipboard.min.js", &theme.clipboard_js)?;
book.write_file("store.js", &theme.store_js)?;
book.write_file(
"_FontAwesome/css/font-awesome.css",
theme::FONT_AWESOME,
)?;
book.write_file(
"_FontAwesome/fonts/fontawesome-webfont.eot",
theme::FONT_AWESOME_EOT,
)?;
book.write_file(
"_FontAwesome/fonts/fontawesome-webfont.svg",
theme::FONT_AWESOME_SVG,
)?;
book.write_file(
"_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,
)?;
book.write_file("_FontAwesome/css/font-awesome.css", theme::FONT_AWESOME)?;
book.write_file("_FontAwesome/fonts/fontawesome-webfont.eot",
theme::FONT_AWESOME_EOT)?;
book.write_file("_FontAwesome/fonts/fontawesome-webfont.svg",
theme::FONT_AWESOME_SVG)?;
book.write_file("_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();
@ -202,12 +188,11 @@ impl HtmlHandlebars {
let name = match custom_file.strip_prefix(book.get_root()) {
Ok(p) => p.to_str().expect("Could not convert to str"),
Err(_) => {
custom_file
.file_name()
.expect("File has a file name")
.to_str()
.expect("Could not convert to str")
},
custom_file.file_name()
.expect("File has a file name")
.to_str()
.expect("Could not convert to str")
}
};
book.write_file(name, &data)?;
@ -216,14 +201,17 @@ impl HtmlHandlebars {
}
/// 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
// the last rendered chapter by removing it from its context
data.remove("title");
data.insert("is_print".to_owned(), json!(true));
data.insert("path".to_owned(), json!("print.md"));
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) {
@ -235,10 +223,9 @@ impl HtmlHandlebars {
/// Copy across any additional CSS and JavaScript files which the book
/// has been configured to use.
fn copy_additional_css_and_js(&self, book: &MDBook) -> Result<()> {
let custom_files = book.get_additional_css().iter().chain(
book.get_additional_js()
.iter(),
);
let custom_files = book.get_additional_css()
.iter()
.chain(book.get_additional_js().iter());
for custom_file in custom_files {
self.write_custom_file(custom_file, book)?;
@ -257,10 +244,7 @@ impl Renderer for HtmlHandlebars {
let theme = theme::Theme::new(book.get_theme_path());
debug!("[*]: Register handlebars template");
handlebars.register_template_string(
"index",
String::from_utf8(theme.index.clone())?,
)?;
handlebars.register_template_string("index", String::from_utf8(theme.index.clone())?)?;
debug!("[*]: Register handlebars helpers");
self.register_hbs_helpers(&mut handlebars);
@ -297,13 +281,12 @@ impl Renderer for HtmlHandlebars {
let rendered = handlebars.render("index", &data)?;
let rendered = self.post_process(rendered, "print.html",
book.get_html_config().get_playpen_config());
let rendered = self.post_process(rendered,
"print.html",
book.get_html_config().get_playpen_config());
book.write_file(
Path::new("print").with_extension("html"),
&rendered.into_bytes(),
)?;
book.write_file(Path::new("print").with_extension("html"),
&rendered.into_bytes())?;
info!("[*] Creating print.html ✓");
// 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()) {
Ok(p) => css.push(p.to_str().expect("Could not convert to str")),
Err(_) => {
css.push(
style
.file_name()
.expect("File has a file name")
.to_str()
.expect("Could not convert to str"),
)
},
css.push(style.file_name()
.expect("File has a file name")
.to_str()
.expect("Could not convert to str"))
}
}
}
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()) {
Ok(p) => js.push(p.to_str().expect("Could not convert to str")),
Err(_) => {
js.push(
script
.file_name()
.expect("File has a file name")
.to_str()
.expect("Could not convert to str"),
)
},
js.push(script.file_name()
.expect("File has a file name")
.to_str()
.expect("Could not convert to str"))
}
}
}
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("mode_rust_js".to_owned(), json!("mode-rust.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![];
@ -398,22 +376,25 @@ fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>
BookItem::Affix(ref ch) => {
chapter.insert("name".to_owned(), json!(ch.name));
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));
},
}
BookItem::Chapter(ref s, ref ch) => {
chapter.insert("section".to_owned(), json!(s));
chapter.insert("name".to_owned(), json!(ch.name));
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));
},
}
BookItem::Spacer => {
chapter.insert("spacer".to_owned(), json!("_spacer_"));
},
}
}
chapters.push(chapter);
@ -431,21 +412,22 @@ fn build_header_links(html: &str, filepath: &str) -> String {
let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();
let mut id_counter = HashMap::new();
regex
.replace_all(html, |caps: &Captures| {
let level = caps[1].parse().expect(
"Regex should ensure we only ever get numbers here",
);
regex.replace_all(html, |caps: &Captures| {
let level = caps[1].parse()
.expect("Regex should ensure we only ever get numbers here");
wrap_header_with_link(level, &caps[2], &mut id_counter, filepath)
})
.into_owned()
wrap_header_with_link(level, &caps[2], &mut id_counter, filepath)
})
.into_owned()
}
/// 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).
fn wrap_header_with_link(level: usize, content: &str, id_counter: &mut HashMap<String, usize>, filepath: &str)
-> String {
fn wrap_header_with_link(level: usize,
content: &str,
id_counter: &mut HashMap<String, usize>,
filepath: &str)
-> String {
let raw_id = id_from_content(content);
let id_count = id_counter.entry(raw_id.clone()).or_insert(0);
@ -466,25 +448,23 @@ fn wrap_header_with_link(level: usize, content: &str, id_counter: &mut HashMap<S
)
}
/// Generate an id for use with anchors which is derived from a "normalised"
/// Generate an id for use with anchors which is derived from a "normalised"
/// string.
fn id_from_content(content: &str) -> String {
let mut content = content.to_string();
// Skip any tags or html-encoded stuff
const REPL_SUB: &[&str] = &[
"<em>",
"</em>",
"<code>",
"</code>",
"<strong>",
"</strong>",
"&lt;",
"&gt;",
"&amp;",
"&#39;",
"&quot;",
];
const REPL_SUB: &[&str] = &["<em>",
"</em>",
"<code>",
"</code>",
"<strong>",
"</strong>",
"&lt;",
"&gt;",
"&amp;",
"&#39;",
"&quot;"];
for sub in REPL_SUB {
content = content.replace(sub, "");
}
@ -500,21 +480,18 @@ fn id_from_content(content: &str) -> String {
// that in a very inelegant way
fn fix_anchor_links(html: &str, filepath: &str) -> String {
let regex = Regex::new(r##"<a([^>]+)href="#([^"]+)"([^>]*)>"##).unwrap();
regex
.replace_all(html, |caps: &Captures| {
let before = &caps[1];
let anchor = &caps[2];
let after = &caps[3];
regex.replace_all(html, |caps: &Captures| {
let before = &caps[1];
let anchor = &caps[2];
let after = &caps[3];
format!(
"<a{before}href=\"{filepath}#{anchor}\"{after}>",
format!("<a{before}href=\"{filepath}#{anchor}\"{after}>",
before = before,
filepath = filepath,
anchor = anchor,
after = after
)
})
.into_owned()
after = after)
})
.into_owned()
}
@ -528,46 +505,53 @@ fn fix_anchor_links(html: &str, filepath: &str) -> String {
// This function replaces all commas by spaces in the code block classes
fn fix_code_blocks(html: &str) -> String {
let regex = Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap();
regex
.replace_all(html, |caps: &Captures| {
let before = &caps[1];
let classes = &caps[2].replace(",", " ");
let after = &caps[3];
regex.replace_all(html, |caps: &Captures| {
let before = &caps[1];
let classes = &caps[2].replace(",", " ");
let after = &caps[3];
format!(r#"<code{before}class="{classes}"{after}>"#, before = before, classes = classes, after = after)
})
.into_owned()
format!(r#"<code{before}class="{classes}"{after}>"#,
before = before,
classes = classes,
after = after)
})
.into_owned()
}
fn add_playpen_pre(html: &str, playpen_config: &PlaypenConfig) -> String {
let regex = Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap();
regex
.replace_all(html, |caps: &Captures| {
let text = &caps[1];
let classes = &caps[2];
let code = &caps[3];
regex.replace_all(html, |caps: &Captures| {
let text = &caps[1];
let classes = &caps[2];
let code = &caps[3];
if (classes.contains("language-rust") && !classes.contains("ignore")) || classes.contains("mdbook-runnable") {
// wrap the contents in an external pre block
if playpen_config.is_editable() &&
classes.contains("editable") || text.contains("fn main") || text.contains("quick_main!") {
format!("<pre class=\"playpen\">{}</pre>", text)
} else {
// we need to inject our own main
let (attrs, code) = partition_source(code);
format!("<pre class=\"playpen\"><code class=\"{}\">\n# #![allow(unused_variables)]\n\
{}#fn main() {{\n\
{}\
#}}</code></pre>",
classes, attrs, code)
}
if (classes.contains("language-rust") && !classes.contains("ignore")) ||
classes.contains("mdbook-runnable")
{
// wrap the contents in an external pre block
if playpen_config.is_editable() && classes.contains("editable") ||
text.contains("fn main") || text.contains("quick_main!")
{
format!("<pre class=\"playpen\">{}</pre>", text)
} else {
// not language-rust, so no-op
text.to_owned()
// we need to inject our own main
let (attrs, code) = partition_source(code);
format!("<pre class=\"playpen\"><code class=\"{}\">\n# \
#![allow(unused_variables)]\n\
{}#fn main() {{\n\
{}\
#}}</code></pre>",
classes,
attrs,
code)
}
})
.into_owned()
} else {
// not language-rust, so no-op
text.to_owned()
}
})
.into_owned()
}
fn partition_source(s: &str) -> (String, String) {
@ -609,16 +593,14 @@ pub fn normalize_path(path: &str) -> String {
pub fn normalize_id(content: &str) -> String {
content.chars()
.filter_map(|ch|
if ch.is_alphanumeric() || ch == '_' || ch == '-' {
Some(ch.to_ascii_lowercase())
} else if ch.is_whitespace() {
Some('-')
} else {
None
}
)
.collect::<String>()
.filter_map(|ch| if ch.is_alphanumeric() || ch == '_' || ch == '-' {
Some(ch.to_ascii_lowercase())
} else if ch.is_whitespace() {
Some('-')
} else {
None
})
.collect::<String>()
}
@ -643,15 +625,15 @@ mod tests {
),
(
"<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>",
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>",
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]
fn anchor_generation() {
assert_eq!(id_from_content("## `--passes`: add more rustdoc passes"), "--passes-add-more-rustdoc-passes");
assert_eq!(id_from_content("## Method-call expressions"), "method-call-expressions");
assert_eq!(id_from_content("## `--passes`: add more rustdoc passes"),
"--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 serde_json;
use handlebars::{Handlebars, RenderError, RenderContext, Helper, Renderable, Context};
use handlebars::{Context, Handlebars, Helper, RenderContext, RenderError, Renderable};
// Handlebars helper for navigation
@ -11,73 +11,78 @@ pub fn previous(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(
debug!("[fn]: previous (handlebars helper)");
debug!("[*]: Get data from context");
let chapters = rc.evaluate_absolute("chapters")
.and_then(|c| {
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
.map_err(|_| RenderError::new("Could not decode the JSON data"))
})?;
let chapters = rc.evaluate_absolute("chapters").and_then(|c| {
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
.map_err(|_| RenderError::new("Could not decode the JSON data"))
})?;
let current = rc.evaluate_absolute("path")?
.as_str().ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", "");
.as_str()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", "");
let mut previous: Option<BTreeMap<String, String>> = None;
debug!("[*]: Search for current Chapter");
// Search for current chapter and return previous entry
for item in chapters {
match item.get("path") {
Some(path) if !path.is_empty() => {
if path == &current {
debug!("[*]: Found current chapter");
if let Some(previous) = previous {
debug!("[*]: Creating BTreeMap to inject in context");
// Create new BTreeMap to extend the context: 'title' and 'link'
let mut previous_chapter = BTreeMap::new();
// Chapter title
previous
.get("name").ok_or_else(|| RenderError::new("No title found for chapter in JSON data"))
.and_then(|n| {
previous_chapter.insert("title".to_owned(), json!(n));
Ok(())
})?;
previous.get("name")
.ok_or_else(|| {
RenderError::new("No title found for chapter in \
JSON data")
})
.and_then(|n| {
previous_chapter.insert("title".to_owned(), json!(n));
Ok(())
})?;
// Chapter link
previous
.get("path").ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))
.and_then(|p| {
Path::new(p)
.with_extension("html")
.to_str().ok_or_else(|| RenderError::new("Link could not be converted to str"))
.and_then(|p| {
previous_chapter
.insert("link".to_owned(), json!(p.replace("\\", "/")));
Ok(())
})
})?;
previous.get("path")
.ok_or_else(|| {
RenderError::new("No path found for chapter in \
JSON data")
})
.and_then(|p| {
Path::new(p).with_extension("html")
.to_str()
.ok_or_else(|| {
RenderError::new("Link could not be \
converted to str")
})
.and_then(|p| {
previous_chapter
.insert("link".to_owned(), json!(p.replace("\\", "/")));
Ok(())
})
})?;
debug!("[*]: Render template");
// Render template
_h.template()
.ok_or_else(|| RenderError::new("Error with the handlebars template"))
.and_then(|t| {
let mut local_rc = rc.with_context(Context::wraps(&previous_chapter)?);
t.render(r, &mut local_rc)
})?;
.ok_or_else(|| RenderError::new("Error with the handlebars template"))
.and_then(|t| {
let mut local_rc = rc.with_context(Context::wraps(&previous_chapter)?);
t.render(r, &mut local_rc)
})?;
}
break;
} else {
previous = Some(item.clone());
}
},
}
_ => continue,
}
}
@ -91,67 +96,71 @@ pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), R
debug!("[fn]: next (handlebars helper)");
debug!("[*]: Get data from context");
let chapters = rc.evaluate_absolute("chapters")
.and_then(|c| {
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
.map_err(|_| RenderError::new("Could not decode the JSON data"))
})?;
let chapters = rc.evaluate_absolute("chapters").and_then(|c| {
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
.map_err(|_| RenderError::new("Could not decode the JSON data"))
})?;
let current = rc.evaluate_absolute("path")?
.as_str().ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", "");
.as_str()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", "");
let mut previous: Option<BTreeMap<String, String>> = None;
debug!("[*]: Search for current Chapter");
// Search for current chapter and return previous entry
for item in chapters {
match item.get("path") {
Some(path) if !path.is_empty() => {
if let Some(previous) = previous {
let previous_path = previous
.get("path").ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))?;
let previous_path = previous.get("path").ok_or_else(|| {
RenderError::new("No path found for chapter in JSON data")
})?;
if previous_path == &current {
debug!("[*]: Found current chapter");
debug!("[*]: Creating BTreeMap to inject in context");
// Create new BTreeMap to extend the context: 'title' and 'link'
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| {
next_chapter.insert("title".to_owned(), json!(n));
Ok(())
})?;
Path::new(path)
.with_extension("html")
.to_str().ok_or_else(|| RenderError::new("Link could not converted to str"))
.and_then(|l| {
debug!("[*]: Inserting link: {:?}", l);
// Hack for windows who tends to use `\` as separator instead of `/`
next_chapter.insert("link".to_owned(), json!(l.replace("\\", "/")));
Ok(())
})?;
Path::new(path).with_extension("html")
.to_str()
.ok_or_else(|| {
RenderError::new("Link could not converted \
to str")
})
.and_then(|l| {
debug!("[*]: Inserting link: {:?}", l);
// Hack for windows who tends to use `\` as separator instead of `/`
next_chapter.insert("link".to_owned(), json!(l.replace("\\", "/")));
Ok(())
})?;
debug!("[*]: Render template");
// Render template
_h.template().ok_or_else(|| RenderError::new("Error with the handlebars template"))
.and_then(|t| {
let mut local_rc = rc.with_context(Context::wraps(&next_chapter)?);
t.render(r, &mut local_rc)
})?;
_h.template()
.ok_or_else(|| RenderError::new("Error with the handlebars template"))
.and_then(|t| {
let mut local_rc = rc.with_context(Context::wraps(&next_chapter)?);
t.render(r, &mut local_rc)
})?;
break;
}
}
previous = Some(item.clone());
},
}
_ => continue,
}

View File

@ -2,8 +2,8 @@ use std::path::Path;
use std::collections::BTreeMap;
use serde_json;
use handlebars::{Handlebars, HelperDef, RenderError, RenderContext, Helper};
use pulldown_cmark::{Parser, html, Event, Tag};
use handlebars::{Handlebars, Helper, HelperDef, RenderContext, RenderError};
use pulldown_cmark::{html, Event, Parser, Tag};
// Handlebars helper to construct TOC
#[derive(Clone, Copy)]
@ -11,29 +11,26 @@ pub struct RenderToc;
impl HelperDef for RenderToc {
fn call(&self, _h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
// get value from context data
// 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
let chapters = rc.evaluate_absolute("chapters")
.and_then(|c| {
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
.map_err(|_| RenderError::new("Could not decode the JSON data"))
})?;
let chapters = rc.evaluate_absolute("chapters").and_then(|c| {
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
.map_err(|_| RenderError::new("Could not decode the JSON data"))
})?;
let current = rc.evaluate_absolute("path")?
.as_str().ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", "");
.as_str()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", "");
rc.writer.write_all(b"<ul class=\"chapter\">")?;
let mut current_level = 1;
for item in chapters {
// Spacer
if item.get("spacer").is_some() {
rc.writer
.write_all(b"<li class=\"spacer\"></li>")?;
rc.writer.write_all(b"<li class=\"spacer\"></li>")?;
continue;
}
@ -126,7 +123,6 @@ impl HelperDef for RenderToc {
}
rc.writer.write_all(b"</li>")?;
}
while current_level > 1 {
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 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_EOT: &'static [u8] = include_bytes!("_FontAwesome/fonts/fontawesome-webfont.eot");
pub static FONT_AWESOME_SVG: &'static [u8] = include_bytes!("_FontAwesome/fonts/fontawesome-webfont.svg");
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_EOT: &'static [u8] =
include_bytes!("_FontAwesome/fonts/fontawesome-webfont.eot");
pub static FONT_AWESOME_SVG: &'static [u8] =
include_bytes!("_FontAwesome/fonts/fontawesome-webfont.svg");
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");
@ -59,19 +64,17 @@ impl Theme {
// Check for individual files, if they exist copy them across
{
let files = vec![
(theme_dir.join("index.hbs"), &mut theme.index),
(theme_dir.join("book.js"), &mut theme.js),
(theme_dir.join("book.css"), &mut theme.css),
(theme_dir.join("favicon.png"), &mut theme.favicon),
(theme_dir.join("highlight.js"), &mut theme.highlight_js),
(theme_dir.join("clipboard.min.js"), &mut theme.clipboard_js),
(theme_dir.join("store.js"), &mut theme.store_js),
(theme_dir.join("highlight.css"), &mut theme.highlight_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("jquery.js"), &mut theme.jquery),
];
let files = vec![(theme_dir.join("index.hbs"), &mut theme.index),
(theme_dir.join("book.js"), &mut theme.js),
(theme_dir.join("book.css"), &mut theme.css),
(theme_dir.join("favicon.png"), &mut theme.favicon),
(theme_dir.join("highlight.js"), &mut theme.highlight_js),
(theme_dir.join("clipboard.min.js"), &mut theme.clipboard_js),
(theme_dir.join("store.js"), &mut theme.store_js),
(theme_dir.join("highlight.css"), &mut theme.highlight_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("jquery.js"), &mut theme.jquery)];
for (filename, dest) in files {
if !filename.exists() {

View File

@ -47,13 +47,12 @@ impl PlaypenEditor {
// Check for individual files if they exist
{
let files = vec![
(src.join("editor.js"), &mut editor.js),
(src.join("ace.js"), &mut editor.ace_js),
(src.join("mode-rust.js"), &mut editor.mode_rust_js),
(src.join("theme-dawn.js"), &mut editor.theme_dawn_js),
(src.join("theme-tomorrow_night.js"), &mut editor.theme_tomorrow_night_js),
];
let files = vec![(src.join("editor.js"), &mut editor.js),
(src.join("ace.js"), &mut editor.ace_js),
(src.join("mode-rust.js"), &mut editor.mode_rust_js),
(src.join("theme-dawn.js"), &mut editor.theme_dawn_js),
(src.join("theme-tomorrow_night.js"),
&mut editor.theme_tomorrow_night_js)];
for (filename, dest) in files {
if !filename.exists() {

View File

@ -1,4 +1,4 @@
use std::path::{Path, PathBuf, Component};
use std::path::{Component, Path, PathBuf};
use errors::*;
use std::io::Read;
use std::fs::{self, File};
@ -11,7 +11,7 @@ pub fn file_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
Err(e) => {
debug!("[*]: Failed to open {:?}", path);
bail!(e);
},
}
};
let mut content = String::new();
@ -56,14 +56,14 @@ pub fn path_to_root<P: Into<PathBuf>>(path: P) -> String {
.expect("")
.components()
.fold(String::new(), |mut s, c| {
match c {
Component::Normal(_) => s.push_str("../"),
_ => {
debug!("[*]: Other path component... {:?}", c);
},
match c {
Component::Normal(_) => s.push_str("../"),
_ => {
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
/// 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<()> {
debug!("[fn] copy_files_except_ext");
// 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()))?;
}
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() {
// Check if it is in the blacklist
if let Some(ext) = entry.path().extension() {
if ext_blacklist.contains(&ext.to_str().unwrap()) {
@ -142,22 +147,19 @@ pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blackl
}
}
debug!("[*] creating path for file: {:?}",
&to.join(entry
.path()
.file_name()
.expect("a file should have a file name...")));
&to.join(entry.path()
.file_name()
.expect("a file should have a file name...")));
info!("[*] Copying file: {:?}\n to {:?}",
entry.path(),
&to.join(entry
.path()
.file_name()
.expect("a file should have a file name...")));
&to.join(entry.path()
.file_name()
.expect("a file should have a file name...")));
fs::copy(entry.path(),
&to.join(entry
.path()
.file_name()
.expect("a file should have a file name...")))?;
&to.join(entry.path()
.file_name()
.expect("a file should have a file name...")))?;
}
}
Ok(())
@ -216,7 +218,7 @@ mod tests {
match copy_files_except_ext(&tmp.path(), &tmp.path().join("output"), true, &["md"]) {
Err(e) => panic!("Error while executing the function:\n{:?}", e),
Ok(_) => {},
Ok(_) => {}
}
// Check if the correct files where created
@ -235,6 +237,5 @@ mod tests {
if !(&tmp.path().join("output/sub_dir_exists/file.txt")).exists() {
panic!("output/sub_dir/file.png should exist")
}
}
}

View File

@ -1,6 +1,7 @@
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;
@ -17,7 +18,8 @@ pub fn render_markdown(text: &str, curly_quotes: bool) -> String {
let p = Parser::new_ext(text, opts);
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);
s
@ -30,7 +32,10 @@ struct EventQuoteConverter {
impl EventQuoteConverter {
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> {
@ -39,17 +44,17 @@ impl EventQuoteConverter {
}
match event {
Event::Start(Tag::CodeBlock(_)) |
Event::Start(Tag::Code) => {
Event::Start(Tag::CodeBlock(_)) | Event::Start(Tag::Code) => {
self.convert_text = false;
event
},
Event::End(Tag::CodeBlock(_)) |
Event::End(Tag::Code) => {
}
Event::End(Tag::CodeBlock(_)) | Event::End(Tag::Code) => {
self.convert_text = true;
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,
}
}
@ -58,13 +63,10 @@ impl EventQuoteConverter {
fn clean_codeblock_headers(event: Event) -> Event {
match event {
Event::Start(Tag::CodeBlock(ref info)) => {
let info: String = info
.chars()
.filter(|ch| !ch.is_whitespace())
.collect();
let info: String = info.chars().filter(|ch| !ch.is_whitespace()).collect();
Event::Start(Tag::CodeBlock(Cow::from(info)))
},
}
_ => event,
}
}
@ -74,20 +76,31 @@ fn convert_quotes_to_curly(original_text: &str) -> String {
// We'll consider the start to be "whitespace".
let mut preceded_by_whitespace = true;
original_text
.chars()
.map(|original_char| {
let converted_char = match original_char {
'\'' => if preceded_by_whitespace { '' } else { '' },
'"' => if preceded_by_whitespace { '“' } else { '”' },
_ => original_char,
};
original_text.chars()
.map(|original_char| {
let converted_char = match original_char {
'\'' => {
if preceded_by_whitespace {
''
} else {
''
}
}
'"' => {
if preceded_by_whitespace {
'“'
} else {
'”'
}
}
_ => original_char,
};
preceded_by_whitespace = original_char.is_whitespace();
preceded_by_whitespace = original_char.is_whitespace();
converted_char
})
.collect()
converted_char
})
.collect()
}
#[cfg(test)]
@ -146,7 +159,8 @@ more text with spaces
```
"#;
let expected = r#"<pre><code class="language-rust,no_run,should_panic,property_3"></code></pre>
let expected =
r#"<pre><code class="language-rust,no_run,should_panic,property_3"></code></pre>
"#;
assert_eq!(render_markdown(input, false), expected);
assert_eq!(render_markdown(input, 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, true), expected);
@ -168,7 +183,7 @@ more text with spaces
#[test]
fn rust_code_block_without_properties_has_proper_html_class() {
let input = r#"
```rust
```rust
```
"#;
@ -183,7 +198,6 @@ more text with spaces
"#;
assert_eq!(render_markdown(input, false), expected);
assert_eq!(render_markdown(input, true), expected);
}
}
@ -192,12 +206,14 @@ more text with spaces
#[test]
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]
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]

View File

@ -13,10 +13,9 @@ use tempdir::TempDir;
fn do_not_overwrite_unspecified_config_values() {
let dir = TempDir::new("mdbook").expect("Could not create a temp dir");
let book = MDBook::new(dir.path())
.with_source("bar")
.with_destination("baz")
.with_mathjax_support(true);
let book = MDBook::new(dir.path()).with_source("bar")
.with_destination("baz")
.with_mathjax_support(true);
assert_eq!(book.get_root(), dir.path());
assert_eq!(book.get_source(), dir.path().join("bar"));
@ -33,7 +32,8 @@ fn do_not_overwrite_unspecified_config_values() {
// Try with a partial config file
let file_path = dir.path().join("book.toml");
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");
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_mathjax_support(), true);
}

View File

@ -63,20 +63,17 @@ impl DummyBook {
let to_substitute = if self.passing_test { "true" } else { "false" };
let nested_text = NESTED.replace("$TEST_STATUS", to_substitute);
let inputs = vec![
(src.join("SUMMARY.md"), SUMMARY_MD),
(src.join("intro.md"), INTRO),
(first.join("index.md"), FIRST),
(first.join("nested.md"), &nested_text),
(src.join("second.md"), SECOND),
(src.join("conclusion.md"), CONCLUSION),
];
let inputs = vec![(src.join("SUMMARY.md"), SUMMARY_MD),
(src.join("intro.md"), INTRO),
(first.join("index.md"), FIRST),
(first.join("nested.md"), &nested_text),
(src.join("second.md"), SECOND),
(src.join("conclusion.md"), CONCLUSION)];
for (path, content) in inputs {
File::create(path)
.unwrap()
.write_all(content.as_bytes())
.unwrap();
File::create(path).unwrap()
.write_all(content.as_bytes())
.unwrap();
}
temp

View File

@ -13,12 +13,15 @@ pub fn assert_contains_strings<P: AsRef<Path>>(filename: P, strings: &[&str]) {
let filename = filename.as_ref();
let mut content = String::new();
File::open(&filename)
.expect("Couldn't open the provided file")
.read_to_string(&mut content)
.expect("Couldn't read the file's contents");
File::open(&filename).expect("Couldn't open the provided file")
.read_to_string(&mut content)
.expect("Couldn't read the file's contents");
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();
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())
.with_source("in")
.with_destination("out");
let mut md = MDBook::new(temp.path()).with_source("in")
.with_destination("out");
md.init().unwrap();
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();
let dest = temp.path().join("book");
let links = vec![
r#"href="intro.html""#,
r#"href="./first/index.html""#,
r#"href="./first/nested.html""#,
r#"href="./second.html""#,
r#"href="./conclusion.html""#,
];
let links = vec![r#"href="intro.html""#,
r#"href="./first/index.html""#,
r#"href="./first/nested.html""#,
r#"href="./second.html""#,
r#"href="./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();
let first = temp.path().join("book").join("first");
let links = vec![
r#"<base href="../">"#,
r#"href="intro.html""#,
r#"href="./first/index.html""#,
r#"href="./first/nested.html""#,
r#"href="./second.html""#,
r#"href="./conclusion.html""#,
];
let links = vec![r#"<base href="../">"#,
r#"href="intro.html""#,
r#"href="./first/index.html""#,
r#"href="./first/nested.html""#,
r#"href="./second.html""#,
r#"href="./conclusion.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("index.html"),
&[
r##"href="./first/index.html#some-section" id="some-section""##
],
);
assert_contains_strings(first.join("index.html"),
&[r##"href="./first/index.html#some-section" id="some-section""##]);
assert_contains_strings(
first.join("nested.html"),
&[
r##"href="./first/nested.html#some-section" id="some-section""##
],
);
assert_contains_strings(first.join("nested.html"),
&[r##"href="./first/nested.html#some-section" id="some-section""##]);
}
#[test]
@ -106,13 +94,11 @@ fn rendered_code_has_playpen_stuff() {
#[test]
fn chapter_content_appears_in_rendered_document() {
let content = vec![
("index.html", "Here's some interesting text"),
("second.html", "Second Chapter"),
("first/nested.html", "testable code"),
("first/index.html", "more text"),
("conclusion.html", "Conclusion"),
];
let content = vec![("index.html", "Here's some interesting text"),
("second.html", "Second Chapter"),
("first/nested.html", "testable code"),
("first/index.html", "more text"),
("conclusion.html", "Conclusion")];
let temp = DummyBook::default().build();
let mut md = MDBook::new(temp.path());

View File

@ -9,9 +9,7 @@ use mdbook::MDBook;
#[test]
fn mdbook_can_correctly_test_a_passing_book() {
let temp = DummyBook::default()
.with_passing_test(true)
.build();
let temp = DummyBook::default().with_passing_test(true).build();
let mut md = MDBook::new(temp.path());
assert!(md.test(vec![]).is_ok());
@ -19,9 +17,7 @@ fn mdbook_can_correctly_test_a_passing_book() {
#[test]
fn mdbook_detects_book_with_failing_tests() {
let temp = DummyBook::default()
.with_passing_test(false)
.build();
let temp = DummyBook::default().with_passing_test(false).build();
let mut md: MDBook = MDBook::new(temp.path());
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 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
@ -69,7 +70,8 @@ fn from_toml_playpen_default() {
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);
}
@ -84,7 +86,8 @@ fn from_toml_playpen_editor() {
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
@ -168,7 +171,9 @@ fn from_toml_output_html_google_analytics() {
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
@ -182,7 +187,9 @@ fn from_toml_output_html_additional_stylesheet() {
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
@ -196,5 +203,7 @@ fn from_toml_output_html_additional_scripts() {
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")]);
}