Merge branch 'draft-no-index' of https://github.com/joshrotenberg/mdBook into draft-no-index
This commit is contained in:
commit
8357811d96
|
@ -31,7 +31,8 @@ jobs:
|
||||||
rust: stable
|
rust: stable
|
||||||
- build: msrv
|
- build: msrv
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
rust: 1.46.0
|
# sync MSRV with docs: guide/src/guide/installation.md
|
||||||
|
rust: 1.54.0
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
|
@ -48,4 +49,4 @@ jobs:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
run: rustup update stable && rustup default stable && rustup component add rustfmt
|
run: rustup update stable && rustup default stable && rustup component add rustfmt
|
||||||
- run: cargo fmt -- --check
|
- run: cargo fmt --check
|
||||||
|
|
35
CHANGELOG.md
35
CHANGELOG.md
|
@ -1,5 +1,40 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## mdBook 0.4.17
|
||||||
|
[a5fddfa...981b79b](https://github.com/rust-lang/mdBook/compare/a5fddfa...981b79b)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed parsing of `output.html.print` configuration table.
|
||||||
|
[#1775](https://github.com/rust-lang/mdBook/pull/1775)
|
||||||
|
|
||||||
|
## mdBook 0.4.16
|
||||||
|
[68a5c09...a5fddfa](https://github.com/rust-lang/mdBook/compare/68a5c09...a5fddfa)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added `output.html.print.page-break` config option to control whether or not
|
||||||
|
there is a page break between chapters in the print output.
|
||||||
|
[#1728](https://github.com/rust-lang/mdBook/pull/1728)
|
||||||
|
- Added `output.html.playground.runnable` config option to globally disable
|
||||||
|
the run button in code blocks.
|
||||||
|
[#1546](https://github.com/rust-lang/mdBook/pull/1546)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- The `cargo serve` live reload websocket now uses the protocol, host, and
|
||||||
|
port of the current page, allowing access through a proxy.
|
||||||
|
[#1771](https://github.com/rust-lang/mdBook/pull/1771)
|
||||||
|
- The 404 not-found page now includes the books title in the HTML title tag.
|
||||||
|
[#1693](https://github.com/rust-lang/mdBook/pull/1693)
|
||||||
|
- Migrated to clap 3.0 which which handles CLI option parsing.
|
||||||
|
[#1731](https://github.com/rust-lang/mdBook/pull/1731)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Minor fixes to the markdown parser.
|
||||||
|
[#1729](https://github.com/rust-lang/mdBook/pull/1729)
|
||||||
|
- Fixed incorrect parsing in `SUMMARY.md` when it didn't start with a title.
|
||||||
|
[#1744](https://github.com/rust-lang/mdBook/pull/1744)
|
||||||
|
- Fixed duplicate anchor IDs for links in search results.
|
||||||
|
[#1749](https://github.com/rust-lang/mdBook/pull/1749)
|
||||||
|
|
||||||
## mdBook 0.4.15
|
## mdBook 0.4.15
|
||||||
[5eb7d46...68a5c09](https://github.com/rust-lang/mdBook/compare/5eb7d46...68a5c09)
|
[5eb7d46...68a5c09](https://github.com/rust-lang/mdBook/compare/5eb7d46...68a5c09)
|
||||||
|
|
||||||
|
|
|
@ -185,17 +185,27 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "2.33.3"
|
version = "3.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
|
checksum = "7a30c3bf9ff12dfe5dae53f0a96e0febcd18420d1c0e7fad77796d9d5c4b5375"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
|
||||||
"atty",
|
"atty",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
"indexmap",
|
||||||
|
"lazy_static",
|
||||||
|
"os_str_bytes",
|
||||||
"strsim",
|
"strsim",
|
||||||
|
"termcolor",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
"unicode-width",
|
]
|
||||||
"vec_map",
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_complete"
|
||||||
|
version = "3.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d044e9db8cd0f68191becdeb5246b7462e4cf0c069b19ae00d1bf3fa9889498d"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -830,13 +840,14 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mdbook"
|
name = "mdbook"
|
||||||
version = "0.4.15"
|
version = "0.4.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ammonia",
|
"ammonia",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
"clap_complete",
|
||||||
"elasticlunr-rs",
|
"elasticlunr-rs",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
@ -1053,6 +1064,15 @@ dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_str_bytes"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "output_vt100"
|
name = "output_vt100"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
@ -1258,9 +1278,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pulldown-cmark"
|
name = "pulldown-cmark"
|
||||||
version = "0.9.0"
|
version = "0.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "acd16514d1af5f7a71f909a44ef253cdb712a376d7ebc8ae4a471a9be9743548"
|
checksum = "34f197a544b0c9ab3ae46c359a7ec9cbbb5c7bf97054266fecb7ead794a181d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -1390,9 +1410,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.5.4"
|
version = "1.5.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -1454,21 +1474,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "0.11.0"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
|
checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
|
||||||
dependencies = [
|
|
||||||
"semver-parser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "semver-parser"
|
|
||||||
version = "0.10.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
|
|
||||||
dependencies = [
|
|
||||||
"pest",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
|
@ -1590,9 +1598,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.8.0"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum"
|
name = "strum"
|
||||||
|
@ -1659,12 +1667,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "textwrap"
|
name = "textwrap"
|
||||||
version = "0.11.0"
|
version = "0.14.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
|
||||||
dependencies = [
|
|
||||||
"unicode-width",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
|
@ -1872,12 +1877,6 @@ version = "1.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
|
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-width"
|
|
||||||
version = "0.1.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
@ -1902,12 +1901,6 @@ version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vec_map"
|
|
||||||
version = "0.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "mdbook"
|
name = "mdbook"
|
||||||
version = "0.4.15"
|
version = "0.4.17"
|
||||||
authors = [
|
authors = [
|
||||||
"Mathieu David <mathieudavid@mathieudavid.org>",
|
"Mathieu David <mathieudavid@mathieudavid.org>",
|
||||||
"Michael-F-Bryan <michaelfbryan@gmail.com>",
|
"Michael-F-Bryan <michaelfbryan@gmail.com>",
|
||||||
|
@ -18,15 +18,16 @@ description = "Creates a book from markdown files"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.28"
|
anyhow = "1.0.28"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
clap = "2.24"
|
clap = { version = "3.0", features = ["cargo"] }
|
||||||
|
clap_complete = "3.0"
|
||||||
env_logger = "0.7.1"
|
env_logger = "0.7.1"
|
||||||
handlebars = "4.0"
|
handlebars = "4.0"
|
||||||
lazy_static = "1.0"
|
lazy_static = "1.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
memchr = "2.0"
|
memchr = "2.0"
|
||||||
opener = "0.5"
|
opener = "0.5"
|
||||||
pulldown-cmark = { version = "0.9", default-features = false }
|
pulldown-cmark = { version = "0.9.1", default-features = false }
|
||||||
regex = "1.0.0"
|
regex = "1.5.5"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
@ -52,7 +53,7 @@ ammonia = { version = "3", optional = true }
|
||||||
assert_cmd = "1"
|
assert_cmd = "1"
|
||||||
predicates = "2"
|
predicates = "2"
|
||||||
select = "0.5"
|
select = "0.5"
|
||||||
semver = "0.11.0"
|
semver = "1.0"
|
||||||
pretty_assertions = "0.6"
|
pretty_assertions = "0.6"
|
||||||
walkdir = "2.0"
|
walkdir = "2.0"
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::nop_lib::Nop;
|
use crate::nop_lib::Nop;
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches};
|
||||||
use mdbook::book::Book;
|
use mdbook::book::Book;
|
||||||
use mdbook::errors::Error;
|
use mdbook::errors::Error;
|
||||||
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
|
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
|
||||||
|
@ -7,12 +7,12 @@ use semver::{Version, VersionReq};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::process;
|
use std::process;
|
||||||
|
|
||||||
pub fn make_app() -> App<'static, 'static> {
|
pub fn make_app() -> App<'static> {
|
||||||
App::new("nop-preprocessor")
|
App::new("nop-preprocessor")
|
||||||
.about("A mdbook preprocessor which does precisely nothing")
|
.about("A mdbook preprocessor which does precisely nothing")
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("supports")
|
App::new("supports")
|
||||||
.arg(Arg::with_name("renderer").required(true))
|
.arg(Arg::new("renderer").required(true))
|
||||||
.about("Check whether a renderer is supported by this preprocessor"),
|
.about("Check whether a renderer is supported by this preprocessor"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ A simple approach would be to use the popular `curl` CLI tool to download the ex
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
mkdir bin
|
mkdir bin
|
||||||
curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.15/mdbook-v0.4.15-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
|
curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.17/mdbook-v0.4.17-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
|
||||||
bin/mdbook build
|
bin/mdbook build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ 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,
|
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.
|
you'll probably want to process the input into something more computer-friendly.
|
||||||
The [`pulldown-cmark`][pc] crate implements a production-quality event-based
|
The [`pulldown-cmark`][pc] crate implements a production-quality event-based
|
||||||
Markdown parser, with the [`pulldown-cmark-to-cmark`][pctc] allowing you to
|
Markdown parser, with the [`pulldown-cmark-to-cmark`][pctc] crate allowing you to
|
||||||
translate events back into markdown text.
|
translate events back into markdown text.
|
||||||
|
|
||||||
The following code block shows how to remove all emphasis from markdown,
|
The following code block shows how to remove all emphasis from markdown,
|
||||||
|
|
|
@ -173,10 +173,12 @@ By default, mdBook will include an icon on the top right of the book (which look
|
||||||
```toml
|
```toml
|
||||||
[output.html.print]
|
[output.html.print]
|
||||||
enable = true # include support for printable output
|
enable = true # include support for printable output
|
||||||
|
page-break = true # insert page-break after each chapter
|
||||||
```
|
```
|
||||||
|
|
||||||
- **enable:** Enable print support. When `false`, all print support will not be
|
- **enable:** Enable print support. When `false`, all print support will not be
|
||||||
rendered. Defaults to `true`.
|
rendered. Defaults to `true`.
|
||||||
|
- **page-break** Insert page breaks between chapters. Defaults to `true`.
|
||||||
|
|
||||||
### `[output.html.fold]`
|
### `[output.html.fold]`
|
||||||
|
|
||||||
|
@ -205,6 +207,7 @@ editable = false # allows editing the source code
|
||||||
copyable = true # include the copy button for copying code snippets
|
copyable = true # include the copy button for copying code snippets
|
||||||
copy-js = true # includes the JavaScript for the code editor
|
copy-js = true # includes the JavaScript for the code editor
|
||||||
line-numbers = false # displays line numbers for editable code
|
line-numbers = false # displays line numbers for editable code
|
||||||
|
runnable = true # displays a run button for rust code
|
||||||
```
|
```
|
||||||
|
|
||||||
- **editable:** Allow editing the source code. Defaults to `false`.
|
- **editable:** Allow editing the source code. Defaults to `false`.
|
||||||
|
@ -212,6 +215,7 @@ line-numbers = false # displays line numbers for editable code
|
||||||
- **copy-js:** Copy JavaScript files for the editor to the output directory.
|
- **copy-js:** Copy JavaScript files for the editor to the output directory.
|
||||||
Defaults to `true`.
|
Defaults to `true`.
|
||||||
- **line-numbers** Display line numbers on editable sections of code. Requires both `editable` and `copy-js` to be `true`. Defaults to `false`.
|
- **line-numbers** Display line numbers on editable sections of code. Requires both `editable` and `copy-js` to be `true`. Defaults to `false`.
|
||||||
|
- **runnable** Displays a run button for rust code snippets. Changing this to `false` will disable the run in playground feature globally. Defaults to `true`.
|
||||||
|
|
||||||
[Ace]: https://ace.c9.io/
|
[Ace]: https://ace.c9.io/
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ println!("Hello, World!");
|
||||||
|
|
||||||
If there is no `main` function, then the code is automatically wrapped inside one.
|
If there is no `main` function, then the code is automatically wrapped inside one.
|
||||||
|
|
||||||
If you wish to disable the play button, you can include the `noplayground` option on the code block like this:
|
If you wish to disable the play button for a code block, you can include the `noplayground` option on the code block like this:
|
||||||
|
|
||||||
~~~markdown
|
~~~markdown
|
||||||
```rust,noplayground
|
```rust,noplayground
|
||||||
|
@ -51,6 +51,13 @@ println!("Hello {}!", name);
|
||||||
```
|
```
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
Or, if you wish to disable the play button for all code blocks in your book, you can write the config to the `book.toml` like this.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[output.html.playground]
|
||||||
|
runnable = false
|
||||||
|
```
|
||||||
|
|
||||||
## Rust code block attributes
|
## Rust code block attributes
|
||||||
|
|
||||||
Additional attributes can be included in Rust code blocks with comma, space, or tab-separated terms just after the language term. For example:
|
Additional attributes can be included in Rust code blocks with comma, space, or tab-separated terms just after the language term. For example:
|
||||||
|
|
|
@ -97,7 +97,7 @@ So if you have images or other static files, just include them somewhere in the
|
||||||
|
|
||||||
Once you've written your book, you may want to host it somewhere for others to view.
|
Once you've written your book, you may want to host it somewhere for others to view.
|
||||||
The first step is to build the output of the book.
|
The first step is to build the output of the book.
|
||||||
This can be done with the `mbdook build` command in the same directory where the `book.toml` file is located:
|
This can be done with the `mdbook build` command in the same directory where the `book.toml` file is located:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
mdbook build
|
mdbook build
|
||||||
|
|
|
@ -20,7 +20,7 @@ To make it easier to run, put the path to the binary into your `PATH`.
|
||||||
|
|
||||||
To build the `mdbook` executable from source, you will first need to install Rust and Cargo.
|
To build the `mdbook` executable from source, you will first need to install Rust and Cargo.
|
||||||
Follow the instructions on the [Rust installation page].
|
Follow the instructions on the [Rust installation page].
|
||||||
mdBook currently requires at least Rust version 1.46.
|
mdBook currently requires at least Rust version 1.54.
|
||||||
|
|
||||||
Once you have installed Rust, the following command can be used to build and install mdBook:
|
Once you have installed Rust, the following command can be used to build and install mdBook:
|
||||||
|
|
||||||
|
|
|
@ -536,6 +536,10 @@ impl<'a> SummaryParser<'a> {
|
||||||
// Skip a HTML element such as a comment line.
|
// Skip a HTML element such as a comment line.
|
||||||
Some(Event::Html(_)) => {}
|
Some(Event::Html(_)) => {}
|
||||||
// Otherwise, no title.
|
// Otherwise, no title.
|
||||||
|
Some(ev) => {
|
||||||
|
self.back(ev);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -647,6 +651,18 @@ mod tests {
|
||||||
assert_eq!(got, should_be);
|
assert_eq!(got, should_be);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_initial_title() {
|
||||||
|
let src = "[Link]()";
|
||||||
|
let mut parser = SummaryParser::new(src);
|
||||||
|
|
||||||
|
assert!(parser.parse_title().is_none());
|
||||||
|
assert!(matches!(
|
||||||
|
parser.next_event(),
|
||||||
|
Some(Event::Start(Tag::Paragraph))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_title_with_styling() {
|
fn parse_title_with_styling() {
|
||||||
let src = "# My **Awesome** Summary";
|
let src = "# My **Awesome** Summary";
|
||||||
|
|
|
@ -1,23 +1,29 @@
|
||||||
use crate::{get_book_dir, open};
|
use crate::{get_book_dir, open};
|
||||||
use clap::{App, ArgMatches, SubCommand};
|
use clap::{arg, App, Arg, ArgMatches};
|
||||||
use mdbook::errors::Result;
|
use mdbook::errors::Result;
|
||||||
use mdbook::MDBook;
|
use mdbook::MDBook;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
// Create clap subcommand arguments
|
// Create clap subcommand arguments
|
||||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
pub fn make_subcommand<'help>() -> App<'help> {
|
||||||
SubCommand::with_name("build")
|
App::new("build")
|
||||||
.about("Builds a book from its markdown files")
|
.about("Builds a book from its markdown files")
|
||||||
.arg_from_usage(
|
.arg(
|
||||||
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
|
Arg::new("dest-dir")
|
||||||
Relative paths are interpreted relative to the book's root directory.{n}\
|
.short('d')
|
||||||
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
|
.long("dest-dir")
|
||||||
|
.value_name("dest-dir")
|
||||||
|
.help(
|
||||||
|
"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`.",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.arg_from_usage(
|
.arg(arg!([dir]
|
||||||
"[dir] 'Root directory for the book{n}\
|
"Root directory for the book{n}\
|
||||||
(Defaults to the Current Directory when omitted)'",
|
(Defaults to the Current Directory when omitted)"
|
||||||
)
|
))
|
||||||
.arg_from_usage("-o, --open 'Opens the compiled book in a web browser'")
|
.arg(arg!(-o --open "Opens the compiled book in a web browser"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build command implementation
|
// Build command implementation
|
||||||
|
|
|
@ -1,23 +1,28 @@
|
||||||
use crate::get_book_dir;
|
use crate::get_book_dir;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::{App, ArgMatches, SubCommand};
|
use clap::{arg, App, Arg, ArgMatches};
|
||||||
use mdbook::MDBook;
|
use mdbook::MDBook;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
// Create clap subcommand arguments
|
// Create clap subcommand arguments
|
||||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
pub fn make_subcommand<'help>() -> App<'help> {
|
||||||
SubCommand::with_name("clean")
|
App::new("clean")
|
||||||
.about("Deletes a built book")
|
.about("Deletes a built book")
|
||||||
.arg_from_usage(
|
.arg(
|
||||||
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
|
Arg::new("dest-dir")
|
||||||
Relative paths are interpreted relative to the book's root directory.{n}\
|
.short('d')
|
||||||
Running this command deletes this directory.{n}\
|
.long("dest-dir")
|
||||||
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
|
.value_name("dest-dir")
|
||||||
)
|
.help(
|
||||||
.arg_from_usage(
|
"Output directory for the book{n}\
|
||||||
"[dir] 'Root directory for the book{n}\
|
Relative paths are interpreted relative to the book's root directory.{n}\
|
||||||
(Defaults to the Current Directory when omitted)'",
|
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
.arg(arg!([dir]
|
||||||
|
"Root directory for the book{n}\
|
||||||
|
(Defaults to the Current Directory when omitted)"
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean command implementation
|
// Clean command implementation
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::get_book_dir;
|
use crate::get_book_dir;
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{arg, App, Arg, ArgMatches};
|
||||||
use mdbook::config;
|
use mdbook::config;
|
||||||
use mdbook::errors::Result;
|
use mdbook::errors::Result;
|
||||||
use mdbook::MDBook;
|
use mdbook::MDBook;
|
||||||
|
@ -8,25 +8,25 @@ use std::io::Write;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
// Create clap subcommand arguments
|
// Create clap subcommand arguments
|
||||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
pub fn make_subcommand<'help>() -> App<'help> {
|
||||||
SubCommand::with_name("init")
|
App::new("init")
|
||||||
.about("Creates the boilerplate structure and files for a new book")
|
.about("Creates the boilerplate structure and files for a new book")
|
||||||
// the {n} denotes a newline which will properly aligned in all help messages
|
// the {n} denotes a newline which will properly aligned in all help messages
|
||||||
.arg_from_usage(
|
.arg(arg!([dir]
|
||||||
"[dir] 'Directory to create the book in{n}\
|
"Directory to create the book in{n}\
|
||||||
(Defaults to the Current Directory when omitted)'",
|
(Defaults to the Current Directory when omitted)"
|
||||||
)
|
))
|
||||||
.arg_from_usage("--theme 'Copies the default theme into your source folder'")
|
.arg(arg!(--theme "Copies the default theme into your source folder"))
|
||||||
.arg_from_usage("--force 'Skips confirmation prompts'")
|
.arg(arg!(--force "Skips confirmation prompts"))
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("title")
|
Arg::new("title")
|
||||||
.long("title")
|
.long("title")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("Sets the book title")
|
.help("Sets the book title")
|
||||||
.required(false),
|
.required(false),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("ignore")
|
Arg::new("ignore")
|
||||||
.long("ignore")
|
.long("ignore")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.possible_values(&["none", "git"])
|
.possible_values(&["none", "git"])
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#[cfg(feature = "watch")]
|
#[cfg(feature = "watch")]
|
||||||
use super::watch;
|
use super::watch;
|
||||||
use crate::{get_book_dir, open};
|
use crate::{get_book_dir, open};
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{arg, App, Arg, ArgMatches};
|
||||||
use futures_util::sink::SinkExt;
|
use futures_util::sink::SinkExt;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use mdbook::errors::*;
|
use mdbook::errors::*;
|
||||||
|
@ -18,37 +18,43 @@ use warp::Filter;
|
||||||
const LIVE_RELOAD_ENDPOINT: &str = "__livereload";
|
const LIVE_RELOAD_ENDPOINT: &str = "__livereload";
|
||||||
|
|
||||||
// Create clap subcommand arguments
|
// Create clap subcommand arguments
|
||||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
pub fn make_subcommand<'help>() -> App<'help> {
|
||||||
SubCommand::with_name("serve")
|
App::new("serve")
|
||||||
.about("Serves a book at http://localhost:3000, and rebuilds it on changes")
|
.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`.'",
|
|
||||||
)
|
|
||||||
.arg_from_usage(
|
|
||||||
"[dir] 'Root directory for the book{n}\
|
|
||||||
(Defaults to the Current Directory when omitted)'",
|
|
||||||
)
|
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("hostname")
|
Arg::new("dest-dir")
|
||||||
.short("n")
|
.short('d')
|
||||||
|
.long("dest-dir")
|
||||||
|
.value_name("dest-dir")
|
||||||
|
.help(
|
||||||
|
"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`.",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.arg(arg!([dir]
|
||||||
|
"Root directory for the book{n}\
|
||||||
|
(Defaults to the Current Directory when omitted)"
|
||||||
|
))
|
||||||
|
.arg(
|
||||||
|
Arg::new("hostname")
|
||||||
|
.short('n')
|
||||||
.long("hostname")
|
.long("hostname")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.default_value("localhost")
|
.default_value("localhost")
|
||||||
.empty_values(false)
|
.forbid_empty_values(true)
|
||||||
.help("Hostname to listen on for HTTP connections"),
|
.help("Hostname to listen on for HTTP connections"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("port")
|
Arg::new("port")
|
||||||
.short("p")
|
.short('p')
|
||||||
.long("port")
|
.long("port")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.default_value("3000")
|
.default_value("3000")
|
||||||
.empty_values(false)
|
.forbid_empty_values(true)
|
||||||
.help("Port to use for HTTP connections"),
|
.help("Port to use for HTTP connections"),
|
||||||
)
|
)
|
||||||
.arg_from_usage("-o, --open 'Opens the book server in a web browser'")
|
.arg(arg!(-o --open "Opens the compiled book in a web browser"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve command implementation
|
// Serve command implementation
|
||||||
|
@ -62,11 +68,10 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||||
|
|
||||||
let address = format!("{}:{}", hostname, port);
|
let address = format!("{}:{}", hostname, port);
|
||||||
|
|
||||||
let livereload_url = format!("ws://{}/{}", address, LIVE_RELOAD_ENDPOINT);
|
|
||||||
let update_config = |book: &mut MDBook| {
|
let update_config = |book: &mut MDBook| {
|
||||||
book.config
|
book.config
|
||||||
.set("output.html.livereload-url", &livereload_url)
|
.set("output.html.live-reload-endpoint", &LIVE_RELOAD_ENDPOINT)
|
||||||
.expect("livereload-url update failed");
|
.expect("live-reload-endpoint update failed");
|
||||||
if let Some(dest_dir) = args.value_of("dest-dir") {
|
if let Some(dest_dir) = args.value_of("dest-dir") {
|
||||||
book.config.build.build_dir = dest_dir.into();
|
book.config.build.build_dir = dest_dir.into();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,37 @@
|
||||||
use crate::get_book_dir;
|
use crate::get_book_dir;
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{arg, App, Arg, ArgMatches};
|
||||||
use mdbook::errors::Result;
|
use mdbook::errors::Result;
|
||||||
use mdbook::MDBook;
|
use mdbook::MDBook;
|
||||||
|
|
||||||
// Create clap subcommand arguments
|
// Create clap subcommand arguments
|
||||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
pub fn make_subcommand<'help>() -> App<'help> {
|
||||||
SubCommand::with_name("test")
|
App::new("test")
|
||||||
.about("Tests that a book's Rust code samples compile")
|
.about("Tests that a book's Rust code samples compile")
|
||||||
.arg_from_usage(
|
.arg(
|
||||||
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
|
Arg::new("dest-dir")
|
||||||
Relative paths are interpreted relative to the book's root directory.{n}\
|
.short('d')
|
||||||
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
|
.long("dest-dir")
|
||||||
|
.value_name("dest-dir")
|
||||||
|
.help(
|
||||||
|
"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`.",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.arg_from_usage(
|
.arg(arg!([dir]
|
||||||
"[dir] 'Root directory for the book{n}\
|
"Root directory for the book{n}\
|
||||||
(Defaults to the Current Directory when omitted)'",
|
(Defaults to the Current Directory when omitted)"
|
||||||
)
|
))
|
||||||
.arg(Arg::with_name("library-path")
|
.arg(Arg::new("library-path")
|
||||||
.short("L")
|
.short('L')
|
||||||
.long("library-path")
|
.long("library-path")
|
||||||
.value_name("dir")
|
.value_name("dir")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
|
.use_delimiter(true)
|
||||||
.require_delimiter(true)
|
.require_delimiter(true)
|
||||||
.multiple(true)
|
.multiple_values(true)
|
||||||
.empty_values(false)
|
.multiple_occurrences(true)
|
||||||
|
.forbid_empty_values(true)
|
||||||
.help("A comma-separated list of directories to add to {n}the crate search path when building tests"))
|
.help("A comma-separated list of directories to add to {n}the crate search path when building tests"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{get_book_dir, open};
|
use crate::{get_book_dir, open};
|
||||||
use clap::{App, ArgMatches, SubCommand};
|
use clap::{arg, App, Arg, ArgMatches};
|
||||||
use mdbook::errors::Result;
|
use mdbook::errors::Result;
|
||||||
use mdbook::utils;
|
use mdbook::utils;
|
||||||
use mdbook::MDBook;
|
use mdbook::MDBook;
|
||||||
|
@ -10,19 +10,25 @@ use std::thread::sleep;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
// Create clap subcommand arguments
|
// Create clap subcommand arguments
|
||||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
pub fn make_subcommand<'help>() -> App<'help> {
|
||||||
SubCommand::with_name("watch")
|
App::new("watch")
|
||||||
.about("Watches a book's files and rebuilds it on changes")
|
.about("Watches a book's files and rebuilds it on changes")
|
||||||
.arg_from_usage(
|
.arg(
|
||||||
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
|
Arg::new("dest-dir")
|
||||||
Relative paths are interpreted relative to the book's root directory.{n}\
|
.short('d')
|
||||||
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
|
.long("dest-dir")
|
||||||
|
.value_name("dest-dir")
|
||||||
|
.help(
|
||||||
|
"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`.",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.arg_from_usage(
|
.arg(arg!([dir]
|
||||||
"[dir] 'Root directory for the book{n}\
|
"Root directory for the book{n}\
|
||||||
(Defaults to the Current Directory when omitted)'",
|
(Defaults to the Current Directory when omitted)"
|
||||||
)
|
))
|
||||||
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
|
.arg(arg!(-o --open "Opens the compiled book in a web browser"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch command implementation
|
// Watch command implementation
|
||||||
|
|
|
@ -533,14 +533,14 @@ pub struct HtmlConfig {
|
||||||
/// directly jumping to editing the currently viewed page.
|
/// directly jumping to editing the currently viewed page.
|
||||||
/// Contains {path} that is replaced with chapter source file path
|
/// Contains {path} that is replaced with chapter source file path
|
||||||
pub edit_url_template: Option<String>,
|
pub edit_url_template: Option<String>,
|
||||||
/// This is used as a bit of a workaround for the `mdbook serve` command.
|
/// Endpoint of websocket, for livereload usage. Value loaded from .toml file
|
||||||
/// Basically, because you set the websocket port from the command line, the
|
/// is ignored, because our code overrides this field with the value [`LIVE_RELOAD_ENDPOINT`]
|
||||||
/// `mdbook serve` command needs a way to let the HTML renderer know where
|
///
|
||||||
/// to point livereloading at, if it has been enabled.
|
/// [`LIVE_RELOAD_ENDPOINT`]: cmd::serve::LIVE_RELOAD_ENDPOINT
|
||||||
///
|
///
|
||||||
/// 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 live_reload_endpoint: Option<String>,
|
||||||
/// The mapping from old pages to new pages/URLs to use when generating
|
/// The mapping from old pages to new pages/URLs to use when generating
|
||||||
/// redirects.
|
/// redirects.
|
||||||
pub redirect: HashMap<String, String>,
|
pub redirect: HashMap<String, String>,
|
||||||
|
@ -569,7 +569,7 @@ impl Default for HtmlConfig {
|
||||||
input_404: None,
|
input_404: None,
|
||||||
site_url: None,
|
site_url: None,
|
||||||
cname: None,
|
cname: None,
|
||||||
livereload_url: None,
|
live_reload_endpoint: None,
|
||||||
redirect: HashMap::new(),
|
redirect: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -588,15 +588,20 @@ impl HtmlConfig {
|
||||||
|
|
||||||
/// Configuration for how to render the print icon, print.html, and print.css.
|
/// Configuration for how to render the print icon, print.html, and print.css.
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(default, rename_all = "kebab-case")]
|
||||||
pub struct Print {
|
pub struct Print {
|
||||||
/// Whether print support is enabled.
|
/// Whether print support is enabled.
|
||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
|
/// Insert page breaks between chapters. Default: `true`.
|
||||||
|
pub page_break: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Print {
|
impl Default for Print {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { enable: true }
|
Self {
|
||||||
|
enable: true,
|
||||||
|
page_break: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -625,6 +630,8 @@ pub struct Playground {
|
||||||
pub copy_js: bool,
|
pub copy_js: bool,
|
||||||
/// Display line numbers on playground snippets. Default: `false`.
|
/// Display line numbers on playground snippets. Default: `false`.
|
||||||
pub line_numbers: bool,
|
pub line_numbers: bool,
|
||||||
|
/// Display the run button. Default: `true`
|
||||||
|
pub runnable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Playground {
|
impl Default for Playground {
|
||||||
|
@ -634,6 +641,7 @@ impl Default for Playground {
|
||||||
copyable: true,
|
copyable: true,
|
||||||
copy_js: true,
|
copy_js: true,
|
||||||
line_numbers: false,
|
line_numbers: false,
|
||||||
|
runnable: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -776,6 +784,7 @@ mod tests {
|
||||||
copyable: true,
|
copyable: true,
|
||||||
copy_js: true,
|
copy_js: true,
|
||||||
line_numbers: false,
|
line_numbers: false,
|
||||||
|
runnable: true,
|
||||||
};
|
};
|
||||||
let html_should_be = HtmlConfig {
|
let html_should_be = HtmlConfig {
|
||||||
curly_quotes: true,
|
curly_quotes: true,
|
||||||
|
@ -806,6 +815,22 @@ mod tests {
|
||||||
assert_eq!(got.html_config().unwrap(), html_should_be);
|
assert_eq!(got.html_config().unwrap(), html_should_be);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn disable_runnable() {
|
||||||
|
let src = r#"
|
||||||
|
[book]
|
||||||
|
title = "Some Book"
|
||||||
|
description = "book book book"
|
||||||
|
authors = ["Shogo Takata"]
|
||||||
|
|
||||||
|
[output.html.playground]
|
||||||
|
runnable = false
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let got = Config::from_str(src).unwrap();
|
||||||
|
assert_eq!(got.html_config().unwrap().playground.runnable, false);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn edition_2015() {
|
fn edition_2015() {
|
||||||
let src = r#"
|
let src = r#"
|
||||||
|
@ -1150,4 +1175,24 @@ mod tests {
|
||||||
|
|
||||||
Config::from_str(src).unwrap();
|
Config::from_str(src).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn print_config() {
|
||||||
|
let src = r#"
|
||||||
|
[output.html.print]
|
||||||
|
enable = false
|
||||||
|
"#;
|
||||||
|
let got = Config::from_str(src).unwrap();
|
||||||
|
let html_config = got.html_config().unwrap();
|
||||||
|
assert_eq!(html_config.print.enable, false);
|
||||||
|
assert_eq!(html_config.print.page_break, true);
|
||||||
|
let src = r#"
|
||||||
|
[output.html.print]
|
||||||
|
page-break = false
|
||||||
|
"#;
|
||||||
|
let got = Config::from_str(src).unwrap();
|
||||||
|
let html_config = got.html_config().unwrap();
|
||||||
|
assert_eq!(html_config.print.enable, true);
|
||||||
|
assert_eq!(html_config.print.page_break, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
43
src/main.rs
43
src/main.rs
|
@ -5,7 +5,8 @@ extern crate log;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use clap::{App, AppSettings, Arg, ArgMatches, Shell, SubCommand};
|
use clap::{App, AppSettings, Arg, ArgMatches};
|
||||||
|
use clap_complete::Shell;
|
||||||
use env_logger::Builder;
|
use env_logger::Builder;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use mdbook::utils;
|
use mdbook::utils;
|
||||||
|
@ -25,25 +26,31 @@ fn main() {
|
||||||
|
|
||||||
// Check which subcomamnd the user ran...
|
// Check which subcomamnd the user ran...
|
||||||
let res = match app.get_matches().subcommand() {
|
let res = match app.get_matches().subcommand() {
|
||||||
("init", Some(sub_matches)) => cmd::init::execute(sub_matches),
|
Some(("init", sub_matches)) => cmd::init::execute(sub_matches),
|
||||||
("build", Some(sub_matches)) => cmd::build::execute(sub_matches),
|
Some(("build", sub_matches)) => cmd::build::execute(sub_matches),
|
||||||
("clean", Some(sub_matches)) => cmd::clean::execute(sub_matches),
|
Some(("clean", sub_matches)) => cmd::clean::execute(sub_matches),
|
||||||
#[cfg(feature = "watch")]
|
#[cfg(feature = "watch")]
|
||||||
("watch", Some(sub_matches)) => cmd::watch::execute(sub_matches),
|
Some(("watch", sub_matches)) => cmd::watch::execute(sub_matches),
|
||||||
#[cfg(feature = "serve")]
|
#[cfg(feature = "serve")]
|
||||||
("serve", Some(sub_matches)) => cmd::serve::execute(sub_matches),
|
Some(("serve", sub_matches)) => cmd::serve::execute(sub_matches),
|
||||||
("test", Some(sub_matches)) => cmd::test::execute(sub_matches),
|
Some(("test", sub_matches)) => cmd::test::execute(sub_matches),
|
||||||
("completions", Some(sub_matches)) => (|| {
|
Some(("completions", sub_matches)) => (|| {
|
||||||
let shell: Shell = sub_matches
|
let shell: Shell = sub_matches
|
||||||
.value_of("shell")
|
.value_of("shell")
|
||||||
.ok_or_else(|| anyhow!("Shell name missing."))?
|
.ok_or_else(|| anyhow!("Shell name missing."))?
|
||||||
.parse()
|
.parse()
|
||||||
.map_err(|s| anyhow!("Invalid shell: {}", s))?;
|
.map_err(|s| anyhow!("Invalid shell: {}", s))?;
|
||||||
|
|
||||||
create_clap_app().gen_completions_to("mdbook", shell, &mut std::io::stdout().lock());
|
let mut complete_app = create_clap_app();
|
||||||
|
clap_complete::generate(
|
||||||
|
shell,
|
||||||
|
&mut complete_app,
|
||||||
|
"mdbook",
|
||||||
|
&mut std::io::stdout().lock(),
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
})(),
|
})(),
|
||||||
(_, _) => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
|
@ -54,14 +61,13 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a list of valid arguments and sub-commands
|
/// Create a list of valid arguments and sub-commands
|
||||||
fn create_clap_app<'a, 'b>() -> App<'a, 'b> {
|
fn create_clap_app() -> App<'static> {
|
||||||
let app = App::new(crate_name!())
|
let app = App::new(crate_name!())
|
||||||
.about(crate_description!())
|
.about(crate_description!())
|
||||||
.author("Mathieu David <mathieudavid@mathieudavid.org>")
|
.author("Mathieu David <mathieudavid@mathieudavid.org>")
|
||||||
.version(VERSION)
|
.version(VERSION)
|
||||||
.setting(AppSettings::GlobalVersion)
|
.setting(AppSettings::PropagateVersion)
|
||||||
.setting(AppSettings::ArgRequiredElseHelp)
|
.setting(AppSettings::ArgRequiredElseHelp)
|
||||||
.setting(AppSettings::ColoredHelp)
|
|
||||||
.after_help(
|
.after_help(
|
||||||
"For more information about a specific command, try `mdbook <command> --help`\n\
|
"For more information about a specific command, try `mdbook <command> --help`\n\
|
||||||
The source code for mdBook is available at: https://github.com/rust-lang/mdBook",
|
The source code for mdBook is available at: https://github.com/rust-lang/mdBook",
|
||||||
|
@ -71,12 +77,12 @@ fn create_clap_app<'a, 'b>() -> App<'a, 'b> {
|
||||||
.subcommand(cmd::test::make_subcommand())
|
.subcommand(cmd::test::make_subcommand())
|
||||||
.subcommand(cmd::clean::make_subcommand())
|
.subcommand(cmd::clean::make_subcommand())
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("completions")
|
App::new("completions")
|
||||||
.about("Generate shell completions for your shell to stdout")
|
.about("Generate shell completions for your shell to stdout")
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("shell")
|
Arg::new("shell")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.possible_values(&Shell::variants())
|
.possible_values(Shell::possible_values())
|
||||||
.help("the shell to generate completions for")
|
.help("the shell to generate completions for")
|
||||||
.value_name("SHELL")
|
.value_name("SHELL")
|
||||||
.required(true),
|
.required(true),
|
||||||
|
@ -137,3 +143,8 @@ fn open<P: AsRef<OsStr>>(path: P) {
|
||||||
error!("Error opening web browser: {}", e);
|
error!("Error opening web browser: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_app() {
|
||||||
|
create_clap_app().debug_assert();
|
||||||
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ impl HtmlHandlebars {
|
||||||
|
|
||||||
let fixed_content =
|
let fixed_content =
|
||||||
utils::render_markdown_with_path(&ch.content, ctx.html_config.curly_quotes, Some(path));
|
utils::render_markdown_with_path(&ch.content, ctx.html_config.curly_quotes, Some(path));
|
||||||
if !ctx.is_index {
|
if !ctx.is_index && ctx.html_config.print.page_break {
|
||||||
// Add page break between chapters
|
// Add page break between chapters
|
||||||
// See https://developer.mozilla.org/en-US/docs/Web/CSS/break-before and https://developer.mozilla.org/en-US/docs/Web/CSS/page-break-before
|
// See https://developer.mozilla.org/en-US/docs/Web/CSS/break-before and https://developer.mozilla.org/en-US/docs/Web/CSS/page-break-before
|
||||||
// Add both two CSS properties because of the compatibility issue
|
// Add both two CSS properties because of the compatibility issue
|
||||||
|
@ -170,6 +170,13 @@ impl HtmlHandlebars {
|
||||||
// Set a dummy path to ensure other paths (e.g. in the TOC) are generated correctly
|
// Set a dummy path to ensure other paths (e.g. in the TOC) are generated correctly
|
||||||
data_404.insert("path".to_owned(), json!("404.md"));
|
data_404.insert("path".to_owned(), json!("404.md"));
|
||||||
data_404.insert("content".to_owned(), json!(html_content_404));
|
data_404.insert("content".to_owned(), json!(html_content_404));
|
||||||
|
|
||||||
|
let mut title = String::from("Page not found");
|
||||||
|
if let Some(book_title) = &ctx.config.book.title {
|
||||||
|
title.push_str(" - ");
|
||||||
|
title.push_str(book_title);
|
||||||
|
}
|
||||||
|
data_404.insert("title".to_owned(), json!(title));
|
||||||
let rendered = handlebars.render("index", &data_404)?;
|
let rendered = handlebars.render("index", &data_404)?;
|
||||||
|
|
||||||
let rendered =
|
let rendered =
|
||||||
|
@ -606,8 +613,11 @@ fn make_data(
|
||||||
if theme.favicon_svg.is_some() {
|
if theme.favicon_svg.is_some() {
|
||||||
data.insert("favicon_svg".to_owned(), json!("favicon.svg"));
|
data.insert("favicon_svg".to_owned(), json!("favicon.svg"));
|
||||||
}
|
}
|
||||||
if let Some(ref livereload) = html_config.livereload_url {
|
if let Some(ref live_reload_endpoint) = html_config.live_reload_endpoint {
|
||||||
data.insert("livereload".to_owned(), json!(livereload));
|
data.insert(
|
||||||
|
"live_reload_endpoint".to_owned(),
|
||||||
|
json!(live_reload_endpoint),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let default_theme = match html_config.default_theme {
|
let default_theme = match html_config.default_theme {
|
||||||
|
@ -768,16 +778,7 @@ fn insert_link_into_header(
|
||||||
content: &str,
|
content: &str,
|
||||||
id_counter: &mut HashMap<String, usize>,
|
id_counter: &mut HashMap<String, usize>,
|
||||||
) -> String {
|
) -> String {
|
||||||
let raw_id = utils::id_from_content(content);
|
let id = utils::unique_id_from_content(content, id_counter);
|
||||||
|
|
||||||
let id_count = id_counter.entry(raw_id.clone()).or_insert(0);
|
|
||||||
|
|
||||||
let id = match *id_count {
|
|
||||||
0 => raw_id,
|
|
||||||
other => format!("{}-{}", raw_id, other),
|
|
||||||
};
|
|
||||||
|
|
||||||
*id_count += 1;
|
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
r##"<h{level} id="{id}"><a class="header" href="#{id}">{text}</a></h{level}>"##,
|
r##"<h{level} id="{id}"><a class="header" href="#{id}">{text}</a></h{level}>"##,
|
||||||
|
@ -828,7 +829,8 @@ fn add_playground_pre(
|
||||||
if classes.contains("language-rust") {
|
if classes.contains("language-rust") {
|
||||||
if (!classes.contains("ignore")
|
if (!classes.contains("ignore")
|
||||||
&& !classes.contains("noplayground")
|
&& !classes.contains("noplayground")
|
||||||
&& !classes.contains("noplaypen"))
|
&& !classes.contains("noplaypen")
|
||||||
|
&& playground_config.runnable)
|
||||||
|| classes.contains("mdbook-runnable")
|
|| classes.contains("mdbook-runnable")
|
||||||
{
|
{
|
||||||
let contains_e2015 = classes.contains("edition2015");
|
let contains_e2015 = classes.contains("edition2015");
|
||||||
|
|
|
@ -97,6 +97,7 @@ fn render_item(
|
||||||
|
|
||||||
breadcrumbs.push(chapter.name.clone());
|
breadcrumbs.push(chapter.name.clone());
|
||||||
|
|
||||||
|
let mut id_counter = HashMap::new();
|
||||||
while let Some(event) = p.next() {
|
while let Some(event) = p.next() {
|
||||||
match event {
|
match event {
|
||||||
Event::Start(Tag::Heading(i, ..)) if i as u32 <= max_section_depth => {
|
Event::Start(Tag::Heading(i, ..)) if i as u32 <= max_section_depth => {
|
||||||
|
@ -120,7 +121,7 @@ fn render_item(
|
||||||
}
|
}
|
||||||
Event::End(Tag::Heading(i, ..)) if i as u32 <= max_section_depth => {
|
Event::End(Tag::Heading(i, ..)) if i as u32 <= max_section_depth => {
|
||||||
in_heading = false;
|
in_heading = false;
|
||||||
section_id = Some(utils::id_from_content(&heading));
|
section_id = Some(utils::unique_id_from_content(&heading, &mut id_counter));
|
||||||
breadcrumbs.push(heading.clone());
|
breadcrumbs.push(heading.clone());
|
||||||
}
|
}
|
||||||
Event::Start(Tag::FootnoteDefinition(name)) => {
|
Event::Start(Tag::FootnoteDefinition(name)) => {
|
||||||
|
|
|
@ -219,10 +219,12 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if livereload}}
|
{{#if live_reload_endpoint}}
|
||||||
<!-- Livereload script (if served using the cli tool) -->
|
<!-- Livereload script (if served using the cli tool) -->
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var socket = new WebSocket("{{{livereload}}}");
|
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
|
const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
|
||||||
|
const socket = new WebSocket(wsAddress);
|
||||||
socket.onmessage = function (event) {
|
socket.onmessage = function (event) {
|
||||||
if (event.data === "reload") {
|
if (event.data === "reload") {
|
||||||
socket.close();
|
socket.close();
|
||||||
|
|
|
@ -9,6 +9,7 @@ use regex::Regex;
|
||||||
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag};
|
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
@ -44,6 +45,8 @@ pub fn normalize_id(content: &str) -> String {
|
||||||
|
|
||||||
/// Generate an ID for use with anchors which is derived from a "normalised"
|
/// Generate an ID for use with anchors which is derived from a "normalised"
|
||||||
/// string.
|
/// string.
|
||||||
|
// This function should be made private when the deprecation expires.
|
||||||
|
#[deprecated(since = "0.4.16", note = "use unique_id_from_content instead")]
|
||||||
pub fn id_from_content(content: &str) -> String {
|
pub fn id_from_content(content: &str) -> String {
|
||||||
let mut content = content.to_string();
|
let mut content = content.to_string();
|
||||||
|
|
||||||
|
@ -59,10 +62,30 @@ pub fn id_from_content(content: &str) -> String {
|
||||||
|
|
||||||
// Remove spaces and hashes indicating a header
|
// Remove spaces and hashes indicating a header
|
||||||
let trimmed = content.trim().trim_start_matches('#').trim();
|
let trimmed = content.trim().trim_start_matches('#').trim();
|
||||||
|
|
||||||
normalize_id(trimmed)
|
normalize_id(trimmed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate an ID for use with anchors which is derived from a "normalised"
|
||||||
|
/// string.
|
||||||
|
///
|
||||||
|
/// Each ID returned will be unique, if the same `id_counter` is provided on
|
||||||
|
/// each call.
|
||||||
|
pub fn unique_id_from_content(content: &str, id_counter: &mut HashMap<String, usize>) -> String {
|
||||||
|
let id = {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
id_from_content(content)
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we have headers with the same normalized id, append an incrementing counter
|
||||||
|
let id_count = id_counter.entry(id.clone()).or_insert(0);
|
||||||
|
let unique_id = match *id_count {
|
||||||
|
0 => id,
|
||||||
|
id_count => format!("{}-{}", id, id_count),
|
||||||
|
};
|
||||||
|
*id_count += 1;
|
||||||
|
unique_id
|
||||||
|
}
|
||||||
|
|
||||||
/// Fix links to the correct location.
|
/// Fix links to the correct location.
|
||||||
///
|
///
|
||||||
/// This adjusts links, such as turning `.md` extensions to `.html`.
|
/// This adjusts links, such as turning `.md` extensions to `.html`.
|
||||||
|
@ -332,8 +355,9 @@ more text with spaces
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod html_munging {
|
#[allow(deprecated)]
|
||||||
use super::super::{id_from_content, normalize_id};
|
mod id_from_content {
|
||||||
|
use super::super::id_from_content;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_generates_anchors() {
|
fn it_generates_anchors() {
|
||||||
|
@ -361,6 +385,10 @@ more text with spaces
|
||||||
);
|
);
|
||||||
assert_eq!(id_from_content("## Über"), "Über");
|
assert_eq!(id_from_content("## Über"), "Über");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod html_munging {
|
||||||
|
use super::super::{normalize_id, unique_id_from_content};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_normalizes_ids() {
|
fn it_normalizes_ids() {
|
||||||
|
@ -379,5 +407,28 @@ more text with spaces
|
||||||
assert_eq!(normalize_id("한국어"), "한국어");
|
assert_eq!(normalize_id("한국어"), "한국어");
|
||||||
assert_eq!(normalize_id(""), "");
|
assert_eq!(normalize_id(""), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_generates_unique_ids_from_content() {
|
||||||
|
// Same id if not given shared state
|
||||||
|
assert_eq!(
|
||||||
|
unique_id_from_content("## 中文標題 CJK title", &mut Default::default()),
|
||||||
|
"中文標題-cjk-title"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unique_id_from_content("## 中文標題 CJK title", &mut Default::default()),
|
||||||
|
"中文標題-cjk-title"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Different id if given shared state
|
||||||
|
let mut id_counter = Default::default();
|
||||||
|
assert_eq!(unique_id_from_content("## Über", &mut id_counter), "Über");
|
||||||
|
assert_eq!(
|
||||||
|
unique_id_from_content("## 中文標題 CJK title", &mut id_counter),
|
||||||
|
"中文標題-cjk-title"
|
||||||
|
);
|
||||||
|
assert_eq!(unique_id_from_content("## Über", &mut id_counter), "Über-1");
|
||||||
|
assert_eq!(unique_id_from_content("## Über", &mut id_counter), "Über-2");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
|
use crate::cli::cmd::mdbook_cmd;
|
||||||
use crate::dummy_book::DummyBook;
|
use crate::dummy_book::DummyBook;
|
||||||
|
|
||||||
use assert_cmd::Command;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mdbook_cli_dummy_book_generates_index_html() {
|
fn mdbook_cli_dummy_book_generates_index_html() {
|
||||||
let temp = DummyBook::new().build().unwrap();
|
let temp = DummyBook::new().build().unwrap();
|
||||||
|
@ -9,7 +8,7 @@ fn mdbook_cli_dummy_book_generates_index_html() {
|
||||||
// doesn't exist before
|
// doesn't exist before
|
||||||
assert!(!temp.path().join("book").exists());
|
assert!(!temp.path().join("book").exists());
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("mdbook").unwrap();
|
let mut cmd = mdbook_cmd();
|
||||||
cmd.arg("build").current_dir(temp.path());
|
cmd.arg("build").current_dir(temp.path());
|
||||||
cmd.assert()
|
cmd.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
use assert_cmd::Command;
|
||||||
|
|
||||||
|
pub(crate) fn mdbook_cmd() -> Command {
|
||||||
|
let mut cmd = Command::cargo_bin("mdbook").unwrap();
|
||||||
|
cmd.env_remove("RUST_LOG");
|
||||||
|
cmd
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
mod build;
|
mod build;
|
||||||
|
mod cmd;
|
||||||
mod test;
|
mod test;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
|
use crate::cli::cmd::mdbook_cmd;
|
||||||
use crate::dummy_book::DummyBook;
|
use crate::dummy_book::DummyBook;
|
||||||
|
|
||||||
use assert_cmd::Command;
|
|
||||||
use predicates::boolean::PredicateBooleanExt;
|
use predicates::boolean::PredicateBooleanExt;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mdbook_cli_can_correctly_test_a_passing_book() {
|
fn mdbook_cli_can_correctly_test_a_passing_book() {
|
||||||
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
|
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("mdbook").unwrap();
|
let mut cmd = mdbook_cmd();
|
||||||
cmd.arg("test").current_dir(temp.path());
|
cmd.arg("test").current_dir(temp.path());
|
||||||
cmd.assert().success()
|
cmd.assert().success()
|
||||||
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]README.md""##).unwrap())
|
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]README.md""##).unwrap())
|
||||||
|
@ -22,7 +22,7 @@ fn mdbook_cli_can_correctly_test_a_passing_book() {
|
||||||
fn mdbook_cli_detects_book_with_failing_tests() {
|
fn mdbook_cli_detects_book_with_failing_tests() {
|
||||||
let temp = DummyBook::new().with_passing_test(false).build().unwrap();
|
let temp = DummyBook::new().with_passing_test(false).build().unwrap();
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("mdbook").unwrap();
|
let mut cmd = mdbook_cmd();
|
||||||
cmd.arg("test").current_dir(temp.path());
|
cmd.arg("test").current_dir(temp.path());
|
||||||
cmd.assert().failure()
|
cmd.assert().failure()
|
||||||
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]README.md""##).unwrap())
|
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]README.md""##).unwrap())
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
- [Markdown](first/markdown.md)
|
- [Markdown](first/markdown.md)
|
||||||
- [Unicode](first/unicode.md)
|
- [Unicode](first/unicode.md)
|
||||||
- [No Headers](first/no-headers.md)
|
- [No Headers](first/no-headers.md)
|
||||||
|
- [Duplicate Headers](first/duplicate-headers.md)
|
||||||
- [Second Chapter](second.md)
|
- [Second Chapter](second.md)
|
||||||
- [Nested Chapter](second/nested.md)
|
- [Nested Chapter](second/nested.md)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Duplicate headers
|
||||||
|
|
||||||
|
This page validates behaviour of duplicate headers.
|
||||||
|
|
||||||
|
# Header Text
|
||||||
|
|
||||||
|
# Header Text
|
||||||
|
|
||||||
|
# header-text
|
|
@ -17,6 +17,7 @@ use std::ffi::OsStr;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
use std::str::FromStr;
|
||||||
use tempfile::Builder as TempFileBuilder;
|
use tempfile::Builder as TempFileBuilder;
|
||||||
use walkdir::{DirEntry, WalkDir};
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
|
@ -35,6 +36,7 @@ const TOC_SECOND_LEVEL: &[&str] = &[
|
||||||
"1.4. Markdown",
|
"1.4. Markdown",
|
||||||
"1.5. Unicode",
|
"1.5. Unicode",
|
||||||
"1.6. No Headers",
|
"1.6. No Headers",
|
||||||
|
"1.7. Duplicate Headers",
|
||||||
"2.1. Nested Chapter",
|
"2.1. Nested Chapter",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -150,6 +152,25 @@ fn rendered_code_has_playground_stuff() {
|
||||||
assert_contains_strings(book_js, &[".playground"]);
|
assert_contains_strings(book_js, &[".playground"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rendered_code_does_not_have_playground_stuff_in_html_when_disabled_in_config() {
|
||||||
|
let temp = DummyBook::new().build().unwrap();
|
||||||
|
let config = Config::from_str(
|
||||||
|
"
|
||||||
|
[output.html.playground]
|
||||||
|
runnable = false
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let md = MDBook::load_with_config(temp.path(), config).unwrap();
|
||||||
|
md.build().unwrap();
|
||||||
|
|
||||||
|
let nested = temp.path().join("book/first/nested.html");
|
||||||
|
let playground_class = vec![r#"class="playground""#];
|
||||||
|
|
||||||
|
assert_doesnt_contain_strings(nested, &playground_class);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn anchors_include_text_between_but_not_anchor_comments() {
|
fn anchors_include_text_between_but_not_anchor_comments() {
|
||||||
let temp = DummyBook::new().build().unwrap();
|
let temp = DummyBook::new().build().unwrap();
|
||||||
|
@ -633,11 +654,12 @@ mod search {
|
||||||
let some_section = get_doc_ref("first/index.html#some-section");
|
let some_section = get_doc_ref("first/index.html#some-section");
|
||||||
let summary = get_doc_ref("first/includes.html#summary");
|
let summary = get_doc_ref("first/includes.html#summary");
|
||||||
let no_headers = get_doc_ref("first/no-headers.html");
|
let no_headers = get_doc_ref("first/no-headers.html");
|
||||||
|
let duplicate_headers_1 = get_doc_ref("first/duplicate-headers.html#header-text-1");
|
||||||
let conclusion = get_doc_ref("conclusion.html#conclusion");
|
let conclusion = get_doc_ref("conclusion.html#conclusion");
|
||||||
|
|
||||||
let bodyidx = &index["index"]["index"]["body"]["root"];
|
let bodyidx = &index["index"]["index"]["body"]["root"];
|
||||||
let textidx = &bodyidx["t"]["e"]["x"]["t"];
|
let textidx = &bodyidx["t"]["e"]["x"]["t"];
|
||||||
assert_eq!(textidx["df"], 2);
|
assert_eq!(textidx["df"], 5);
|
||||||
assert_eq!(textidx["docs"][&first_chapter]["tf"], 1.0);
|
assert_eq!(textidx["docs"][&first_chapter]["tf"], 1.0);
|
||||||
assert_eq!(textidx["docs"][&introduction]["tf"], 1.0);
|
assert_eq!(textidx["docs"][&introduction]["tf"], 1.0);
|
||||||
|
|
||||||
|
@ -646,7 +668,7 @@ mod search {
|
||||||
assert_eq!(docs[&some_section]["body"], "");
|
assert_eq!(docs[&some_section]["body"], "");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
docs[&summary]["body"],
|
docs[&summary]["body"],
|
||||||
"Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode No Headers Second Chapter Nested Chapter Conclusion"
|
"Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode No Headers Duplicate Headers Second Chapter Nested Chapter Conclusion"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
docs[&summary]["breadcrumbs"],
|
docs[&summary]["breadcrumbs"],
|
||||||
|
@ -657,6 +679,10 @@ mod search {
|
||||||
docs[&no_headers]["breadcrumbs"],
|
docs[&no_headers]["breadcrumbs"],
|
||||||
"First Chapter » No Headers"
|
"First Chapter » No Headers"
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
docs[&duplicate_headers_1]["breadcrumbs"],
|
||||||
|
"First Chapter » Duplicate Headers » Header Text"
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
docs[&no_headers]["body"],
|
docs[&no_headers]["body"],
|
||||||
"Capybara capybara capybara. Capybara capybara capybara."
|
"Capybara capybara capybara. Capybara capybara capybara."
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue