diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bd3c7558..bfd114ca 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,7 +31,8 @@ jobs: rust: stable - build: msrv os: ubuntu-latest - rust: 1.46.0 + # sync MSRV with docs: guide/src/guide/installation.md + rust: 1.54.0 steps: - uses: actions/checkout@master - name: Install Rust @@ -48,4 +49,4 @@ jobs: - uses: actions/checkout@master - name: Install Rust run: rustup update stable && rustup default stable && rustup component add rustfmt - - run: cargo fmt -- --check + - run: cargo fmt --check diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b3b6a2d..87d281c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # 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 [5eb7d46...68a5c09](https://github.com/rust-lang/mdBook/compare/5eb7d46...68a5c09) diff --git a/Cargo.lock b/Cargo.lock index b85c0ce1..2428b308 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,17 +185,27 @@ dependencies = [ [[package]] name = "clap" -version = "2.33.3" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "7a30c3bf9ff12dfe5dae53f0a96e0febcd18420d1c0e7fad77796d9d5c4b5375" dependencies = [ - "ansi_term", "atty", "bitflags", + "indexmap", + "lazy_static", + "os_str_bytes", "strsim", + "termcolor", "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]] @@ -830,13 +840,14 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "mdbook" -version = "0.4.15" +version = "0.4.17" dependencies = [ "ammonia", "anyhow", "assert_cmd", "chrono", "clap", + "clap_complete", "elasticlunr-rs", "env_logger", "futures-util", @@ -1053,6 +1064,15 @@ dependencies = [ "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]] name = "output_vt100" version = "0.1.2" @@ -1258,9 +1278,9 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd16514d1af5f7a71f909a44ef253cdb712a376d7ebc8ae4a471a9be9743548" +checksum = "34f197a544b0c9ab3ae46c359a7ec9cbbb5c7bf97054266fecb7ead794a181d6" dependencies = [ "bitflags", "memchr", @@ -1390,9 +1410,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" dependencies = [ "aho-corasick", "memchr", @@ -1454,21 +1474,9 @@ dependencies = [ [[package]] name = "semver" -version = "0.11.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -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", -] +checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" [[package]] name = "serde" @@ -1590,9 +1598,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" @@ -1659,12 +1667,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.11.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" [[package]] name = "time" @@ -1872,12 +1877,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" -[[package]] -name = "unicode-width" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" - [[package]] name = "unicode-xid" version = "0.2.2" @@ -1902,12 +1901,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.3" diff --git a/Cargo.toml b/Cargo.toml index eef90de7..15f0e5ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mdbook" -version = "0.4.15" +version = "0.4.17" authors = [ "Mathieu David ", "Michael-F-Bryan ", @@ -18,15 +18,16 @@ description = "Creates a book from markdown files" [dependencies] anyhow = "1.0.28" chrono = "0.4" -clap = "2.24" +clap = { version = "3.0", features = ["cargo"] } +clap_complete = "3.0" env_logger = "0.7.1" handlebars = "4.0" lazy_static = "1.0" log = "0.4" memchr = "2.0" opener = "0.5" -pulldown-cmark = { version = "0.9", default-features = false } -regex = "1.0.0" +pulldown-cmark = { version = "0.9.1", default-features = false } +regex = "1.5.5" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" @@ -52,7 +53,7 @@ ammonia = { version = "3", optional = true } assert_cmd = "1" predicates = "2" select = "0.5" -semver = "0.11.0" +semver = "1.0" pretty_assertions = "0.6" walkdir = "2.0" diff --git a/examples/nop-preprocessor.rs b/examples/nop-preprocessor.rs index 486fd86d..ace40093 100644 --- a/examples/nop-preprocessor.rs +++ b/examples/nop-preprocessor.rs @@ -1,5 +1,5 @@ use crate::nop_lib::Nop; -use clap::{App, Arg, ArgMatches, SubCommand}; +use clap::{App, Arg, ArgMatches}; use mdbook::book::Book; use mdbook::errors::Error; use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext}; @@ -7,12 +7,12 @@ use semver::{Version, VersionReq}; use std::io; use std::process; -pub fn make_app() -> App<'static, 'static> { +pub fn make_app() -> App<'static> { App::new("nop-preprocessor") .about("A mdbook preprocessor which does precisely nothing") .subcommand( - SubCommand::with_name("supports") - .arg(Arg::with_name("renderer").required(true)) + App::new("supports") + .arg(Arg::new("renderer").required(true)) .about("Check whether a renderer is supported by this preprocessor"), ) } diff --git a/guide/src/continuous-integration.md b/guide/src/continuous-integration.md index 3503445f..0196200c 100644 --- a/guide/src/continuous-integration.md +++ b/guide/src/continuous-integration.md @@ -21,7 +21,7 @@ A simple approach would be to use the popular `curl` CLI tool to download the ex ```sh 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 ``` diff --git a/guide/src/for_developers/preprocessors.md b/guide/src/for_developers/preprocessors.md index 0054f242..1ac46256 100644 --- a/guide/src/for_developers/preprocessors.md +++ b/guide/src/for_developers/preprocessors.md @@ -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, you'll probably want to process the input into something more computer-friendly. The [`pulldown-cmark`][pc] crate implements a production-quality event-based -Markdown parser, with the [`pulldown-cmark-to-cmark`][pctc] allowing you to +Markdown parser, with the [`pulldown-cmark-to-cmark`][pctc] crate allowing you to translate events back into markdown text. The following code block shows how to remove all emphasis from markdown, diff --git a/guide/src/format/configuration/renderers.md b/guide/src/format/configuration/renderers.md index a29265e4..f1d5ee15 100644 --- a/guide/src/format/configuration/renderers.md +++ b/guide/src/format/configuration/renderers.md @@ -173,10 +173,12 @@ By default, mdBook will include an icon on the top right of the book (which look ```toml [output.html.print] 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 rendered. Defaults to `true`. +- **page-break** Insert page breaks between chapters. Defaults to `true`. ### `[output.html.fold]` @@ -205,6 +207,7 @@ editable = false # allows editing the source code copyable = true # include the copy button for copying code snippets copy-js = true # includes the JavaScript for the code editor 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`. @@ -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. 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`. +- **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/ diff --git a/guide/src/format/mdbook.md b/guide/src/format/mdbook.md index e4c76f1b..62e89843 100644 --- a/guide/src/format/mdbook.md +++ b/guide/src/format/mdbook.md @@ -41,7 +41,7 @@ println!("Hello, World!"); 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 ```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 Additional attributes can be included in Rust code blocks with comma, space, or tab-separated terms just after the language term. For example: diff --git a/guide/src/guide/creating.md b/guide/src/guide/creating.md index 6e0df0a8..f68a8c60 100644 --- a/guide/src/guide/creating.md +++ b/guide/src/guide/creating.md @@ -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. 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 mdbook build @@ -106,4 +106,4 @@ mdbook build This will generate a directory named `book` which contains the HTML content of your book. You can then place this directory on any web server to host it. -For more information about publishing and deploying, check out the [Continuous Integration chapter](../continuous-integration.md) for more. +For more information about publishing and deploying, check out the [Continuous Integration chapter](../continuous-integration.md) for more. \ No newline at end of file diff --git a/guide/src/guide/installation.md b/guide/src/guide/installation.md index b68f807a..d7946587 100644 --- a/guide/src/guide/installation.md +++ b/guide/src/guide/installation.md @@ -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. 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: diff --git a/src/book/summary.rs b/src/book/summary.rs index 1ade05ec..4bb8e969 100644 --- a/src/book/summary.rs +++ b/src/book/summary.rs @@ -536,6 +536,10 @@ impl<'a> SummaryParser<'a> { // Skip a HTML element such as a comment line. Some(Event::Html(_)) => {} // Otherwise, no title. + Some(ev) => { + self.back(ev); + return None; + } _ => return None, } } @@ -647,6 +651,18 @@ mod tests { 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] fn parse_title_with_styling() { let src = "# My **Awesome** Summary"; diff --git a/src/cmd/build.rs b/src/cmd/build.rs index e9112f9b..dacf45cf 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -1,23 +1,29 @@ use crate::{get_book_dir, open}; -use clap::{App, ArgMatches, SubCommand}; +use clap::{arg, App, Arg, ArgMatches}; use mdbook::errors::Result; use mdbook::MDBook; use std::path::Path; // Create clap subcommand arguments -pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { - SubCommand::with_name("build") +pub fn make_subcommand<'help>() -> App<'help> { + App::new("build") .about("Builds a book from its markdown files") - .arg_from_usage( - "-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\ - Relative paths are interpreted relative to the book's root directory.{n}\ - If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'", + .arg( + Arg::new("dest-dir") + .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_from_usage( - "[dir] 'Root directory for the book{n}\ - (Defaults to the Current Directory when omitted)'", - ) - .arg_from_usage("-o, --open 'Opens the compiled book in a web browser'") + .arg(arg!([dir] + "Root directory for the book{n}\ + (Defaults to the Current Directory when omitted)" + )) + .arg(arg!(-o --open "Opens the compiled book in a web browser")) } // Build command implementation diff --git a/src/cmd/clean.rs b/src/cmd/clean.rs index b58f937e..0569726e 100644 --- a/src/cmd/clean.rs +++ b/src/cmd/clean.rs @@ -1,23 +1,28 @@ use crate::get_book_dir; use anyhow::Context; -use clap::{App, ArgMatches, SubCommand}; +use clap::{arg, App, Arg, ArgMatches}; use mdbook::MDBook; use std::fs; // Create clap subcommand arguments -pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { - SubCommand::with_name("clean") +pub fn make_subcommand<'help>() -> App<'help> { + App::new("clean") .about("Deletes a built book") - .arg_from_usage( - "-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\ - Relative paths are interpreted relative to the book's root directory.{n}\ - Running this command deletes this directory.{n}\ - If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'", - ) - .arg_from_usage( - "[dir] 'Root directory for the book{n}\ - (Defaults to the Current Directory when omitted)'", + .arg( + Arg::new("dest-dir") + .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)" + )) } // Clean command implementation diff --git a/src/cmd/init.rs b/src/cmd/init.rs index ed0aa17d..1ee5ff21 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -1,5 +1,5 @@ use crate::get_book_dir; -use clap::{App, Arg, ArgMatches, SubCommand}; +use clap::{arg, App, Arg, ArgMatches}; use mdbook::config; use mdbook::errors::Result; use mdbook::MDBook; @@ -8,25 +8,25 @@ use std::io::Write; use std::process::Command; // Create clap subcommand arguments -pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { - SubCommand::with_name("init") +pub fn make_subcommand<'help>() -> App<'help> { + App::new("init") .about("Creates the boilerplate structure and files for a new book") // the {n} denotes a newline which will properly aligned in all help messages - .arg_from_usage( - "[dir] 'Directory to create the book in{n}\ - (Defaults to the Current Directory when omitted)'", - ) - .arg_from_usage("--theme 'Copies the default theme into your source folder'") - .arg_from_usage("--force 'Skips confirmation prompts'") + .arg(arg!([dir] + "Directory to create the book in{n}\ + (Defaults to the Current Directory when omitted)" + )) + .arg(arg!(--theme "Copies the default theme into your source folder")) + .arg(arg!(--force "Skips confirmation prompts")) .arg( - Arg::with_name("title") + Arg::new("title") .long("title") .takes_value(true) .help("Sets the book title") .required(false), ) .arg( - Arg::with_name("ignore") + Arg::new("ignore") .long("ignore") .takes_value(true) .possible_values(&["none", "git"]) diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index c5394f8a..bafbfd52 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -1,7 +1,7 @@ #[cfg(feature = "watch")] use super::watch; 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::StreamExt; use mdbook::errors::*; @@ -18,37 +18,43 @@ use warp::Filter; const LIVE_RELOAD_ENDPOINT: &str = "__livereload"; // Create clap subcommand arguments -pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { - SubCommand::with_name("serve") +pub fn make_subcommand<'help>() -> App<'help> { + App::new("serve") .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::with_name("hostname") - .short("n") + Arg::new("dest-dir") + .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") .takes_value(true) .default_value("localhost") - .empty_values(false) + .forbid_empty_values(true) .help("Hostname to listen on for HTTP connections"), ) .arg( - Arg::with_name("port") - .short("p") + Arg::new("port") + .short('p') .long("port") .takes_value(true) .default_value("3000") - .empty_values(false) + .forbid_empty_values(true) .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 @@ -62,11 +68,10 @@ pub fn execute(args: &ArgMatches) -> Result<()> { let address = format!("{}:{}", hostname, port); - let livereload_url = format!("ws://{}/{}", address, LIVE_RELOAD_ENDPOINT); let update_config = |book: &mut MDBook| { book.config - .set("output.html.livereload-url", &livereload_url) - .expect("livereload-url update failed"); + .set("output.html.live-reload-endpoint", &LIVE_RELOAD_ENDPOINT) + .expect("live-reload-endpoint update failed"); if let Some(dest_dir) = args.value_of("dest-dir") { book.config.build.build_dir = dest_dir.into(); } diff --git a/src/cmd/test.rs b/src/cmd/test.rs index f6d97aa6..02f982a4 100644 --- a/src/cmd/test.rs +++ b/src/cmd/test.rs @@ -1,29 +1,37 @@ use crate::get_book_dir; -use clap::{App, Arg, ArgMatches, SubCommand}; +use clap::{arg, App, Arg, ArgMatches}; use mdbook::errors::Result; use mdbook::MDBook; // Create clap subcommand arguments -pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { - SubCommand::with_name("test") +pub fn make_subcommand<'help>() -> App<'help> { + App::new("test") .about("Tests that a book's Rust code samples compile") - .arg_from_usage( - "-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\ - Relative paths are interpreted relative to the book's root directory.{n}\ - If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'", + .arg( + Arg::new("dest-dir") + .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_from_usage( - "[dir] 'Root directory for the book{n}\ - (Defaults to the Current Directory when omitted)'", - ) - .arg(Arg::with_name("library-path") - .short("L") + .arg(arg!([dir] + "Root directory for the book{n}\ + (Defaults to the Current Directory when omitted)" + )) + .arg(Arg::new("library-path") + .short('L') .long("library-path") .value_name("dir") .takes_value(true) + .use_delimiter(true) .require_delimiter(true) - .multiple(true) - .empty_values(false) + .multiple_values(true) + .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")) } diff --git a/src/cmd/watch.rs b/src/cmd/watch.rs index b27516b0..78ae1968 100644 --- a/src/cmd/watch.rs +++ b/src/cmd/watch.rs @@ -1,5 +1,5 @@ use crate::{get_book_dir, open}; -use clap::{App, ArgMatches, SubCommand}; +use clap::{arg, App, Arg, ArgMatches}; use mdbook::errors::Result; use mdbook::utils; use mdbook::MDBook; @@ -10,19 +10,25 @@ use std::thread::sleep; use std::time::Duration; // Create clap subcommand arguments -pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { - SubCommand::with_name("watch") +pub fn make_subcommand<'help>() -> App<'help> { + App::new("watch") .about("Watches a book's files and rebuilds it on changes") - .arg_from_usage( - "-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\ - Relative paths are interpreted relative to the book's root directory.{n}\ - If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'", + .arg( + Arg::new("dest-dir") + .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_from_usage( - "[dir] 'Root directory for the book{n}\ - (Defaults to the Current Directory when omitted)'", - ) - .arg_from_usage("-o, --open 'Open the compiled book in a web browser'") + .arg(arg!([dir] + "Root directory for the book{n}\ + (Defaults to the Current Directory when omitted)" + )) + .arg(arg!(-o --open "Opens the compiled book in a web browser")) } // Watch command implementation diff --git a/src/config.rs b/src/config.rs index bf4aabbb..951957bd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -533,14 +533,14 @@ pub struct HtmlConfig { /// directly jumping to editing the currently viewed page. /// Contains {path} that is replaced with chapter source file path pub edit_url_template: Option, - /// 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 - /// `mdbook serve` command needs a way to let the HTML renderer know where - /// to point livereloading at, if it has been enabled. + /// Endpoint of websocket, for livereload usage. Value loaded from .toml file + /// is ignored, because our code overrides this field with the value [`LIVE_RELOAD_ENDPOINT`] + /// + /// [`LIVE_RELOAD_ENDPOINT`]: cmd::serve::LIVE_RELOAD_ENDPOINT /// /// This config item *should not be edited* by the end user. #[doc(hidden)] - pub livereload_url: Option, + pub live_reload_endpoint: Option, /// The mapping from old pages to new pages/URLs to use when generating /// redirects. pub redirect: HashMap, @@ -569,7 +569,7 @@ impl Default for HtmlConfig { input_404: None, site_url: None, cname: None, - livereload_url: None, + live_reload_endpoint: None, redirect: HashMap::new(), } } @@ -588,15 +588,20 @@ impl HtmlConfig { /// Configuration for how to render the print icon, print.html, and print.css. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] +#[serde(default, rename_all = "kebab-case")] pub struct Print { /// Whether print support is enabled. pub enable: bool, + /// Insert page breaks between chapters. Default: `true`. + pub page_break: bool, } impl Default for Print { fn default() -> Self { - Self { enable: true } + Self { + enable: true, + page_break: true, + } } } @@ -625,6 +630,8 @@ pub struct Playground { pub copy_js: bool, /// Display line numbers on playground snippets. Default: `false`. pub line_numbers: bool, + /// Display the run button. Default: `true` + pub runnable: bool, } impl Default for Playground { @@ -634,6 +641,7 @@ impl Default for Playground { copyable: true, copy_js: true, line_numbers: false, + runnable: true, } } } @@ -776,6 +784,7 @@ mod tests { copyable: true, copy_js: true, line_numbers: false, + runnable: true, }; let html_should_be = HtmlConfig { curly_quotes: true, @@ -806,6 +815,22 @@ mod tests { 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] fn edition_2015() { let src = r#" @@ -1150,4 +1175,24 @@ mod tests { 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); + } } diff --git a/src/main.rs b/src/main.rs index 1f286d2d..35562e64 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,8 @@ extern crate log; use anyhow::anyhow; 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 log::LevelFilter; use mdbook::utils; @@ -25,25 +26,31 @@ fn main() { // Check which subcomamnd the user ran... let res = match app.get_matches().subcommand() { - ("init", Some(sub_matches)) => cmd::init::execute(sub_matches), - ("build", Some(sub_matches)) => cmd::build::execute(sub_matches), - ("clean", Some(sub_matches)) => cmd::clean::execute(sub_matches), + Some(("init", sub_matches)) => cmd::init::execute(sub_matches), + Some(("build", sub_matches)) => cmd::build::execute(sub_matches), + Some(("clean", sub_matches)) => cmd::clean::execute(sub_matches), #[cfg(feature = "watch")] - ("watch", Some(sub_matches)) => cmd::watch::execute(sub_matches), + Some(("watch", sub_matches)) => cmd::watch::execute(sub_matches), #[cfg(feature = "serve")] - ("serve", Some(sub_matches)) => cmd::serve::execute(sub_matches), - ("test", Some(sub_matches)) => cmd::test::execute(sub_matches), - ("completions", Some(sub_matches)) => (|| { + Some(("serve", sub_matches)) => cmd::serve::execute(sub_matches), + Some(("test", sub_matches)) => cmd::test::execute(sub_matches), + Some(("completions", sub_matches)) => (|| { let shell: Shell = sub_matches .value_of("shell") .ok_or_else(|| anyhow!("Shell name missing."))? .parse() .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(()) })(), - (_, _) => unreachable!(), + _ => unreachable!(), }; if let Err(e) = res { @@ -54,14 +61,13 @@ fn main() { } /// 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!()) .about(crate_description!()) .author("Mathieu David ") .version(VERSION) - .setting(AppSettings::GlobalVersion) + .setting(AppSettings::PropagateVersion) .setting(AppSettings::ArgRequiredElseHelp) - .setting(AppSettings::ColoredHelp) .after_help( "For more information about a specific command, try `mdbook --help`\n\ 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::clean::make_subcommand()) .subcommand( - SubCommand::with_name("completions") + App::new("completions") .about("Generate shell completions for your shell to stdout") .arg( - Arg::with_name("shell") + Arg::new("shell") .takes_value(true) - .possible_values(&Shell::variants()) + .possible_values(Shell::possible_values()) .help("the shell to generate completions for") .value_name("SHELL") .required(true), @@ -137,3 +143,8 @@ fn open>(path: P) { error!("Error opening web browser: {}", e); } } + +#[test] +fn verify_app() { + create_clap_app().debug_assert(); +} diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index e8da5b4a..3d2d1afe 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -56,7 +56,7 @@ impl HtmlHandlebars { let fixed_content = 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 // 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 @@ -170,6 +170,13 @@ impl HtmlHandlebars { // 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("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 = @@ -606,8 +613,11 @@ fn make_data( if theme.favicon_svg.is_some() { data.insert("favicon_svg".to_owned(), json!("favicon.svg")); } - if let Some(ref livereload) = html_config.livereload_url { - data.insert("livereload".to_owned(), json!(livereload)); + if let Some(ref live_reload_endpoint) = html_config.live_reload_endpoint { + data.insert( + "live_reload_endpoint".to_owned(), + json!(live_reload_endpoint), + ); } let default_theme = match html_config.default_theme { @@ -768,16 +778,7 @@ fn insert_link_into_header( content: &str, id_counter: &mut HashMap, ) -> String { - let raw_id = utils::id_from_content(content); - - 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; + let id = utils::unique_id_from_content(content, id_counter); format!( r##"{text}"##, @@ -828,7 +829,8 @@ fn add_playground_pre( if classes.contains("language-rust") { if (!classes.contains("ignore") && !classes.contains("noplayground") - && !classes.contains("noplaypen")) + && !classes.contains("noplaypen") + && playground_config.runnable) || classes.contains("mdbook-runnable") { let contains_e2015 = classes.contains("edition2015"); diff --git a/src/renderer/html_handlebars/search.rs b/src/renderer/html_handlebars/search.rs index 39b59800..5dd063da 100644 --- a/src/renderer/html_handlebars/search.rs +++ b/src/renderer/html_handlebars/search.rs @@ -97,6 +97,7 @@ fn render_item( breadcrumbs.push(chapter.name.clone()); + let mut id_counter = HashMap::new(); while let Some(event) = p.next() { match event { 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 => { 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()); } Event::Start(Tag::FootnoteDefinition(name)) => { diff --git a/src/theme/index.hbs b/src/theme/index.hbs index 966eedbc..18d984a2 100644 --- a/src/theme/index.hbs +++ b/src/theme/index.hbs @@ -219,10 +219,12 @@ - {{#if livereload}} + {{#if live_reload_endpoint}}