Compare commits

..

8 Commits

Author SHA1 Message Date
Ramon Buckland
a00ccf8c72 Updated the documentation for new preprocessor format (#787)
* Updated the documentation for new preprocessor format

* adjusted the descriptions for preprocessors
2018-09-10 18:58:55 +08:00
Michael Bryan
18a36ec1fa Fixed the build.use-default-preprocessors flag 2018-09-09 19:02:50 +08:00
Michael Bryan
40ede19103 Got my logic around the wrong way 2018-08-30 22:57:01 +08:00
Michael Bryan
4d7027f8a7 You can normally use default preprocessors by default 2018-08-30 22:51:46 +08:00
Michael Bryan
d0a6d7c87c Users can now manually specify whether a preprocessor should run for a renderer 2018-08-30 22:33:33 +08:00
Michael Bryan
995efc22d5 Made sure preprocessors get their renderer's name 2018-08-30 21:51:18 +08:00
Michael Bryan
a0702037bd A preprocessor is told which render it's running for 2018-08-30 21:37:29 +08:00
Michael Bryan
769fd94868 The preprocessor trait now returns a modified book instead of editing in place 2018-08-30 21:33:56 +08:00
56 changed files with 1276 additions and 4438 deletions

View File

@@ -48,54 +48,6 @@ mdBook builds on stable Rust, if you want to build mdBook from source, here are
The resulting binary can be found in `mdBook/target/debug/` under the name `mdBook` or `mdBook.exe`.
### Code Quality
We love code quality and Rust has some excellent tools to assist you with contributions.
#### Formatting Code with rustfmt
Before you make your Pull Request to the project, please run it through the `rustfmt` utility.
This will ensure we have good quality source code that is better for us all to maintain.
[rustfmt](https://github.com/rust-lang-nursery/rustfmt) has a lot more information on the project.
The quick guide is
1. Install it
```
rustup component add rustfmt
```
1. You can now run `rustfmt` on a single file simply by...
```
rustfmt src/path/to/your/file.rs
```
... or you can format the entire project with
```
cargo fmt
```
When run through `cargo` it will format all bin and lib files in the current crate.
For more information, such as running it from your favourite editor, please see the `rustfmt` project. [rustfmt](https://github.com/rust-lang-nursery/rustfmt)
#### Finding Issues with Clippy
Clippy is a code analyser/linter detecting mistakes, and therfore helps to improve your code.
Like formatting your code with `rustfmt`, running clippy regularly and before your Pull Request will
help us maintain awesome code.
The best documentation can be found over at [rust-clippy](https://github.com/rust-lang-nursery/rust-clippy)
1. To install
```
rustup component add clippy
```
2. Running 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).
### Making a pull-request
When you feel comfortable that your changes could be integrated into mdBook, you can create a pull-request on GitHub.

668
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "mdbook"
version = "0.2.3"
version = "0.2.2-alpha.0"
authors = [
"Mathieu David <mathieudavid@mathieudavid.org>",
"Michael-F-Bryan <michaelfbryan@gmail.com>",
@@ -26,7 +26,7 @@ pulldown-cmark = "0.1.2"
lazy_static = "1.0"
log = "0.4"
env_logger = "0.5"
toml = "0.4.8"
toml = "0.4"
memchr = "2.0"
open = "1.1"
regex = "1.0.0"

View File

@@ -145,57 +145,6 @@ explanation, check out the [User Guide].
Delete directory in which generated book is located.
### 3rd Party Plugins
The way a book is loaded and rendered can be configured by the user via third
party plugins. These plugins are just programs which will be invoked during the
build process and are split into roughly two categories, *preprocessors* and
*renderers*.
Preprocessors are used to transform a book before it is sent to a renderer.
One example would be to replace all occurrences of
`{{#include some_file.ext}}` with the contents of that file. Some existing
preprocessors are:
- `index` - a built-in preprocessor (enabled by default) which will transform
all `README.md` chapters to `index.md` so `foo/README.md` can be accessed via
the url `foo/` when published to a browser
- `links` - a built-in preprocessor (enabled by default) for expanding the
`{{# playpen}}` and `{{# include}}` helpers in a chapter.
Renderers are given the final book so they can do something with it. This is
typically used for, as the name suggests, rendering the document in a particular
format, however there's nothing stopping a renderer from doing static analysis
of a book in order to validate links or run tests. Some existing renderers are:
- `html` - the built-in renderer which will generate a HTML version of the book
- [`linkcheck`] - a backend which will check that all links are valid
- [`epub`] - an experimental EPUB generator
> **Note for Developers:** Feel free to send us a PR if you've developed your
> own plugin and want it mentioned here.
A preprocessor or renderer is enabled by installing the appropriate program and
then mentioning it in the book's `book.toml` file.
```console
$ cargo install mdbook-linkcheck
$ edit book.toml && cat book.toml
[book]
title = "My Awesome Book"
authors = ["Michael-F-Bryan"]
[output.html]
[output.linkcheck] # enable the "mdbook-linkcheck" renderer
$ mdbook build
2018-10-20 13:57:51 [INFO] (mdbook::book): Book building has started
2018-10-20 13:57:51 [INFO] (mdbook::book): Running the html backend
2018-10-20 13:57:53 [INFO] (mdbook::book): Running the linkcheck backend
```
For more information on the plugin system, consult the [User Guide].
### As a library
@@ -240,5 +189,3 @@ All the code in this repository is released under the ***Mozilla Public License
[Rust]: https://www.rust-lang.org/
[CLI docs]: http://rust-lang-nursery.github.io/mdBook/cli/init.html
[master-docs]: http://rust-lang-nursery.github.io/mdBook/mdbook/
[`linkcheck`]: https://crates.io/crates/mdbook-linkcheck
[`epub`]: https://crates.io/crates/mdbook-epub

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
to be compiled with **Cargo**. If you haven't already installed Rust, please go
ahead and [install it](https://www.rust-lang.org/tools/install) now.
ahead and [install it](https://www.rust-lang.org/downloads.html) now.
### Install Crates.io version

View File

@@ -29,9 +29,8 @@ your default web browser after building it.
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for the
book. Relative paths are interpreted relative to the book's root directory. If
not specified it will default to the value of the `build.build-dir` key in
`book.toml`, or to `./book`.
book. If not specified it will default to the value of the `build.build-dir` key
in `book.toml`, or to `./book` relative to the book's root directory.
-------------------

View File

@@ -19,9 +19,9 @@ mdbook clean path/to/book
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to override the book's output
directory, which will be deleted by this command. Relative paths are interpreted
relative to the book's root directory. If not specified it will default to the
value of the `build.build-dir` key in `book.toml`, or to `./book`.
directory, which will be deleted by this command. If not specified it will
default to the value of the `build.build-dir` key in `book.toml`, or to `./book`
relative to the book's root directory.
```bash
mdbook clean --dest-dir=path/to/book

View File

@@ -5,9 +5,6 @@ The serve command is used to preview a book by serving it over HTTP at
changes, rebuilding the book and refreshing clients for each change. A websocket
connection is used to trigger the client-side refresh.
***Note:*** *The `serve` command is for testing a book's HTML output, and is not
intended to be a complete HTTP server for a website.*
#### Specify a directory
The `serve` command can take a directory as an argument to use as the book's
@@ -43,6 +40,10 @@ default web browser after starting the server.
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for the
book. Relative paths are interpreted relative to the book's root directory. If
not specified it will default to the value of the `build.build-dir` key in
`book.toml`, or to `./book`.
book. If not specified it will default to the value of the `build.build-dir` key
in `book.toml`, or to `./book` relative to the book's root directory.
-----
***Note:*** *The `serve` command is for testing, and is not intended to be a
complete HTTP server for a website.*

View File

@@ -48,6 +48,5 @@ comma-delimited list (`-L foo,bar`).
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for the
book. Relative paths are interpreted relative to the book's root directory. If
not specified it will default to the value of the `build.build-dir` key in
`book.toml`, or to `./book`.
book. If not specified it will default to the value of the `build.build-dir` key
in `book.toml`, or to `./book` relative to the book's root directory.

View File

@@ -22,6 +22,5 @@ your default web browser.
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for the
book. Relative paths are interpreted relative to the book's root directory. If
not specified it will default to the value of the `build.build-dir` key in
`book.toml`, or to `./book`.
book. If not specified it will default to the value of the `build.build-dir` key
in `book.toml`, or to `./book` relative to the book's root directory.

View File

@@ -26,7 +26,7 @@ before_script:
- cargo install-update -a
script:
- mdbook build path/to/mybook && mdbook test path/to/mybook
- cd path/to/mybook && mdbook build && mdbook test
```
## Deploying Your Book to GitHub Pages

View File

@@ -42,5 +42,5 @@ explanation on the configuration system.
[`MDBook`]: http://rust-lang-nursery.github.io/mdBook/mdbook/book/struct.MDBook.html
[API Docs]: https://docs.rs/mdbook/*/mdbook/
[API Docs]: http://rust-lang-nursery.github.io/mdBook/mdbook/
[config]: file:///home/michael/Documents/forks/mdBook/target/doc/mdbook/config/index.html

View File

@@ -11,71 +11,68 @@ the book. Possible use cases are:
mathjax equivalents
## Hooking Into MDBook
## Implementing a Preprocessor
MDBook uses a fairly simple mechanism for discovering third party plugins.
A new table is added to `book.toml` (e.g. `preprocessor.foo` for the `foo`
preprocessor) and then `mdbook` will try to invoke the `mdbook-foo` program as
part of the build process.
While preprocessors can be hard-coded to specify which backend it should be run
for (e.g. it doesn't make sense for MathJax to be used for non-HTML renderers)
with the `preprocessor.foo.renderer` key.
```toml
[book]
title = "My Book"
authors = ["Michael-F-Bryan"]
[preprocessor.foo]
# The command can also be specified manually
command = "python3 /path/to/foo.py"
# Only run the `foo` preprocessor for the HTML and EPUB renderer
renderer = ["html", "epub"]
```
In typical unix style, all inputs to the plugin will be written to `stdin` as
JSON and `mdbook` will read from `stdout` if it is expecting output.
The easiest way to get started is by creating your own implementation of the
`Preprocessor` trait (e.g. in `lib.rs`) and then creating a shell binary which
translates inputs to the correct `Preprocessor` method. For convenience, there
is [an example no-op preprocessor] in the `examples/` directory which can easily
be adapted for other preprocessors.
<details>
<summary>Example no-op preprocessor</summary>
A preprocessor is represented by the `Preprocessor` trait.
```rust
// nop-preprocessors.rs
{{#include ../../../examples/nop-preprocessor.rs}}
pub trait Preprocessor {
fn name(&self) -> &str;
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book>;
fn supports_renderer(&self, _renderer: &str) -> bool {
true
}
}
```
</details>
## Hints For Implementing A Preprocessor
Where the `PreprocessorContext` is defined as
By pulling in `mdbook` as a library, preprocessors can have access to the
existing infrastructure for dealing with books.
```rust
pub struct PreprocessorContext {
pub root: PathBuf,
pub config: Config,
/// The `Renderer` this preprocessor is being used with.
pub renderer: String,
}
```
For example, a custom preprocessor could use the
[`CmdPreprocessor::parse_input()`] function to deserialize the JSON written to
`stdin`. Then each chapter of the `Book` can be mutated in-place via
[`Book::for_each_mut()`], and then written to `stdout` with the `serde_json`
crate.
The `renderer` value allows you react accordingly, for example, PDF or HTML.
Chapters can be accessed either directly (by recursively iterating over
chapters) or via the `Book::for_each_mut()` convenience method.
## A complete Example
The `chapter.content` is just a string which happens to be markdown. While it's
entirely possible to use regular expressions or do a manual find & replace,
you'll probably want to process the input into something more computer-friendly.
The [`pulldown-cmark`][pc] crate implements a production-quality event-based
Markdown parser, with the [`pulldown-cmark-to-cmark`][pctc] allowing you to
translate events back into markdown text.
The magic happens within the `run(...)` method of the
[`Preprocessor`][preprocessor-docs] trait implementation.
The following code block shows how to remove all emphasis from markdown,
without accidentally breaking the document.
As direct access to the chapters is not possible, you will probably end up
iterating them using `for_each_mut(...)`:
```rust
book.for_each_mut(|item: &mut BookItem| {
if let BookItem::Chapter(ref mut chapter) = *item {
eprintln!("{}: processing chapter '{}'", self.name(), chapter.name);
res = Some(
match Deemphasize::remove_emphasis(&mut num_removed_items, chapter) {
Ok(md) => {
chapter.content = md;
Ok(())
}
Err(err) => Err(err),
},
);
}
});
```
The `chapter.content` is just a markdown formatted string, and you will have to
process it in some way. Even though it's entirely possible to implement some
sort of manual find & replace operation, if that feels too unsafe you can use
[`pulldown-cmark`][pc] to parse the string into events and work on them instead.
Finally you can use [`pulldown-cmark-to-cmark`][pctc] to transform these events
back to a string.
The following code block shows how to remove all emphasis from markdown, and do
so safely.
```rust
fn remove_emphasis(
@@ -110,6 +107,3 @@ For everything else, have a look [at the complete example][example].
[pc]: https://crates.io/crates/pulldown-cmark
[pctc]: https://crates.io/crates/pulldown-cmark-to-cmark
[example]: https://github.com/rust-lang-nursery/mdBook/blob/master/examples/de-emphasize.rs
[an example no-op preprocessor]: https://github.com/rust-lang-nursery/mdBook/blob/master/examples/nop-preprocessor.rs
[`CmdPreprocessor::parse_input()`]: https://docs.rs/mdbook/latest/mdbook/preprocess/trait.Preprocessor.html#method.parse_input
[`Book::for_each_mut()`]: https://docs.rs/mdbook/latest/mdbook/book/struct.Book.html#method.for_each_mut

View File

@@ -79,8 +79,8 @@ This controls the build process of your book.
The following preprocessors are available and included by default:
- `links`: Expand the `{{ #playpen }}` and `{{ #include }}` handlebars
helpers in a chapter to include the contents of a file.
- `links`: Expand the `{{ #playpen }}` and `{{ #include }}` handlebars helpers in
a chapter to include the contents of a file.
- `index`: Convert all chapter files named `README.md` into `index.md`. That is
to say, all `README.md` would be rendered to an index file `index.html` in the
rendered book.
@@ -99,9 +99,8 @@ create-missing = false
### Custom Preprocessor Configuration
Like renderers, preprocessor will need to be given its own table (e.g.
`[preprocessor.mathjax]`). In the section, you may then pass extra
configuration to the preprocessor by adding key-value pairs to the table.
Like renderers, preprocessor will need to be given its own table (e.g. `[preprocessor.mathjax]`).
In the section, you may then pass extra configuration to the preprocessor by adding key-value pairs to the table.
For example
@@ -114,26 +113,13 @@ some_extra_feature = true
#### Locking a Preprocessor dependency to a renderer
You can explicitly specify that a preprocessor should run for a renderer by
binding the two together.
You can explicitly specify that a preprocessor should run for a renderer by binding the two together.
```
[preprocessor.mathjax]
renderers = ["html"] # mathjax only makes sense with the HTML renderer
```
### Provide Your Own Command
By default when you add a `[preprocessor.foo]` table to your `book.toml` file,
`mdbook` will try to invoke the `mdbook-foo` executa`ble. If you want to use a
different program name or pass in command-line arguments, this behaviour can
be overridden by adding a `command` field.
```toml
[preprocessor.random]
command = "python random.py"
```
## Configuring Renderers
### HTML renderer options
@@ -146,8 +132,6 @@ The following configuration options are available:
- **theme:** mdBook comes with a default theme and all the resource files needed
for it. But if this option is set, mdBook will selectively overwrite the theme
files with the ones found in the specified folder.
- **default-theme:** The theme color scheme to select by default in the
'Change Theme' dropdown. Defaults to `light`.
- **curly-quotes:** Convert straight quotes to curly quotes, except for those
that occur in code blocks and code spans. Defaults to `false`.
- **google-analytics:** If you use Google Analytics, this option lets you enable
@@ -165,10 +149,6 @@ The following configuration options are available:
- **playpen:** A subtable for configuring various playpen settings.
- **search:** A subtable for configuring the in-browser search functionality.
mdBook must be compiled with the `search` feature enabled (on by default).
- **git_repository_url:** A url to the git repository for the book. If provided
an icon link will be output in the menu bar of the book.
- **git_repository_icon:** The FontAwesome icon class to use for the git
repository link. Defaults to `fa-github`.
Available configuration options for the `[output.html.playpen]` table:
@@ -201,8 +181,7 @@ Available configuration options for the `[output.html.search]` table:
- **copy-js:** Copy JavaScript files for the search implementation to the output
directory. Defaults to `true`.
This shows all available HTML output options in the **book.toml**:
This shows all available options in the **book.toml**:
```toml
[book]
title = "Example book"
@@ -239,16 +218,6 @@ heading-split-level = 3
copy-js = true
```
### Custom Renderers
A custom renderer can be enabled by adding a `[output.foo]` table to your
`book.toml`. Similar to [preprocessors](#configuring-preprocessors) this will
instruct `mdbook` to pass a representation of the book to `mdbook-foo` for
rendering.
Custom renderers will have access to all configuration within their table
(i.e. anything under `[output.foo]`), and the command to be invoked can be
manually specified with the `command` field.
## Environment Variables

View File

@@ -1,6 +1,4 @@
//! An example preprocessor for removing all forms of emphasis from a markdown
//! book.
//! This program removes all forms of emphasis from the markdown of the book.
extern crate mdbook;
extern crate pulldown_cmark;
extern crate pulldown_cmark_to_cmark;
@@ -8,16 +6,33 @@ extern crate pulldown_cmark_to_cmark;
use mdbook::book::{Book, BookItem, Chapter};
use mdbook::errors::{Error, Result};
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
use mdbook::MDBook;
use pulldown_cmark::{Event, Parser, Tag};
use pulldown_cmark_to_cmark::fmt::cmark;
use std::env::{args, args_os};
use std::ffi::OsString;
use std::process;
const NAME: &str = "md-links-to-html-links";
fn main() {
panic!("This example is intended to be part of a library");
fn do_it(book: OsString) -> Result<()> {
let mut book = MDBook::load(book)?;
book.with_preprecessor(Deemphasize);
book.build()
}
fn main() {
if args_os().count() != 2 {
eprintln!("USAGE: {} <book>", args().next().expect("executable"));
return;
}
if let Err(e) = do_it(args_os().skip(1).next().expect("one argument")) {
eprintln!("{}", e);
process::exit(1);
}
}
#[allow(dead_code)]
struct Deemphasize;
impl Preprocessor for Deemphasize {
@@ -57,7 +72,10 @@ where
Ok(())
}
fn remove_emphasis(num_removed_items: &mut usize, chapter: &mut Chapter) -> Result<String> {
fn remove_emphasis(
num_removed_items: &mut usize,
chapter: &mut Chapter,
) -> Result<String> {
let mut buf = String::with_capacity(chapter.content.len());
let events = Parser::new(&chapter.content).filter(|e| {
@@ -74,7 +92,7 @@ fn remove_emphasis(num_removed_items: &mut usize, chapter: &mut Chapter) -> Resu
should_keep
});
cmark(events, &mut buf, None)
.map(|_| buf)
.map_err(|err| Error::from(format!("Markdown serialization failed: {}", err)))
cmark(events, &mut buf, None).map(|_| buf).map_err(|err| {
Error::from(format!("Markdown serialization failed: {}", err))
})
}

View File

@@ -1,108 +0,0 @@
extern crate clap;
extern crate mdbook;
extern crate serde_json;
use clap::{App, Arg, ArgMatches, SubCommand};
use mdbook::book::Book;
use mdbook::errors::Error;
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
use nop_lib::Nop;
use std::io;
use std::process;
pub fn make_app() -> App<'static, 'static> {
App::new("nop-preprocessor")
.about("A mdbook preprocessor which does precisely nothing")
.subcommand(
SubCommand::with_name("supports")
.arg(Arg::with_name("renderer").required(true))
.about("Check whether a renderer is supported by this preprocessor"),
)
}
fn main() {
let matches = make_app().get_matches();
// Users will want to construct their own preprocessor here
let preprocessor = Nop::new();
if let Some(sub_args) = matches.subcommand_matches("supports") {
handle_supports(&preprocessor, sub_args);
} else {
if let Err(e) = handle_preprocessing(&preprocessor) {
eprintln!("{}", e);
process::exit(1);
}
}
}
fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
if ctx.mdbook_version != mdbook::MDBOOK_VERSION {
// We should probably use the `semver` crate to check compatibility
// here...
eprintln!(
"Warning: The {} plugin was built against version {} of mdbook, \
but we're being called from version {}",
pre.name(),
mdbook::MDBOOK_VERSION,
ctx.mdbook_version
);
}
let processed_book = pre.run(&ctx, book)?;
serde_json::to_writer(io::stdout(), &processed_book)?;
Ok(())
}
fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! {
let renderer = sub_args.value_of("renderer").expect("Required argument");
let supported = pre.supports_renderer(&renderer);
// Signal whether the renderer is supported by exiting with 1 or 0.
if supported {
process::exit(0);
} else {
process::exit(1);
}
}
/// The actual implementation of the `Nop` preprocessor. This would usually go
/// in your main `lib.rs` file.
mod nop_lib {
use super::*;
/// A no-op preprocessor.
pub struct Nop;
impl Nop {
pub fn new() -> Nop {
Nop
}
}
impl Preprocessor for Nop {
fn name(&self) -> &str {
"nop-preprocessor"
}
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book, Error> {
// In testing we want to tell the preprocessor to blow up by setting a
// particular config value
if let Some(nop_cfg) = ctx.config.get_preprocessor(self.name()) {
if nop_cfg.contains_key("blow-up") {
return Err("Boom!!1!".into());
}
}
// we *are* a no-op preprocessor after all
Ok(book)
}
fn supports_renderer(&self, renderer: &str) -> bool {
renderer != "not-supported"
}
}
}

View File

@@ -167,9 +167,9 @@ impl Chapter {
) -> Chapter {
Chapter {
name: name.to_string(),
content,
content: content,
path: path.into(),
parent_names,
parent_names: parent_names,
..Default::default()
}
}
@@ -210,7 +210,7 @@ fn load_summary_item<P: AsRef<Path>>(
match *item {
SummaryItem::Separator => Ok(BookItem::Separator),
SummaryItem::Link(ref link) => {
load_chapter(link, src_dir, parent_names).map(BookItem::Chapter)
load_chapter(link, src_dir, parent_names).map(|c| BookItem::Chapter(c))
}
}
}

View File

@@ -175,7 +175,7 @@ impl BookBuilder {
let summary = src_dir.join("SUMMARY.md");
let mut f = File::create(&summary).chain_err(|| "Unable to create SUMMARY.md")?;
writeln!(f, "# Summary")?;
writeln!(f)?;
writeln!(f, "")?;
writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
let chapter_1 = src_dir.join("chapter_1.md");

View File

@@ -20,9 +20,7 @@ use tempfile::Builder as TempFileBuilder;
use toml::Value;
use errors::*;
use preprocess::{
CmdPreprocessor, IndexPreprocessor, LinkPreprocessor, Preprocessor, PreprocessorContext,
};
use preprocess::{IndexPreprocessor, LinkPreprocessor, Preprocessor, PreprocessorContext};
use renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer};
use utils;
@@ -161,16 +159,15 @@ impl MDBook {
/// Run the entire build process for a particular `Renderer`.
fn execute_build_process(&self, renderer: &Renderer) -> Result<()> {
let mut preprocessed_book = self.book.clone();
let preprocess_ctx = PreprocessorContext::new(
self.root.clone(),
self.config.clone(),
renderer.name().to_string(),
);
let preprocess_ctx = PreprocessorContext::new(self.root.clone(),
self.config.clone(),
renderer.name().to_string());
for preprocessor in &self.preprocessors {
if preprocessor_should_run(&**preprocessor, renderer, &self.config) {
debug!("Running the {} preprocessor.", preprocessor.name());
preprocessed_book = preprocessor.run(&preprocess_ctx, preprocessed_book)?;
preprocessed_book =
preprocessor.run(&preprocess_ctx, preprocessed_book)?;
}
}
@@ -180,7 +177,11 @@ impl MDBook {
Ok(())
}
fn render(&self, preprocessed_book: &Book, renderer: &Renderer) -> Result<()> {
fn render(
&self,
preprocessed_book: &Book,
renderer: &Renderer,
) -> Result<()> {
let name = renderer.name();
let build_dir = self.build_dir_for(name);
if build_dir.exists() {
@@ -231,8 +232,9 @@ impl MDBook {
let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
// FIXME: Is "test" the proper renderer name to use here?
let preprocess_context =
PreprocessorContext::new(self.root.clone(), self.config.clone(), "test".to_string());
let preprocess_context = PreprocessorContext::new(self.root.clone(),
self.config.clone(),
"test".to_string());
let book = LinkPreprocessor::new().run(&preprocess_context, self.book.clone())?;
// Index Preprocessor is disabled so that chapter paths continue to point to the
@@ -354,39 +356,36 @@ fn is_default_preprocessor(pre: &Preprocessor) -> bool {
/// Look at the `MDBook` and try to figure out what preprocessors to run.
fn determine_preprocessors(config: &Config) -> Result<Vec<Box<Preprocessor>>> {
let mut preprocessors = Vec::new();
let preprocessor_keys = config.get("preprocessor")
.and_then(|value| value.as_table())
.map(|table| table.keys());
if config.build.use_default_preprocessors {
preprocessors.extend(default_preprocessors());
}
let mut preprocessors = if config.build.use_default_preprocessors {
default_preprocessors()
} else {
Vec::new()
};
if let Some(preprocessor_table) = config.get("preprocessor").and_then(|v| v.as_table()) {
for key in preprocessor_table.keys() {
match key.as_ref() {
"links" => preprocessors.push(Box::new(LinkPreprocessor::new())),
"index" => preprocessors.push(Box::new(IndexPreprocessor::new())),
name => preprocessors.push(interpret_custom_preprocessor(
name,
&preprocessor_table[name],
)),
}
let preprocessor_keys = match preprocessor_keys {
Some(keys) => keys,
// If no preprocessor field is set, default to the LinkPreprocessor and
// IndexPreprocessor. This allows you to disable default preprocessors
// by setting "preprocess" to an empty list.
None => return Ok(preprocessors),
};
for key in preprocessor_keys {
match key.as_ref() {
"links" => preprocessors.push(Box::new(LinkPreprocessor::new())),
"index" => preprocessors.push(Box::new(IndexPreprocessor::new())),
_ => bail!("{:?} is not a recognised preprocessor", key),
}
}
Ok(preprocessors)
}
fn interpret_custom_preprocessor(key: &str, table: &Value) -> Box<CmdPreprocessor> {
let command = table
.get("command")
.and_then(|c| c.as_str())
.map(|s| s.to_string())
.unwrap_or_else(|| format!("mdbook-{}", key));
Box::new(CmdPreprocessor::new(key.to_string(), command.to_string()))
}
fn interpret_custom_renderer(key: &str, table: &Value) -> Box<CmdRenderer> {
fn interpret_custom_renderer(key: &str, table: &Value) -> Box<Renderer> {
// look for the `command` field, falling back to using the key
// prepended by "mdbook-"
let table_dot_command = table
@@ -415,8 +414,7 @@ fn preprocessor_should_run(preprocessor: &Preprocessor, renderer: &Renderer, cfg
let renderer_name = renderer.name();
if let Some(Value::Array(ref explicit_renderers)) = cfg.get(&key) {
return explicit_renderers
.iter()
return explicit_renderers.into_iter()
.filter_map(|val| val.as_str())
.any(|name| name == renderer_name);
}
@@ -424,6 +422,7 @@ fn preprocessor_should_run(preprocessor: &Preprocessor, renderer: &Renderer, cfg
preprocessor.supports_renderer(renderer_name)
}
#[cfg(test)]
mod tests {
use super::*;
@@ -493,7 +492,7 @@ mod tests {
}
#[test]
fn can_determine_third_party_preprocessors() {
fn config_complains_if_unimplemented_preprocessor() {
let cfg_str: &'static str = r#"
[book]
title = "Some Book"
@@ -510,25 +509,9 @@ mod tests {
// make sure the `preprocessor.random` table exists
assert!(cfg.get_preprocessor("random").is_some());
let got = determine_preprocessors(&cfg).unwrap();
let got = determine_preprocessors(&cfg);
assert!(got.into_iter().any(|p| p.name() == "random"));
}
#[test]
fn preprocessors_can_provide_their_own_commands() {
let cfg_str = r#"
[preprocessor.random]
command = "python random.py"
"#;
let cfg = Config::from_str(cfg_str).unwrap();
// make sure the `preprocessor.random` table exists
let random = cfg.get_preprocessor("random").unwrap();
let random = interpret_custom_preprocessor("random", &Value::Table(random.clone()));
assert_eq!(random.cmd(), "python random.py");
assert!(got.is_err());
}
#[test]
@@ -541,8 +524,7 @@ mod tests {
let cfg = Config::from_str(cfg_str).unwrap();
// 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(|renderers| renderers.as_array())
.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."))
} else {
Ok(Link {
name,
name: name,
location: PathBuf::from(href.to_string()),
number: None,
nested_items: Vec::new(),
@@ -292,7 +292,6 @@ impl<'a> SummaryParser<'a> {
/// already been consumed by a previous parser.
fn parse_numbered(&mut self) -> Result<Vec<SummaryItem>> {
let mut items = Vec::new();
let mut root_items = 0;
let root_number = SectionNumber::default();
// we need to do this funny loop-match-if-let dance because a rule will
@@ -309,8 +308,7 @@ impl<'a> SummaryParser<'a> {
// if we've resumed after something like a rule the root sections
// will be numbered from 1. We need to manually go back and update
// them
update_section_numbers(&mut bunch_of_items, 0, root_items);
root_items += bunch_of_items.len() as u32;
update_section_numbers(&mut bunch_of_items, 0, items.len() as u32);
items.extend(bunch_of_items);
match self.next_event() {
@@ -726,41 +724,4 @@ mod tests {
let got = parser.parse_numbered();
assert!(got.is_err());
}
/// Regression test for https://github.com/rust-lang-nursery/mdBook/issues/779
/// Ensure section numbers are correctly incremented after a horizontal separator.
#[test]
fn keep_numbering_after_separator() {
let src =
"- [First](./first.md)\n---\n- [Second](./second.md)\n---\n- [Third](./third.md)\n";
let should_be = vec![
SummaryItem::Link(Link {
name: String::from("First"),
location: PathBuf::from("./first.md"),
number: Some(SectionNumber(vec![1])),
nested_items: Vec::new(),
}),
SummaryItem::Separator,
SummaryItem::Link(Link {
name: String::from("Second"),
location: PathBuf::from("./second.md"),
number: Some(SectionNumber(vec![2])),
nested_items: Vec::new(),
}),
SummaryItem::Separator,
SummaryItem::Link(Link {
name: String::from("Third"),
location: PathBuf::from("./third.md"),
number: Some(SectionNumber(vec![3])),
nested_items: Vec::new(),
}),
];
let mut parser = SummaryParser::new(src);
let _ = parser.stream.next();
let got = parser.parse_numbered().unwrap();
assert_eq!(got, should_be);
}
}

View File

@@ -9,8 +9,7 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
.about("Builds a book from its markdown files")
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
(If omitted, uses build.build-dir from book.toml or defaults to ./book)'",
).arg_from_usage(
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",

View File

@@ -10,9 +10,7 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
.about("Deletes a built book")
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
Running this command deletes this directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
(If omitted, uses build.build-dir from book.toml or defaults to ./book)'",
).arg_from_usage(
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",

View File

@@ -12,10 +12,9 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("init")
.about("Creates the boilerplate structure and files for a new book")
// the {n} denotes a newline which will properly aligned in all help messages
.arg_from_usage(
"[dir] 'Directory to create the book in{n}\
(Defaults to the Current Directory when omitted)'",
).arg_from_usage("--theme 'Copies the default theme into your source folder'")
.arg_from_usage("[dir] 'Directory to create the book in{n}\
(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'")
}

View File

@@ -22,8 +22,7 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
.about("Serves a book at http://localhost:3000, and rebuilds it on changes")
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
(If omitted, uses build.build-dir from book.toml or defaults to ./book)'",
)
.arg_from_usage(
"[dir] 'Root directory for the book{n}\
@@ -76,7 +75,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
let port = args.value_of("port").unwrap();
let ws_port = args.value_of("websocket-port").unwrap();
let hostname = args.value_of("hostname").unwrap();
let public_address = args.value_of("websocket-hostname").unwrap_or(hostname);
let public_address = args.value_of("websocket-address").unwrap_or(hostname);
let open_browser = args.is_present("open");
let address = format!("{}:{}", hostname, port);
@@ -115,7 +114,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
}
#[cfg(feature = "watch")]
watch::trigger_on_change(&book, move |path, book_dir| {
watch::trigger_on_change(&mut book, move |path, book_dir| {
info!("File changed: {:?}", path);
info!("Building book...");

View File

@@ -9,8 +9,7 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
.about("Tests that a book's Rust code samples compile")
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
(If omitted, uses build.build-dir from book.toml or defaults to ./book)'",
)
.arg_from_usage(
"[dir] 'Root directory for the book{n}\

View File

@@ -16,8 +16,7 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
.about("Watches a book's files and rebuilds it on changes")
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
(If omitted, uses build.build-dir from book.toml or defaults to ./book)'",
).arg_from_usage(
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",

View File

@@ -301,8 +301,8 @@ impl<'de> Deserialize<'de> for Config {
.unwrap_or_default();
Ok(Config {
book,
build,
book: book,
build: build,
rest: Value::Table(table),
})
}
@@ -415,8 +415,6 @@ impl Default for BuildConfig {
pub struct HtmlConfig {
/// The theme directory, if specified.
pub theme: Option<PathBuf>,
/// The default theme to use, defaults to 'light'
pub default_theme: Option<String>,
/// Use "smart quotes" instead of the usual `"` character.
pub curly_quotes: bool,
/// Should mathjax be enabled?
@@ -438,15 +436,10 @@ pub struct HtmlConfig {
/// This config item *should not be edited* by the end user.
#[doc(hidden)]
pub livereload_url: Option<String>,
/// Don't render section labels.
/// Should section labels be rendered?
pub no_section_label: bool,
/// Search settings. If `None`, the default will be used.
pub search: Option<Search>,
/// Git repository url. If `None`, the git button will not be shown.
pub git_repository_url: Option<String>,
/// FontAwesome icon class to use for the Git repository link.
/// Defaults to `fa-github` if `None`.
pub git_repository_icon: Option<String>,
}
impl HtmlConfig {
@@ -575,12 +568,9 @@ mod tests {
[output.html]
theme = "./themedir"
default-theme = "rust"
curly-quotes = true
google-analytics = "123456"
additional-css = ["./foo/bar/baz.css"]
git-repository-url = "https://foo.com/"
git-repository-icon = "fa-code-fork"
[output.html.playpen]
editable = true
@@ -617,10 +607,7 @@ mod tests {
google_analytics: Some(String::from("123456")),
additional_css: vec![PathBuf::from("./foo/bar/baz.css")],
theme: Some(PathBuf::from("./themedir")),
default_theme: Some(String::from("rust")),
playpen: playpen_should_be,
git_repository_url: Some(String::from("https://foo.com/")),
git_repository_icon: Some(String::from("fa-code-fork")),
..Default::default()
};

View File

@@ -114,12 +114,6 @@ pub mod renderer;
pub mod theme;
pub mod utils;
/// The current version of `mdbook`.
///
/// This is provided as a way for custom preprocessors and renderers to do
/// compatibility checks.
pub const MDBOOK_VERSION: &str = env!("CARGO_PKG_VERSION");
pub use book::BookItem;
pub use book::MDBook;
pub use config::Config;

View File

@@ -20,8 +20,8 @@ use std::path::{Path, PathBuf};
mod cmd;
const NAME: &str = "mdBook";
const VERSION: &str = concat!("v", crate_version!());
const NAME: &'static str = "mdBook";
const VERSION: &'static str = concat!("v", crate_version!());
fn main() {
init_logger();

View File

@@ -1,197 +0,0 @@
use super::{Preprocessor, PreprocessorContext};
use book::Book;
use errors::*;
use serde_json;
use shlex::Shlex;
use std::io::{self, Read, Write};
use std::process::{Child, Command, Stdio};
/// A custom preprocessor which will shell out to a 3rd-party program.
///
/// # Preprocessing Protocol
///
/// When the `supports_renderer()` method is executed, `CmdPreprocessor` will
/// execute the shell command `$cmd supports $renderer`. If the renderer is
/// supported, custom preprocessors should exit with a exit code of `0`,
/// any other exit code be considered as unsupported.
///
/// The `run()` method is implemented by passing a `(PreprocessorContext, Book)`
/// tuple to the spawned command (`$cmd`) as JSON via `stdin`. Preprocessors
/// should then "return" a processed book by printing it to `stdout` as JSON.
/// For convenience, the `CmdPreprocessor::parse_input()` function can be used
/// to parse the input provided by `mdbook`.
///
/// Exiting with a non-zero exit code while preprocessing is considered an
/// error. `stderr` is passed directly through to the user, so it can be used
/// for logging or emitting warnings if desired.
///
/// # Examples
///
/// An example preprocessor is available in this project's `examples/`
/// directory.
#[derive(Debug, Clone, PartialEq)]
pub struct CmdPreprocessor {
name: String,
cmd: String,
}
impl CmdPreprocessor {
/// Create a new `CmdPreprocessor`.
pub fn new(name: String, cmd: String) -> CmdPreprocessor {
CmdPreprocessor { name, cmd }
}
/// A convenience function custom preprocessors can use to parse the input
/// written to `stdin` by a `CmdRenderer`.
pub fn parse_input<R: Read>(reader: R) -> Result<(PreprocessorContext, Book)> {
serde_json::from_reader(reader).chain_err(|| "Unable to parse the input")
}
fn write_input_to_child(&self, child: &mut Child, book: &Book, ctx: &PreprocessorContext) {
let stdin = child.stdin.take().expect("Child has stdin");
if let Err(e) = self.write_input(stdin, &book, &ctx) {
// Looks like the backend hung up before we could finish
// sending it the render context. Log the error and keep going
warn!("Error writing the RenderContext to the backend, {}", e);
}
}
fn write_input<W: Write>(
&self,
writer: W,
book: &Book,
ctx: &PreprocessorContext,
) -> Result<()> {
serde_json::to_writer(writer, &(ctx, book)).map_err(Into::into)
}
/// The command this `Preprocessor` will invoke.
pub fn cmd(&self) -> &str {
&self.cmd
}
fn command(&self) -> Result<Command> {
let mut words = Shlex::new(&self.cmd);
let executable = match words.next() {
Some(e) => e,
None => bail!("Command string was empty"),
};
let mut cmd = Command::new(executable);
for arg in words {
cmd.arg(arg);
}
Ok(cmd)
}
}
impl Preprocessor for CmdPreprocessor {
fn name(&self) -> &str {
&self.name
}
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book> {
let mut cmd = self.command()?;
let mut child = cmd
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.chain_err(|| {
format!(
"Unable to start the \"{}\" preprocessor. Is it installed?",
self.name()
)
})?;
self.write_input_to_child(&mut child, &book, ctx);
let output = child
.wait_with_output()
.chain_err(|| "Error waiting for the preprocessor to complete")?;
trace!("{} exited with output: {:?}", self.cmd, output);
ensure!(
output.status.success(),
"The preprocessor exited unsuccessfully"
);
serde_json::from_slice(&output.stdout).chain_err(|| "Unable to parse the preprocessed book")
}
fn supports_renderer(&self, renderer: &str) -> bool {
debug!(
"Checking if the \"{}\" preprocessor supports \"{}\"",
self.name(),
renderer
);
let mut cmd = match self.command() {
Ok(c) => c,
Err(e) => {
warn!(
"Unable to create the command for the \"{}\" preprocessor, {}",
self.name(),
e
);
return false;
}
};
let outcome = cmd
.arg("supports")
.arg(renderer)
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.map(|status| status.code() == Some(0));
if let Err(ref e) = outcome {
if e.kind() == io::ErrorKind::NotFound {
warn!(
"The command wasn't found, is the \"{}\" preprocessor installed?",
self.name
);
warn!("\tCommand: {}", self.cmd);
}
}
outcome.unwrap_or(false)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
use MDBook;
fn book_example() -> MDBook {
let example = Path::new(env!("CARGO_MANIFEST_DIR")).join("book-example");
MDBook::load(example).unwrap()
}
#[test]
fn round_trip_write_and_parse_input() {
let cmd = CmdPreprocessor::new("test".to_string(), "test".to_string());
let md = book_example();
let ctx = PreprocessorContext::new(
md.root.clone(),
md.config.clone(),
"some-renderer".to_string(),
);
let mut buffer = Vec::new();
cmd.write_input(&mut buffer, &md.book, &ctx).unwrap();
let (got_ctx, got_book) = CmdPreprocessor::parse_input(buffer.as_slice()).unwrap();
assert_eq!(got_book, md.book);
assert_eq!(got_ctx, ctx);
}
}

View File

@@ -7,7 +7,7 @@ use super::{Preprocessor, PreprocessorContext};
use book::{Book, BookItem};
/// A preprocessor for converting file name `README.md` to `index.md` since
/// `README.md` is the de facto index file in markdown-based documentation.
/// `README.md` is the de facto index file in a markdown-based documentation.
pub struct IndexPreprocessor;
impl IndexPreprocessor {

View File

@@ -83,10 +83,6 @@ where
}
Err(e) => {
error!("Error updating \"{}\", {}", playpen.link_text, e);
for cause in e.iter().skip(1) {
warn!("Caused By: {}", cause);
}
// This should make sure we include the raw `{{# ... }}` snippet
// in the page content if there are any errors.
previous_end_index = playpen.start_index;
@@ -142,21 +138,27 @@ fn parse_include_path(path: &str) -> LinkType<'static> {
let end = end.and_then(|s| s.parse::<usize>().ok());
match start {
Some(start) => match end {
Some(end) => LinkType::IncludeRange(path, Range { start, end }),
Some(end) => LinkType::IncludeRange(
path,
Range {
start: start,
end: end,
},
),
None => if has_end {
LinkType::IncludeRangeFrom(path, RangeFrom { start })
LinkType::IncludeRangeFrom(path, RangeFrom { start: start })
} else {
LinkType::IncludeRange(
path,
Range {
start,
start: start,
end: start + 1,
},
)
},
},
None => match end {
Some(end) => LinkType::IncludeRangeTo(path, RangeTo { end }),
Some(end) => LinkType::IncludeRangeTo(path, RangeTo { end: end }),
None => LinkType::IncludeRangeFull(path, RangeFull),
},
}
@@ -205,66 +207,20 @@ impl<'a> Link<'a> {
match self.link {
// omit the escape char
LinkType::Escaped => Ok((&self.link_text[1..]).to_owned()),
LinkType::IncludeRange(ref pat, ref range) => {
let target = base.join(pat);
file_to_string(&target)
.map(|s| take_lines(&s, range.clone()))
.chain_err(|| {
format!(
"Could not read file for link {} ({})",
self.link_text,
target.display(),
)
})
}
LinkType::IncludeRangeFrom(ref pat, ref range) => {
let target = base.join(pat);
file_to_string(&target)
.map(|s| take_lines(&s, range.clone()))
.chain_err(|| {
format!(
"Could not read file for link {} ({})",
self.link_text,
target.display(),
)
})
}
LinkType::IncludeRangeTo(ref pat, ref range) => {
let target = base.join(pat);
file_to_string(&target)
.map(|s| take_lines(&s, *range))
.chain_err(|| {
format!(
"Could not read file for link {} ({})",
self.link_text,
target.display(),
)
})
}
LinkType::IncludeRangeFull(ref pat, _) => {
let target = base.join(pat);
file_to_string(&target).chain_err(|| {
format!(
"Could not read file for link {} ({})",
self.link_text,
target.display()
)
})
}
LinkType::IncludeRange(ref pat, ref range) => file_to_string(base.join(pat))
.map(|s| take_lines(&s, range.clone()))
.chain_err(|| format!("Could not read file for link {}", self.link_text)),
LinkType::IncludeRangeFrom(ref pat, ref range) => file_to_string(base.join(pat))
.map(|s| take_lines(&s, range.clone()))
.chain_err(|| format!("Could not read file for link {}", self.link_text)),
LinkType::IncludeRangeTo(ref pat, ref range) => file_to_string(base.join(pat))
.map(|s| take_lines(&s, range.clone()))
.chain_err(|| format!("Could not read file for link {}", self.link_text)),
LinkType::IncludeRangeFull(ref pat, _) => 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 target = base.join(pat);
let contents = file_to_string(&target).chain_err(|| {
format!(
"Could not read file for link {} ({})",
self.link_text,
target.display()
)
})?;
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",

View File

@@ -1,10 +1,8 @@
//! Book preprocessing.
pub use self::cmd::CmdPreprocessor;
pub use self::index::IndexPreprocessor;
pub use self::links::LinkPreprocessor;
mod cmd;
mod index;
mod links;
@@ -16,7 +14,6 @@ use std::path::PathBuf;
/// Extra information for a `Preprocessor` to give them more context when
/// processing a book.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PreprocessorContext {
/// The location of the book directory on disk.
pub root: PathBuf,
@@ -24,22 +21,12 @@ pub struct PreprocessorContext {
pub config: Config,
/// The `Renderer` this preprocessor is being used with.
pub renderer: String,
/// The calling `mdbook` version.
pub mdbook_version: String,
#[serde(skip)]
__non_exhaustive: (),
}
impl PreprocessorContext {
/// Create a new `PreprocessorContext`.
pub(crate) fn new(root: PathBuf, config: Config, renderer: String) -> Self {
PreprocessorContext {
root,
config,
renderer,
mdbook_version: ::MDBOOK_VERSION.to_string(),
__non_exhaustive: (),
}
PreprocessorContext { root, config, renderer }
}
}

View File

@@ -30,71 +30,75 @@ impl HtmlHandlebars {
print_content: &mut String,
) -> Result<()> {
// FIXME: This should be made DRY-er and rely less on mutable state
if let BookItem::Chapter(ref ch) = *item {
let content = ch.content.clone();
let content = utils::render_markdown(&content, ctx.html_config.curly_quotes);
match *item {
BookItem::Chapter(ref ch) => {
let content = ch.content.clone();
let content = utils::render_markdown(&content, ctx.html_config.curly_quotes);
print_content.push_str(&content);
let string_path = ch.path.parent().unwrap().display().to_string();
// Update the context with data for this file
let path = ch
.path
.to_str()
.chain_err(|| "Could not convert path to str")?;
let filepath = Path::new(&ch.path).with_extension("html");
let fixed_content = utils::render_markdown_with_base(&ch.content, ctx.html_config.curly_quotes, &string_path);
print_content.push_str(&fixed_content);
// "print.html" is used for the print page.
if ch.path == Path::new("print.md") {
bail!(ErrorKind::ReservedFilenameError(ch.path.clone()));
};
// Update the context with data for this file
let path = ch
.path
.to_str()
.chain_err(|| "Could not convert path to str")?;
let filepath = Path::new(&ch.path).with_extension("html");
// Non-lexical lifetimes needed :'(
let title: String;
{
let book_title = ctx
.data
.get("book_title")
.and_then(serde_json::Value::as_str)
.unwrap_or("");
title = ch.name.clone() + " - " + book_title;
}
// "print.html" is used for the print page.
if ch.path == Path::new("print.md") {
bail!(ErrorKind::ReservedFilenameError(ch.path.clone()));
};
ctx.data.insert("path".to_owned(), json!(path));
ctx.data.insert("content".to_owned(), json!(content));
ctx.data.insert("chapter_title".to_owned(), json!(ch.name));
ctx.data.insert("title".to_owned(), json!(title));
ctx.data.insert(
"path_to_root".to_owned(),
json!(utils::fs::path_to_root(&ch.path)),
);
// Non-lexical lifetimes needed :'(
let title: String;
{
let book_title = ctx
.data
.get("book_title")
.and_then(serde_json::Value::as_str)
.unwrap_or("");
title = ch.name.clone() + " - " + book_title;
}
ctx.data.insert("path".to_owned(), json!(path));
ctx.data.insert("content".to_owned(), json!(content));
ctx.data.insert("chapter_title".to_owned(), json!(ch.name));
ctx.data.insert("title".to_owned(), json!(title));
ctx.data.insert(
"path_to_root".to_owned(),
json!(utils::fs::path_to_root(&ch.path)),
);
// 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())?;
// 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(())
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::let_and_return))]
#[cfg_attr(feature = "cargo-clippy", allow(let_and_return))]
fn post_process(&self, rendered: String, playpen_config: &Playpen) -> String {
let rendered = build_header_links(&rendered);
let rendered = fix_code_blocks(&rendered);
@@ -210,7 +214,6 @@ impl HtmlHandlebars {
);
handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
handlebars.register_helper("next", Box::new(helpers::navigation::next));
handlebars.register_helper("theme_option", Box::new(helpers::theme::theme_option));
}
/// Copy across any additional CSS and JavaScript files which the book
@@ -324,7 +327,7 @@ impl Renderer for HtmlHandlebars {
handlebars: &handlebars,
destination: destination.to_path_buf(),
data: data.clone(),
is_index,
is_index: is_index,
html_config: html_config.clone(),
};
self.render_item(item, ctx, &mut print_content)?;
@@ -392,12 +395,6 @@ fn make_data(
data.insert("livereload".to_owned(), json!(livereload));
}
let default_theme = match html_config.default_theme {
Some(ref theme) => theme,
None => "light",
};
data.insert("default_theme".to_owned(), json!(default_theme));
// Add google analytics tag
if let Some(ref ga) = config.html_config().and_then(|html| html.google_analytics) {
data.insert("google_analytics".to_owned(), json!(ga));
@@ -457,15 +454,6 @@ fn make_data(
)
}
if let Some(ref git_repository_url) = html_config.git_repository_url {
data.insert("git_repository_url".to_owned(), json!(git_repository_url));
}
let git_repository_icon = match html_config.git_repository_icon {
Some(ref git_repository_icon) => git_repository_icon,
None => "fa-github",
};
data.insert("git_repository_icon".to_owned(), json!(git_repository_icon));
let mut chapters = vec![];
for item in book.iter() {

View File

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

View File

@@ -18,13 +18,13 @@ impl Target {
/// Returns target if found.
fn find(
&self,
base_path: &str,
current_path: &str,
base_path: &String,
current_path: &String,
current_item: &StringMap,
previous_item: &StringMap,
) -> Result<Option<StringMap>, RenderError> {
match *self {
Target::Next => {
match self {
&Target::Next => {
let previous_path = previous_item
.get("path")
.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 {
return Ok(Some(previous_item.clone()));
}

View File

@@ -1,30 +0,0 @@
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError};
pub fn theme_option(
h: &Helper,
_r: &Handlebars,
ctx: &Context,
rc: &mut RenderContext,
out: &mut Output,
) -> Result<(), RenderError> {
trace!("theme_option (handlebars helper)");
let param = h
.param(0)
.and_then(|v| v.value().as_str())
.ok_or(RenderError::new(
"Param 0 with String type is required for theme_option helper.",
))?;
let theme_name = rc
.evaluate_absolute(ctx, "default_theme", true)?
.as_str()
.ok_or_else(|| RenderError::new("Type error for `default_theme`, string expected"))?;
out.write(param)?;
if param.to_lowercase() == theme_name.to_lowercase() {
out.write(" (default)")?;
}
Ok(())
}

View File

@@ -54,7 +54,7 @@ fn add_doc(
section_id: &Option<String>,
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))
} else {
Cow::Borrowed(anchor_base)
@@ -74,8 +74,8 @@ fn render_item(
doc_urls: &mut Vec<String>,
item: &BookItem,
) -> Result<()> {
let chapter = match *item {
BookItem::Chapter(ref ch) => ch,
let chapter = match item {
&BookItem::Chapter(ref ch) => ch,
_ => return Ok(()),
};
@@ -101,7 +101,7 @@ fn render_item(
for event in p {
match event {
Event::Start(Tag::Header(i)) if i <= max_section_depth => {
if !heading.is_empty() {
if heading.len() > 0 {
// Section finished, the next header is following now
// Write the data to the index, and clear it for the next section
add_doc(
@@ -155,7 +155,7 @@ fn render_item(
}
}
if !heading.is_empty() {
if heading.len() > 0 {
// Make sure the last section is added to the index
add_doc(
index,

View File

@@ -8,7 +8,7 @@
//!
//! The definition for [RenderContext] may be useful though.
//!
//! [For Developers]: https://rust-lang-nursery.github.io/mdBook/for_developers/index.html
//! [For Developers]: https://rust-lang-nursery.github.io/mdBook/lib/index.html
//! [RenderContext]: struct.RenderContext.html
pub use self::html_handlebars::HtmlHandlebars;
@@ -26,6 +26,8 @@ use book::Book;
use config::Config;
use errors::*;
const MDBOOK_VERSION: &str = env!("CARGO_PKG_VERSION");
/// An arbitrary `mdbook` backend.
///
/// Although it's quite possible for you to import `mdbook` as a library and
@@ -64,8 +66,6 @@ pub struct RenderContext {
/// renderers to cache intermediate results, this directory is not
/// guaranteed to be empty or even exist.
pub destination: PathBuf,
#[serde(skip)]
__non_exhaustive: (),
}
impl RenderContext {
@@ -76,12 +76,11 @@ impl RenderContext {
Q: Into<PathBuf>,
{
RenderContext {
book,
config,
version: ::MDBOOK_VERSION.to_string(),
book: book,
config: config,
version: MDBOOK_VERSION.to_string(),
root: root.into(),
destination: destination.into(),
__non_exhaustive: (),
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 434 KiB

After

Width:  |  Height:  |  Size: 348 KiB

View File

@@ -352,7 +352,7 @@ function playpen_text(playpen) {
var previousTheme;
try { previousTheme = localStorage.getItem('mdbook-theme'); } catch (e) { }
if (previousTheme === null || previousTheme === undefined) { previousTheme = default_theme; }
if (previousTheme === null || previousTheme === undefined) { previousTheme = 'light'; }
try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
@@ -364,7 +364,7 @@ function playpen_text(playpen) {
// Set theme
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
if (theme === null || theme === undefined) { theme = 'light'; }
set_theme(theme);

View File

@@ -63,12 +63,9 @@ a > .hljs {
margin: 0;
}
.right-buttons {
#print-button {
margin: 0 15px;
}
.right-buttons a {
text-decoration: none;
}
html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-container {
transform: translateY(-60px);

View File

@@ -27,7 +27,7 @@
<!-- Custom theme stylesheets -->
{{#each additional_css}}
<link rel="stylesheet" href="{{ ../path_to_root }}{{ this }}">
<link rel="stylesheet" href="{{ this }}">
{{/each}}
{{#if mathjax_support}}
@@ -35,12 +35,9 @@
<script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
{{/if}}
</head>
<body class="{{ default_theme }}">
<body class="light">
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "{{ path_to_root }}";
var default_theme = "{{ default_theme }}";
</script>
<script type="text/javascript">var path_to_root = "{{ path_to_root }}";</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
@@ -62,7 +59,7 @@
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
if (theme === null || theme === undefined) { theme = 'light'; }
document.body.className = theme;
document.querySelector('html').className = theme + ' js';
</script>
@@ -97,11 +94,11 @@
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">{{ theme_option "Light" }}</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">{{ theme_option "Rust" }}</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">{{ theme_option "Coal" }}</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">{{ theme_option "Navy" }}</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">{{ theme_option "Ayu" }}</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light <span class="default">(default)</span></button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
{{#if search_enabled}}
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
@@ -116,11 +113,6 @@
<a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
{{#if git_repository_url}}
<a href="{{git_repository_url}}" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
</a>
{{/if}}
</div>
</div>
</div>
@@ -243,7 +235,7 @@
<!-- Custom JS scripts -->
{{#each additional_js}}
<script type="text/javascript" src="{{ ../path_to_root }}{{this}}"></script>
<script type="text/javascript" src="{{ path_to_root }}{{this}}"></script>
{{/each}}
{{#if is_print}}

View File

@@ -21,10 +21,9 @@ pub fn collapse_whitespace<'a>(text: &'a str) -> Cow<'a, str> {
RE.replace_all(text, " ")
}
/// Convert the given string to a valid HTML element ID.
/// The only restriction is that the ID must not contain any ASCII whitespace.
/// Convert the given string to a valid HTML element ID
pub fn normalize_id(content: &str) -> String {
content
let mut ret = content
.chars()
.filter_map(|ch| {
if ch.is_alphanumeric() || ch == '_' || ch == '-' {
@@ -34,7 +33,16 @@ pub fn normalize_id(content: &str) -> String {
} else {
None
}
}).collect::<String>()
}).collect::<String>();
// Ensure that the first character is [A-Za-z]
if ret
.chars()
.next()
.map_or(false, |c| !c.is_ascii_alphabetic())
{
ret.insert(0, 'a');
}
ret
}
/// Generate an ID for use with anchors which is derived from a "normalised"
@@ -66,21 +74,15 @@ pub fn id_from_content(content: &str) -> String {
normalize_id(trimmed)
}
fn adjust_links<'a>(event: Event<'a>, with_base: &str) -> Event<'a> {
fn adjust_links(event: Event) -> Event {
lazy_static! {
static ref HTTP_LINK: Regex = Regex::new("^https?://").unwrap();
static ref MD_LINK: Regex = Regex::new(r"(?P<link>.*)\.md(?P<anchor>#.*)?").unwrap();
static ref MD_LINK: Regex = Regex::new("(?P<link>.*).md(?P<anchor>#.*)?").unwrap();
}
match event {
Event::Start(Tag::Link(dest, title)) => {
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) {
let mut html_link = [&caps["link"], ".html"].concat();
@@ -100,10 +102,6 @@ fn adjust_links<'a>(event: Event<'a>, with_base: &str) -> Event<'a> {
/// Wrapper around the pulldown-cmark parser for rendering markdown to HTML.
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 opts = Options::empty();
@@ -114,7 +112,7 @@ pub fn render_markdown_with_base(text: &str, curly_quotes: bool, base: &str) ->
let mut converter = EventQuoteConverter::new(curly_quotes);
let events = p
.map(clean_codeblock_headers)
.map(|event| adjust_links(event, base))
.map(adjust_links)
.map(|event| converter.convert(event));
html::push_html(&mut s, events);
@@ -129,7 +127,7 @@ struct EventQuoteConverter {
impl EventQuoteConverter {
fn new(enabled: bool) -> Self {
EventQuoteConverter {
enabled,
enabled: enabled,
convert_text: true,
}
}
@@ -230,12 +228,6 @@ mod tests {
render_markdown("[example_anchor](example.md#anchor)", false),
"<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]
@@ -336,42 +328,28 @@ more text with spaces
#[test]
fn it_generates_anchors() {
assert_eq!(
id_from_content("## `--passes`: add more rustdoc passes"),
"a--passes-add-more-rustdoc-passes"
);
assert_eq!(
id_from_content("## Method-call expressions"),
"method-call-expressions"
);
assert_eq!(id_from_content("## **Bold** title"), "bold-title");
assert_eq!(id_from_content("## `Code` title"), "code-title");
}
#[test]
fn it_generates_anchors_from_non_ascii_initial() {
assert_eq!(
id_from_content("## `--passes`: add more rustdoc passes"),
"--passes-add-more-rustdoc-passes"
);
assert_eq!(
id_from_content("## 中文標題 CJK title"),
"中文標題-cjk-title"
);
assert_eq!(id_from_content("## Über"), "Über");
}
#[test]
fn it_normalizes_ids() {
assert_eq!(
normalize_id("`--passes`: add more rustdoc passes"),
"--passes-add-more-rustdoc-passes"
"a--passes-add-more-rustdoc-passes"
);
assert_eq!(
normalize_id("Method-call 🐙 expressions \u{1f47c}"),
"method-call--expressions-"
);
assert_eq!(normalize_id("_-_12345"), "_-_12345");
assert_eq!(normalize_id("12345"), "12345");
assert_eq!(normalize_id("中文"), "中文");
assert_eq!(normalize_id("にほんご"), "にほんご");
assert_eq!(normalize_id("한국어"), "한국어");
assert_eq!(normalize_id("_-_12345"), "a_-_12345");
assert_eq!(normalize_id("12345"), "a12345");
assert_eq!(normalize_id(""), "");
}
}

View File

@@ -11,14 +11,14 @@ use tempfile::{Builder as TempFileBuilder, TempDir};
#[test]
fn passing_alternate_backend() {
let (md, _temp) = dummy_book_with_backend("passing", success_cmd());
let (md, _temp) = dummy_book_with_backend("passing", "true");
md.build().unwrap();
}
#[test]
fn failing_alternate_backend() {
let (md, _temp) = dummy_book_with_backend("failing", fail_cmd());
let (md, _temp) = dummy_book_with_backend("failing", "false");
md.build().unwrap_err();
}
@@ -84,19 +84,3 @@ fn dummy_book_with_backend(name: &str, command: &str) -> (MDBook, TempDir) {
(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

@@ -1,58 +0,0 @@
extern crate mdbook;
mod dummy_book;
use dummy_book::DummyBook;
use mdbook::preprocess::{CmdPreprocessor, Preprocessor};
use mdbook::MDBook;
fn example() -> CmdPreprocessor {
CmdPreprocessor::new(
"nop-preprocessor".to_string(),
"cargo run --example nop-preprocessor --".to_string(),
)
}
#[test]
fn example_supports_whatever() {
let cmd = example();
let got = cmd.supports_renderer("whatever");
assert_eq!(got, true);
}
#[test]
fn example_doesnt_support_not_supported() {
let cmd = example();
let got = cmd.supports_renderer("not-supported");
assert_eq!(got, false);
}
#[test]
fn ask_the_preprocessor_to_blow_up() {
let dummy_book = DummyBook::new();
let temp = dummy_book.build().unwrap();
let mut md = MDBook::load(temp.path()).unwrap();
md.with_preprecessor(example());
md.config
.set("preprocessor.nop-preprocessor.blow-up", true)
.unwrap();
let got = md.build();
assert!(got.is_err());
}
#[test]
fn process_the_dummy_book() {
let dummy_book = DummyBook::new();
let temp = dummy_book.build().unwrap();
let mut md = MDBook::load(temp.path()).unwrap();
md.with_preprecessor(example());
md.build().unwrap();
}

View File

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

View File

@@ -1,4 +0,0 @@
# 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",
];
const TOC_SECOND_LEVEL: &[&'static str] =
&["1.1. Nested Chapter", "1.2. Includes", "2.1. Nested Chapter", "1.3. Recursive"];
&["1.1. Nested Chapter", "1.2. Includes", "1.3. Recursive"];
/// Make sure you can load the dummy book and build it without panicking.
#[test]
@@ -109,20 +109,6 @@ 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]
fn rendered_code_has_playpen_stuff() {
let temp = DummyBook::new().build().unwrap();
@@ -457,7 +443,7 @@ mod search {
assert_eq!(docs[&some_section]["body"], "");
assert_eq!(
docs[&summary]["body"],
"Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Second Chapter Nested Chapter Conclusion"
"Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Second Chapter Conclusion"
);
assert_eq!(docs[&summary]["breadcrumbs"], "First Chapter » Summary");
assert_eq!(docs[&conclusion]["body"], "I put &lt;HTML&gt; in here!");

View File

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