Merge branch 'master' of github.com:rust-lang-nursery/mdBook

This commit is contained in:
Donald Pinckney 2019-04-18 12:29:32 -04:00
commit 0dc2728fa9
31 changed files with 558 additions and 301 deletions

View File

@ -62,7 +62,7 @@ The quick guide is
1. Install it 1. Install it
``` ```
rustup component add rustfmt-preview rustup component add rustfmt
``` ```
1. You can now run `rustfmt` on a single file simply by... 1. You can now run `rustfmt` on a single file simply by...
``` ```
@ -87,14 +87,11 @@ The best documentation can be found over at [rust-clippy](https://github.com/rus
1. To install 1. To install
``` ```
rustup update rustup component add clippy
rustup install nightly
rustup component add clippy-preview --toolchain=nightly
``` ```
2. Running clippy 2. Running clippy
As you may notice from the previous step, Clippy is on the nightly branch, so running it is like
``` ```
cargo +nightly clippy cargo clippy
``` ```
Clippy has an ever growing list of checks, that are managed in [lint files](https://rust-lang-nursery.github.io/rust-clippy/master/index.html). Clippy has an ever growing list of checks, that are managed in [lint files](https://rust-lang-nursery.github.io/rust-clippy/master/index.html).

2
Cargo.lock generated
View File

@ -525,7 +525,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "mdbook" name = "mdbook"
version = "0.2.3-alpha.0" version = "0.2.4-alpha.0"
dependencies = [ dependencies = [
"ammonia 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "ammonia 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "mdbook" name = "mdbook"
version = "0.2.3-alpha.0" version = "0.2.4-alpha.0"
authors = [ authors = [
"Mathieu David <mathieudavid@mathieudavid.org>", "Mathieu David <mathieudavid@mathieudavid.org>",
"Michael-F-Bryan <michaelfbryan@gmail.com>", "Michael-F-Bryan <michaelfbryan@gmail.com>",

View File

@ -18,7 +18,7 @@ mdBook can also be installed from source
mdBook is written in **[Rust](https://www.rust-lang.org/)** and therefore needs mdBook is written in **[Rust](https://www.rust-lang.org/)** and therefore needs
to be compiled with **Cargo**. If you haven't already installed Rust, please go to be compiled with **Cargo**. If you haven't already installed Rust, please go
ahead and [install it](https://www.rust-lang.org/downloads.html) now. ahead and [install it](https://www.rust-lang.org/tools/install) now.
### Install Crates.io version ### Install Crates.io version

View File

@ -37,7 +37,7 @@ configured.
#### --open #### --open
When you use the `--open` (`-o`) flag, mdbook will open the book in your your When you use the `--open` (`-o`) flag, mdbook will open the book in your
default web browser after starting the server. default web browser after starting the server.
#### --dest-dir #### --dest-dir

View File

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

View File

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

View File

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

View File

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

View File

@ -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.config.clone(), self.root.clone(),
renderer.name().to_string()); self.config.clone(),
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))

View File

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

View File

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

View File

@ -76,7 +76,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
let port = args.value_of("port").unwrap(); let port = args.value_of("port").unwrap();
let ws_port = args.value_of("websocket-port").unwrap(); let ws_port = args.value_of("websocket-port").unwrap();
let hostname = args.value_of("hostname").unwrap(); let hostname = args.value_of("hostname").unwrap();
let public_address = args.value_of("websocket-address").unwrap_or(hostname); let public_address = args.value_of("websocket-hostname").unwrap_or(hostname);
let open_browser = args.is_present("open"); let open_browser = args.is_present("open");
let address = format!("{}:{}", hostname, port); let address = format!("{}:{}", hostname, port);
@ -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...");

View File

@ -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),
}) })
} }
@ -444,7 +444,7 @@ pub struct HtmlConfig {
pub search: Option<Search>, pub search: Option<Search>,
/// Git repository url. If `None`, the git button will not be shown. /// Git repository url. If `None`, the git button will not be shown.
pub git_repository_url: Option<String>, pub git_repository_url: Option<String>,
/// FontAwesome icon class to use for the Git repository link. /// FontAwesome icon class to use for the Git repository link.
/// Defaults to `fa-github` if `None`. /// Defaults to `fa-github` if `None`.
pub git_repository_icon: Option<String>, pub git_repository_icon: Option<String>,
} }

View File

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

View File

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

View File

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

View File

@ -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,22 +248,23 @@ 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!(
self.link_text, "Could not read file for link {} ({})",
target.display()) self.link_text,
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, target.display()
target.display() )
) })?;
})?;
let ftype = if !attrs.is_empty() { "rust," } else { "rust" }; let ftype = if !attrs.is_empty() { "rust," } else { "rust" };
Ok(format!( Ok(format!(
"```{}{}\n{}\n```\n", "```{}{}\n{}\n```\n",
@ -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}}",
}, },
] ]
); );

View File

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

View File

@ -30,75 +30,71 @@ 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);
// Update the context with data for this file let string_path = ch.path.parent().unwrap().display().to_string();
let path = ch
.path
.to_str()
.chain_err(|| "Could not convert path to str")?;
let filepath = Path::new(&ch.path).with_extension("html");
// "print.html" is used for the print page. let fixed_content = utils::render_markdown_with_base(&ch.content, ctx.html_config.curly_quotes, &string_path);
if ch.path == Path::new("print.md") { print_content.push_str(&fixed_content);
bail!(ErrorKind::ReservedFilenameError(ch.path.clone()));
};
// Non-lexical lifetimes needed :'( // Update the context with data for this file
let title: String; let path = ch
{ .path
let book_title = ctx .to_str()
.data .chain_err(|| "Could not convert path to str")?;
.get("book_title") let filepath = Path::new(&ch.path).with_extension("html");
.and_then(serde_json::Value::as_str)
.unwrap_or("");
title = ch.name.clone() + " - " + book_title;
}
ctx.data.insert("path".to_owned(), json!(path)); // "print.html" is used for the print page.
ctx.data.insert("content".to_owned(), json!(content)); if ch.path == Path::new("print.md") {
ctx.data.insert("chapter_title".to_owned(), json!(ch.name)); bail!(ErrorKind::ReservedFilenameError(ch.path.clone()));
ctx.data.insert("title".to_owned(), json!(title)); };
ctx.data.insert(
"path_to_root".to_owned(),
json!(utils::fs::path_to_root(&ch.path)),
);
// Render the handlebars template with the data // Non-lexical lifetimes needed :'(
debug!("Render template"); let title: String;
let rendered = ctx.handlebars.render("index", &ctx.data)?; {
let book_title = ctx
let rendered = self.post_process(rendered, &ctx.html_config.playpen); .data
.get("book_title")
// Write to file .and_then(serde_json::Value::as_str)
debug!("Creating {}", filepath.display()); .unwrap_or("");
utils::fs::write_file(&ctx.destination, &filepath, rendered.as_bytes())?; title = ch.name.clone() + " - " + book_title;
}
if ctx.is_index {
ctx.data.insert("path".to_owned(), json!("index.html")); ctx.data.insert("path".to_owned(), json!(path));
ctx.data.insert("path_to_root".to_owned(), json!("")); ctx.data.insert("content".to_owned(), json!(content));
let rendered_index = ctx.handlebars.render("index", &ctx.data)?; ctx.data.insert("chapter_title".to_owned(), json!(ch.name));
let rendered_index = ctx.data.insert("title".to_owned(), json!(title));
self.post_process(rendered_index, &ctx.html_config.playpen); ctx.data.insert(
debug!("Creating index.html from {}", path); "path_to_root".to_owned(),
utils::fs::write_file( json!(utils::fs::path_to_root(&ch.path)),
&ctx.destination, );
"index.html",
rendered_index.as_bytes(), // Render the handlebars template with the data
)?; debug!("Render template");
} let rendered = ctx.handlebars.render("index", &ctx.data)?;
let rendered = self.post_process(rendered, &ctx.html_config.playpen);
// Write to file
debug!("Creating {}", filepath.display());
utils::fs::write_file(&ctx.destination, &filepath, rendered.as_bytes())?;
if ctx.is_index {
ctx.data.insert("path".to_owned(), json!("index.html"));
ctx.data.insert("path_to_root".to_owned(), json!(""));
let rendered_index = ctx.handlebars.render("index", &ctx.data)?;
let rendered_index = self.post_process(rendered_index, &ctx.html_config.playpen);
debug!("Creating index.html from {}", path);
utils::fs::write_file(&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 +324,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)?;

View File

@ -1,3 +1,3 @@
pub mod navigation; pub mod navigation;
pub mod theme;
pub mod toc; pub mod toc;
pub mod theme;

View File

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

View File

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

View File

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

View File

@ -66,15 +66,21 @@ pub fn id_from_content(content: &str) -> String {
normalize_id(trimmed) normalize_id(trimmed)
} }
fn adjust_links(event: Event) -> Event { fn adjust_links<'a>(event: Event<'a>, with_base: &str) -> Event<'a> {
lazy_static! { lazy_static! {
static ref HTTP_LINK: Regex = Regex::new("^https?://").unwrap(); static ref HTTP_LINK: Regex = Regex::new("^https?://").unwrap();
static ref MD_LINK: Regex = Regex::new("(?P<link>.*).md(?P<anchor>#.*)?").unwrap(); static ref MD_LINK: Regex = Regex::new(r"(?P<link>.*)\.md(?P<anchor>#.*)?").unwrap();
} }
match event { match event {
Event::Start(Tag::Link(dest, title)) => { Event::Start(Tag::Link(dest, title)) => {
if !HTTP_LINK.is_match(&dest) { if !HTTP_LINK.is_match(&dest) {
let dest = if !with_base.is_empty() {
format!("{}/{}", with_base, dest)
} else {
dest.clone().into_owned()
};
if let Some(caps) = MD_LINK.captures(&dest) { if let Some(caps) = MD_LINK.captures(&dest) {
let mut html_link = [&caps["link"], ".html"].concat(); let mut html_link = [&caps["link"], ".html"].concat();
@ -94,6 +100,10 @@ fn adjust_links(event: Event) -> Event {
/// Wrapper around the pulldown-cmark parser for rendering markdown to HTML. /// Wrapper around the pulldown-cmark parser for rendering markdown to HTML.
pub fn render_markdown(text: &str, curly_quotes: bool) -> String { pub fn render_markdown(text: &str, curly_quotes: bool) -> String {
render_markdown_with_base(text, curly_quotes, "")
}
pub fn render_markdown_with_base(text: &str, curly_quotes: bool, base: &str) -> String {
let mut s = String::with_capacity(text.len() * 3 / 2); let mut s = String::with_capacity(text.len() * 3 / 2);
let mut opts = Options::empty(); let mut opts = Options::empty();
@ -104,7 +114,7 @@ pub fn render_markdown(text: &str, curly_quotes: bool) -> String {
let mut converter = EventQuoteConverter::new(curly_quotes); let mut converter = EventQuoteConverter::new(curly_quotes);
let events = p let events = p
.map(clean_codeblock_headers) .map(clean_codeblock_headers)
.map(adjust_links) .map(|event| adjust_links(event, base))
.map(|event| converter.convert(event)); .map(|event| converter.convert(event));
html::push_html(&mut s, events); html::push_html(&mut s, events);
@ -119,7 +129,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,
} }
} }
@ -220,6 +230,12 @@ mod tests {
render_markdown("[example_anchor](example.md#anchor)", false), render_markdown("[example_anchor](example.md#anchor)", false),
"<p><a href=\"example.html#anchor\">example_anchor</a></p>\n" "<p><a href=\"example.html#anchor\">example_anchor</a></p>\n"
); );
// this anchor contains 'md' inside of it
assert_eq!(
render_markdown("[phantom data](foo.html#phantomdata)", false),
"<p><a href=\"foo.html#phantomdata\">phantom data</a></p>\n"
);
} }
#[test] #[test]
@ -324,14 +340,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 +354,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]

View File

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

View File

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

View File

@ -8,6 +8,7 @@
- [Includes](first/includes.md) - [Includes](first/includes.md)
- [Recursive](first/recursive.md) - [Recursive](first/recursive.md)
- [Second Chapter](second.md) - [Second Chapter](second.md)
- [Nested Chapter](second/nested.md)
--- ---

View File

@ -0,0 +1,4 @@
# Testing relative links for the print page
When we link to [the first section](../first/nested.md), it should work on
both the print page and the non-print page.

View File

@ -31,7 +31,7 @@ const TOC_TOP_LEVEL: &[&'static str] = &[
"Introduction", "Introduction",
]; ];
const TOC_SECOND_LEVEL: &[&'static str] = const TOC_SECOND_LEVEL: &[&'static str] =
&["1.1. Nested Chapter", "1.2. Includes", "1.3. Recursive"]; &["1.1. Nested Chapter", "1.2. Includes", "2.1. Nested Chapter", "1.3. Recursive"];
/// Make sure you can load the dummy book and build it without panicking. /// Make sure you can load the dummy book and build it without panicking.
#[test] #[test]
@ -109,6 +109,20 @@ fn check_correct_cross_links_in_nested_dir() {
); );
} }
#[test]
fn check_correct_relative_links_in_print_page() {
let temp = DummyBook::new().build().unwrap();
let md = MDBook::load(temp.path()).unwrap();
md.build().unwrap();
let first = temp.path().join("book");
assert_contains_strings(
first.join("print.html"),
&[r##"<a href="second/../first/nested.html">the first section</a>,"##],
);
}
#[test] #[test]
fn rendered_code_has_playpen_stuff() { fn rendered_code_has_playpen_stuff() {
let temp = DummyBook::new().build().unwrap(); let temp = DummyBook::new().build().unwrap();
@ -443,7 +457,7 @@ mod search {
assert_eq!(docs[&some_section]["body"], ""); assert_eq!(docs[&some_section]["body"], "");
assert_eq!( assert_eq!(
docs[&summary]["body"], docs[&summary]["body"],
"Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Second Chapter Conclusion" "Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Second Chapter Nested Chapter Conclusion"
); );
assert_eq!(docs[&summary]["breadcrumbs"], "First Chapter » Summary"); assert_eq!(docs[&summary]["breadcrumbs"], "First Chapter » Summary");
assert_eq!(docs[&conclusion]["body"], "I put &lt;HTML&gt; in here!"); assert_eq!(docs[&conclusion]["body"], "I put &lt;HTML&gt; in here!");

View File

@ -9,6 +9,7 @@
"first/includes.html#includes", "first/includes.html#includes",
"first/includes.html#summary", "first/includes.html#summary",
"second.html#second-chapter", "second.html#second-chapter",
"second/nested.html#testing-relative-links-for-the-print-page",
"conclusion.html#conclusion" "conclusion.html#conclusion"
], ],
"index": { "index": {
@ -24,6 +25,11 @@
"breadcrumbs": 1, "breadcrumbs": 1,
"title": 1 "title": 1
}, },
"10": {
"body": 3,
"breadcrumbs": 1,
"title": 1
},
"2": { "2": {
"body": 2, "body": 2,
"breadcrumbs": 2, "breadcrumbs": 2,
@ -50,7 +56,7 @@
"title": 1 "title": 1
}, },
"7": { "7": {
"body": 12, "body": 14,
"breadcrumbs": 3, "breadcrumbs": 3,
"title": 1 "title": 1
}, },
@ -60,9 +66,9 @@
"title": 2 "title": 2
}, },
"9": { "9": {
"body": 3, "body": 10,
"breadcrumbs": 1, "breadcrumbs": 7,
"title": 1 "title": 5
} }
}, },
"docs": { "docs": {
@ -78,6 +84,12 @@
"id": "1", "id": "1",
"title": "Introduction" "title": "Introduction"
}, },
"10": {
"body": "I put &lt;HTML&gt; in here!",
"breadcrumbs": "Conclusion",
"id": "10",
"title": "Conclusion"
},
"2": { "2": {
"body": "more text.", "body": "more text.",
"breadcrumbs": "First Chapter", "breadcrumbs": "First Chapter",
@ -109,7 +121,7 @@
"title": "Includes" "title": "Includes"
}, },
"7": { "7": {
"body": "Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Second Chapter Conclusion", "body": "Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Second Chapter Nested Chapter Conclusion",
"breadcrumbs": "First Chapter » Summary", "breadcrumbs": "First Chapter » Summary",
"id": "7", "id": "7",
"title": "Summary" "title": "Summary"
@ -121,13 +133,13 @@
"title": "Second Chapter" "title": "Second Chapter"
}, },
"9": { "9": {
"body": "I put &lt;HTML&gt; in here!", "body": "When we link to the first section , it should work on both the print page and the non-print page.",
"breadcrumbs": "Conclusion", "breadcrumbs": "Second Chapter » Testing relative links for the print page",
"id": "9", "id": "9",
"title": "Conclusion" "title": "Testing relative links for the print page"
} }
}, },
"length": 10, "length": 11,
"save": true "save": true
}, },
"fields": [ "fields": [
@ -206,6 +218,18 @@
} }
} }
} }
},
"t": {
"df": 0,
"docs": {},
"h": {
"df": 1,
"docs": {
"9": {
"tf": 1.0
}
}
}
} }
} }
}, },
@ -251,7 +275,7 @@
"tf": 1.0 "tf": 1.0
}, },
"7": { "7": {
"tf": 1.7320508075688773 "tf": 2.0
}, },
"8": { "8": {
"tf": 1.0 "tf": 1.0
@ -296,10 +320,10 @@
"s": { "s": {
"df": 2, "df": 2,
"docs": { "docs": {
"7": { "10": {
"tf": 1.0 "tf": 1.0
}, },
"9": { "7": {
"tf": 1.0 "tf": 1.0
} }
} }
@ -420,13 +444,16 @@
"df": 0, "df": 0,
"docs": {}, "docs": {},
"t": { "t": {
"df": 2, "df": 3,
"docs": { "docs": {
"2": { "2": {
"tf": 1.0 "tf": 1.0
}, },
"7": { "7": {
"tf": 1.0 "tf": 1.0
},
"9": {
"tf": 1.0
} }
} }
} }
@ -485,7 +512,7 @@
"0": { "0": {
"tf": 1.0 "tf": 1.0
}, },
"9": { "10": {
"tf": 1.0 "tf": 1.0
} }
} }
@ -683,6 +710,14 @@
"tf": 1.0 "tf": 1.0
} }
} }
},
"k": {
"df": 1,
"docs": {
"9": {
"tf": 1.4142135623730951
}
}
} }
} }
}, },
@ -709,7 +744,7 @@
"t": { "t": {
"df": 1, "df": 1,
"docs": { "docs": {
"9": { "10": {
"tf": 1.0 "tf": 1.0
} }
} }
@ -791,14 +826,42 @@
"tf": 1.0 "tf": 1.0
}, },
"7": { "7": {
"tf": 1.0 "tf": 1.4142135623730951
} }
} }
} }
} }
},
"o": {
"df": 0,
"docs": {},
"n": {
"df": 1,
"docs": {
"9": {
"tf": 1.0
}
}
}
} }
}, },
"p": { "p": {
"a": {
"df": 0,
"docs": {},
"g": {
"df": 0,
"docs": {},
"e": {
"df": 1,
"docs": {
"9": {
"tf": 1.7320508075688772
}
}
}
}
},
"df": 0, "df": 0,
"docs": {}, "docs": {},
"r": { "r": {
@ -871,8 +934,12 @@
"df": 0, "df": 0,
"docs": {}, "docs": {},
"t": { "t": {
"df": 0, "df": 1,
"docs": {}, "docs": {
"9": {
"tf": 1.7320508075688772
}
},
"l": { "l": {
"df": 0, "df": 0,
"docs": {}, "docs": {},
@ -935,7 +1002,7 @@
"t": { "t": {
"df": 1, "df": 1,
"docs": { "docs": {
"9": { "10": {
"tf": 1.0 "tf": 1.0
} }
} }
@ -967,7 +1034,15 @@
} }
}, },
"df": 0, "df": 0,
"docs": {} "docs": {},
"l": {
"df": 1,
"docs": {
"9": {
"tf": 1.0
}
}
}
}, },
"u": { "u": {
"df": 0, "df": 0,
@ -1050,13 +1125,16 @@
"df": 0, "df": 0,
"docs": {}, "docs": {},
"n": { "n": {
"df": 2, "df": 3,
"docs": { "docs": {
"3": { "3": {
"tf": 1.0 "tf": 1.0
}, },
"5": { "5": {
"tf": 1.0 "tf": 1.0
},
"9": {
"tf": 1.0
} }
} }
} }
@ -1170,8 +1248,12 @@
"df": 0, "df": 0,
"docs": {} "docs": {}
}, },
"df": 0, "df": 1,
"docs": {} "docs": {
"9": {
"tf": 1.0
}
}
} }
}, },
"x": { "x": {
@ -1200,6 +1282,14 @@
"r": { "r": {
"df": 0, "df": 0,
"docs": {}, "docs": {},
"k": {
"df": 1,
"docs": {
"9": {
"tf": 1.0
}
}
},
"l": { "l": {
"d": { "d": {
"df": 1, "df": 1,
@ -1280,13 +1370,25 @@
"df": 2, "df": 2,
"docs": { "docs": {
"0": { "0": {
"tf": 1.4142135623730952 "tf": 1.4142135623730951
}, },
"7": { "7": {
"tf": 1.0 "tf": 1.0
} }
} }
} }
},
"t": {
"df": 0,
"docs": {},
"h": {
"df": 1,
"docs": {
"9": {
"tf": 1.0
}
}
}
} }
} }
}, },
@ -1323,13 +1425,13 @@
"df": 0, "df": 0,
"docs": {}, "docs": {},
"r": { "r": {
"df": 6, "df": 7,
"docs": { "docs": {
"2": { "2": {
"tf": 1.4142135623730952 "tf": 1.4142135623730951
}, },
"4": { "4": {
"tf": 1.7320508075688773 "tf": 1.7320508075688772
}, },
"5": { "5": {
"tf": 1.0 "tf": 1.0
@ -1338,10 +1440,13 @@
"tf": 1.0 "tf": 1.0
}, },
"7": { "7": {
"tf": 2.0 "tf": 2.23606797749979
}, },
"8": { "8": {
"tf": 1.4142135623730952 "tf": 1.4142135623730951
},
"9": {
"tf": 1.0
} }
} }
} }
@ -1383,11 +1488,11 @@
"s": { "s": {
"df": 2, "df": 2,
"docs": { "docs": {
"10": {
"tf": 1.4142135623730951
},
"7": { "7": {
"tf": 1.0 "tf": 1.0
},
"9": {
"tf": 1.4142135623730952
} }
} }
} }
@ -1419,7 +1524,7 @@
"df": 2, "df": 2,
"docs": { "docs": {
"0": { "0": {
"tf": 1.4142135623730952 "tf": 1.4142135623730951
}, },
"7": { "7": {
"tf": 1.0 "tf": 1.0
@ -1507,10 +1612,10 @@
"df": 0, "df": 0,
"docs": {}, "docs": {},
"t": { "t": {
"df": 5, "df": 6,
"docs": { "docs": {
"2": { "2": {
"tf": 1.4142135623730952 "tf": 1.4142135623730951
}, },
"4": { "4": {
"tf": 1.0 "tf": 1.0
@ -1522,7 +1627,10 @@
"tf": 1.0 "tf": 1.0
}, },
"7": { "7": {
"tf": 1.4142135623730952 "tf": 1.4142135623730951
},
"9": {
"tf": 1.0
} }
} }
} }
@ -1581,7 +1689,7 @@
"0": { "0": {
"tf": 1.0 "tf": 1.0
}, },
"9": { "10": {
"tf": 1.0 "tf": 1.0
} }
} }
@ -1636,7 +1744,7 @@
"df": 2, "df": 2,
"docs": { "docs": {
"6": { "6": {
"tf": 1.4142135623730952 "tf": 1.4142135623730951
}, },
"7": { "7": {
"tf": 1.0 "tf": 1.0
@ -1728,7 +1836,7 @@
"df": 2, "df": 2,
"docs": { "docs": {
"1": { "1": {
"tf": 1.4142135623730952 "tf": 1.4142135623730951
}, },
"7": { "7": {
"tf": 1.0 "tf": 1.0
@ -1779,6 +1887,14 @@
"tf": 1.0 "tf": 1.0
} }
} }
},
"k": {
"df": 1,
"docs": {
"9": {
"tf": 1.7320508075688772
}
}
} }
} }
}, },
@ -1805,7 +1921,7 @@
"t": { "t": {
"df": 1, "df": 1,
"docs": { "docs": {
"9": { "10": {
"tf": 1.0 "tf": 1.0
} }
} }
@ -1884,17 +2000,45 @@
"df": 2, "df": 2,
"docs": { "docs": {
"4": { "4": {
"tf": 1.4142135623730952 "tf": 1.4142135623730951
}, },
"7": { "7": {
"tf": 1.0 "tf": 1.4142135623730951
} }
} }
} }
} }
},
"o": {
"df": 0,
"docs": {},
"n": {
"df": 1,
"docs": {
"9": {
"tf": 1.0
}
}
}
} }
}, },
"p": { "p": {
"a": {
"df": 0,
"docs": {},
"g": {
"df": 0,
"docs": {},
"e": {
"df": 1,
"docs": {
"9": {
"tf": 2.0
}
}
}
}
},
"df": 0, "df": 0,
"docs": {}, "docs": {},
"r": { "r": {
@ -1967,8 +2111,12 @@
"df": 0, "df": 0,
"docs": {}, "docs": {},
"t": { "t": {
"df": 0, "df": 1,
"docs": {}, "docs": {
"9": {
"tf": 2.0
}
},
"l": { "l": {
"df": 0, "df": 0,
"docs": {}, "docs": {},
@ -2031,7 +2179,7 @@
"t": { "t": {
"df": 1, "df": 1,
"docs": { "docs": {
"9": { "10": {
"tf": 1.0 "tf": 1.0
} }
} }
@ -2063,7 +2211,15 @@
} }
}, },
"df": 0, "df": 0,
"docs": {} "docs": {},
"l": {
"df": 1,
"docs": {
"9": {
"tf": 1.4142135623730951
}
}
}
}, },
"u": { "u": {
"df": 0, "df": 0,
@ -2122,13 +2278,16 @@
"docs": {}, "docs": {},
"n": { "n": {
"d": { "d": {
"df": 2, "df": 3,
"docs": { "docs": {
"7": { "7": {
"tf": 1.0 "tf": 1.0
}, },
"8": { "8": {
"tf": 1.4142135623730952 "tf": 1.4142135623730951
},
"9": {
"tf": 1.0
} }
} }
}, },
@ -2146,13 +2305,16 @@
"df": 0, "df": 0,
"docs": {}, "docs": {},
"n": { "n": {
"df": 2, "df": 3,
"docs": { "docs": {
"3": { "3": {
"tf": 1.4142135623730952 "tf": 1.4142135623730951
}, },
"5": { "5": {
"tf": 1.4142135623730952 "tf": 1.4142135623730951
},
"9": {
"tf": 1.0
} }
} }
} }
@ -2216,7 +2378,7 @@
"df": 1, "df": 1,
"docs": { "docs": {
"7": { "7": {
"tf": 1.4142135623730952 "tf": 1.4142135623730951
} }
} }
} }
@ -2266,8 +2428,12 @@
"df": 0, "df": 0,
"docs": {} "docs": {}
}, },
"df": 0, "df": 1,
"docs": {} "docs": {
"9": {
"tf": 1.4142135623730951
}
}
} }
}, },
"x": { "x": {
@ -2296,6 +2462,14 @@
"r": { "r": {
"df": 0, "df": 0,
"docs": {}, "docs": {},
"k": {
"df": 1,
"docs": {
"9": {
"tf": 1.0
}
}
},
"l": { "l": {
"d": { "d": {
"df": 1, "df": 1,
@ -2388,7 +2562,7 @@
"s": { "s": {
"df": 1, "df": 1,
"docs": { "docs": {
"9": { "10": {
"tf": 1.0 "tf": 1.0
} }
} }
@ -2511,6 +2685,26 @@
} }
} }
}, },
"l": {
"df": 0,
"docs": {},
"i": {
"df": 0,
"docs": {},
"n": {
"df": 0,
"docs": {},
"k": {
"df": 1,
"docs": {
"9": {
"tf": 1.0
}
}
}
}
}
},
"n": { "n": {
"df": 0, "df": 0,
"docs": {}, "docs": {},
@ -2531,6 +2725,62 @@
} }
} }
}, },
"p": {
"a": {
"df": 0,
"docs": {},
"g": {
"df": 0,
"docs": {},
"e": {
"df": 1,
"docs": {
"9": {
"tf": 1.0
}
}
}
}
},
"df": 0,
"docs": {},
"r": {
"df": 0,
"docs": {},
"i": {
"df": 0,
"docs": {},
"n": {
"df": 0,
"docs": {},
"t": {
"df": 1,
"docs": {
"9": {
"tf": 1.0
}
}
}
}
}
}
},
"r": {
"df": 0,
"docs": {},
"e": {
"df": 0,
"docs": {},
"l": {
"df": 1,
"docs": {
"9": {
"tf": 1.0
}
}
}
}
},
"s": { "s": {
"df": 0, "df": 0,
"docs": {}, "docs": {},
@ -2609,6 +2859,26 @@
} }
} }
} }
},
"t": {
"df": 0,
"docs": {},
"e": {
"df": 0,
"docs": {},
"s": {
"df": 0,
"docs": {},
"t": {
"df": 1,
"docs": {
"9": {
"tf": 1.0
}
}
}
}
}
} }
} }
} }