Increase Documentation Coverage (#543)
* Added documentation to the `config` module * Added an example to the `config` module * Updated the docs in lib.rs regarding implementing backends * Started writing an alternate backends walkthrough * Mentioned the output.foo.command key * Added example output * Added a config section to the backends tutorial * Finished off the backends tutorial * Made sure travis checks mdbook-wordcount * Fixed the broken link at in the user guide * Changed how travis builds the project * Added a conclusion * Went through and documented a lot of stuff * Added a preprocessors chapter and updated For Developers
This commit is contained in:
parent
232a923676
commit
9fe19d8f31
10
Cargo.toml
10
Cargo.toml
|
@ -20,19 +20,19 @@ chrono = "0.4"
|
||||||
handlebars = "0.29"
|
handlebars = "0.29"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
error-chain = "0.11.0"
|
error-chain = "0.11"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
pulldown-cmark = "0.1"
|
pulldown-cmark = "0.1"
|
||||||
lazy_static = "1.0"
|
lazy_static = "1.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.5.0-rc.1"
|
env_logger = "0.5.0-rc.1"
|
||||||
toml = "0.4"
|
toml = "0.4"
|
||||||
memchr = "2.0.1"
|
memchr = "2.0"
|
||||||
open = "1.1"
|
open = "1.1"
|
||||||
regex = "0.2.1"
|
regex = "0.2.1"
|
||||||
tempdir = "0.3.4"
|
tempdir = "0.3.4"
|
||||||
itertools = "0.7.4"
|
itertools = "0.7"
|
||||||
shlex = "0.1.1"
|
shlex = "0.1"
|
||||||
toml-query = "0.6"
|
toml-query = "0.6"
|
||||||
|
|
||||||
# Watch feature
|
# Watch feature
|
||||||
|
@ -66,3 +66,5 @@ doc = false
|
||||||
name = "mdbook"
|
name = "mdbook"
|
||||||
path = "src/bin/mdbook.rs"
|
path = "src/bin/mdbook.rs"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["book-example/src/for_developers/mdbook-wordcount"]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[book]
|
[book]
|
||||||
title = "mdBook Documentation"
|
title = "mdBook Documentation"
|
||||||
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
|
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
|
||||||
author = "Mathieu David"
|
authors = ["Mathieu David", "Michael-F-Bryan"]
|
||||||
|
|
||||||
[output.html]
|
[output.html]
|
||||||
mathjax-support = true
|
mathjax-support = true
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
- [Editor](format/theme/editor.md)
|
- [Editor](format/theme/editor.md)
|
||||||
- [MathJax Support](format/mathjax.md)
|
- [MathJax Support](format/mathjax.md)
|
||||||
- [Rust code specific features](format/rust.md)
|
- [Rust code specific features](format/rust.md)
|
||||||
- [For Developers](lib/index.md)
|
- [For Developers](for_developers/index.md)
|
||||||
|
- [Preprocessors](for_developers/preprocessors.md)
|
||||||
|
- [Alternate Backends](for_developers/backends.md)
|
||||||
-----------
|
-----------
|
||||||
[Contributors](misc/contributors.md)
|
[Contributors](misc/contributors.md)
|
||||||
|
|
|
@ -0,0 +1,352 @@
|
||||||
|
# Alternate Backends
|
||||||
|
|
||||||
|
A "backend" is simply a program which `mdbook` will invoke during the book
|
||||||
|
rendering process. This program is passed a JSON representation of the book and
|
||||||
|
configuration information via `stdin`. Once the backend receives this
|
||||||
|
information it is free to do whatever it wants.
|
||||||
|
|
||||||
|
There are already several alternate backends on GitHub which can be used as a
|
||||||
|
rough example of how this is accomplished in practice.
|
||||||
|
|
||||||
|
- [mdbook-linkcheck] - a simple program for verifying the book doesn't contain
|
||||||
|
any broken links
|
||||||
|
- [mdbook-epub] - an EPUB renderer
|
||||||
|
- [mdbook-test] - a program to run the book's contents through [rust-skeptic] to
|
||||||
|
verify everything compiles and runs correctly (similar to `rustdoc --test`)
|
||||||
|
|
||||||
|
This page will step you through creating your own alternate backend in the form
|
||||||
|
of a simple word counting program. Although it will be written in Rust, there's
|
||||||
|
no reason why it couldn't be accomplished using something like Python or Ruby.
|
||||||
|
|
||||||
|
|
||||||
|
## Setting Up
|
||||||
|
|
||||||
|
First you'll want to create a new binary program and add `mdbook` as a
|
||||||
|
dependency.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cargo new --bin mdbook-wordcount
|
||||||
|
$ cd mdbook-wordcount
|
||||||
|
$ cargo add mdbook
|
||||||
|
```
|
||||||
|
|
||||||
|
When our `mdbook-wordcount` plugin is invoked, `mdbook` will send it a JSON
|
||||||
|
version of [`RenderContext`] via our plugin's `stdin`. For convenience, there's
|
||||||
|
a [`RenderContext::from_json()`] constructor which will load a `RenderContext`.
|
||||||
|
|
||||||
|
This is all the boilerplate necessary for our backend to load the book.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// src/main.rs
|
||||||
|
extern crate mdbook;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use mdbook::renderer::RenderContext;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut stdin = io::stdin();
|
||||||
|
let ctx = RenderContext::from_json(&mut stdin).unwrap();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** The `RenderContext` contains a `version` field. This lets backends
|
||||||
|
figure out whether they are compatible with the version of `mdbook` it's being
|
||||||
|
called by. This `version` comes directly from the corresponding field in
|
||||||
|
`mdbook`'s `Cargo.toml`.
|
||||||
|
|
||||||
|
It is recommended that backends use the [`semver`] crate to inspect this field
|
||||||
|
and emit a warning if there may be a compatibility issue.
|
||||||
|
|
||||||
|
|
||||||
|
## Inspecting the Book
|
||||||
|
|
||||||
|
Now our backend has a copy of the book, lets count how many words are in each
|
||||||
|
chapter!
|
||||||
|
|
||||||
|
Because the `RenderContext` contains a [`Book`] field (`book`), and a `Book` has
|
||||||
|
the [`Book::iter()`] method for iterating over all items in a `Book`, this step
|
||||||
|
turns out to be just as easy as the first.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut stdin = io::stdin();
|
||||||
|
let ctx = RenderContext::from_json(&mut stdin).unwrap();
|
||||||
|
|
||||||
|
for item in ctx.book.iter() {
|
||||||
|
if let BookItem::Chapter(ref ch) = *item {
|
||||||
|
let num_words = count_words(ch);
|
||||||
|
println!("{}: {}", ch.name, num_words);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_words(ch: &Chapter) -> usize {
|
||||||
|
ch.content.split_whitespace().count()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Enabling the Backend
|
||||||
|
|
||||||
|
Now we've got the basics running, we want to actually use it. First, install
|
||||||
|
the program.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cargo install
|
||||||
|
```
|
||||||
|
|
||||||
|
Then `cd` to the particular book you'd like to count the words of and update its
|
||||||
|
`book.toml` file.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
[book]
|
||||||
|
title = "mdBook Documentation"
|
||||||
|
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
|
||||||
|
authors = ["Mathieu David", "Michael-F-Bryan"]
|
||||||
|
|
||||||
|
+ [output.html]
|
||||||
|
|
||||||
|
+ [output.wordcount]
|
||||||
|
```
|
||||||
|
|
||||||
|
When it loads a book into memory, `mdbook` will inspect your `book.toml` file
|
||||||
|
to try and figure out which backends to use by looking for all `output.*`
|
||||||
|
tables. If none are provided it'll fall back to using the default HTML
|
||||||
|
renderer.
|
||||||
|
|
||||||
|
Notably, this means if you want to add your own custom backend you'll also
|
||||||
|
need to make sure to add the HTML backend, even if its tabke just stays empty.
|
||||||
|
|
||||||
|
Now you just need to build your book like normal, and everything should *Just
|
||||||
|
Work*.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ mdbook build
|
||||||
|
...
|
||||||
|
2018-01-16 07:31:15 [INFO] (mdbook::renderer): Invoking the "mdbook-wordcount" renderer
|
||||||
|
mdBook: 126
|
||||||
|
Command Line Tool: 224
|
||||||
|
init: 283
|
||||||
|
build: 145
|
||||||
|
watch: 146
|
||||||
|
serve: 292
|
||||||
|
test: 139
|
||||||
|
Format: 30
|
||||||
|
SUMMARY.md: 259
|
||||||
|
Configuration: 784
|
||||||
|
Theme: 304
|
||||||
|
index.hbs: 447
|
||||||
|
Syntax highlighting: 314
|
||||||
|
MathJax Support: 153
|
||||||
|
Rust code specific features: 148
|
||||||
|
For Developers: 788
|
||||||
|
Alternate Backends: 710
|
||||||
|
Contributors: 85
|
||||||
|
```
|
||||||
|
|
||||||
|
The reason we didn't need to specify the full name/path of our `wordcount`
|
||||||
|
backend is because `mdbook` will try to *infer* the program's name via
|
||||||
|
convention. The executable for the `foo` backend is typically called
|
||||||
|
`mdbook-foo`, with an associated `[output.foo]` entry in the `book.toml`. To
|
||||||
|
explicitly tell `mdbook` what command to invoke (it may require command line
|
||||||
|
arguments or be an interpreted script), you can use the `command` field.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
[book]
|
||||||
|
title = "mdBook Documentation"
|
||||||
|
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
|
||||||
|
authors = ["Mathieu David", "Michael-F-Bryan"]
|
||||||
|
|
||||||
|
[output.html]
|
||||||
|
|
||||||
|
[output.wordcount]
|
||||||
|
+ command = "python /path/to/wordcount.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Now imagine you don't want to count the number of words on a particular chapter
|
||||||
|
(it might be generated text/code, etc). The canonical way to do this is via
|
||||||
|
the usual `book.toml` configuration file by adding items to your `[output.foo]`
|
||||||
|
table.
|
||||||
|
|
||||||
|
The `Config` can be treated roughly as a nested hashmap which lets you call
|
||||||
|
methods like `get()` to access the config's contents, with a
|
||||||
|
`get_deserialized()` convenience method for retrieving a value and
|
||||||
|
automatically deserializing to some arbitrary type `T`.
|
||||||
|
|
||||||
|
To implement this, we'll create our own serializable `WordcountConfig` struct
|
||||||
|
which will encapsulate all configuration for this backend.
|
||||||
|
|
||||||
|
First add `serde` and `serde_derive` to your `Cargo.toml`,
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cargo add serde serde_derive
|
||||||
|
```
|
||||||
|
|
||||||
|
And then you can create the config struct,
|
||||||
|
|
||||||
|
```rust
|
||||||
|
extern crate serde;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
|
#[serde(default, rename_all = "kebab-case")]
|
||||||
|
pub struct WordcountConfig {
|
||||||
|
pub ignores: Vec<String>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we just need to deserialize the `WordcountConfig` from our `RenderContext`
|
||||||
|
and then add a check to make sure we skip ignored chapters.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
fn main() {
|
||||||
|
let mut stdin = io::stdin();
|
||||||
|
let ctx = RenderContext::from_json(&mut stdin).unwrap();
|
||||||
|
+ let cfg: WordcountConfig = ctx.config
|
||||||
|
+ .get_deserialized("output.wordcount")
|
||||||
|
+ .unwrap_or_default();
|
||||||
|
|
||||||
|
for item in ctx.book.iter() {
|
||||||
|
if let BookItem::Chapter(ref ch) = *item {
|
||||||
|
+ if cfg.ignores.contains(&ch.name) {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
let num_words = count_words(ch);
|
||||||
|
println!("{}: {}", ch.name, num_words);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Output and Signalling Failure
|
||||||
|
|
||||||
|
While it's nice to print word counts to the terminal when a book is built, it
|
||||||
|
might also be a good idea to output them to a file somewhere. `mdbook` tells a
|
||||||
|
backend where it should place any generated output via the `destination` field
|
||||||
|
in [`RenderContext`].
|
||||||
|
|
||||||
|
```diff
|
||||||
|
+ use std::fs::{self, File};
|
||||||
|
+ use std::io::{self, Write};
|
||||||
|
- use std::io;
|
||||||
|
use mdbook::renderer::RenderContext;
|
||||||
|
use mdbook::book::{BookItem, Chapter};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
...
|
||||||
|
|
||||||
|
+ let _ = fs::create_dir_all(&ctx.destination);
|
||||||
|
+ let mut f = File::create(ctx.destination.join("wordcounts.txt")).unwrap();
|
||||||
|
+
|
||||||
|
for item in ctx.book.iter() {
|
||||||
|
if let BookItem::Chapter(ref ch) = *item {
|
||||||
|
...
|
||||||
|
|
||||||
|
let num_words = count_words(ch);
|
||||||
|
println!("{}: {}", ch.name, num_words);
|
||||||
|
+ writeln!(f, "{}: {}", ch.name, num_words).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** There is no guarantee that the destination directory exists or is
|
||||||
|
> empty (`mdbook` may leave the previous contents to let backends do caching),
|
||||||
|
> so it's always a good idea to create it with `fs::create_dir_all()`.
|
||||||
|
|
||||||
|
There's always the possibility that an error will occur while processing a book
|
||||||
|
(just look at all the `unwrap()`'s we've written already), so `mdbook` will
|
||||||
|
interpret a non-zero exit code as a rendering failure.
|
||||||
|
|
||||||
|
For example, if we wanted to make sure all chapters have an *even* number of
|
||||||
|
words, erroring out if an odd number is encountered, then you may do something
|
||||||
|
like this:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
+ use std::process;
|
||||||
|
...
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
...
|
||||||
|
|
||||||
|
for item in ctx.book.iter() {
|
||||||
|
if let BookItem::Chapter(ref ch) = *item {
|
||||||
|
...
|
||||||
|
|
||||||
|
let num_words = count_words(ch);
|
||||||
|
println!("{}: {}", ch.name, num_words);
|
||||||
|
writeln!(f, "{}: {}", ch.name, num_words).unwrap();
|
||||||
|
|
||||||
|
+ if cfg.deny_odds && num_words % 2 == 1 {
|
||||||
|
+ eprintln!("{} has an odd number of words!", ch.name);
|
||||||
|
+ process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
|
#[serde(default, rename_all = "kebab-case")]
|
||||||
|
pub struct WordcountConfig {
|
||||||
|
pub ignores: Vec<String>,
|
||||||
|
+ pub deny_odds: bool,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, if we reinstall the backend and build a book,
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cargo install --force
|
||||||
|
$ mdbook build /path/to/book
|
||||||
|
...
|
||||||
|
2018-01-16 21:21:39 [INFO] (mdbook::renderer): Invoking the "wordcount" renderer
|
||||||
|
mdBook: 126
|
||||||
|
Command Line Tool: 224
|
||||||
|
init: 283
|
||||||
|
init has an odd number of words!
|
||||||
|
2018-01-16 21:21:39 [ERROR] (mdbook::renderer): Renderer exited with non-zero return code.
|
||||||
|
2018-01-16 21:21:39 [ERROR] (mdbook::utils): Error: Rendering failed
|
||||||
|
2018-01-16 21:21:39 [ERROR] (mdbook::utils): Caused By: The "mdbook-wordcount" renderer failed
|
||||||
|
```
|
||||||
|
|
||||||
|
As you've probably already noticed, output from the plugin's subprocess is
|
||||||
|
immediately passed through to the user. It is encouraged for plugins to
|
||||||
|
follow the "rule of silence" and only generate output when necessary (e.g. an
|
||||||
|
error in generation or a warning).
|
||||||
|
|
||||||
|
All environment variables are passed through to the backend, allowing you to
|
||||||
|
use the usual `RUST_LOG` to control logging verbosity.
|
||||||
|
|
||||||
|
|
||||||
|
## Wrapping Up
|
||||||
|
|
||||||
|
Although contrived, hopefully this example was enough to show how you'd create
|
||||||
|
an alternate backend for `mdbook`. If you feel it's missing something, don't
|
||||||
|
hesitate to create an issue in the [issue tracker] so we can improve the user
|
||||||
|
guide.
|
||||||
|
|
||||||
|
The existing backends mentioned towards the start of this chapter should serve
|
||||||
|
as a good example of how it's done in real life, so feel free to skim through
|
||||||
|
the source code or ask questions.
|
||||||
|
|
||||||
|
|
||||||
|
[mdbook-linkcheck]: https://github.com/Michael-F-Bryan/mdbook-linkcheck
|
||||||
|
[mdbook-epub]: https://github.com/Michael-F-Bryan/mdbook-epub
|
||||||
|
[mdbook-test]: https://github.com/Michael-F-Bryan/mdbook-test
|
||||||
|
[rust-skeptic]: https://github.com/budziq/rust-skeptic
|
||||||
|
[`RenderContext`]: http://rust-lang-nursery.github.io/mdBook/mdbook/renderer/struct.RenderContext.html
|
||||||
|
[`RenderContext::from_json()`]: http://rust-lang-nursery.github.io/mdBook/mdbook/renderer/struct.RenderContext.html#method.from_json
|
||||||
|
[`semver`]: https://crates.io/crates/semver
|
||||||
|
[`Book`]: http://rust-lang-nursery.github.io/mdBook/mdbook/book/struct.Book.html
|
||||||
|
[`Book::iter()`]: http://rust-lang-nursery.github.io/mdBook/mdbook/book/struct.Book.html#method.iter
|
||||||
|
[`Config`]: http://rust-lang-nursery.github.io/mdBook/mdbook/config/struct.Config.html
|
||||||
|
[issue tracker]: https://github.com/rust-lang-nursery/mdBook/issues
|
|
@ -0,0 +1,46 @@
|
||||||
|
# For Developers
|
||||||
|
|
||||||
|
While `mdbook` is mainly used as a command line tool, you can also import the
|
||||||
|
underlying library directly and use that to manage a book. It also has a fairly
|
||||||
|
flexible plugin mechanism, allowing you to create your own custom tooling and
|
||||||
|
consumers (often referred to as *backends*) if you need to do some analysis of
|
||||||
|
the book or render it in a different format.
|
||||||
|
|
||||||
|
The *For Developers* chapters are here to show you the more advanced usage of
|
||||||
|
`mdbook`.
|
||||||
|
|
||||||
|
The two main ways a developer can hook into the book's build process is via,
|
||||||
|
|
||||||
|
- [Preprocessors](for_developers/preprocessors.html)
|
||||||
|
- [Alternate Backends](for_developers/backends.html)
|
||||||
|
|
||||||
|
|
||||||
|
## The Build Process
|
||||||
|
|
||||||
|
The process of rendering a book project goes through several steps.
|
||||||
|
|
||||||
|
1. Load the book
|
||||||
|
- Parse the `book.toml`, falling back to the default `Config` if it doesn't
|
||||||
|
exist.
|
||||||
|
- Load the book chapters into memory
|
||||||
|
- Discover which preprocessors/backends should be used
|
||||||
|
2. Run the preprocessors
|
||||||
|
3. Call each backend in turn
|
||||||
|
|
||||||
|
|
||||||
|
## Using `mdbook` as a Library
|
||||||
|
|
||||||
|
The `mdbook` binary is just a wrapper around the `mdbook` crate, exposing its
|
||||||
|
functionality as a command-line program. As such it is quite easy to create your
|
||||||
|
own programs which use `mdbook` internally, adding your own functionality (e.g.
|
||||||
|
a custom preprocessor) or tweaking the build process.
|
||||||
|
|
||||||
|
The easiest way to find out how to use the `mdbook` crate is by looking at the
|
||||||
|
[API Docs]. The top level documentation explains how one would use the
|
||||||
|
[`MDBook`] type to load and build a book, while the [config] module gives a good
|
||||||
|
explanation on the configuration system.
|
||||||
|
|
||||||
|
|
||||||
|
[`MDBook`]: http://rust-lang-nursery.github.io/mdBook/mdbook/book/struct.MDBook.html
|
||||||
|
[API Docs]: http://rust-lang-nursery.github.io/mdBook/mdbook/
|
||||||
|
[config]: file:///home/michael/Documents/forks/mdBook/target/doc/mdbook/config/index.html
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "mdbook-wordcount"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Michael Bryan <michaelfbryan@gmail.com>"]
|
||||||
|
workspace = "../../../.."
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mdbook = { path = "../../../.." }
|
||||||
|
serde = "1.0"
|
||||||
|
serde_derive = "1.0"
|
|
@ -0,0 +1,49 @@
|
||||||
|
extern crate mdbook;
|
||||||
|
extern crate serde;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
use std::process;
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use mdbook::renderer::RenderContext;
|
||||||
|
use mdbook::book::{BookItem, Chapter};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut stdin = io::stdin();
|
||||||
|
let ctx = RenderContext::from_json(&mut stdin).unwrap();
|
||||||
|
let cfg: WordcountConfig = ctx.config
|
||||||
|
.get_deserialized("output.wordcount")
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let _ = fs::create_dir_all(&ctx.destination);
|
||||||
|
let mut f = File::create(ctx.destination.join("wordcounts.txt")).unwrap();
|
||||||
|
|
||||||
|
for item in ctx.book.iter() {
|
||||||
|
if let BookItem::Chapter(ref ch) = *item {
|
||||||
|
if cfg.ignores.contains(&ch.name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let num_words = count_words(ch);
|
||||||
|
println!("{}: {}", ch.name, num_words);
|
||||||
|
writeln!(f, "{}: {}", ch.name, num_words).unwrap();
|
||||||
|
|
||||||
|
if cfg.deny_odds && num_words % 2 == 1 {
|
||||||
|
eprintln!("{} has an odd number of words!", ch.name);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_words(ch: &Chapter) -> usize {
|
||||||
|
ch.content.split_whitespace().count()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
|
#[serde(default, rename_all = "kebab-case")]
|
||||||
|
pub struct WordcountConfig {
|
||||||
|
pub ignores: Vec<String>,
|
||||||
|
pub deny_odds: bool,
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Preprocessors
|
||||||
|
|
||||||
|
A *preprocessor* is simply a bit of code which gets run immediately after the
|
||||||
|
book is loaded and before it gets rendered, allowing you to update and mutate
|
||||||
|
the book. Possible use cases are:
|
||||||
|
|
||||||
|
- Creating custom helpers like `{{#include /path/to/file.md}}`
|
||||||
|
- Updating links so `[some chapter](some_chapter.md)` is automatically changed
|
||||||
|
to `[some chapter](some_chapter.html)` for the HTML renderer
|
||||||
|
- Substituting in latex-style expressions (`$$ \frac{1}{3} $$`) with their
|
||||||
|
mathjax equivalents
|
||||||
|
|
||||||
|
|
||||||
|
## Implementing a Preprocessor
|
||||||
|
|
||||||
|
A preprocessor is represented by the `Preprocessor` trait.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub trait Preprocessor {
|
||||||
|
fn name(&self) -> &str;
|
||||||
|
fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Where the `PreprocessorContext` is defined as
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct PreprocessorContext {
|
||||||
|
pub root: PathBuf,
|
||||||
|
pub config: Config,
|
||||||
|
}
|
||||||
|
```
|
|
@ -35,8 +35,10 @@ With the following syntax, you can insert runnable Rust files into your book:
|
||||||
|
|
||||||
The path to the Rust file has to be relative from the current source file.
|
The path to the Rust file has to be relative from the current source file.
|
||||||
|
|
||||||
When play is clicked, the code snippet will be send to the [Rust Playpen]() to be compiled and run. The result is send back and displayed directly underneath the code.
|
When play is clicked, the code snippet will be send to the [Rust Playpen] to be compiled and run. The result is send back and displayed directly underneath the code.
|
||||||
|
|
||||||
Here is what a rendered code snippet looks like:
|
Here is what a rendered code snippet looks like:
|
||||||
|
|
||||||
{{#playpen example.rs}}
|
{{#playpen example.rs}}
|
||||||
|
|
||||||
|
[Rust Playpen]: https://play.rust-lang.org/
|
|
@ -1,176 +0,0 @@
|
||||||
# For Developers
|
|
||||||
|
|
||||||
While `mdbook` is mainly used as a command line tool, you can also import the
|
|
||||||
underlying library directly and use that to manage a book.
|
|
||||||
|
|
||||||
- Creating custom backends
|
|
||||||
- Automatically generating and reloading a book on the fly
|
|
||||||
- Integration with existing projects
|
|
||||||
|
|
||||||
The best source for examples on using the `mdbook` crate from your own Rust
|
|
||||||
programs is the [API Docs].
|
|
||||||
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
The mechanism for using alternative backends is very simple, you add an extra
|
|
||||||
table to your `book.toml` and the `MDBook::load()` function will automatically
|
|
||||||
detect the backends being used.
|
|
||||||
|
|
||||||
For example, if you wanted to use a hypothetical `latex` backend you would add
|
|
||||||
an empty `output.latex` table to `book.toml`.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
# book.toml
|
|
||||||
|
|
||||||
[book]
|
|
||||||
...
|
|
||||||
|
|
||||||
[output.latex]
|
|
||||||
```
|
|
||||||
|
|
||||||
And then during the rendering stage `mdbook` will run the `mdbook-latex`
|
|
||||||
program, piping it a JSON serialized [RenderContext] via stdin.
|
|
||||||
|
|
||||||
You can set the command used via the `command` key.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
# book.toml
|
|
||||||
|
|
||||||
[book]
|
|
||||||
...
|
|
||||||
|
|
||||||
[output.latex]
|
|
||||||
command = "python3 my_plugin.py"
|
|
||||||
```
|
|
||||||
|
|
||||||
If no backend is supplied (i.e. there are no `output.*` tables), `mdbook` will
|
|
||||||
fall back to the `html` backend.
|
|
||||||
|
|
||||||
### The `Config` Struct
|
|
||||||
|
|
||||||
If you are developing a plugin or alternate backend then whenever your code is
|
|
||||||
called you will almost certainly be passed a reference to the book's `Config`.
|
|
||||||
This can be treated roughly as a nested hashmap which lets you call methods like
|
|
||||||
`get()` and `get_mut()` to get access to the config's contents.
|
|
||||||
|
|
||||||
By convention, plugin developers will have their settings as a subtable inside
|
|
||||||
`plugins` (e.g. a link checker would put its settings in `plugins.link_check`)
|
|
||||||
and backends should put their configuration under `output`, like the HTML
|
|
||||||
renderer does in the previous examples.
|
|
||||||
|
|
||||||
As an example, some hypothetical `random` renderer would typically want to load
|
|
||||||
its settings from the `Config` at the very start of its rendering process. The
|
|
||||||
author can take advantage of serde to deserialize the generic `toml::Value`
|
|
||||||
object retrieved from `Config` into a struct specific to its use case.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
extern crate serde;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
extern crate toml;
|
|
||||||
extern crate mdbook;
|
|
||||||
|
|
||||||
use toml::Value;
|
|
||||||
use mdbook::config::Config;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq)]
|
|
||||||
struct RandomOutput {
|
|
||||||
foo: u32,
|
|
||||||
bar: String,
|
|
||||||
baz: Vec<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
# fn run() -> Result<(), Box<::std::error::Error>> {
|
|
||||||
let src = r#"
|
|
||||||
[output.random]
|
|
||||||
foo = 5
|
|
||||||
bar = "Hello World"
|
|
||||||
baz = [true, true, false]
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let book_config = Config::from_str(src)?; // usually passed in via the RenderContext
|
|
||||||
let random = book_config.get("output.random")
|
|
||||||
.cloned()
|
|
||||||
.ok_or("output.random not found")?;
|
|
||||||
let got: RandomOutput = random.try_into()?;
|
|
||||||
|
|
||||||
let should_be = RandomOutput {
|
|
||||||
foo: 5,
|
|
||||||
bar: "Hello World".to_string(),
|
|
||||||
baz: vec![true, true, false]
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(got, should_be);
|
|
||||||
|
|
||||||
let baz: Vec<bool> = book_config.get_deserialized("output.random.baz")?;
|
|
||||||
println!("{:?}", baz); // prints [true, true, false]
|
|
||||||
|
|
||||||
// do something interesting with baz
|
|
||||||
# Ok(())
|
|
||||||
# }
|
|
||||||
# fn main() { run().unwrap() }
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Render Context
|
|
||||||
|
|
||||||
The `RenderContext` encapsulates all the information a backend needs to know
|
|
||||||
in order to generate output. Its Rust definition looks something like this:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct RenderContext {
|
|
||||||
pub version: String,
|
|
||||||
pub root: PathBuf,
|
|
||||||
pub book: Book,
|
|
||||||
pub config: Config,
|
|
||||||
pub destination: PathBuf,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
A backend will receive the `RenderContext` via `stdin` as one big JSON blob. If
|
|
||||||
possible, it is recommended to import the `mdbook` crate and use the
|
|
||||||
`RenderContext::from_json()` method. This way you should always be able to
|
|
||||||
deserialize the `RenderContext`, and as a bonus will also have access to the
|
|
||||||
methods already defined on the underlying types.
|
|
||||||
|
|
||||||
Although backends are told the book's root directory on disk, it is *strongly
|
|
||||||
discouraged* to load chapter content from the filesystem. The `root` key is
|
|
||||||
provided as an escape hatch for certain plugins which may load additional,
|
|
||||||
non-markdown, files.
|
|
||||||
|
|
||||||
|
|
||||||
## Output Directory
|
|
||||||
|
|
||||||
To make things more deterministic, a backend will be told where it should place
|
|
||||||
its generated artefacts.
|
|
||||||
|
|
||||||
The general algorithm for deciding the output directory goes something like
|
|
||||||
this:
|
|
||||||
|
|
||||||
- If there is only one backend:
|
|
||||||
- `destination` is `config.build.build_dir` (usually `book/`)
|
|
||||||
- Otherwise:
|
|
||||||
- `destination` is `config.build.build_dir` joined with the backend's name
|
|
||||||
(e.g. `build/latex/` for the "latex" backend)
|
|
||||||
|
|
||||||
|
|
||||||
## Output and Signalling Failure
|
|
||||||
|
|
||||||
To signal that the plugin failed it just needs to exit with a non-zero return
|
|
||||||
code.
|
|
||||||
|
|
||||||
All output from the plugin's subprocess is immediately passed through to the
|
|
||||||
user, so it is encouraged for plugins to follow the ["rule of silence"] and
|
|
||||||
by default only tell the user about things they directly need to respond to
|
|
||||||
(e.g. an error in generation or a warning).
|
|
||||||
|
|
||||||
This "silent by default" behaviour can be overridden via the `RUST_LOG`
|
|
||||||
environment variable (which `mdbook` will pass through to the backend if set)
|
|
||||||
as is typical with Rust applications.
|
|
||||||
|
|
||||||
|
|
||||||
[API Docs]: https://docs.rs/mdbook
|
|
||||||
[RenderContext]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html
|
|
||||||
["rule of silence"]: http://www.linfo.org/rule_of_silence.html
|
|
|
@ -2,10 +2,9 @@
|
||||||
|
|
||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
# TODO This is the "test phase", tweak it as you see fit
|
|
||||||
main() {
|
main() {
|
||||||
cross build --target $TARGET
|
cross build --target $TARGET --all
|
||||||
cross build --target $TARGET --release
|
cross build --target $TARGET --all --release
|
||||||
|
|
||||||
if [ ! -z $DISABLE_TESTS ]; then
|
if [ ! -z $DISABLE_TESTS ]; then
|
||||||
return
|
return
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
//!
|
//!
|
||||||
//! [1]: ../index.html
|
//! [1]: ../index.html
|
||||||
|
|
||||||
#![deny(missing_docs)]
|
|
||||||
|
|
||||||
mod summary;
|
mod summary;
|
||||||
mod book;
|
mod book;
|
||||||
mod init;
|
mod init;
|
||||||
|
@ -38,9 +36,6 @@ pub struct MDBook {
|
||||||
pub book: Book,
|
pub book: Book,
|
||||||
renderers: Vec<Box<Renderer>>,
|
renderers: Vec<Box<Renderer>>,
|
||||||
|
|
||||||
/// The URL used for live reloading when serving up the book.
|
|
||||||
pub livereload: Option<String>,
|
|
||||||
|
|
||||||
/// List of pre-processors to be run on the book
|
/// List of pre-processors to be run on the book
|
||||||
preprocessors: Vec<Box<Preprocessor>>
|
preprocessors: Vec<Box<Preprocessor>>
|
||||||
}
|
}
|
||||||
|
@ -85,7 +80,6 @@ impl MDBook {
|
||||||
|
|
||||||
let src_dir = root.join(&config.book.src);
|
let src_dir = root.join(&config.book.src);
|
||||||
let book = book::load_book(&src_dir, &config.build)?;
|
let book = book::load_book(&src_dir, &config.build)?;
|
||||||
let livereload = None;
|
|
||||||
|
|
||||||
let renderers = determine_renderers(&config);
|
let renderers = determine_renderers(&config);
|
||||||
let preprocessors = determine_preprocessors(&config)?;
|
let preprocessors = determine_preprocessors(&config)?;
|
||||||
|
@ -95,7 +89,6 @@ impl MDBook {
|
||||||
config,
|
config,
|
||||||
book,
|
book,
|
||||||
renderers,
|
renderers,
|
||||||
livereload,
|
|
||||||
preprocessors,
|
preprocessors,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,54 @@
|
||||||
//! Mdbook's configuration system.
|
//! Mdbook's configuration system.
|
||||||
|
//!
|
||||||
|
//! The main entrypoint of the `config` module is the `Config` struct. This acts
|
||||||
|
//! essentially as a bag of configuration information, with a couple
|
||||||
|
//! pre-determined tables (`BookConfig` and `BuildConfig`) as well as support
|
||||||
|
//! for arbitrary data which is exposed to plugins and alternate backends.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # extern crate mdbook;
|
||||||
|
//! # use mdbook::errors::*;
|
||||||
|
//! # extern crate toml;
|
||||||
|
//! use std::path::PathBuf;
|
||||||
|
//! use mdbook::Config;
|
||||||
|
//! use toml::Value;
|
||||||
|
//!
|
||||||
|
//! # fn run() -> Result<()> {
|
||||||
|
//! let src = r#"
|
||||||
|
//! [book]
|
||||||
|
//! title = "My Book"
|
||||||
|
//! authors = ["Michael-F-Bryan"]
|
||||||
|
//!
|
||||||
|
//! [build]
|
||||||
|
//! src = "out"
|
||||||
|
//!
|
||||||
|
//! [other-table.foo]
|
||||||
|
//! bar = 123
|
||||||
|
//! "#;
|
||||||
|
//!
|
||||||
|
//! // load the `Config` from a toml string
|
||||||
|
//! let mut cfg = Config::from_str(src)?;
|
||||||
|
//!
|
||||||
|
//! // retrieve a nested value
|
||||||
|
//! let bar = cfg.get("other-table.foo.bar").cloned();
|
||||||
|
//! assert_eq!(bar, Some(Value::Integer(123)));
|
||||||
|
//!
|
||||||
|
//! // Set the `output.html.theme` directory
|
||||||
|
//! assert!(cfg.get("output.html").is_none());
|
||||||
|
//! cfg.set("output.html.theme", "./themes");
|
||||||
|
//!
|
||||||
|
//! // then load it again, automatically deserializing to a `PathBuf`.
|
||||||
|
//! let got: PathBuf = cfg.get_deserialized("output.html.theme")?;
|
||||||
|
//! assert_eq!(got, PathBuf::from("./themes"));
|
||||||
|
//! # Ok(())
|
||||||
|
//! # }
|
||||||
|
//! # fn main() { run().unwrap() }
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
@ -14,11 +64,13 @@ use serde_json;
|
||||||
|
|
||||||
use errors::*;
|
use errors::*;
|
||||||
|
|
||||||
/// The overall configuration object for MDBook.
|
/// The overall configuration object for MDBook, essentially an in-memory
|
||||||
|
/// representation of `book.toml`.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Metadata about the book.
|
/// Metadata about the book.
|
||||||
pub book: BookConfig,
|
pub book: BookConfig,
|
||||||
|
/// Information about the build environment.
|
||||||
pub build: BuildConfig,
|
pub build: BuildConfig,
|
||||||
rest: Value,
|
rest: Value,
|
||||||
}
|
}
|
||||||
|
@ -344,15 +396,24 @@ impl Default for BuildConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configuration for the HTML renderer.
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(default, rename_all = "kebab-case")]
|
#[serde(default, rename_all = "kebab-case")]
|
||||||
pub struct HtmlConfig {
|
pub struct HtmlConfig {
|
||||||
|
/// The theme directory, if specified.
|
||||||
pub theme: Option<PathBuf>,
|
pub theme: Option<PathBuf>,
|
||||||
|
/// Use "smart quotes" instead of the usual `"` character.
|
||||||
pub curly_quotes: bool,
|
pub curly_quotes: bool,
|
||||||
|
/// Should mathjax be enabled?
|
||||||
pub mathjax_support: bool,
|
pub mathjax_support: bool,
|
||||||
|
/// An optional google analytics code.
|
||||||
pub google_analytics: Option<String>,
|
pub google_analytics: Option<String>,
|
||||||
|
/// Additional CSS stylesheets to include in the rendered page's `<head>`.
|
||||||
pub additional_css: Vec<PathBuf>,
|
pub additional_css: Vec<PathBuf>,
|
||||||
|
/// Additional JS scripts to include at the bottom of the rendered page's
|
||||||
|
/// `<body>`.
|
||||||
pub additional_js: Vec<PathBuf>,
|
pub additional_js: Vec<PathBuf>,
|
||||||
|
/// Playpen settings.
|
||||||
pub playpen: Playpen,
|
pub playpen: Playpen,
|
||||||
/// This is used as a bit of a workaround for the `mdbook serve` command.
|
/// This is used as a bit of a workaround for the `mdbook serve` command.
|
||||||
/// Basically, because you set the websocket port from the command line, the
|
/// Basically, because you set the websocket port from the command line, the
|
||||||
|
@ -362,6 +423,7 @@ pub struct HtmlConfig {
|
||||||
/// This config item *should not be edited* by the end user.
|
/// This config item *should not be edited* by the end user.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub livereload_url: Option<String>,
|
pub livereload_url: Option<String>,
|
||||||
|
/// Should section labels be rendered?
|
||||||
pub no_section_label: bool,
|
pub no_section_label: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,7 +431,11 @@ pub struct HtmlConfig {
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(default, rename_all = "kebab-case")]
|
#[serde(default, rename_all = "kebab-case")]
|
||||||
pub struct Playpen {
|
pub struct Playpen {
|
||||||
|
/// The path to the editor to use. Defaults to the [Ace Editor].
|
||||||
|
///
|
||||||
|
/// [Ace Editor]: https://ace.c9.io/
|
||||||
pub editor: PathBuf,
|
pub editor: PathBuf,
|
||||||
|
/// Should playpen snippets be editable? Defaults to `false`.
|
||||||
pub editable: bool,
|
pub editable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
72
src/lib.rs
72
src/lib.rs
|
@ -12,7 +12,7 @@
|
||||||
//! - Integrate mdbook in a current project
|
//! - Integrate mdbook in a current project
|
||||||
//! - Extend the capabilities of mdBook
|
//! - Extend the capabilities of mdBook
|
||||||
//! - Do some processing or test before building your book
|
//! - Do some processing or test before building your book
|
||||||
//! - Write a new Renderer
|
//! - Accessing the public API to help create a new Renderer
|
||||||
//! - ...
|
//! - ...
|
||||||
//!
|
//!
|
||||||
//! # Examples
|
//! # Examples
|
||||||
|
@ -50,48 +50,30 @@
|
||||||
//! md.build().expect("Building failed");
|
//! md.build().expect("Building failed");
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Implementing a new Renderer
|
//! ## Implementing a new Backend
|
||||||
//!
|
//!
|
||||||
//! If you want to create a new renderer for mdBook, the only thing you have to
|
//! `mdbook` has a fairly flexible mechanism for creating additional backends
|
||||||
//! do is to implement the [Renderer](renderer/renderer/trait.Renderer.html)
|
//! for your book. The general idea is you'll add an extra table in the book's
|
||||||
//! trait.
|
//! `book.toml` which specifies an executable to be invoked by `mdbook`. This
|
||||||
|
//! executable will then be called during a build, with an in-memory
|
||||||
|
//! representation ([`RenderContext`]) of the book being passed to the
|
||||||
|
//! subprocess via `stdin`.
|
||||||
//!
|
//!
|
||||||
//! And then you can swap in your renderer like this:
|
//! The [`RenderContext`] gives the backend access to the contents of
|
||||||
|
//! `book.toml` and lets it know which directory all generated artefacts should
|
||||||
|
//! be placed in. For a much more in-depth explanation, consult the [relevant
|
||||||
|
//! chapter] in the *For Developers* section of the user guide.
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! To make creating a backend easier, the `mdbook` crate can be imported
|
||||||
//! # extern crate mdbook;
|
//! directly, making deserializing the `RenderContext` easy and giving you
|
||||||
//! #
|
//! access to the various methods for working with the [`Config`].
|
||||||
//! # use mdbook::MDBook;
|
|
||||||
//! # use mdbook::renderer::HtmlHandlebars;
|
|
||||||
//! #
|
|
||||||
//! # #[allow(unused_variables)]
|
|
||||||
//! # fn main() {
|
|
||||||
//! # let your_renderer = HtmlHandlebars::new();
|
|
||||||
//! #
|
|
||||||
//! let mut book = MDBook::load("my-book").unwrap();
|
|
||||||
//! book.with_renderer(your_renderer);
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! If you make a renderer, you get the book constructed in form of
|
|
||||||
//! `Vec<BookItems>` and you get ! the book config in a `BookConfig` struct.
|
|
||||||
//!
|
|
||||||
//! It's your responsability to create the necessary files in the correct
|
|
||||||
//! directories.
|
|
||||||
//!
|
|
||||||
//! ## utils
|
|
||||||
//!
|
|
||||||
//! I have regrouped some useful functions in the [utils](utils/index.html)
|
|
||||||
//! module, like the following function [`utils::fs::create_file(path:
|
|
||||||
//! &Path)`](utils/fs/fn.create_file.html).
|
|
||||||
//!
|
|
||||||
//! This function creates a file and returns it. But before creating the file
|
|
||||||
//! it checks every directory in the path to see if it exists, and if it does
|
|
||||||
//! not it will be created.
|
|
||||||
//!
|
|
||||||
//! Make sure to take a look at it.
|
|
||||||
//!
|
//!
|
||||||
//! [user guide]: https://rust-lang-nursery.github.io/mdBook/
|
//! [user guide]: https://rust-lang-nursery.github.io/mdBook/
|
||||||
|
//! [`RenderContext`]: renderer/struct.RenderContext.html
|
||||||
|
//! [relevant chapter]: https://rust-lang-nursery.github.io/mdBook/for_developers/backends.html
|
||||||
|
//! [`Config`]: config/struct.Config.html
|
||||||
|
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate error_chain;
|
extern crate error_chain;
|
||||||
|
@ -128,6 +110,7 @@ pub mod utils;
|
||||||
pub use book::MDBook;
|
pub use book::MDBook;
|
||||||
pub use book::BookItem;
|
pub use book::BookItem;
|
||||||
pub use renderer::Renderer;
|
pub use renderer::Renderer;
|
||||||
|
pub use config::Config;
|
||||||
|
|
||||||
/// The error types used through out this crate.
|
/// The error types used through out this crate.
|
||||||
pub mod errors {
|
pub mod errors {
|
||||||
|
@ -135,27 +118,30 @@ pub mod errors {
|
||||||
|
|
||||||
error_chain!{
|
error_chain!{
|
||||||
foreign_links {
|
foreign_links {
|
||||||
Io(::std::io::Error);
|
Io(::std::io::Error) #[doc = "A wrapper around `std::io::Error`"];
|
||||||
HandlebarsRender(::handlebars::RenderError);
|
HandlebarsRender(::handlebars::RenderError) #[doc = "Handlebars rendering failed"];
|
||||||
HandlebarsTemplate(Box<::handlebars::TemplateError>);
|
HandlebarsTemplate(Box<::handlebars::TemplateError>) #[doc = "Unable to parse the template"];
|
||||||
Utf8(::std::string::FromUtf8Error);
|
Utf8(::std::string::FromUtf8Error) #[doc = "Invalid UTF-8"];
|
||||||
}
|
}
|
||||||
|
|
||||||
links {
|
links {
|
||||||
TomlQuery(::toml_query::error::Error, ::toml_query::error::ErrorKind);
|
TomlQuery(::toml_query::error::Error, ::toml_query::error::ErrorKind) #[doc = "A TomlQuery error"];
|
||||||
}
|
}
|
||||||
|
|
||||||
errors {
|
errors {
|
||||||
|
/// A subprocess exited with an unsuccessful return code.
|
||||||
Subprocess(message: String, output: ::std::process::Output) {
|
Subprocess(message: String, output: ::std::process::Output) {
|
||||||
description("A subprocess failed")
|
description("A subprocess failed")
|
||||||
display("{}: {}", message, String::from_utf8_lossy(&output.stdout))
|
display("{}: {}", message, String::from_utf8_lossy(&output.stdout))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An error was encountered while parsing the `SUMMARY.md` file.
|
||||||
ParseError(line: usize, col: usize, message: String) {
|
ParseError(line: usize, col: usize, message: String) {
|
||||||
description("A SUMMARY.md parsing error")
|
description("A SUMMARY.md parsing error")
|
||||||
display("Error at line {}, column {}: {}", line, col, message)
|
display("Error at line {}, column {}: {}", line, col, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The user tried to use a reserved filename.
|
||||||
ReservedFilenameError(filename: PathBuf) {
|
ReservedFilenameError(filename: PathBuf) {
|
||||||
description("Reserved Filename")
|
description("Reserved Filename")
|
||||||
display("{} is reserved for internal use", filename.display())
|
display("{} is reserved for internal use", filename.display())
|
||||||
|
|
|
@ -10,9 +10,12 @@ use book::{Book, BookItem};
|
||||||
|
|
||||||
const ESCAPE_CHAR: char = '\\';
|
const ESCAPE_CHAR: char = '\\';
|
||||||
|
|
||||||
|
/// A preprocessor for expanding the `{{# playpen}}` and `{{# include}}`
|
||||||
|
/// helpers in a chapter.
|
||||||
pub struct LinkPreprocessor;
|
pub struct LinkPreprocessor;
|
||||||
|
|
||||||
impl LinkPreprocessor {
|
impl LinkPreprocessor {
|
||||||
|
/// Create a new `LinkPreprocessor`.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
LinkPreprocessor
|
LinkPreprocessor
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Book preprocessing.
|
||||||
|
|
||||||
pub use self::links::LinkPreprocessor;
|
pub use self::links::LinkPreprocessor;
|
||||||
|
|
||||||
mod links;
|
mod links;
|
||||||
|
@ -8,18 +10,29 @@ use errors::*;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Extra information for a `Preprocessor` to give them more context when
|
||||||
|
/// processing a book.
|
||||||
pub struct PreprocessorContext {
|
pub struct PreprocessorContext {
|
||||||
|
/// The location of the book directory on disk.
|
||||||
pub root: PathBuf,
|
pub root: PathBuf,
|
||||||
|
/// The book configuration (`book.toml`).
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PreprocessorContext {
|
impl PreprocessorContext {
|
||||||
pub fn new(root: PathBuf, config: Config) -> Self {
|
/// Create a new `PreprocessorContext`.
|
||||||
|
pub(crate) fn new(root: PathBuf, config: Config) -> Self {
|
||||||
PreprocessorContext { root, config }
|
PreprocessorContext { root, config }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An operation which is run immediately after loading a book into memory and
|
||||||
|
/// before it gets rendered.
|
||||||
pub trait Preprocessor {
|
pub trait Preprocessor {
|
||||||
|
/// Get the `Preprocessor`'s name.
|
||||||
fn name(&self) -> &str;
|
fn name(&self) -> &str;
|
||||||
|
|
||||||
|
/// Run this `Preprocessor`, allowing it to update the book before it is
|
||||||
|
/// given to a renderer.
|
||||||
fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()>;
|
fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()>;
|
||||||
}
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
#![allow(missing_docs)] // FIXME: Document this
|
||||||
|
|
||||||
pub use self::hbs_renderer::HtmlHandlebars;
|
pub use self::hbs_renderer::HtmlHandlebars;
|
||||||
|
|
||||||
mod hbs_renderer;
|
mod hbs_renderer;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#![allow(missing_docs)] // FIXME: Document this
|
||||||
pub mod playpen_editor;
|
pub mod playpen_editor;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#![allow(missing_docs)] // FIXME: Document this
|
||||||
|
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
mod string;
|
mod string;
|
||||||
use errors::Error;
|
use errors::Error;
|
||||||
|
|
Loading…
Reference in New Issue