run rustfmt on the repository #398(Updated) (#438)
rustfmt the repository #398
This commit is contained in:
parent
b45e5e4420
commit
382fc4139b
20
build.rs
20
build.rs
|
@ -32,26 +32,23 @@ error_chain!{
|
|||
}
|
||||
|
||||
fn program_exists(program: &str) -> Result<()> {
|
||||
execs::cmd(program)
|
||||
.arg("-v")
|
||||
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"])
|
||||
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)
|
||||
if !execs::cmd("stylus").arg(stylus_dir)
|
||||
.arg("--out")
|
||||
.arg(theme_dir)
|
||||
.arg("--use")
|
||||
.arg("nib")
|
||||
.status()?
|
||||
.success() {
|
||||
.success()
|
||||
{
|
||||
bail!("Stylus encoutered an error");
|
||||
}
|
||||
}
|
||||
|
|
21
rustfmt.toml
21
rustfmt.toml
|
@ -1,16 +1,7 @@
|
|||
write_mode = "Overwrite"
|
||||
array_layout = "Visual"
|
||||
chain_indent = "Visual"
|
||||
fn_args_layout = "Visual"
|
||||
fn_call_style = "Visual"
|
||||
format_strings = true
|
||||
generics_indent = "Visual"
|
||||
|
||||
max_width = 120
|
||||
ideal_width = 120
|
||||
fn_call_width = 100
|
||||
|
||||
fn_args_density = "Compressed"
|
||||
|
||||
enum_trailing_comma = true
|
||||
match_block_trailing_comma = true
|
||||
struct_trailing_comma = "Always"
|
||||
wrap_comments = true
|
||||
use_try_shorthand = true
|
||||
|
||||
report_todo = "Always"
|
||||
report_fixme = "Always"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use clap::{ArgMatches, SubCommand, App};
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use mdbook::MDBook;
|
||||
use mdbook::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
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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) {{
|
||||
|
@ -70,7 +91,8 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
|||
"#,
|
||||
public_address,
|
||||
ws_port,
|
||||
RELOAD_COMMAND));
|
||||
RELOAD_COMMAND
|
||||
));
|
||||
|
||||
book.build()?;
|
||||
|
||||
|
|
|
@ -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()?;
|
||||
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
107
src/book/mod.rs
107
src/book/mod.rs
|
@ -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")
|
||||
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,11 +286,9 @@ 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)
|
||||
utils::fs::create_file(&path)?.write_all(content)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::path::{PathBuf, Path};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
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();
|
||||
|
||||
if let Some(editor) = tomlplaypenconfig.editor {
|
||||
|
|
|
@ -53,8 +53,7 @@ pub struct TomlPlaypenConfig {
|
|||
/// ```
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
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,
|
||||
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,
|
||||
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()
|
||||
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;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,14 +50,15 @@ 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| {
|
||||
cap.get(0).map(|mat| {
|
||||
Link {
|
||||
start_index: mat.start(),
|
||||
end_index: mat.end(),
|
||||
|
@ -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}}",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
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,13 +50,18 @@ 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 :'(
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -62,20 +69,20 @@ impl HtmlHandlebars {
|
|||
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()
|
||||
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()
|
||||
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",
|
||||
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()
|
||||
css.push(style.file_name()
|
||||
.expect("File has a file name")
|
||||
.to_str()
|
||||
.expect("Could not convert 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()
|
||||
js.push(script.file_name()
|
||||
.expect("File has a file name")
|
||||
.to_str()
|
||||
.expect("Could not convert 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,11 +412,9 @@ 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)
|
||||
})
|
||||
|
@ -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
|
||||
/// 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 {
|
||||
let raw_id = id_from_content(content);
|
||||
|
||||
|
@ -472,8 +454,7 @@ 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>",
|
||||
const REPL_SUB: &[&str] = &["<em>",
|
||||
"</em>",
|
||||
"<code>",
|
||||
"</code>",
|
||||
|
@ -483,8 +464,7 @@ fn id_from_content(content: &str) -> String {
|
|||
">",
|
||||
"&",
|
||||
"'",
|
||||
""",
|
||||
];
|
||||
"""];
|
||||
for sub in REPL_SUB {
|
||||
content = content.replace(sub, "");
|
||||
}
|
||||
|
@ -500,19 +480,16 @@ 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| {
|
||||
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
|
||||
)
|
||||
after = after)
|
||||
})
|
||||
.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
|
||||
fn fix_code_blocks(html: &str) -> String {
|
||||
let regex = Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap();
|
||||
regex
|
||||
.replace_all(html, |caps: &Captures| {
|
||||
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)
|
||||
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| {
|
||||
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") {
|
||||
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!") {
|
||||
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\
|
||||
format!("<pre class=\"playpen\"><code class=\"{}\">\n# \
|
||||
#![allow(unused_variables)]\n\
|
||||
{}#fn main() {{\n\
|
||||
{}\
|
||||
#}}</code></pre>",
|
||||
classes, attrs, code)
|
||||
classes,
|
||||
attrs,
|
||||
code)
|
||||
}
|
||||
} else {
|
||||
// not language-rust, so no-op
|
||||
|
@ -609,15 +593,13 @@ 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 == '-' {
|
||||
.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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,14 +11,14 @@ 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| {
|
||||
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"))?
|
||||
.as_str()
|
||||
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
||||
.replace("\"", "");
|
||||
|
||||
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");
|
||||
// Search for current chapter and return previous entry
|
||||
for item in chapters {
|
||||
|
||||
match item.get("path") {
|
||||
Some(path) if !path.is_empty() => {
|
||||
if path == ¤t {
|
||||
|
||||
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"))
|
||||
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(())
|
||||
|
@ -48,12 +48,18 @@ pub fn previous(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(
|
|||
|
||||
|
||||
// Chapter link
|
||||
previous
|
||||
.get("path").ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))
|
||||
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"))
|
||||
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("\\", "/")));
|
||||
|
@ -75,9 +81,8 @@ pub fn previous(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(
|
|||
} else {
|
||||
previous = Some(item.clone());
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => continue,
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,13 +96,13 @@ 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| {
|
||||
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"))?
|
||||
.as_str()
|
||||
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
||||
.replace("\"", "");
|
||||
|
||||
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");
|
||||
// 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 == ¤t {
|
||||
|
||||
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"))
|
||||
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 `/`
|
||||
|
@ -141,7 +149,8 @@ pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), R
|
|||
debug!("[*]: 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| {
|
||||
let mut local_rc = rc.with_context(Context::wraps(&next_chapter)?);
|
||||
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());
|
||||
},
|
||||
}
|
||||
|
||||
_ => continue,
|
||||
}
|
||||
|
|
|
@ -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,17 +11,16 @@ 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| {
|
||||
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"))?
|
||||
.as_str()
|
||||
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
||||
.replace("\"", "");
|
||||
|
||||
rc.writer.write_all(b"<ul class=\"chapter\">")?;
|
||||
|
@ -29,11 +28,9 @@ impl HelperDef for RenderToc {
|
|||
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>")?;
|
||||
|
|
|
@ -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,8 +64,7 @@ impl Theme {
|
|||
|
||||
// Check for individual files, if they exist copy them across
|
||||
{
|
||||
let files = vec![
|
||||
(theme_dir.join("index.hbs"), &mut theme.index),
|
||||
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),
|
||||
|
@ -70,8 +74,7 @@ impl Theme {
|
|||
(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),
|
||||
];
|
||||
(theme_dir.join("jquery.js"), &mut theme.jquery)];
|
||||
|
||||
for (filename, dest) in files {
|
||||
if !filename.exists() {
|
||||
|
|
|
@ -47,13 +47,12 @@ impl PlaypenEditor {
|
|||
|
||||
// Check for individual files if they exist
|
||||
{
|
||||
let files = vec![
|
||||
(src.join("editor.js"), &mut editor.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),
|
||||
];
|
||||
(src.join("theme-tomorrow_night.js"),
|
||||
&mut editor.theme_tomorrow_night_js)];
|
||||
|
||||
for (filename, dest) in files {
|
||||
if !filename.exists() {
|
||||
|
|
|
@ -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();
|
||||
|
@ -60,7 +60,7 @@ pub fn path_to_root<P: Into<PathBuf>>(path: P) -> String {
|
|||
Component::Normal(_) => s.push_str("../"),
|
||||
_ => {
|
||||
debug!("[*]: Other path component... {:?}", c);
|
||||
},
|
||||
}
|
||||
}
|
||||
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,20 +147,17 @@ pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blackl
|
|||
}
|
||||
}
|
||||
debug!("[*] creating path for file: {:?}",
|
||||
&to.join(entry
|
||||
.path()
|
||||
&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()
|
||||
&to.join(entry.path()
|
||||
.file_name()
|
||||
.expect("a file should have a file name...")));
|
||||
fs::copy(entry.path(),
|
||||
&to.join(entry
|
||||
.path()
|
||||
&to.join(entry.path()
|
||||
.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"]) {
|
||||
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")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,12 +76,23 @@ 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()
|
||||
original_text.chars()
|
||||
.map(|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,
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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]
|
||||
|
|
|
@ -13,8 +13,7 @@ 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")
|
||||
let book = MDBook::new(dir.path()).with_source("bar")
|
||||
.with_destination("baz")
|
||||
.with_mathjax_support(true);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -63,18 +63,15 @@ 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),
|
||||
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),
|
||||
];
|
||||
(src.join("conclusion.md"), CONCLUSION)];
|
||||
|
||||
for (path, content) in inputs {
|
||||
File::create(path)
|
||||
.unwrap()
|
||||
File::create(path).unwrap()
|
||||
.write_all(content.as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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""#,
|
||||
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""#,
|
||||
];
|
||||
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="../">"#,
|
||||
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""#,
|
||||
];
|
||||
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"),
|
||||
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"),
|
||||
];
|
||||
("conclusion.html", "Conclusion")];
|
||||
|
||||
let temp = DummyBook::default().build();
|
||||
let mut md = MDBook::new(temp.path());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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")]);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue