commit
1052ee92e1
|
@ -17,6 +17,7 @@ fn main() {
|
||||||
panic!("This example is intended to be part of a library");
|
panic!("This example is intended to be part of a library");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
struct Deemphasize;
|
struct Deemphasize;
|
||||||
|
|
||||||
impl Preprocessor for Deemphasize {
|
impl Preprocessor for Deemphasize {
|
||||||
|
@ -56,10 +57,7 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_emphasis(
|
fn remove_emphasis(num_removed_items: &mut usize, chapter: &mut Chapter) -> Result<String> {
|
||||||
num_removed_items: &mut usize,
|
|
||||||
chapter: &mut Chapter,
|
|
||||||
) -> Result<String> {
|
|
||||||
let mut buf = String::with_capacity(chapter.content.len());
|
let mut buf = String::with_capacity(chapter.content.len());
|
||||||
|
|
||||||
let events = Parser::new(&chapter.content).filter(|e| {
|
let events = Parser::new(&chapter.content).filter(|e| {
|
||||||
|
@ -76,7 +74,7 @@ fn remove_emphasis(
|
||||||
should_keep
|
should_keep
|
||||||
});
|
});
|
||||||
|
|
||||||
cmark(events, &mut buf, None).map(|_| buf).map_err(|err| {
|
cmark(events, &mut buf, None)
|
||||||
Error::from(format!("Markdown serialization failed: {}", err))
|
.map(|_| buf)
|
||||||
})
|
.map_err(|err| Error::from(format!("Markdown serialization failed: {}", err)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,9 @@ use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use mdbook::book::Book;
|
use mdbook::book::Book;
|
||||||
use mdbook::errors::Error;
|
use mdbook::errors::Error;
|
||||||
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
|
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
|
||||||
|
use nop_lib::Nop;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::process;
|
use std::process;
|
||||||
use nop_lib::Nop;
|
|
||||||
|
|
||||||
pub fn make_app() -> App<'static, 'static> {
|
pub fn make_app() -> App<'static, 'static> {
|
||||||
App::new("nop-preprocessor")
|
App::new("nop-preprocessor")
|
||||||
|
@ -16,7 +16,8 @@ pub fn make_app() -> App<'static, 'static> {
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("supports")
|
SubCommand::with_name("supports")
|
||||||
.arg(Arg::with_name("renderer").required(true))
|
.arg(Arg::with_name("renderer").required(true))
|
||||||
.about("Check whether a renderer is supported by this preprocessor"))
|
.about("Check whether a renderer is supported by this preprocessor"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -87,11 +88,7 @@ mod nop_lib {
|
||||||
"nop-preprocessor"
|
"nop-preprocessor"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book, Error> {
|
||||||
&self,
|
|
||||||
ctx: &PreprocessorContext,
|
|
||||||
book: Book,
|
|
||||||
) -> Result<Book, Error> {
|
|
||||||
// In testing we want to tell the preprocessor to blow up by setting a
|
// In testing we want to tell the preprocessor to blow up by setting a
|
||||||
// particular config value
|
// particular config value
|
||||||
if let Some(nop_cfg) = ctx.config.get_preprocessor(self.name()) {
|
if let Some(nop_cfg) = ctx.config.get_preprocessor(self.name()) {
|
||||||
|
@ -109,4 +106,3 @@ mod nop_lib {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -167,9 +167,9 @@ impl Chapter {
|
||||||
) -> Chapter {
|
) -> Chapter {
|
||||||
Chapter {
|
Chapter {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
content: content,
|
content,
|
||||||
path: path.into(),
|
path: path.into(),
|
||||||
parent_names: parent_names,
|
parent_names,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,7 +210,7 @@ fn load_summary_item<P: AsRef<Path>>(
|
||||||
match *item {
|
match *item {
|
||||||
SummaryItem::Separator => Ok(BookItem::Separator),
|
SummaryItem::Separator => Ok(BookItem::Separator),
|
||||||
SummaryItem::Link(ref link) => {
|
SummaryItem::Link(ref link) => {
|
||||||
load_chapter(link, src_dir, parent_names).map(|c| BookItem::Chapter(c))
|
load_chapter(link, src_dir, parent_names).map(BookItem::Chapter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,7 +175,7 @@ impl BookBuilder {
|
||||||
let summary = src_dir.join("SUMMARY.md");
|
let summary = src_dir.join("SUMMARY.md");
|
||||||
let mut f = File::create(&summary).chain_err(|| "Unable to create SUMMARY.md")?;
|
let mut f = File::create(&summary).chain_err(|| "Unable to create SUMMARY.md")?;
|
||||||
writeln!(f, "# Summary")?;
|
writeln!(f, "# Summary")?;
|
||||||
writeln!(f, "")?;
|
writeln!(f)?;
|
||||||
writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
|
writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
|
||||||
|
|
||||||
let chapter_1 = src_dir.join("chapter_1.md");
|
let chapter_1 = src_dir.join("chapter_1.md");
|
||||||
|
|
|
@ -20,8 +20,9 @@ use tempfile::Builder as TempFileBuilder;
|
||||||
use toml::Value;
|
use toml::Value;
|
||||||
|
|
||||||
use errors::*;
|
use errors::*;
|
||||||
use preprocess::{IndexPreprocessor, LinkPreprocessor, Preprocessor,
|
use preprocess::{
|
||||||
PreprocessorContext, CmdPreprocessor};
|
CmdPreprocessor, IndexPreprocessor, LinkPreprocessor, Preprocessor, PreprocessorContext,
|
||||||
|
};
|
||||||
use renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer};
|
use renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer};
|
||||||
use utils;
|
use utils;
|
||||||
|
|
||||||
|
@ -160,15 +161,16 @@ impl MDBook {
|
||||||
/// Run the entire build process for a particular `Renderer`.
|
/// Run the entire build process for a particular `Renderer`.
|
||||||
fn execute_build_process(&self, renderer: &Renderer) -> Result<()> {
|
fn execute_build_process(&self, renderer: &Renderer) -> Result<()> {
|
||||||
let mut preprocessed_book = self.book.clone();
|
let mut preprocessed_book = self.book.clone();
|
||||||
let preprocess_ctx = PreprocessorContext::new(self.root.clone(),
|
let preprocess_ctx = PreprocessorContext::new(
|
||||||
|
self.root.clone(),
|
||||||
self.config.clone(),
|
self.config.clone(),
|
||||||
renderer.name().to_string());
|
renderer.name().to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
for preprocessor in &self.preprocessors {
|
for preprocessor in &self.preprocessors {
|
||||||
if preprocessor_should_run(&**preprocessor, renderer, &self.config) {
|
if preprocessor_should_run(&**preprocessor, renderer, &self.config) {
|
||||||
debug!("Running the {} preprocessor.", preprocessor.name());
|
debug!("Running the {} preprocessor.", preprocessor.name());
|
||||||
preprocessed_book =
|
preprocessed_book = preprocessor.run(&preprocess_ctx, preprocessed_book)?;
|
||||||
preprocessor.run(&preprocess_ctx, preprocessed_book)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,11 +180,7 @@ impl MDBook {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(
|
fn render(&self, preprocessed_book: &Book, renderer: &Renderer) -> Result<()> {
|
||||||
&self,
|
|
||||||
preprocessed_book: &Book,
|
|
||||||
renderer: &Renderer,
|
|
||||||
) -> Result<()> {
|
|
||||||
let name = renderer.name();
|
let name = renderer.name();
|
||||||
let build_dir = self.build_dir_for(name);
|
let build_dir = self.build_dir_for(name);
|
||||||
if build_dir.exists() {
|
if build_dir.exists() {
|
||||||
|
@ -233,9 +231,8 @@ impl MDBook {
|
||||||
let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
|
let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
|
||||||
|
|
||||||
// FIXME: Is "test" the proper renderer name to use here?
|
// FIXME: Is "test" the proper renderer name to use here?
|
||||||
let preprocess_context = PreprocessorContext::new(self.root.clone(),
|
let preprocess_context =
|
||||||
self.config.clone(),
|
PreprocessorContext::new(self.root.clone(), self.config.clone(), "test".to_string());
|
||||||
"test".to_string());
|
|
||||||
|
|
||||||
let book = LinkPreprocessor::new().run(&preprocess_context, self.book.clone())?;
|
let book = LinkPreprocessor::new().run(&preprocess_context, self.book.clone())?;
|
||||||
// Index Preprocessor is disabled so that chapter paths continue to point to the
|
// Index Preprocessor is disabled so that chapter paths continue to point to the
|
||||||
|
@ -363,17 +360,11 @@ fn determine_preprocessors(config: &Config) -> Result<Vec<Box<Preprocessor>>> {
|
||||||
preprocessors.extend(default_preprocessors());
|
preprocessors.extend(default_preprocessors());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(preprocessor_table) =
|
if let Some(preprocessor_table) = config.get("preprocessor").and_then(|v| v.as_table()) {
|
||||||
config.get("preprocessor").and_then(|v| v.as_table())
|
|
||||||
{
|
|
||||||
for key in preprocessor_table.keys() {
|
for key in preprocessor_table.keys() {
|
||||||
match key.as_ref() {
|
match key.as_ref() {
|
||||||
"links" => {
|
"links" => preprocessors.push(Box::new(LinkPreprocessor::new())),
|
||||||
preprocessors.push(Box::new(LinkPreprocessor::new()))
|
"index" => preprocessors.push(Box::new(IndexPreprocessor::new())),
|
||||||
}
|
|
||||||
"index" => {
|
|
||||||
preprocessors.push(Box::new(IndexPreprocessor::new()))
|
|
||||||
}
|
|
||||||
name => preprocessors.push(interpret_custom_preprocessor(
|
name => preprocessors.push(interpret_custom_preprocessor(
|
||||||
name,
|
name,
|
||||||
&preprocessor_table[name],
|
&preprocessor_table[name],
|
||||||
|
@ -385,10 +376,7 @@ fn determine_preprocessors(config: &Config) -> Result<Vec<Box<Preprocessor>>> {
|
||||||
Ok(preprocessors)
|
Ok(preprocessors)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn interpret_custom_preprocessor(
|
fn interpret_custom_preprocessor(key: &str, table: &Value) -> Box<CmdPreprocessor> {
|
||||||
key: &str,
|
|
||||||
table: &Value,
|
|
||||||
) -> Box<CmdPreprocessor> {
|
|
||||||
let command = table
|
let command = table
|
||||||
.get("command")
|
.get("command")
|
||||||
.and_then(|c| c.as_str())
|
.and_then(|c| c.as_str())
|
||||||
|
@ -406,8 +394,7 @@ fn interpret_custom_renderer(key: &str, table: &Value) -> Box<CmdRenderer> {
|
||||||
.and_then(|c| c.as_str())
|
.and_then(|c| c.as_str())
|
||||||
.map(|s| s.to_string());
|
.map(|s| s.to_string());
|
||||||
|
|
||||||
let command =
|
let command = table_dot_command.unwrap_or_else(|| format!("mdbook-{}", key));
|
||||||
table_dot_command.unwrap_or_else(|| format!("mdbook-{}", key));
|
|
||||||
|
|
||||||
Box::new(CmdRenderer::new(key.to_string(), command.to_string()))
|
Box::new(CmdRenderer::new(key.to_string(), command.to_string()))
|
||||||
}
|
}
|
||||||
|
@ -428,7 +415,8 @@ fn preprocessor_should_run(preprocessor: &Preprocessor, renderer: &Renderer, cfg
|
||||||
let renderer_name = renderer.name();
|
let renderer_name = renderer.name();
|
||||||
|
|
||||||
if let Some(Value::Array(ref explicit_renderers)) = cfg.get(&key) {
|
if let Some(Value::Array(ref explicit_renderers)) = cfg.get(&key) {
|
||||||
return explicit_renderers.into_iter()
|
return explicit_renderers
|
||||||
|
.iter()
|
||||||
.filter_map(|val| val.as_str())
|
.filter_map(|val| val.as_str())
|
||||||
.any(|name| name == renderer_name);
|
.any(|name| name == renderer_name);
|
||||||
}
|
}
|
||||||
|
@ -436,7 +424,6 @@ fn preprocessor_should_run(preprocessor: &Preprocessor, renderer: &Renderer, cfg
|
||||||
preprocessor.supports_renderer(renderer_name)
|
preprocessor.supports_renderer(renderer_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -539,10 +526,7 @@ mod tests {
|
||||||
|
|
||||||
// make sure the `preprocessor.random` table exists
|
// make sure the `preprocessor.random` table exists
|
||||||
let random = cfg.get_preprocessor("random").unwrap();
|
let random = cfg.get_preprocessor("random").unwrap();
|
||||||
let random = interpret_custom_preprocessor(
|
let random = interpret_custom_preprocessor("random", &Value::Table(random.clone()));
|
||||||
"random",
|
|
||||||
&Value::Table(random.clone()),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(random.cmd(), "python random.py");
|
assert_eq!(random.cmd(), "python random.py");
|
||||||
}
|
}
|
||||||
|
@ -557,7 +541,8 @@ mod tests {
|
||||||
let cfg = Config::from_str(cfg_str).unwrap();
|
let cfg = Config::from_str(cfg_str).unwrap();
|
||||||
|
|
||||||
// double-check that we can access preprocessor.links.renderers[0]
|
// double-check that we can access preprocessor.links.renderers[0]
|
||||||
let html = cfg.get_preprocessor("links")
|
let html = cfg
|
||||||
|
.get_preprocessor("links")
|
||||||
.and_then(|links| links.get("renderers"))
|
.and_then(|links| links.get("renderers"))
|
||||||
.and_then(|renderers| renderers.as_array())
|
.and_then(|renderers| renderers.as_array())
|
||||||
.and_then(|renderers| renderers.get(0))
|
.and_then(|renderers| renderers.get(0))
|
||||||
|
|
|
@ -280,7 +280,7 @@ impl<'a> SummaryParser<'a> {
|
||||||
Err(self.parse_error("You can't have an empty link."))
|
Err(self.parse_error("You can't have an empty link."))
|
||||||
} else {
|
} else {
|
||||||
Ok(Link {
|
Ok(Link {
|
||||||
name: name,
|
name,
|
||||||
location: PathBuf::from(href.to_string()),
|
location: PathBuf::from(href.to_string()),
|
||||||
number: None,
|
number: None,
|
||||||
nested_items: Vec::new(),
|
nested_items: Vec::new(),
|
||||||
|
@ -313,7 +313,6 @@ impl<'a> SummaryParser<'a> {
|
||||||
root_items += bunch_of_items.len() as u32;
|
root_items += bunch_of_items.len() as u32;
|
||||||
items.extend(bunch_of_items);
|
items.extend(bunch_of_items);
|
||||||
|
|
||||||
|
|
||||||
match self.next_event() {
|
match self.next_event() {
|
||||||
Some(Event::Start(Tag::Paragraph)) => {
|
Some(Event::Start(Tag::Paragraph)) => {
|
||||||
// we're starting the suffix chapters
|
// we're starting the suffix chapters
|
||||||
|
@ -732,7 +731,8 @@ mod tests {
|
||||||
/// Ensure section numbers are correctly incremented after a horizontal separator.
|
/// Ensure section numbers are correctly incremented after a horizontal separator.
|
||||||
#[test]
|
#[test]
|
||||||
fn keep_numbering_after_separator() {
|
fn keep_numbering_after_separator() {
|
||||||
let src = "- [First](./first.md)\n---\n- [Second](./second.md)\n---\n- [Third](./third.md)\n";
|
let src =
|
||||||
|
"- [First](./first.md)\n---\n- [Second](./second.md)\n---\n- [Third](./third.md)\n";
|
||||||
let should_be = vec![
|
let should_be = vec![
|
||||||
SummaryItem::Link(Link {
|
SummaryItem::Link(Link {
|
||||||
name: String::from("First"),
|
name: String::from("First"),
|
||||||
|
|
|
@ -12,9 +12,10 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||||
SubCommand::with_name("init")
|
SubCommand::with_name("init")
|
||||||
.about("Creates the boilerplate structure and files for a new book")
|
.about("Creates the boilerplate structure and files for a new book")
|
||||||
// the {n} denotes a newline which will properly aligned in all help messages
|
// the {n} denotes a newline which will properly aligned in all help messages
|
||||||
.arg_from_usage("[dir] 'Directory to create the book in{n}\
|
.arg_from_usage(
|
||||||
(Defaults to the Current Directory when omitted)'")
|
"[dir] 'Directory to create the book in{n}\
|
||||||
.arg_from_usage("--theme 'Copies the default theme into your source folder'")
|
(Defaults to the Current Directory when omitted)'",
|
||||||
|
).arg_from_usage("--theme 'Copies the default theme into your source folder'")
|
||||||
.arg_from_usage("--force 'Skips confirmation prompts'")
|
.arg_from_usage("--force 'Skips confirmation prompts'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "watch")]
|
#[cfg(feature = "watch")]
|
||||||
watch::trigger_on_change(&mut book, move |path, book_dir| {
|
watch::trigger_on_change(&book, move |path, book_dir| {
|
||||||
info!("File changed: {:?}", path);
|
info!("File changed: {:?}", path);
|
||||||
info!("Building book...");
|
info!("Building book...");
|
||||||
|
|
||||||
|
|
|
@ -301,8 +301,8 @@ impl<'de> Deserialize<'de> for Config {
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
Ok(Config {
|
Ok(Config {
|
||||||
book: book,
|
book,
|
||||||
build: build,
|
build,
|
||||||
rest: Value::Table(table),
|
rest: Value::Table(table),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,8 @@ use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
mod cmd;
|
mod cmd;
|
||||||
|
|
||||||
const NAME: &'static str = "mdBook";
|
const NAME: &str = "mdBook";
|
||||||
const VERSION: &'static str = concat!("v", crate_version!());
|
const VERSION: &str = concat!("v", crate_version!());
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
init_logger();
|
init_logger();
|
||||||
|
|
|
@ -43,19 +43,11 @@ impl CmdPreprocessor {
|
||||||
|
|
||||||
/// A convenience function custom preprocessors can use to parse the input
|
/// A convenience function custom preprocessors can use to parse the input
|
||||||
/// written to `stdin` by a `CmdRenderer`.
|
/// written to `stdin` by a `CmdRenderer`.
|
||||||
pub fn parse_input<R: Read>(
|
pub fn parse_input<R: Read>(reader: R) -> Result<(PreprocessorContext, Book)> {
|
||||||
reader: R,
|
serde_json::from_reader(reader).chain_err(|| "Unable to parse the input")
|
||||||
) -> Result<(PreprocessorContext, Book)> {
|
|
||||||
serde_json::from_reader(reader)
|
|
||||||
.chain_err(|| "Unable to parse the input")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_input_to_child(
|
fn write_input_to_child(&self, child: &mut Child, book: &Book, ctx: &PreprocessorContext) {
|
||||||
&self,
|
|
||||||
child: &mut Child,
|
|
||||||
book: &Book,
|
|
||||||
ctx: &PreprocessorContext,
|
|
||||||
) {
|
|
||||||
let stdin = child.stdin.take().expect("Child has stdin");
|
let stdin = child.stdin.take().expect("Child has stdin");
|
||||||
|
|
||||||
if let Err(e) = self.write_input(stdin, &book, &ctx) {
|
if let Err(e) = self.write_input(stdin, &book, &ctx) {
|
||||||
|
@ -128,8 +120,7 @@ impl Preprocessor for CmdPreprocessor {
|
||||||
"The preprocessor exited unsuccessfully"
|
"The preprocessor exited unsuccessfully"
|
||||||
);
|
);
|
||||||
|
|
||||||
serde_json::from_slice(&output.stdout)
|
serde_json::from_slice(&output.stdout).chain_err(|| "Unable to parse the preprocessed book")
|
||||||
.chain_err(|| "Unable to parse the preprocessed book")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports_renderer(&self, renderer: &str) -> bool {
|
fn supports_renderer(&self, renderer: &str) -> bool {
|
||||||
|
@ -142,7 +133,11 @@ impl Preprocessor for CmdPreprocessor {
|
||||||
let mut cmd = match self.command() {
|
let mut cmd = match self.command() {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Unable to create the command for the \"{}\" preprocessor, {}", self.name(), e);
|
warn!(
|
||||||
|
"Unable to create the command for the \"{}\" preprocessor, {}",
|
||||||
|
self.name(),
|
||||||
|
e
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -177,8 +172,7 @@ mod tests {
|
||||||
use MDBook;
|
use MDBook;
|
||||||
|
|
||||||
fn book_example() -> MDBook {
|
fn book_example() -> MDBook {
|
||||||
let example =
|
let example = Path::new(env!("CARGO_MANIFEST_DIR")).join("book-example");
|
||||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("book-example");
|
|
||||||
MDBook::load(example).unwrap()
|
MDBook::load(example).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,8 +189,7 @@ mod tests {
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
cmd.write_input(&mut buffer, &md.book, &ctx).unwrap();
|
cmd.write_input(&mut buffer, &md.book, &ctx).unwrap();
|
||||||
|
|
||||||
let (got_ctx, got_book) =
|
let (got_ctx, got_book) = CmdPreprocessor::parse_input(buffer.as_slice()).unwrap();
|
||||||
CmdPreprocessor::parse_input(buffer.as_slice()).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(got_book, md.book);
|
assert_eq!(got_book, md.book);
|
||||||
assert_eq!(got_ctx, ctx);
|
assert_eq!(got_ctx, ctx);
|
||||||
|
|
|
@ -7,7 +7,7 @@ use super::{Preprocessor, PreprocessorContext};
|
||||||
use book::{Book, BookItem};
|
use book::{Book, BookItem};
|
||||||
|
|
||||||
/// A preprocessor for converting file name `README.md` to `index.md` since
|
/// A preprocessor for converting file name `README.md` to `index.md` since
|
||||||
/// `README.md` is the de facto index file in a markdown-based documentation.
|
/// `README.md` is the de facto index file in markdown-based documentation.
|
||||||
pub struct IndexPreprocessor;
|
pub struct IndexPreprocessor;
|
||||||
|
|
||||||
impl IndexPreprocessor {
|
impl IndexPreprocessor {
|
||||||
|
|
|
@ -69,12 +69,7 @@ where
|
||||||
Ok(new_content) => {
|
Ok(new_content) => {
|
||||||
if depth < MAX_LINK_NESTED_DEPTH {
|
if depth < MAX_LINK_NESTED_DEPTH {
|
||||||
if let Some(rel_path) = playpen.link.relative_path(path) {
|
if let Some(rel_path) = playpen.link.relative_path(path) {
|
||||||
replaced.push_str(&replace_all(
|
replaced.push_str(&replace_all(&new_content, rel_path, source, depth + 1));
|
||||||
&new_content,
|
|
||||||
rel_path,
|
|
||||||
source,
|
|
||||||
depth + 1,
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
replaced.push_str(&new_content);
|
replaced.push_str(&new_content);
|
||||||
}
|
}
|
||||||
|
@ -118,18 +113,10 @@ impl<'a> LinkType<'a> {
|
||||||
let base = base.as_ref();
|
let base = base.as_ref();
|
||||||
match self {
|
match self {
|
||||||
LinkType::Escaped => None,
|
LinkType::Escaped => None,
|
||||||
LinkType::IncludeRange(p, _) => {
|
LinkType::IncludeRange(p, _) => Some(return_relative_path(base, &p)),
|
||||||
Some(return_relative_path(base, &p))
|
LinkType::IncludeRangeFrom(p, _) => Some(return_relative_path(base, &p)),
|
||||||
}
|
LinkType::IncludeRangeTo(p, _) => Some(return_relative_path(base, &p)),
|
||||||
LinkType::IncludeRangeFrom(p, _) => {
|
LinkType::IncludeRangeFull(p, _) => Some(return_relative_path(base, &p)),
|
||||||
Some(return_relative_path(base, &p))
|
|
||||||
}
|
|
||||||
LinkType::IncludeRangeTo(p, _) => {
|
|
||||||
Some(return_relative_path(base, &p))
|
|
||||||
}
|
|
||||||
LinkType::IncludeRangeFull(p, _) => {
|
|
||||||
Some(return_relative_path(base, &p))
|
|
||||||
}
|
|
||||||
LinkType::Playpen(p, _) => Some(return_relative_path(base, &p)),
|
LinkType::Playpen(p, _) => Some(return_relative_path(base, &p)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,27 +142,21 @@ fn parse_include_path(path: &str) -> LinkType<'static> {
|
||||||
let end = end.and_then(|s| s.parse::<usize>().ok());
|
let end = end.and_then(|s| s.parse::<usize>().ok());
|
||||||
match start {
|
match start {
|
||||||
Some(start) => match end {
|
Some(start) => match end {
|
||||||
Some(end) => LinkType::IncludeRange(
|
Some(end) => LinkType::IncludeRange(path, Range { start, end }),
|
||||||
path,
|
|
||||||
Range {
|
|
||||||
start: start,
|
|
||||||
end: end,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
None => if has_end {
|
None => if has_end {
|
||||||
LinkType::IncludeRangeFrom(path, RangeFrom { start: start })
|
LinkType::IncludeRangeFrom(path, RangeFrom { start })
|
||||||
} else {
|
} else {
|
||||||
LinkType::IncludeRange(
|
LinkType::IncludeRange(
|
||||||
path,
|
path,
|
||||||
Range {
|
Range {
|
||||||
start: start,
|
start,
|
||||||
end: start + 1,
|
end: start + 1,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
None => match end {
|
None => match end {
|
||||||
Some(end) => LinkType::IncludeRangeTo(path, RangeTo { end: end }),
|
Some(end) => LinkType::IncludeRangeTo(path, RangeTo { end }),
|
||||||
None => LinkType::IncludeRangeFull(path, RangeFull),
|
None => LinkType::IncludeRangeFull(path, RangeFull),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -199,15 +180,11 @@ impl<'a> Link<'a> {
|
||||||
|
|
||||||
match (typ.as_str(), file_arg) {
|
match (typ.as_str(), file_arg) {
|
||||||
("include", Some(pth)) => Some(parse_include_path(pth)),
|
("include", Some(pth)) => Some(parse_include_path(pth)),
|
||||||
("playpen", Some(pth)) => {
|
("playpen", Some(pth)) => Some(LinkType::Playpen(pth.into(), props)),
|
||||||
Some(LinkType::Playpen(pth.into(), props))
|
|
||||||
}
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Some(mat), None, None)
|
(Some(mat), None, None) if mat.as_str().starts_with(ESCAPE_CHAR) => {
|
||||||
if mat.as_str().starts_with(ESCAPE_CHAR) =>
|
|
||||||
{
|
|
||||||
Some(LinkType::Escaped)
|
Some(LinkType::Escaped)
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
|
@ -258,7 +235,7 @@ impl<'a> Link<'a> {
|
||||||
let target = base.join(pat);
|
let target = base.join(pat);
|
||||||
|
|
||||||
file_to_string(&target)
|
file_to_string(&target)
|
||||||
.map(|s| take_lines(&s, range.clone()))
|
.map(|s| take_lines(&s, *range))
|
||||||
.chain_err(|| {
|
.chain_err(|| {
|
||||||
format!(
|
format!(
|
||||||
"Could not read file for link {} ({})",
|
"Could not read file for link {} ({})",
|
||||||
|
@ -271,16 +248,17 @@ impl<'a> Link<'a> {
|
||||||
let target = base.join(pat);
|
let target = base.join(pat);
|
||||||
|
|
||||||
file_to_string(&target).chain_err(|| {
|
file_to_string(&target).chain_err(|| {
|
||||||
format!("Could not read file for link {} ({})",
|
format!(
|
||||||
|
"Could not read file for link {} ({})",
|
||||||
self.link_text,
|
self.link_text,
|
||||||
target.display())
|
target.display()
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
LinkType::Playpen(ref pat, ref attrs) => {
|
LinkType::Playpen(ref pat, ref attrs) => {
|
||||||
let target = base.join(pat);
|
let target = base.join(pat);
|
||||||
|
|
||||||
let contents =
|
let contents = file_to_string(&target).chain_err(|| {
|
||||||
file_to_string(&target).chain_err(|| {
|
|
||||||
format!(
|
format!(
|
||||||
"Could not read file for link {} ({})",
|
"Could not read file for link {} ({})",
|
||||||
self.link_text,
|
self.link_text,
|
||||||
|
@ -531,10 +509,7 @@ mod tests {
|
||||||
Link {
|
Link {
|
||||||
start_index: 38,
|
start_index: 38,
|
||||||
end_index: 68,
|
end_index: 68,
|
||||||
link: LinkType::Playpen(
|
link: LinkType::Playpen(PathBuf::from("file.rs"), vec!["editable"]),
|
||||||
PathBuf::from("file.rs"),
|
|
||||||
vec!["editable"]
|
|
||||||
),
|
|
||||||
link_text: "{{#playpen file.rs editable }}",
|
link_text: "{{#playpen file.rs editable }}",
|
||||||
},
|
},
|
||||||
Link {
|
Link {
|
||||||
|
@ -544,8 +519,7 @@ mod tests {
|
||||||
PathBuf::from("my.rs"),
|
PathBuf::from("my.rs"),
|
||||||
vec!["editable", "no_run", "should_panic"],
|
vec!["editable", "no_run", "should_panic"],
|
||||||
),
|
),
|
||||||
link_text:
|
link_text: "{{#playpen my.rs editable no_run should_panic}}",
|
||||||
"{{#playpen my.rs editable no_run should_panic}}",
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
//! Book preprocessing.
|
//! Book preprocessing.
|
||||||
|
|
||||||
|
pub use self::cmd::CmdPreprocessor;
|
||||||
pub use self::index::IndexPreprocessor;
|
pub use self::index::IndexPreprocessor;
|
||||||
pub use self::links::LinkPreprocessor;
|
pub use self::links::LinkPreprocessor;
|
||||||
pub use self::cmd::CmdPreprocessor;
|
|
||||||
|
|
||||||
|
mod cmd;
|
||||||
mod index;
|
mod index;
|
||||||
mod links;
|
mod links;
|
||||||
mod cmd;
|
|
||||||
|
|
||||||
use book::Book;
|
use book::Book;
|
||||||
use config::Config;
|
use config::Config;
|
||||||
|
|
|
@ -30,8 +30,7 @@ impl HtmlHandlebars {
|
||||||
print_content: &mut String,
|
print_content: &mut String,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// FIXME: This should be made DRY-er and rely less on mutable state
|
// FIXME: This should be made DRY-er and rely less on mutable state
|
||||||
match *item {
|
if let BookItem::Chapter(ref ch) = *item {
|
||||||
BookItem::Chapter(ref ch) => {
|
|
||||||
let content = ch.content.clone();
|
let content = ch.content.clone();
|
||||||
let content = utils::render_markdown(&content, ctx.html_config.curly_quotes);
|
let content = utils::render_markdown(&content, ctx.html_config.curly_quotes);
|
||||||
print_content.push_str(&content);
|
print_content.push_str(&content);
|
||||||
|
@ -82,23 +81,16 @@ impl HtmlHandlebars {
|
||||||
ctx.data.insert("path".to_owned(), json!("index.html"));
|
ctx.data.insert("path".to_owned(), json!("index.html"));
|
||||||
ctx.data.insert("path_to_root".to_owned(), json!(""));
|
ctx.data.insert("path_to_root".to_owned(), json!(""));
|
||||||
let rendered_index = ctx.handlebars.render("index", &ctx.data)?;
|
let rendered_index = ctx.handlebars.render("index", &ctx.data)?;
|
||||||
let rendered_index =
|
let rendered_index = self.post_process(rendered_index, &ctx.html_config.playpen);
|
||||||
self.post_process(rendered_index, &ctx.html_config.playpen);
|
|
||||||
debug!("Creating index.html from {}", path);
|
debug!("Creating index.html from {}", path);
|
||||||
utils::fs::write_file(
|
utils::fs::write_file(&ctx.destination, "index.html", rendered_index.as_bytes())?;
|
||||||
&ctx.destination,
|
|
||||||
"index.html",
|
|
||||||
rendered_index.as_bytes(),
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(let_and_return))]
|
#[cfg_attr(feature = "cargo-clippy", allow(clippy::let_and_return))]
|
||||||
fn post_process(&self, rendered: String, playpen_config: &Playpen) -> String {
|
fn post_process(&self, rendered: String, playpen_config: &Playpen) -> String {
|
||||||
let rendered = build_header_links(&rendered);
|
let rendered = build_header_links(&rendered);
|
||||||
let rendered = fix_code_blocks(&rendered);
|
let rendered = fix_code_blocks(&rendered);
|
||||||
|
@ -328,7 +320,7 @@ impl Renderer for HtmlHandlebars {
|
||||||
handlebars: &handlebars,
|
handlebars: &handlebars,
|
||||||
destination: destination.to_path_buf(),
|
destination: destination.to_path_buf(),
|
||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
is_index: is_index,
|
is_index,
|
||||||
html_config: html_config.clone(),
|
html_config: html_config.clone(),
|
||||||
};
|
};
|
||||||
self.render_item(item, ctx, &mut print_content)?;
|
self.render_item(item, ctx, &mut print_content)?;
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
pub mod navigation;
|
pub mod navigation;
|
||||||
pub mod toc;
|
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
|
pub mod toc;
|
||||||
|
|
|
@ -18,13 +18,13 @@ impl Target {
|
||||||
/// Returns target if found.
|
/// Returns target if found.
|
||||||
fn find(
|
fn find(
|
||||||
&self,
|
&self,
|
||||||
base_path: &String,
|
base_path: &str,
|
||||||
current_path: &String,
|
current_path: &str,
|
||||||
current_item: &StringMap,
|
current_item: &StringMap,
|
||||||
previous_item: &StringMap,
|
previous_item: &StringMap,
|
||||||
) -> Result<Option<StringMap>, RenderError> {
|
) -> Result<Option<StringMap>, RenderError> {
|
||||||
match self {
|
match *self {
|
||||||
&Target::Next => {
|
Target::Next => {
|
||||||
let previous_path = previous_item
|
let previous_path = previous_item
|
||||||
.get("path")
|
.get("path")
|
||||||
.ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))?;
|
.ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))?;
|
||||||
|
@ -34,7 +34,7 @@ impl Target {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&Target::Previous => {
|
Target::Previous => {
|
||||||
if current_path == base_path {
|
if current_path == base_path {
|
||||||
return Ok(Some(previous_item.clone()));
|
return Ok(Some(previous_item.clone()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ fn add_doc(
|
||||||
section_id: &Option<String>,
|
section_id: &Option<String>,
|
||||||
items: &[&str],
|
items: &[&str],
|
||||||
) {
|
) {
|
||||||
let url = if let &Some(ref id) = section_id {
|
let url = if let Some(ref id) = *section_id {
|
||||||
Cow::Owned(format!("{}#{}", anchor_base, id))
|
Cow::Owned(format!("{}#{}", anchor_base, id))
|
||||||
} else {
|
} else {
|
||||||
Cow::Borrowed(anchor_base)
|
Cow::Borrowed(anchor_base)
|
||||||
|
@ -74,8 +74,8 @@ fn render_item(
|
||||||
doc_urls: &mut Vec<String>,
|
doc_urls: &mut Vec<String>,
|
||||||
item: &BookItem,
|
item: &BookItem,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let chapter = match item {
|
let chapter = match *item {
|
||||||
&BookItem::Chapter(ref ch) => ch,
|
BookItem::Chapter(ref ch) => ch,
|
||||||
_ => return Ok(()),
|
_ => return Ok(()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ fn render_item(
|
||||||
for event in p {
|
for event in p {
|
||||||
match event {
|
match event {
|
||||||
Event::Start(Tag::Header(i)) if i <= max_section_depth => {
|
Event::Start(Tag::Header(i)) if i <= max_section_depth => {
|
||||||
if heading.len() > 0 {
|
if !heading.is_empty() {
|
||||||
// Section finished, the next header is following now
|
// Section finished, the next header is following now
|
||||||
// Write the data to the index, and clear it for the next section
|
// Write the data to the index, and clear it for the next section
|
||||||
add_doc(
|
add_doc(
|
||||||
|
@ -155,7 +155,7 @@ fn render_item(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if heading.len() > 0 {
|
if !heading.is_empty() {
|
||||||
// Make sure the last section is added to the index
|
// Make sure the last section is added to the index
|
||||||
add_doc(
|
add_doc(
|
||||||
index,
|
index,
|
||||||
|
|
|
@ -76,8 +76,8 @@ impl RenderContext {
|
||||||
Q: Into<PathBuf>,
|
Q: Into<PathBuf>,
|
||||||
{
|
{
|
||||||
RenderContext {
|
RenderContext {
|
||||||
book: book,
|
book,
|
||||||
config: config,
|
config,
|
||||||
version: ::MDBOOK_VERSION.to_string(),
|
version: ::MDBOOK_VERSION.to_string(),
|
||||||
root: root.into(),
|
root: root.into(),
|
||||||
destination: destination.into(),
|
destination: destination.into(),
|
||||||
|
|
|
@ -119,7 +119,7 @@ struct EventQuoteConverter {
|
||||||
impl EventQuoteConverter {
|
impl EventQuoteConverter {
|
||||||
fn new(enabled: bool) -> Self {
|
fn new(enabled: bool) -> Self {
|
||||||
EventQuoteConverter {
|
EventQuoteConverter {
|
||||||
enabled: enabled,
|
enabled,
|
||||||
convert_text: true,
|
convert_text: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -324,14 +324,8 @@ more text with spaces
|
||||||
id_from_content("## Method-call expressions"),
|
id_from_content("## Method-call expressions"),
|
||||||
"method-call-expressions"
|
"method-call-expressions"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(id_from_content("## **Bold** title"), "bold-title");
|
||||||
id_from_content("## **Bold** title"),
|
assert_eq!(id_from_content("## `Code` title"), "code-title");
|
||||||
"bold-title"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
id_from_content("## `Code` title"),
|
|
||||||
"code-title"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -344,10 +338,7 @@ more text with spaces
|
||||||
id_from_content("## 中文標題 CJK title"),
|
id_from_content("## 中文標題 CJK title"),
|
||||||
"中文標題-cjk-title"
|
"中文標題-cjk-title"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(id_from_content("## Über"), "Über");
|
||||||
id_from_content("## Über"),
|
|
||||||
"Über"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -11,14 +11,14 @@ use tempfile::{Builder as TempFileBuilder, TempDir};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn passing_alternate_backend() {
|
fn passing_alternate_backend() {
|
||||||
let (md, _temp) = dummy_book_with_backend("passing", "true");
|
let (md, _temp) = dummy_book_with_backend("passing", success_cmd());
|
||||||
|
|
||||||
md.build().unwrap();
|
md.build().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn failing_alternate_backend() {
|
fn failing_alternate_backend() {
|
||||||
let (md, _temp) = dummy_book_with_backend("failing", "false");
|
let (md, _temp) = dummy_book_with_backend("failing", fail_cmd());
|
||||||
|
|
||||||
md.build().unwrap_err();
|
md.build().unwrap_err();
|
||||||
}
|
}
|
||||||
|
@ -84,3 +84,19 @@ fn dummy_book_with_backend(name: &str, command: &str) -> (MDBook, TempDir) {
|
||||||
|
|
||||||
(md, temp)
|
(md, temp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fail_cmd() -> &'static str {
|
||||||
|
if cfg!(windows) {
|
||||||
|
r#"cmd.exe /c "exit 1""#
|
||||||
|
} else {
|
||||||
|
"false"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn success_cmd() -> &'static str {
|
||||||
|
if cfg!(windows) {
|
||||||
|
r#"cmd.exe /c "exit 0""#
|
||||||
|
} else {
|
||||||
|
"true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,10 @@ use mdbook::preprocess::{CmdPreprocessor, Preprocessor};
|
||||||
use mdbook::MDBook;
|
use mdbook::MDBook;
|
||||||
|
|
||||||
fn example() -> CmdPreprocessor {
|
fn example() -> CmdPreprocessor {
|
||||||
CmdPreprocessor::new("nop-preprocessor".to_string(), "cargo run --example nop-preprocessor --".to_string())
|
CmdPreprocessor::new(
|
||||||
|
"nop-preprocessor".to_string(),
|
||||||
|
"cargo run --example nop-preprocessor --".to_string(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -35,7 +38,9 @@ fn ask_the_preprocessor_to_blow_up() {
|
||||||
let mut md = MDBook::load(temp.path()).unwrap();
|
let mut md = MDBook::load(temp.path()).unwrap();
|
||||||
md.with_preprecessor(example());
|
md.with_preprecessor(example());
|
||||||
|
|
||||||
md.config.set("preprocessor.nop-preprocessor.blow-up", true).unwrap();
|
md.config
|
||||||
|
.set("preprocessor.nop-preprocessor.blow-up", true)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let got = md.build();
|
let got = md.build();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue