Merge branch 'master' into add_option_remove_indentation
# Conflicts: # src/utils/string.rs
This commit is contained in:
commit
10e9cbb52b
|
@ -32,7 +32,7 @@ jobs:
|
||||||
- build: msrv
|
- build: msrv
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
# sync MSRV with docs: guide/src/guide/installation.md
|
# sync MSRV with docs: guide/src/guide/installation.md
|
||||||
rust: 1.54.0
|
rust: 1.56.0
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## mdBook 0.4.21
|
||||||
|
[92afe9b...8f01d02](https://github.com/rust-lang/mdBook/compare/92afe9b...8f01d02)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed an issue where mdBook would fail to compile with Rust nightly-2022-07-22.
|
||||||
|
[#1861](https://github.com/rust-lang/mdBook/pull/1861)
|
||||||
|
|
||||||
## mdBook 0.4.20
|
## mdBook 0.4.20
|
||||||
[53055e0...da166e0](https://github.com/rust-lang/mdBook/compare/53055e0...da166e0)
|
[53055e0...da166e0](https://github.com/rust-lang/mdBook/compare/53055e0...da166e0)
|
||||||
|
|
||||||
|
|
|
@ -796,7 +796,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mdbook"
|
name = "mdbook"
|
||||||
version = "0.4.20"
|
version = "0.4.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ammonia",
|
"ammonia",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
@ -809,10 +809,10 @@ dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"gitignore",
|
"gitignore",
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"memchr",
|
"memchr",
|
||||||
"notify",
|
"notify",
|
||||||
|
"once_cell",
|
||||||
"opener",
|
"opener",
|
||||||
"predicates",
|
"predicates",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
|
@ -997,6 +997,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -1,13 +1,13 @@
|
||||||
[package]
|
[package]
|
||||||
name = "mdbook"
|
name = "mdbook"
|
||||||
version = "0.4.20"
|
version = "0.4.21"
|
||||||
authors = [
|
authors = [
|
||||||
"Mathieu David <mathieudavid@mathieudavid.org>",
|
"Mathieu David <mathieudavid@mathieudavid.org>",
|
||||||
"Michael-F-Bryan <michaelfbryan@gmail.com>",
|
"Michael-F-Bryan <michaelfbryan@gmail.com>",
|
||||||
"Matt Ickstadt <mattico8@gmail.com>"
|
"Matt Ickstadt <mattico8@gmail.com>"
|
||||||
]
|
]
|
||||||
documentation = "http://rust-lang.github.io/mdBook/index.html"
|
documentation = "http://rust-lang.github.io/mdBook/index.html"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
exclude = ["/guide/*"]
|
exclude = ["/guide/*"]
|
||||||
keywords = ["book", "gitbook", "rustbook", "markdown"]
|
keywords = ["book", "gitbook", "rustbook", "markdown"]
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
|
@ -20,9 +20,9 @@ anyhow = "1.0.28"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
clap = { version = "3.0", features = ["cargo"] }
|
clap = { version = "3.0", features = ["cargo"] }
|
||||||
clap_complete = "3.0"
|
clap_complete = "3.0"
|
||||||
|
once_cell = "1"
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.9.0"
|
||||||
handlebars = "4.0"
|
handlebars = "4.0"
|
||||||
lazy_static = "1.0"
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
memchr = "2.0"
|
memchr = "2.0"
|
||||||
opener = "0.5"
|
opener = "0.5"
|
||||||
|
@ -65,3 +65,7 @@ search = ["elasticlunr-rs", "ammonia"]
|
||||||
[[bin]]
|
[[bin]]
|
||||||
doc = false
|
doc = false
|
||||||
name = "mdbook"
|
name = "mdbook"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "nop-preprocessor"
|
||||||
|
test = true
|
||||||
|
|
|
@ -101,4 +101,58 @@ mod nop_lib {
|
||||||
renderer != "not-supported"
|
renderer != "not-supported"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nop_preprocessor_run() {
|
||||||
|
let input_json = r##"[
|
||||||
|
{
|
||||||
|
"root": "/path/to/book",
|
||||||
|
"config": {
|
||||||
|
"book": {
|
||||||
|
"authors": ["AUTHOR"],
|
||||||
|
"language": "en",
|
||||||
|
"multilingual": false,
|
||||||
|
"src": "src",
|
||||||
|
"title": "TITLE"
|
||||||
|
},
|
||||||
|
"preprocessor": {
|
||||||
|
"nop": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"renderer": "html",
|
||||||
|
"mdbook_version": "0.4.21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"Chapter": {
|
||||||
|
"name": "Chapter 1",
|
||||||
|
"content": "# Chapter 1\n",
|
||||||
|
"number": [1],
|
||||||
|
"sub_items": [],
|
||||||
|
"path": "chapter_1.md",
|
||||||
|
"source_path": "chapter_1.md",
|
||||||
|
"parent_names": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"__non_exhaustive": null
|
||||||
|
}
|
||||||
|
]"##;
|
||||||
|
let input_json = input_json.as_bytes();
|
||||||
|
|
||||||
|
let (ctx, book) = mdbook::preprocess::CmdPreprocessor::parse_input(input_json).unwrap();
|
||||||
|
let expected_book = book.clone();
|
||||||
|
let result = Nop::new().run(&ctx, book);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
// The nop-preprocessor should not have made any changes to the book content.
|
||||||
|
let actual_book = result.unwrap();
|
||||||
|
assert_eq!(actual_book, expected_book);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,3 +61,8 @@ The `--dest-dir` (`-d`) option allows you to change the output directory for the
|
||||||
book. Relative paths are interpreted relative to the book's root directory. If
|
book. Relative paths are interpreted relative to the book's root directory. If
|
||||||
not specified it will default to the value of the `build.build-dir` key in
|
not specified it will default to the value of the `build.build-dir` key in
|
||||||
`book.toml`, or to `./book`.
|
`book.toml`, or to `./book`.
|
||||||
|
|
||||||
|
#### --chapter
|
||||||
|
|
||||||
|
The `--chapter` (`-c`) option allows you to test a specific chapter of the
|
||||||
|
book using the chapter name or the relative path to the chapter.
|
|
@ -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.20/mdbook-v0.4.20-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
|
curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.21/mdbook-v0.4.21-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
|
||||||
bin/mdbook build
|
bin/mdbook build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -157,7 +157,8 @@ The following configuration options are available:
|
||||||
Defaults to `404.md`.
|
Defaults to `404.md`.
|
||||||
- **site-url:** The url where the book will be hosted. This is required to ensure
|
- **site-url:** The url where the book will be hosted. This is required to ensure
|
||||||
navigation links and script/css imports in the 404 file work correctly, even when accessing
|
navigation links and script/css imports in the 404 file work correctly, even when accessing
|
||||||
urls in subdirectories. Defaults to `/`.
|
urls in subdirectories. Defaults to `/`. If `site-url` is set,
|
||||||
|
make sure to use document relative links for your assets, meaning they should not start with `/`.
|
||||||
- **cname:** The DNS subdomain or apex domain at which your book will be hosted.
|
- **cname:** The DNS subdomain or apex domain at which your book will be hosted.
|
||||||
This string will be written to a file named CNAME in the root of your site, as
|
This string will be written to a file named CNAME in the root of your site, as
|
||||||
required by GitHub Pages (see [*Managing a custom domain for your GitHub Pages
|
required by GitHub Pages (see [*Managing a custom domain for your GitHub Pages
|
||||||
|
|
|
@ -8,7 +8,7 @@ use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
|
||||||
use crate::config::BuildConfig;
|
use crate::config::BuildConfig;
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
use crate::utils::bracket_escape;
|
use crate::utils::bracket_escape;
|
||||||
|
use log::debug;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Load a book into memory from its `src/` directory.
|
/// Load a book into memory from its `src/` directory.
|
||||||
|
|
|
@ -6,6 +6,7 @@ use super::MDBook;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
use crate::theme;
|
use crate::theme;
|
||||||
|
use log::{debug, error, info, trace};
|
||||||
|
|
||||||
/// A helper for setting up a new book and its directory structure.
|
/// A helper for setting up a new book and its directory structure.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub use self::book::{load_book, Book, BookItem, BookItems, Chapter};
|
||||||
pub use self::init::BookBuilder;
|
pub use self::init::BookBuilder;
|
||||||
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
|
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
|
||||||
|
|
||||||
|
use log::{debug, error, info, log_enabled, trace, warn};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
@ -246,6 +247,13 @@ impl MDBook {
|
||||||
|
|
||||||
/// Run `rustdoc` tests on the book, linking against the provided libraries.
|
/// Run `rustdoc` tests on the book, linking against the provided libraries.
|
||||||
pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
|
pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
|
||||||
|
// test_chapter with chapter:None will run all tests.
|
||||||
|
self.test_chapter(library_paths, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run `rustdoc` tests on a specific chapter of the book, linking against the provided libraries.
|
||||||
|
/// If `chapter` is `None`, all tests will be run.
|
||||||
|
pub fn test_chapter(&mut self, library_paths: Vec<&str>, chapter: Option<&str>) -> Result<()> {
|
||||||
let library_args: Vec<&str> = (0..library_paths.len())
|
let library_args: Vec<&str> = (0..library_paths.len())
|
||||||
.map(|_| "-L")
|
.map(|_| "-L")
|
||||||
.zip(library_paths.into_iter())
|
.zip(library_paths.into_iter())
|
||||||
|
@ -254,6 +262,8 @@ impl MDBook {
|
||||||
|
|
||||||
let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
|
let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
|
||||||
|
|
||||||
|
let mut chapter_found = false;
|
||||||
|
|
||||||
// FIXME: Is "test" the proper renderer name to use here?
|
// FIXME: Is "test" the proper renderer name to use here?
|
||||||
let preprocess_context =
|
let preprocess_context =
|
||||||
PreprocessorContext::new(self.root.clone(), self.config.clone(), "test".to_string());
|
PreprocessorContext::new(self.root.clone(), self.config.clone(), "test".to_string());
|
||||||
|
@ -270,8 +280,16 @@ impl MDBook {
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = self.source_dir().join(&chapter_path);
|
if let Some(chapter) = chapter {
|
||||||
info!("Testing file: {:?}", path);
|
if ch.name != chapter && chapter_path.to_str() != Some(chapter) {
|
||||||
|
if chapter == "?" {
|
||||||
|
info!("Skipping chapter '{}'...", ch.name);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chapter_found = true;
|
||||||
|
info!("Testing chapter '{}': {:?}", ch.name, chapter_path);
|
||||||
|
|
||||||
// write preprocessed file to tempdir
|
// write preprocessed file to tempdir
|
||||||
let path = temp_dir.path().join(&chapter_path);
|
let path = temp_dir.path().join(&chapter_path);
|
||||||
|
@ -311,6 +329,11 @@ impl MDBook {
|
||||||
if failed {
|
if failed {
|
||||||
bail!("One or more tests failed");
|
bail!("One or more tests failed");
|
||||||
}
|
}
|
||||||
|
if let Some(chapter) = chapter {
|
||||||
|
if !chapter_found {
|
||||||
|
bail!("Chapter not found: {}", chapter);
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
|
use log::{debug, trace, warn};
|
||||||
use memchr::{self, Memchr};
|
use memchr::{self, Memchr};
|
||||||
use pulldown_cmark::{self, Event, HeadingLevel, Tag};
|
use pulldown_cmark::{self, Event, HeadingLevel, Tag};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
@ -89,8 +89,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||||
let input_404 = book
|
let input_404 = book
|
||||||
.config
|
.config
|
||||||
.get("output.html.input-404")
|
.get("output.html.input-404")
|
||||||
.map(toml::Value::as_str)
|
.and_then(toml::Value::as_str)
|
||||||
.and_then(std::convert::identity) // flatten
|
|
||||||
.map(ToString::to_string);
|
.map(ToString::to_string);
|
||||||
let file_404 = get_404_output_file(&input_404);
|
let file_404 = get_404_output_file(&input_404);
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,16 @@ pub fn make_subcommand<'help>() -> App<'help> {
|
||||||
Relative paths are interpreted relative to the book's root directory.{n}\
|
Relative paths are interpreted relative to the book's root directory.{n}\
|
||||||
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
|
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
|
||||||
),
|
),
|
||||||
|
).arg(
|
||||||
|
Arg::new("chapter")
|
||||||
|
.short('c')
|
||||||
|
.long("chapter")
|
||||||
|
.value_name("chapter")
|
||||||
|
.help(
|
||||||
|
"Only test the specified chapter{n}\
|
||||||
|
Where the name of the chapter is defined in the SUMMARY.md file.{n}\
|
||||||
|
Use the special name \"?\" to the list of chapter names."
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.arg(arg!([dir]
|
.arg(arg!([dir]
|
||||||
"Root directory for the book{n}\
|
"Root directory for the book{n}\
|
||||||
|
@ -41,14 +51,18 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||||
.values_of("library-path")
|
.values_of("library-path")
|
||||||
.map(std::iter::Iterator::collect)
|
.map(std::iter::Iterator::collect)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
let chapter: Option<&str> = args.value_of("chapter");
|
||||||
|
|
||||||
let book_dir = get_book_dir(args);
|
let book_dir = get_book_dir(args);
|
||||||
let mut book = MDBook::load(&book_dir)?;
|
let mut book = MDBook::load(&book_dir)?;
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
match chapter {
|
||||||
book.test(library_paths)?;
|
Some(_) => book.test_chapter(library_paths, chapter),
|
||||||
|
None => book.test(library_paths),
|
||||||
|
}?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
use log::{debug, trace, warn};
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
@ -295,7 +296,7 @@ impl Default for Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Config {
|
impl<'de> serde::Deserialize<'de> for Config {
|
||||||
fn deserialize<D: Deserializer<'de>>(de: D) -> std::result::Result<Self, D::Error> {
|
fn deserialize<D: Deserializer<'de>>(de: D) -> std::result::Result<Self, D::Error> {
|
||||||
let raw = Value::deserialize(de)?;
|
let raw = Value::deserialize(de)?;
|
||||||
|
|
||||||
|
@ -717,6 +718,7 @@ impl<'de, T> Updateable<'de> for T where T: Serialize + Deserialize<'de> {}
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::utils::fs::get_404_output_file;
|
use crate::utils::fs::get_404_output_file;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
const COMPLEX_CONFIG: &str = r#"
|
const COMPLEX_CONFIG: &str = r#"
|
||||||
[book]
|
[book]
|
||||||
|
|
11
src/lib.rs
11
src/lib.rs
|
@ -83,17 +83,6 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms)]
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_json;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate pretty_assertions;
|
|
||||||
|
|
||||||
pub mod book;
|
pub mod book;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod preprocess;
|
pub mod preprocess;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use super::{Preprocessor, PreprocessorContext};
|
use super::{Preprocessor, PreprocessorContext};
|
||||||
use crate::book::Book;
|
use crate::book::Book;
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
|
use log::{debug, trace, warn};
|
||||||
use shlex::Shlex;
|
use shlex::Shlex;
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
use std::process::{Child, Command, Stdio};
|
use std::process::{Child, Command, Stdio};
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::errors::*;
|
|
||||||
|
|
||||||
use super::{Preprocessor, PreprocessorContext};
|
use super::{Preprocessor, PreprocessorContext};
|
||||||
use crate::book::{Book, BookItem};
|
use crate::book::{Book, BookItem};
|
||||||
|
use crate::errors::*;
|
||||||
|
use log::warn;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
/// A preprocessor for converting file name `README.md` to `index.md` since
|
/// A preprocessor for converting file name `README.md` to `index.md` since
|
||||||
/// `README.md` is the de facto index file in markdown-based documentation.
|
/// `README.md` is the de facto index file in markdown-based documentation.
|
||||||
|
@ -67,9 +68,8 @@ fn warn_readme_name_conflict<P: AsRef<Path>>(readme_path: P, index_path: P) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_readme_file<P: AsRef<Path>>(path: P) -> bool {
|
fn is_readme_file<P: AsRef<Path>>(path: P) -> bool {
|
||||||
lazy_static! {
|
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)^readme$").unwrap());
|
||||||
static ref RE: Regex = Regex::new(r"(?i)^readme$").unwrap();
|
|
||||||
}
|
|
||||||
RE.is_match(
|
RE.is_match(
|
||||||
path.as_ref()
|
path.as_ref()
|
||||||
.file_stem()
|
.file_stem()
|
||||||
|
|
|
@ -10,6 +10,8 @@ use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use super::{Preprocessor, PreprocessorContext};
|
use super::{Preprocessor, PreprocessorContext};
|
||||||
use crate::book::{Book, BookItem};
|
use crate::book::{Book, BookItem};
|
||||||
|
use log::{error, warn};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
const ESCAPE_CHAR: char = '\\';
|
const ESCAPE_CHAR: char = '\\';
|
||||||
const MAX_LINK_NESTED_DEPTH: usize = 10;
|
const MAX_LINK_NESTED_DEPTH: usize = 10;
|
||||||
|
@ -434,19 +436,20 @@ impl<'a> Iterator for LinkIter<'a> {
|
||||||
fn find_links(contents: &str) -> LinkIter<'_> {
|
fn find_links(contents: &str) -> LinkIter<'_> {
|
||||||
// lazily compute following regex
|
// lazily compute following regex
|
||||||
// r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([^}]+)\}\}")?;
|
// r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([^}]+)\}\}")?;
|
||||||
lazy_static! {
|
static RE: Lazy<Regex> = Lazy::new(|| {
|
||||||
static ref RE: Regex = Regex::new(
|
Regex::new(
|
||||||
r"(?x) # insignificant whitespace mode
|
r"(?x) # insignificant whitespace mode
|
||||||
\\\{\{\#.*\}\} # match escaped link
|
\\\{\{\#.*\}\} # match escaped link
|
||||||
| # or
|
| # or
|
||||||
\{\{\s* # link opening parens and whitespace
|
\{\{\s* # link opening parens and whitespace
|
||||||
\#([a-zA-Z0-9_]+) # link type
|
\#([a-zA-Z0-9_]+) # link type
|
||||||
\s+ # separating whitespace
|
\s+ # separating whitespace
|
||||||
([^}]+) # link target path and space separated properties
|
([^}]+) # link target path and space separated properties
|
||||||
\}\} # link closing parens"
|
\}\} # link closing parens",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap()
|
||||||
}
|
});
|
||||||
|
|
||||||
LinkIter(RE.captures_iter(contents))
|
LinkIter(RE.captures_iter(contents))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,11 @@ use crate::book::Book;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// Extra information for a `Preprocessor` to give them more context when
|
/// Extra information for a `Preprocessor` to give them more context when
|
||||||
/// processing a book.
|
/// processing a book.
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
|
|
@ -14,7 +14,10 @@ use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use crate::utils::fs::get_404_output_file;
|
use crate::utils::fs::get_404_output_file;
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
|
use log::{debug, trace, warn};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use regex::{Captures, Regex};
|
use regex::{Captures, Regex};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct HtmlHandlebars;
|
pub struct HtmlHandlebars;
|
||||||
|
@ -764,9 +767,8 @@ fn make_data(
|
||||||
/// Goes through the rendered HTML, making sure all header tags have
|
/// Goes through the rendered HTML, making sure all header tags have
|
||||||
/// an anchor respectively so people can link to sections directly.
|
/// an anchor respectively so people can link to sections directly.
|
||||||
fn build_header_links(html: &str) -> String {
|
fn build_header_links(html: &str) -> String {
|
||||||
lazy_static! {
|
static BUILD_HEADER_LINKS: Lazy<Regex> =
|
||||||
static ref BUILD_HEADER_LINKS: Regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();
|
Lazy::new(|| Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap());
|
||||||
}
|
|
||||||
|
|
||||||
let mut id_counter = HashMap::new();
|
let mut id_counter = HashMap::new();
|
||||||
|
|
||||||
|
@ -807,10 +809,8 @@ fn insert_link_into_header(
|
||||||
// ```
|
// ```
|
||||||
// This function replaces all commas by spaces in the code block classes
|
// This function replaces all commas by spaces in the code block classes
|
||||||
fn fix_code_blocks(html: &str) -> String {
|
fn fix_code_blocks(html: &str) -> String {
|
||||||
lazy_static! {
|
static FIX_CODE_BLOCKS: Lazy<Regex> =
|
||||||
static ref FIX_CODE_BLOCKS: Regex =
|
Lazy::new(|| Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap());
|
||||||
Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
FIX_CODE_BLOCKS
|
FIX_CODE_BLOCKS
|
||||||
.replace_all(html, |caps: &Captures<'_>| {
|
.replace_all(html, |caps: &Captures<'_>| {
|
||||||
|
@ -833,10 +833,9 @@ fn add_playground_pre(
|
||||||
playground_config: &Playground,
|
playground_config: &Playground,
|
||||||
edition: Option<RustEdition>,
|
edition: Option<RustEdition>,
|
||||||
) -> String {
|
) -> String {
|
||||||
lazy_static! {
|
static ADD_PLAYGROUND_PRE: Lazy<Regex> =
|
||||||
static ref ADD_PLAYGROUND_PRE: Regex =
|
Lazy::new(|| Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap());
|
||||||
Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap();
|
|
||||||
}
|
|
||||||
ADD_PLAYGROUND_PRE
|
ADD_PLAYGROUND_PRE
|
||||||
.replace_all(html, |caps: &Captures<'_>| {
|
.replace_all(html, |caps: &Captures<'_>| {
|
||||||
let text = &caps[1];
|
let text = &caps[1];
|
||||||
|
@ -899,18 +898,19 @@ fn add_playground_pre(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hide_lines(content: &str) -> String {
|
fn hide_lines(content: &str) -> String {
|
||||||
lazy_static! {
|
static BORING_LINES_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(\s*)#(.?)(.*)$").unwrap());
|
||||||
static ref BORING_LINES_REGEX: Regex = Regex::new(r"^(\s*)#(.?)(.*)$").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut result = String::with_capacity(content.len());
|
let mut result = String::with_capacity(content.len());
|
||||||
for line in content.lines() {
|
let mut lines = content.lines().peekable();
|
||||||
|
while let Some(line) = lines.next() {
|
||||||
|
// Don't include newline on the last line.
|
||||||
|
let newline = if lines.peek().is_none() { "" } else { "\n" };
|
||||||
if let Some(caps) = BORING_LINES_REGEX.captures(line) {
|
if let Some(caps) = BORING_LINES_REGEX.captures(line) {
|
||||||
if &caps[2] == "#" {
|
if &caps[2] == "#" {
|
||||||
result += &caps[1];
|
result += &caps[1];
|
||||||
result += &caps[2];
|
result += &caps[2];
|
||||||
result += &caps[3];
|
result += &caps[3];
|
||||||
result += "\n";
|
result += newline;
|
||||||
continue;
|
continue;
|
||||||
} else if &caps[2] != "!" && &caps[2] != "[" {
|
} else if &caps[2] != "!" && &caps[2] != "[" {
|
||||||
result += "<span class=\"boring\">";
|
result += "<span class=\"boring\">";
|
||||||
|
@ -919,13 +919,13 @@ fn hide_lines(content: &str) -> String {
|
||||||
result += &caps[2];
|
result += &caps[2];
|
||||||
}
|
}
|
||||||
result += &caps[3];
|
result += &caps[3];
|
||||||
result += "\n";
|
result += newline;
|
||||||
result += "</span>";
|
result += "</span>";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result += line;
|
result += line;
|
||||||
result += "\n";
|
result += newline;
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@ -1005,19 +1005,19 @@ mod tests {
|
||||||
fn add_playground() {
|
fn add_playground() {
|
||||||
let inputs = [
|
let inputs = [
|
||||||
("<code class=\"language-rust\">x()</code>",
|
("<code class=\"language-rust\">x()</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</span></code></pre>"),
|
||||||
("<code class=\"language-rust\">fn main() {}</code>",
|
("<code class=\"language-rust\">fn main() {}</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust\">fn main() {}\n</code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust\">fn main() {}</code></pre>"),
|
||||||
("<code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code>",
|
("<code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";\n</code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";</code></pre>"),
|
||||||
("<code class=\"language-rust editable\">let s = \"foo\n ## bar\n\";</code>",
|
("<code class=\"language-rust editable\">let s = \"foo\n ## bar\n\";</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n\";\n</code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code></pre>"),
|
||||||
("<code class=\"language-rust editable\">let s = \"foo\n # bar\n#\n\";</code>",
|
("<code class=\"language-rust editable\">let s = \"foo\n # bar\n#\n\";</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span><span class=\"boring\">\n</span>\";\n</code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span><span class=\"boring\">\n</span>\";</code></pre>"),
|
||||||
("<code class=\"language-rust ignore\">let s = \"foo\n # bar\n\";</code>",
|
("<code class=\"language-rust ignore\">let s = \"foo\n # bar\n\";</code>",
|
||||||
"<code class=\"language-rust ignore\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";\n</code>"),
|
"<code class=\"language-rust ignore\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";</code>"),
|
||||||
("<code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code>",
|
("<code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]\n</code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code></pre>"),
|
||||||
];
|
];
|
||||||
for (src, should_be) in &inputs {
|
for (src, should_be) in &inputs {
|
||||||
let got = add_playground_pre(
|
let got = add_playground_pre(
|
||||||
|
@ -1035,13 +1035,13 @@ mod tests {
|
||||||
fn add_playground_edition2015() {
|
fn add_playground_edition2015() {
|
||||||
let inputs = [
|
let inputs = [
|
||||||
("<code class=\"language-rust\">x()</code>",
|
("<code class=\"language-rust\">x()</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust edition2015\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</span></code></pre>"),
|
||||||
("<code class=\"language-rust\">fn main() {}</code>",
|
("<code class=\"language-rust\">fn main() {}</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
|
||||||
("<code class=\"language-rust edition2015\">fn main() {}</code>",
|
("<code class=\"language-rust edition2015\">fn main() {}</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
|
||||||
("<code class=\"language-rust edition2018\">fn main() {}</code>",
|
("<code class=\"language-rust edition2018\">fn main() {}</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
|
||||||
];
|
];
|
||||||
for (src, should_be) in &inputs {
|
for (src, should_be) in &inputs {
|
||||||
let got = add_playground_pre(
|
let got = add_playground_pre(
|
||||||
|
@ -1059,13 +1059,13 @@ mod tests {
|
||||||
fn add_playground_edition2018() {
|
fn add_playground_edition2018() {
|
||||||
let inputs = [
|
let inputs = [
|
||||||
("<code class=\"language-rust\">x()</code>",
|
("<code class=\"language-rust\">x()</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust edition2018\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</span></code></pre>"),
|
||||||
("<code class=\"language-rust\">fn main() {}</code>",
|
("<code class=\"language-rust\">fn main() {}</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
|
||||||
("<code class=\"language-rust edition2015\">fn main() {}</code>",
|
("<code class=\"language-rust edition2015\">fn main() {}</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
|
||||||
("<code class=\"language-rust edition2018\">fn main() {}</code>",
|
("<code class=\"language-rust edition2018\">fn main() {}</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
|
||||||
];
|
];
|
||||||
for (src, should_be) in &inputs {
|
for (src, should_be) in &inputs {
|
||||||
let got = add_playground_pre(
|
let got = add_playground_pre(
|
||||||
|
@ -1083,13 +1083,13 @@ mod tests {
|
||||||
fn add_playground_edition2021() {
|
fn add_playground_edition2021() {
|
||||||
let inputs = [
|
let inputs = [
|
||||||
("<code class=\"language-rust\">x()</code>",
|
("<code class=\"language-rust\">x()</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust edition2021\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust edition2021\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</span></code></pre>"),
|
||||||
("<code class=\"language-rust\">fn main() {}</code>",
|
("<code class=\"language-rust\">fn main() {}</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust edition2021\">fn main() {}\n</code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust edition2021\">fn main() {}</code></pre>"),
|
||||||
("<code class=\"language-rust edition2015\">fn main() {}</code>",
|
("<code class=\"language-rust edition2015\">fn main() {}</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
|
||||||
("<code class=\"language-rust edition2018\">fn main() {}</code>",
|
("<code class=\"language-rust edition2018\">fn main() {}</code>",
|
||||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
|
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
|
||||||
];
|
];
|
||||||
for (src, should_be) in &inputs {
|
for (src, should_be) in &inputs {
|
||||||
let got = add_playground_pre(
|
let got = add_playground_pre(
|
||||||
|
|
|
@ -4,6 +4,8 @@ use std::path::Path;
|
||||||
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError, Renderable};
|
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError, Renderable};
|
||||||
|
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
|
use log::{debug, trace};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
type StringMap = BTreeMap<String, String>;
|
type StringMap = BTreeMap<String, String>;
|
||||||
|
|
||||||
|
@ -146,15 +148,12 @@ fn render(
|
||||||
|
|
||||||
trace!("Render template");
|
trace!("Render template");
|
||||||
|
|
||||||
_h.template()
|
let t = _h
|
||||||
.ok_or_else(|| RenderError::new("Error with the handlebars template"))
|
.template()
|
||||||
.and_then(|t| {
|
.ok_or_else(|| RenderError::new("Error with the handlebars template"))?;
|
||||||
let mut local_rc = rc.clone();
|
let local_ctx = Context::wraps(&context)?;
|
||||||
let local_ctx = Context::wraps(&context)?;
|
let mut local_rc = rc.clone();
|
||||||
t.render(r, &local_ctx, &mut local_rc, out)
|
t.render(r, &local_ctx, &mut local_rc, out)
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn previous(
|
pub fn previous(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError};
|
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError};
|
||||||
|
use log::trace;
|
||||||
|
|
||||||
pub fn theme_option(
|
pub fn theme_option(
|
||||||
h: &Helper<'_, '_>,
|
h: &Helper<'_, '_>,
|
||||||
|
|
|
@ -117,35 +117,35 @@ impl HelperDef for RenderToc {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link
|
// Link
|
||||||
let path_exists = if let Some(path) =
|
let path_exists: bool;
|
||||||
item.get("path")
|
match item.get("path") {
|
||||||
.and_then(|p| if p.is_empty() { None } else { Some(p) })
|
Some(path) if !path.is_empty() => {
|
||||||
{
|
out.write("<a href=\"")?;
|
||||||
out.write("<a href=\"")?;
|
let tmp = Path::new(path)
|
||||||
|
.with_extension("html")
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
// Hack for windows who tends to use `\` as separator instead of `/`
|
||||||
|
.replace('\\', "/");
|
||||||
|
|
||||||
let tmp = Path::new(item.get("path").expect("Error: path should be Some(_)"))
|
// Add link
|
||||||
.with_extension("html")
|
out.write(&utils::fs::path_to_root(¤t_path))?;
|
||||||
.to_str()
|
out.write(&tmp)?;
|
||||||
.unwrap()
|
out.write("\"")?;
|
||||||
// Hack for windows who tends to use `\` as separator instead of `/`
|
|
||||||
.replace('\\', "/");
|
|
||||||
|
|
||||||
// Add link
|
if path == ¤t_path || is_first_chapter {
|
||||||
out.write(&utils::fs::path_to_root(¤t_path))?;
|
is_first_chapter = false;
|
||||||
out.write(&tmp)?;
|
out.write(" class=\"active\"")?;
|
||||||
out.write("\"")?;
|
}
|
||||||
|
|
||||||
if path == ¤t_path || is_first_chapter {
|
out.write(">")?;
|
||||||
is_first_chapter = false;
|
path_exists = true;
|
||||||
out.write(" class=\"active\"")?;
|
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
out.write(">")?;
|
out.write("<div>")?;
|
||||||
true
|
path_exists = false;
|
||||||
} else {
|
}
|
||||||
out.write("<div>")?;
|
}
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
if !self.no_section_label {
|
if !self.no_section_label {
|
||||||
// Section does not necessarily exist
|
// Section does not necessarily exist
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use elasticlunr::{Index, IndexBuilder};
|
use elasticlunr::{Index, IndexBuilder};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use pulldown_cmark::*;
|
use pulldown_cmark::*;
|
||||||
|
|
||||||
use crate::book::{Book, BookItem};
|
use crate::book::{Book, BookItem};
|
||||||
|
@ -10,7 +11,7 @@ use crate::config::Search;
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
use crate::theme::searcher;
|
use crate::theme::searcher;
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
|
use log::{debug, warn};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
const MAX_WORD_LENGTH_TO_INDEX: usize = 80;
|
const MAX_WORD_LENGTH_TO_INDEX: usize = 80;
|
||||||
|
@ -266,21 +267,19 @@ fn write_to_json(index: Index, search_config: &Search, doc_urls: Vec<String>) ->
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clean_html(html: &str) -> String {
|
fn clean_html(html: &str) -> String {
|
||||||
lazy_static! {
|
static AMMONIA: Lazy<ammonia::Builder<'static>> = Lazy::new(|| {
|
||||||
static ref AMMONIA: ammonia::Builder<'static> = {
|
let mut clean_content = HashSet::new();
|
||||||
let mut clean_content = HashSet::new();
|
clean_content.insert("script");
|
||||||
clean_content.insert("script");
|
clean_content.insert("style");
|
||||||
clean_content.insert("style");
|
let mut builder = ammonia::Builder::new();
|
||||||
let mut builder = ammonia::Builder::new();
|
builder
|
||||||
builder
|
.tags(HashSet::new())
|
||||||
.tags(HashSet::new())
|
.tag_attributes(HashMap::new())
|
||||||
.tag_attributes(HashMap::new())
|
.generic_attributes(HashSet::new())
|
||||||
.generic_attributes(HashSet::new())
|
.link_rel(None)
|
||||||
.link_rel(None)
|
.allowed_classes(HashMap::new())
|
||||||
.allowed_classes(HashMap::new())
|
.clean_content_tags(clean_content);
|
||||||
.clean_content_tags(clean_content);
|
builder
|
||||||
builder
|
});
|
||||||
};
|
|
||||||
}
|
|
||||||
AMMONIA.clean(html).to_string()
|
AMMONIA.clean(html).to_string()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::book::BookItem;
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
use crate::renderer::{RenderContext, Renderer};
|
use crate::renderer::{RenderContext, Renderer};
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
|
use log::trace;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
|
@ -27,6 +27,7 @@ use std::process::{Command, Stdio};
|
||||||
use crate::book::Book;
|
use crate::book::Book;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
|
use log::{error, info, trace, warn};
|
||||||
use toml::Value;
|
use toml::Value;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
@ -51,18 +51,18 @@
|
||||||
|
|
||||||
{{#if mathjax_support}}
|
{{#if mathjax_support}}
|
||||||
<!-- MathJax -->
|
<!-- MathJax -->
|
||||||
<script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Provide site root to javascript -->
|
<!-- Provide site root to javascript -->
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
var path_to_root = "{{ path_to_root }}";
|
var path_to_root = "{{ path_to_root }}";
|
||||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
|
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
try {
|
try {
|
||||||
var theme = localStorage.getItem('mdbook-theme');
|
var theme = localStorage.getItem('mdbook-theme');
|
||||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||||
|
@ -78,7 +78,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
var theme;
|
var theme;
|
||||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||||
|
@ -90,7 +90,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Hide / unhide sidebar before it is displayed -->
|
<!-- Hide / unhide sidebar before it is displayed -->
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
var html = document.querySelector('html');
|
var html = document.querySelector('html');
|
||||||
var sidebar = 'hidden';
|
var sidebar = 'hidden';
|
||||||
if (document.body.clientWidth >= 1080) {
|
if (document.body.clientWidth >= 1080) {
|
||||||
|
@ -171,7 +171,7 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||||
|
@ -221,7 +221,7 @@
|
||||||
|
|
||||||
{{#if live_reload_endpoint}}
|
{{#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>
|
||||||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
|
const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
|
||||||
const socket = new WebSocket(wsAddress);
|
const socket = new WebSocket(wsAddress);
|
||||||
|
@ -240,7 +240,7 @@
|
||||||
|
|
||||||
{{#if google_analytics}}
|
{{#if google_analytics}}
|
||||||
<!-- Google Analytics Tag -->
|
<!-- Google Analytics Tag -->
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
var localAddrs = ["localhost", "127.0.0.1", ""];
|
var localAddrs = ["localhost", "127.0.0.1", ""];
|
||||||
|
|
||||||
// make sure we don't activate google analytics if the developer is
|
// make sure we don't activate google analytics if the developer is
|
||||||
|
@ -258,43 +258,43 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if playground_line_numbers}}
|
{{#if playground_line_numbers}}
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
window.playground_line_numbers = true;
|
window.playground_line_numbers = true;
|
||||||
</script>
|
</script>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if playground_copyable}}
|
{{#if playground_copyable}}
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
window.playground_copyable = true;
|
window.playground_copyable = true;
|
||||||
</script>
|
</script>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if playground_js}}
|
{{#if playground_js}}
|
||||||
<script src="{{ path_to_root }}ace.js" type="text/javascript" charset="utf-8"></script>
|
<script src="{{ path_to_root }}ace.js" charset="utf-8"></script>
|
||||||
<script src="{{ path_to_root }}editor.js" type="text/javascript" charset="utf-8"></script>
|
<script src="{{ path_to_root }}editor.js" charset="utf-8"></script>
|
||||||
<script src="{{ path_to_root }}mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
<script src="{{ path_to_root }}mode-rust.js" charset="utf-8"></script>
|
||||||
<script src="{{ path_to_root }}theme-dawn.js" type="text/javascript" charset="utf-8"></script>
|
<script src="{{ path_to_root }}theme-dawn.js" charset="utf-8"></script>
|
||||||
<script src="{{ path_to_root }}theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
|
<script src="{{ path_to_root }}theme-tomorrow_night.js" charset="utf-8"></script>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if search_js}}
|
{{#if search_js}}
|
||||||
<script src="{{ path_to_root }}elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
<script src="{{ path_to_root }}elasticlunr.min.js" charset="utf-8"></script>
|
||||||
<script src="{{ path_to_root }}mark.min.js" type="text/javascript" charset="utf-8"></script>
|
<script src="{{ path_to_root }}mark.min.js" charset="utf-8"></script>
|
||||||
<script src="{{ path_to_root }}searcher.js" type="text/javascript" charset="utf-8"></script>
|
<script src="{{ path_to_root }}searcher.js" charset="utf-8"></script>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<script src="{{ path_to_root }}clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
<script src="{{ path_to_root }}clipboard.min.js" charset="utf-8"></script>
|
||||||
<script src="{{ path_to_root }}highlight.js" type="text/javascript" charset="utf-8"></script>
|
<script src="{{ path_to_root }}highlight.js" charset="utf-8"></script>
|
||||||
<script src="{{ path_to_root }}book.js" type="text/javascript" charset="utf-8"></script>
|
<script src="{{ path_to_root }}book.js" charset="utf-8"></script>
|
||||||
|
|
||||||
<!-- Custom JS scripts -->
|
<!-- Custom JS scripts -->
|
||||||
{{#each additional_js}}
|
{{#each additional_js}}
|
||||||
<script type="text/javascript" src="{{ ../path_to_root }}{{this}}"></script>
|
<script src="{{ ../path_to_root }}{{this}}"></script>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
{{#if is_print}}
|
{{#if is_print}}
|
||||||
{{#if mathjax_support}}
|
{{#if mathjax_support}}
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
window.addEventListener('load', function() {
|
window.addEventListener('load', function() {
|
||||||
MathJax.Hub.Register.StartupHook('End', function() {
|
MathJax.Hub.Register.StartupHook('End', function() {
|
||||||
window.setTimeout(window.print, 100);
|
window.setTimeout(window.print, 100);
|
||||||
|
@ -302,7 +302,7 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{{else}}
|
{{else}}
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
window.addEventListener('load', function() {
|
window.addEventListener('load', function() {
|
||||||
window.setTimeout(window.print, 100);
|
window.setTimeout(window.print, 100);
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ use std::io::Read;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
|
use log::warn;
|
||||||
pub static INDEX: &[u8] = include_bytes!("index.hbs");
|
pub static INDEX: &[u8] = include_bytes!("index.hbs");
|
||||||
pub static HEAD: &[u8] = include_bytes!("head.hbs");
|
pub static HEAD: &[u8] = include_bytes!("head.hbs");
|
||||||
pub static REDIRECT: &[u8] = include_bytes!("redirect.hbs");
|
pub static REDIRECT: &[u8] = include_bytes!("redirect.hbs");
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
|
use log::{debug, trace};
|
||||||
use std::convert::Into;
|
use std::convert::Into;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
|
@ -4,9 +4,10 @@ pub mod fs;
|
||||||
mod string;
|
mod string;
|
||||||
pub(crate) mod toml_ext;
|
pub(crate) mod toml_ext;
|
||||||
use crate::errors::Error;
|
use crate::errors::Error;
|
||||||
use regex::Regex;
|
use log::error;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag};
|
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag};
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -20,9 +21,7 @@ pub use self::string::{
|
||||||
|
|
||||||
/// Replaces multiple consecutive whitespace characters with a single space character.
|
/// Replaces multiple consecutive whitespace characters with a single space character.
|
||||||
pub fn collapse_whitespace(text: &str) -> Cow<'_, str> {
|
pub fn collapse_whitespace(text: &str) -> Cow<'_, str> {
|
||||||
lazy_static! {
|
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\s\s+").unwrap());
|
||||||
static ref RE: Regex = Regex::new(r"\s\s+").unwrap();
|
|
||||||
}
|
|
||||||
RE.replace_all(text, " ")
|
RE.replace_all(text, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,9 +50,7 @@ pub fn id_from_content(content: &str) -> String {
|
||||||
let mut content = content.to_string();
|
let mut content = content.to_string();
|
||||||
|
|
||||||
// Skip any tags or html-encoded stuff
|
// Skip any tags or html-encoded stuff
|
||||||
lazy_static! {
|
static HTML: Lazy<Regex> = Lazy::new(|| Regex::new(r"(<.*?>)").unwrap());
|
||||||
static ref HTML: Regex = Regex::new(r"(<.*?>)").unwrap();
|
|
||||||
}
|
|
||||||
content = HTML.replace_all(&content, "").into();
|
content = HTML.replace_all(&content, "").into();
|
||||||
const REPL_SUB: &[&str] = &["<", ">", "&", "'", """];
|
const REPL_SUB: &[&str] = &["<", ">", "&", "'", """];
|
||||||
for sub in REPL_SUB {
|
for sub in REPL_SUB {
|
||||||
|
@ -96,10 +93,9 @@ pub fn unique_id_from_content(content: &str, id_counter: &mut HashMap<String, us
|
||||||
/// None. Ideally, print page links would link to anchors on the print page,
|
/// None. Ideally, print page links would link to anchors on the print page,
|
||||||
/// but that is very difficult.
|
/// but that is very difficult.
|
||||||
fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
|
fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
|
||||||
lazy_static! {
|
static SCHEME_LINK: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[a-z][a-z0-9+.-]*:").unwrap());
|
||||||
static ref SCHEME_LINK: Regex = Regex::new(r"^[a-z][a-z0-9+.-]*:").unwrap();
|
static MD_LINK: Lazy<Regex> =
|
||||||
static ref MD_LINK: Regex = Regex::new(r"(?P<link>.*)\.md(?P<anchor>#.*)?").unwrap();
|
Lazy::new(|| Regex::new(r"(?P<link>.*)\.md(?P<anchor>#.*)?").unwrap());
|
||||||
}
|
|
||||||
|
|
||||||
fn fix<'a>(dest: CowStr<'a>, path: Option<&Path>) -> CowStr<'a> {
|
fn fix<'a>(dest: CowStr<'a>, path: Option<&Path>) -> CowStr<'a> {
|
||||||
if dest.starts_with('#') {
|
if dest.starts_with('#') {
|
||||||
|
@ -152,10 +148,8 @@ fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
|
||||||
// There are dozens of HTML tags/attributes that contain paths, so
|
// There are dozens of HTML tags/attributes that contain paths, so
|
||||||
// feel free to add more tags if desired; these are the only ones I
|
// feel free to add more tags if desired; these are the only ones I
|
||||||
// care about right now.
|
// care about right now.
|
||||||
lazy_static! {
|
static HTML_LINK: Lazy<Regex> =
|
||||||
static ref HTML_LINK: Regex =
|
Lazy::new(|| Regex::new(r#"(<(?:a|img) [^>]*?(?:src|href)=")([^"]+?)""#).unwrap());
|
||||||
Regex::new(r#"(<(?:a|img) [^>]*?(?:src|href)=")([^"]+?)""#).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
HTML_LINK
|
HTML_LINK
|
||||||
.replace_all(&html, |caps: ®ex::Captures<'_>| {
|
.replace_all(&html, |caps: ®ex::Captures<'_>| {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::ops::Bound::{Excluded, Included, Unbounded};
|
use std::ops::Bound::{Excluded, Included, Unbounded};
|
||||||
|
@ -24,10 +25,10 @@ pub fn take_lines<R: RangeBounds<usize>>(s: &str, range: R) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
static ANCHOR_START: Lazy<Regex> =
|
||||||
static ref ANCHOR_START: Regex = Regex::new(r"ANCHOR:\s*(?P<anchor_name>[\w_-]+)").unwrap();
|
Lazy::new(|| Regex::new(r"ANCHOR:\s*(?P<anchor_name>[\w_-]+)").unwrap());
|
||||||
static ref ANCHOR_END: Regex = Regex::new(r"ANCHOR_END:\s*(?P<anchor_name>[\w_-]+)").unwrap();
|
static ANCHOR_END: Lazy<Regex> =
|
||||||
}
|
Lazy::new(|| Regex::new(r"ANCHOR_END:\s*(?P<anchor_name>[\w_-]+)").unwrap());
|
||||||
|
|
||||||
/// Take anchored lines from a string.
|
/// Take anchored lines from a string.
|
||||||
/// Lines containing anchor are ignored.
|
/// Lines containing anchor are ignored.
|
||||||
|
|
|
@ -10,11 +10,11 @@ fn mdbook_cli_can_correctly_test_a_passing_book() {
|
||||||
let mut cmd = mdbook_cmd();
|
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 chapter [^:]*: "README.md""##).unwrap())
|
||||||
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]intro.md""##).unwrap())
|
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "intro.md""##).unwrap())
|
||||||
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]index.md""##).unwrap())
|
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]index.md""##).unwrap())
|
||||||
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]nested.md""##).unwrap())
|
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]nested.md""##).unwrap())
|
||||||
.stderr(predicates::str::is_match(r##"rustdoc returned an error:\n\n"##).unwrap().not())
|
.stderr(predicates::str::is_match(r##"returned an error:\n\n"##).unwrap().not())
|
||||||
.stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap().not());
|
.stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap().not());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,10 +25,10 @@ fn mdbook_cli_detects_book_with_failing_tests() {
|
||||||
let mut cmd = mdbook_cmd();
|
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 chapter [^:]*: "README.md""##).unwrap())
|
||||||
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]intro.md""##).unwrap())
|
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "intro.md""##).unwrap())
|
||||||
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]index.md""##).unwrap())
|
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]index.md""##).unwrap())
|
||||||
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]nested.md""##).unwrap())
|
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]nested.md""##).unwrap())
|
||||||
.stderr(predicates::str::is_match(r##"rustdoc returned an error:\n\n"##).unwrap())
|
.stderr(predicates::str::is_match(r##"returned an error:\n\n"##).unwrap())
|
||||||
.stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap());
|
.stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap());
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,3 +24,24 @@ fn mdbook_detects_book_with_failing_tests() {
|
||||||
|
|
||||||
assert!(md.test(vec![]).is_err());
|
assert!(md.test(vec![]).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mdbook_test_chapter() {
|
||||||
|
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
|
||||||
|
let mut md = MDBook::load(temp.path()).unwrap();
|
||||||
|
|
||||||
|
let result = md.test_chapter(vec![], Some("Introduction"));
|
||||||
|
assert!(
|
||||||
|
result.is_ok(),
|
||||||
|
"test_chapter failed with {}",
|
||||||
|
result.err().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mdbook_test_chapter_not_found() {
|
||||||
|
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
|
||||||
|
let mut md = MDBook::load(temp.path()).unwrap();
|
||||||
|
|
||||||
|
assert!(md.test_chapter(vec![], Some("Bogus Chapter Name")).is_err());
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue