├── tests ├── cli_tests.rs ├── dummy_book │ ├── src2 │ │ ├── README.md │ │ ├── first │ │ │ └── README.md │ │ ├── second │ │ │ ├── README.md │ │ │ └── index.md │ │ └── SUMMARY.md │ ├── src │ │ ├── first │ │ │ ├── nested-test.rs │ │ │ ├── includes.md │ │ │ ├── index.md │ │ │ ├── no-headers.md │ │ │ ├── recursive.md │ │ │ ├── partially-included-test.rs │ │ │ ├── duplicate-headers.md │ │ │ ├── partially-included-test-with-anchors.rs │ │ │ ├── unicode.md │ │ │ ├── markdown.md │ │ │ ├── nested-test-with-anchors.rs │ │ │ └── nested.md │ │ ├── intro.md │ │ ├── second.md │ │ ├── README.md │ │ ├── example.rs │ │ ├── conclusion.md │ │ ├── second │ │ │ └── nested.md │ │ └── SUMMARY.md │ └── mod.rs ├── cli │ ├── mod.rs │ ├── cmd.rs │ ├── build.rs │ └── test.rs ├── summary_md_files │ ├── rust_ffi_guide.md │ └── example_book.md ├── testing.rs ├── custom_preprocessors.rs ├── parse_existing_summary_files.rs ├── build_process.rs ├── init.rs └── alternative_backends.rs ├── src ├── theme │ ├── head.hbs │ ├── header.hbs │ ├── favicon.png │ ├── FontAwesome │ │ └── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ ├── fonts │ │ ├── open-sans-v17-all-charsets-300.woff2 │ │ ├── open-sans-v17-all-charsets-600.woff2 │ │ ├── open-sans-v17-all-charsets-700.woff2 │ │ ├── open-sans-v17-all-charsets-800.woff2 │ │ ├── open-sans-v17-all-charsets-italic.woff2 │ │ ├── open-sans-v17-all-charsets-regular.woff2 │ │ ├── open-sans-v17-all-charsets-300italic.woff2 │ │ ├── open-sans-v17-all-charsets-600italic.woff2 │ │ ├── open-sans-v17-all-charsets-700italic.woff2 │ │ ├── open-sans-v17-all-charsets-800italic.woff2 │ │ ├── source-code-pro-v11-all-charsets-500.woff2 │ │ ├── mod.rs │ │ ├── fonts.css │ │ └── SOURCE-CODE-PRO-LICENSE.txt │ ├── searcher │ │ └── mod.rs │ ├── redirect.hbs │ ├── playground_editor │ │ ├── mod.rs │ │ ├── editor.js │ │ ├── theme-dawn.js │ │ └── theme-tomorrow_night.js │ ├── css │ │ ├── print.css │ │ └── general.css │ ├── ayu-highlight.css │ ├── highlight.css │ ├── favicon.svg │ └── tomorrow-night.css ├── renderer │ ├── html_handlebars │ │ ├── helpers │ │ │ ├── mod.rs │ │ │ └── theme.rs │ │ └── mod.rs │ └── markdown_renderer.rs ├── cmd │ ├── mod.rs │ ├── clean.rs │ ├── build.rs │ ├── test.rs │ ├── init.rs │ └── watch.rs ├── preprocess │ ├── mod.rs │ └── index.rs ├── utils │ └── toml_ext.rs ├── lib.rs └── main.rs ├── test_book ├── src │ ├── rust │ │ ├── README.md │ │ └── rust_codeblock.md │ ├── prefix.md │ ├── suffix.md │ ├── individual │ │ ├── strikethrough.md │ │ ├── linebreak.md │ │ ├── task.md │ │ ├── heading.md │ │ ├── README.md │ │ ├── emphasis.md │ │ ├── link_hr.md │ │ ├── list.md │ │ ├── blockquote.md │ │ ├── code.md │ │ ├── image.md │ │ ├── mixed.md │ │ ├── table.md │ │ └── paragraph.md │ ├── languages │ │ └── README.md │ ├── README.md │ └── SUMMARY.md └── book.toml ├── release.toml ├── guide ├── src │ ├── 404.md │ ├── format │ │ ├── example.rs │ │ ├── README.md │ │ ├── configuration │ │ │ ├── README.md │ │ │ ├── environment-variables.md │ │ │ ├── preprocessors.md │ │ │ └── general.md │ │ ├── theme │ │ │ ├── editor.md │ │ │ ├── README.md │ │ │ ├── syntax-highlighting.md │ │ │ └── index-hbs.md │ │ ├── mathjax.md │ │ ├── images │ │ │ └── rust-logo-blk.svg │ │ └── summary.md │ ├── guide │ │ ├── README.md │ │ ├── installation.md │ │ ├── reading.md │ │ └── creating.md │ ├── for_developers │ │ ├── mdbook-wordcount │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── main.rs │ │ ├── README.md │ │ └── preprocessors.md │ ├── cli │ │ ├── completions.md │ │ ├── clean.md │ │ ├── README.md │ │ ├── build.md │ │ ├── watch.md │ │ ├── serve.md │ │ ├── init.md │ │ └── test.md │ ├── misc │ │ └── contributors.md │ ├── SUMMARY.md │ ├── README.md │ └── continuous-integration.md └── book.toml ├── CODE_OF_CONDUCT.md ├── .gitattributes ├── triagebot.toml ├── .gitignore ├── ci ├── install-rust.sh ├── install-hub.sh └── make-release.sh ├── README.md ├── .github └── workflows │ ├── deploy.yml │ └── main.yml ├── Cargo.toml └── examples └── nop-preprocessor.rs /tests/cli_tests.rs: -------------------------------------------------------------------------------- 1 | mod cli; 2 | mod dummy_book; 3 | -------------------------------------------------------------------------------- /tests/dummy_book/src2/README.md: -------------------------------------------------------------------------------- 1 | # Root README 2 | -------------------------------------------------------------------------------- /tests/cli/mod.rs: -------------------------------------------------------------------------------- 1 | mod build; 2 | mod cmd; 3 | mod test; 4 | -------------------------------------------------------------------------------- /tests/dummy_book/src2/first/README.md: -------------------------------------------------------------------------------- 1 | # First README 2 | -------------------------------------------------------------------------------- /tests/dummy_book/src2/second/README.md: -------------------------------------------------------------------------------- 1 | # Second README 2 | -------------------------------------------------------------------------------- /tests/dummy_book/src2/second/index.md: -------------------------------------------------------------------------------- 1 | # Second index 2 | -------------------------------------------------------------------------------- /src/theme/head.hbs: -------------------------------------------------------------------------------- 1 | {{!-- Put your head HTML text here --}} 2 | -------------------------------------------------------------------------------- /src/theme/header.hbs: -------------------------------------------------------------------------------- 1 | {{!-- Put your header HTML text here --}} -------------------------------------------------------------------------------- /test_book/src/rust/README.md: -------------------------------------------------------------------------------- 1 | # Rust specific code examples 2 | -------------------------------------------------------------------------------- /tests/dummy_book/src/first/nested-test.rs: -------------------------------------------------------------------------------- 1 | assert!($TEST_STATUS); 2 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | sign-commit = true 2 | push-remote = "origin" 3 | tag-prefix = "v" 4 | -------------------------------------------------------------------------------- /tests/dummy_book/src/first/includes.md: -------------------------------------------------------------------------------- 1 | # Includes 2 | 3 | {{#include ../SUMMARY.md::}} -------------------------------------------------------------------------------- /tests/dummy_book/src/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Here's some interesting text... -------------------------------------------------------------------------------- /src/theme/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/favicon.png -------------------------------------------------------------------------------- /guide/src/404.md: -------------------------------------------------------------------------------- 1 | # Document not found (404) 2 | 3 | This URL is invalid, sorry. Try the search instead! -------------------------------------------------------------------------------- /src/renderer/html_handlebars/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod navigation; 2 | pub mod theme; 3 | pub mod toc; 4 | -------------------------------------------------------------------------------- /tests/dummy_book/src/first/index.md: -------------------------------------------------------------------------------- 1 | # First Chapter 2 | 3 | more text. 4 | 5 | ## Some Section 6 | -------------------------------------------------------------------------------- /tests/dummy_book/src/first/no-headers.md: -------------------------------------------------------------------------------- 1 | Capybara capybara capybara. 2 | 3 | Capybara capybara capybara. -------------------------------------------------------------------------------- /tests/dummy_book/src/first/recursive.md: -------------------------------------------------------------------------------- 1 | Around the world, around the world 2 | {{#include recursive.md}} 3 | -------------------------------------------------------------------------------- /test_book/src/prefix.md: -------------------------------------------------------------------------------- 1 | # Prefix Chapter 2 | 3 | This is to verify the placement and style of prefix chapter in book index. 4 | -------------------------------------------------------------------------------- /test_book/src/suffix.md: -------------------------------------------------------------------------------- 1 | # Suffix Chapter 2 | 3 | This is to verify the placement and style of suffix chapter in book index. 4 | -------------------------------------------------------------------------------- /src/theme/FontAwesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/FontAwesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /tests/dummy_book/src/second.md: -------------------------------------------------------------------------------- 1 | # Second Chapter 2 | 3 | This makes sure you can insert runnable Rust files. 4 | 5 | {{#playground example.rs}} 6 | -------------------------------------------------------------------------------- /src/theme/FontAwesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/FontAwesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/theme/FontAwesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/FontAwesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/theme/FontAwesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/FontAwesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/theme/fonts/open-sans-v17-all-charsets-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/fonts/open-sans-v17-all-charsets-300.woff2 -------------------------------------------------------------------------------- /src/theme/fonts/open-sans-v17-all-charsets-600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/fonts/open-sans-v17-all-charsets-600.woff2 -------------------------------------------------------------------------------- /src/theme/fonts/open-sans-v17-all-charsets-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/fonts/open-sans-v17-all-charsets-700.woff2 -------------------------------------------------------------------------------- /src/theme/fonts/open-sans-v17-all-charsets-800.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/fonts/open-sans-v17-all-charsets-800.woff2 -------------------------------------------------------------------------------- /tests/dummy_book/src/README.md: -------------------------------------------------------------------------------- 1 | # Dummy Book 2 | 3 | This file is just here to cause the index preprocessor to run. 4 | 5 | Does a pretty good job, too. -------------------------------------------------------------------------------- /src/theme/FontAwesome/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/FontAwesome/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/theme/fonts/open-sans-v17-all-charsets-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/fonts/open-sans-v17-all-charsets-italic.woff2 -------------------------------------------------------------------------------- /test_book/src/individual/strikethrough.md: -------------------------------------------------------------------------------- 1 | # Strikethrough 2 | 3 | ~~This is Striked~~ 4 | 5 | ~~This is **strong**, _italic_ , **_both_** and striked~~ 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # The Rust Code of Conduct 2 | 3 | The Code of Conduct for this repository [can be found online](https://www.rust-lang.org/conduct.html). 4 | -------------------------------------------------------------------------------- /src/theme/fonts/open-sans-v17-all-charsets-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/fonts/open-sans-v17-all-charsets-regular.woff2 -------------------------------------------------------------------------------- /src/theme/fonts/open-sans-v17-all-charsets-300italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/fonts/open-sans-v17-all-charsets-300italic.woff2 -------------------------------------------------------------------------------- /src/theme/fonts/open-sans-v17-all-charsets-600italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/fonts/open-sans-v17-all-charsets-600italic.woff2 -------------------------------------------------------------------------------- /src/theme/fonts/open-sans-v17-all-charsets-700italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/fonts/open-sans-v17-all-charsets-700italic.woff2 -------------------------------------------------------------------------------- /src/theme/fonts/open-sans-v17-all-charsets-800italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/fonts/open-sans-v17-all-charsets-800italic.woff2 -------------------------------------------------------------------------------- /src/theme/fonts/source-code-pro-v11-all-charsets-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoR/mdBook/master/src/theme/fonts/source-code-pro-v11-all-charsets-500.woff2 -------------------------------------------------------------------------------- /tests/dummy_book/src/first/partially-included-test.rs: -------------------------------------------------------------------------------- 1 | fn some_function() { 2 | assert!($TEST_STATUS); 3 | } 4 | 5 | fn main() { 6 | some_function(); 7 | } 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | [attr]rust text eol=lf whitespace=tab-in-indent,trailing-space,tabwidth=4 2 | 3 | * text=auto eol=lf 4 | *.rs rust 5 | *.woff binary 6 | *.ttf binary 7 | *.otf binary 8 | *.png binary 9 | -------------------------------------------------------------------------------- /guide/src/format/example.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello World!"); 3 | # 4 | # // You can even hide lines! :D 5 | # println!("I am hidden! Expand the code snippet to see me"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/dummy_book/src/example.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello World!"); 3 | # 4 | # // You can even hide lines! :D 5 | # println!("I am hidden! Expand the code snippet to see me"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/cli/cmd.rs: -------------------------------------------------------------------------------- 1 | use assert_cmd::Command; 2 | 3 | pub(crate) fn mdbook_cmd() -> Command { 4 | let mut cmd = Command::cargo_bin("mdbook").unwrap(); 5 | cmd.env_remove("RUST_LOG"); 6 | cmd 7 | } 8 | -------------------------------------------------------------------------------- /tests/dummy_book/src/first/duplicate-headers.md: -------------------------------------------------------------------------------- 1 | # Duplicate headers 2 | 3 | This page validates behaviour of duplicate headers. 4 | 5 | # Header Text 6 | 7 | # Header Text 8 | 9 | # header-text 10 | -------------------------------------------------------------------------------- /test_book/src/individual/linebreak.md: -------------------------------------------------------------------------------- 1 | # Line breaks 2 | 3 | This is a long 4 | line with a couple of 5 | line breaks in
6 | between : both with two 7 | spaces and return,
8 | and with HTML tags. 9 | -------------------------------------------------------------------------------- /test_book/src/individual/task.md: -------------------------------------------------------------------------------- 1 | # Tasks 2 | 3 | - [ ] Task 1 4 | - [ ] Task 2 5 | - [x] Completed Task 1 6 | - [x] Completed Task 2 7 | 8 | --- 9 | 10 | - [ ] **Important Task** 11 | - [x] _Completed Important task_ 12 | -------------------------------------------------------------------------------- /guide/src/format/README.md: -------------------------------------------------------------------------------- 1 | # Format 2 | 3 | In this section you will learn how to: 4 | 5 | - Structure your book correctly 6 | - Format your `SUMMARY.md` file 7 | - Configure your book using `book.toml` 8 | - Customize your theme 9 | -------------------------------------------------------------------------------- /guide/src/guide/README.md: -------------------------------------------------------------------------------- 1 | # User Guide 2 | 3 | This user guide provides an introduction to basic concepts of using mdBook. 4 | 5 | - [Installation](installation.md) 6 | - [Reading Books](reading.md) 7 | - [Creating a Book](creating.md) 8 | -------------------------------------------------------------------------------- /src/renderer/html_handlebars/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] // FIXME: Document this 2 | 3 | pub use self::hbs_renderer::HtmlHandlebars; 4 | 5 | mod hbs_renderer; 6 | mod helpers; 7 | 8 | #[cfg(feature = "search")] 9 | mod search; 10 | -------------------------------------------------------------------------------- /src/cmd/mod.rs: -------------------------------------------------------------------------------- 1 | //! Subcommand modules for the `mdbook` binary. 2 | 3 | pub mod build; 4 | pub mod clean; 5 | pub mod init; 6 | #[cfg(feature = "serve")] 7 | pub mod serve; 8 | pub mod test; 9 | #[cfg(feature = "watch")] 10 | pub mod watch; 11 | -------------------------------------------------------------------------------- /triagebot.toml: -------------------------------------------------------------------------------- 1 | # This will allow users to self assign, and/or drop assignment 2 | [assign] 3 | 4 | 5 | [relabel] 6 | allow-unauthenticated = [ 7 | # For Issue areas 8 | "A-*", 9 | "E-Help-Wanted", 10 | "Bug", 11 | "Feature-Request" 12 | ] -------------------------------------------------------------------------------- /tests/dummy_book/src2/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # This dummy book is for testing the conversion of README.md to index.html by IndexPreprocessor 2 | 3 | [Root README](README.md) 4 | 5 | - [1st README](first/README.md) 6 | - [2nd README](second/README.md) 7 | - [2nd index](second/index.md) 8 | -------------------------------------------------------------------------------- /test_book/src/individual/heading.md: -------------------------------------------------------------------------------- 1 | # Chapter Heading 2 | 3 | --- 4 | 5 | # Really Big Heading 6 | 7 | ## Big Heading 8 | 9 | ### Normal-ish Heading 10 | 11 | #### Small Heading...? 12 | 13 | ##### Really Small Heading 14 | 15 | ###### Is it even a heading anymore - heading 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | 3 | # MacOS temp file 4 | .DS_Store 5 | 6 | book-test 7 | guide/book 8 | 9 | .vscode 10 | tests/dummy_book/book/ 11 | test_book/book/ 12 | 13 | # Ignore Jetbrains specific files. 14 | .idea/ 15 | 16 | # Ignore Vim temporary and swap files. 17 | *.sw? 18 | *~ 19 | -------------------------------------------------------------------------------- /guide/src/for_developers/mdbook-wordcount/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mdbook-wordcount" 3 | version = "0.1.0" 4 | authors = ["Michael Bryan "] 5 | 6 | [dependencies] 7 | mdbook = { path = "../../../..", version = "*" } 8 | serde = "1.0" 9 | serde_derive = "1.0" 10 | -------------------------------------------------------------------------------- /src/theme/searcher/mod.rs: -------------------------------------------------------------------------------- 1 | //! Theme dependencies for in-browser search. Not included in mdbook when 2 | //! the "search" cargo feature is disabled. 3 | 4 | pub static JS: &[u8] = include_bytes!("searcher.js"); 5 | pub static MARK_JS: &[u8] = include_bytes!("mark.min.js"); 6 | pub static ELASTICLUNR_JS: &[u8] = include_bytes!("elasticlunr.min.js"); 7 | -------------------------------------------------------------------------------- /test_book/src/individual/README.md: -------------------------------------------------------------------------------- 1 | # Individual Common mark tags 2 | 3 | This contains following tags: 4 | 5 | - Headings 6 | - Paragraphs 7 | - Line breaks 8 | - Emphasis 9 | - Blockquotes 10 | - Lists 11 | - Code blocks 12 | - Images 13 | - Links and Horizontal rules 14 | - Github tables 15 | - Github Task Lists 16 | - Strikethrough 17 | - Mixed 18 | -------------------------------------------------------------------------------- /test_book/src/individual/emphasis.md: -------------------------------------------------------------------------------- 1 | # Emphasis 2 | 3 | This has **bold text** in between normal. 4 | 5 | This has _italic text_ in between normal. 6 | 7 | A **line** having _both_, bold and italic text. 8 | 9 | **A bold line _having_ italic text** 10 | 11 | _An Italic line having **bold** text_ 12 | 13 | Now this is going **_out of hands_**. 14 | -------------------------------------------------------------------------------- /src/theme/redirect.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Redirecting... 6 | 7 | 8 | 9 | 10 |

Redirecting to... {{url}}.

11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/dummy_book/src/first/partially-included-test-with-anchors.rs: -------------------------------------------------------------------------------- 1 | fn some_other_function() { 2 | // ANCHOR: unused-anchor-that-should-be-stripped 3 | assert!($TEST_STATUS); 4 | // ANCHOR_END: unused-anchor-that-should-be-stripped 5 | } 6 | 7 | // ANCHOR: rustdoc-include-anchor 8 | fn main() { 9 | some_other_function(); 10 | } 11 | // ANCHOR_END: rustdoc-include-anchor 12 | -------------------------------------------------------------------------------- /tests/dummy_book/src/conclusion.md: -------------------------------------------------------------------------------- 1 | # Conclusion 2 | 3 |

4 | 5 | I put <HTML> in here!
6 |

7 | 14 | 21 | -------------------------------------------------------------------------------- /test_book/src/individual/link_hr.md: -------------------------------------------------------------------------------- 1 | # Links and Horizontal Rule 2 | 3 | This is followed by a Horizontal rule 4 | 5 | --- 6 | 7 | And this is preceded by a horizontal rule. 8 | 9 | [This](www.rust-lang.org) should link to rust-lang website 10 | [So should this][rl]. 11 | **[This][rl]** is a strong link. 12 | _[This][rl]_ is italic. 13 | **_[This][rl]_** is both. 14 | 15 | [rl]: www.rust-lang.org 16 | -------------------------------------------------------------------------------- /test_book/src/individual/list.md: -------------------------------------------------------------------------------- 1 | # Lists 2 | 3 | 1. A 4 | 2. Normal 5 | 3. Ordered 6 | 4. List 7 | 8 | --- 9 | 10 | 1. A 11 | 1. Nested 12 | 2. List 13 | 2. But 14 | 3. Still 15 | 4. Normal 16 | 17 | --- 18 | 19 | - An 20 | - Unordered 21 | - Normal 22 | - List 23 | 24 | --- 25 | 26 | - Nested 27 | - Unordered 28 | - List 29 | 30 | --- 31 | 32 | - This 33 | 1. Is 34 | 2. Normal 35 | - ?! 36 | -------------------------------------------------------------------------------- /src/theme/playground_editor/mod.rs: -------------------------------------------------------------------------------- 1 | //! Theme dependencies for the playground editor. 2 | 3 | pub static JS: &[u8] = include_bytes!("editor.js"); 4 | pub static ACE_JS: &[u8] = include_bytes!("ace.js"); 5 | pub static MODE_RUST_JS: &[u8] = include_bytes!("mode-rust.js"); 6 | pub static THEME_DAWN_JS: &[u8] = include_bytes!("theme-dawn.js"); 7 | pub static THEME_TOMORROW_NIGHT_JS: &[u8] = include_bytes!("theme-tomorrow_night.js"); 8 | -------------------------------------------------------------------------------- /test_book/src/rust/rust_codeblock.md: -------------------------------------------------------------------------------- 1 | ## Rust codeblocks 2 | 3 | This contains various examples of codeblocks, specific to rust 4 | 5 | ## Simple 6 | 7 | ```rust 8 | fn main(){ 9 | println!("Hello world!"); 10 | } 11 | ``` 12 | 13 | ## With Hidden lines 14 | 15 | ```rust 16 | # fn main(){ 17 | println!("Hello world!"); 18 | # } 19 | ``` 20 | 21 | ## Editable 22 | 23 | ```rust,editable 24 | fn main(){ 25 | println!("Hello world!"); 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /test_book/src/individual/blockquote.md: -------------------------------------------------------------------------------- 1 | # Blockquote 2 | 3 | > This is a quoted sentence. 4 | 5 | > This is a quoted paragraph 6 | > 7 | > separated lines 8 | > here 9 | 10 | > Nested 11 | > 12 | > > Quoted 13 | > > Paragraph 14 | 15 | > ### And now, 16 | > 17 | > **Let us _introduce_** 18 | > All kinds of 19 | > 20 | > - tags 21 | > - etc 22 | > - stuff 23 | > 24 | > 1. In 25 | > 2. The 26 | > 3. blockquote 27 | > 28 | > > cause we can 29 | > > 30 | > > > Cause we can 31 | -------------------------------------------------------------------------------- /tests/dummy_book/src/first/unicode.md: -------------------------------------------------------------------------------- 1 | # Unicode stress tests 2 | 3 | Please be careful editing, this contains carefully crafted characters. 4 | 5 | Two byte character: spatiëring 6 | 7 | Combining character: spatiëring 8 | 9 | Three byte character: 书こんにちは 10 | 11 | Four byte character: 𐌀‮𐌁‮𐌂‮𐌃‮𐌄‮𐌅‮𐌆‮𐌇‮𐌈‬ 12 | 13 | Right-to-left: مرحبا 14 | 15 | Emoticons: 🔊 😍 💜 1️⃣ 16 | 17 | right-to-left mark: hello באמת!‏ 18 | 19 | 20 | Zalgo: ǫ̛̖̱̗̝͈̋͒͋̏ͥͫ̒̆ͩ̏͌̾͊͐ͪ̾̚ 21 | 22 | -------------------------------------------------------------------------------- /tests/dummy_book/src/second/nested.md: -------------------------------------------------------------------------------- 1 | # Testing relative links for the print page 2 | 3 | When we link to [the first section](../first/nested.md), it should work on 4 | both the print page and the non-print page. 5 | 6 | A [fragment link](#some-section) should work. 7 | 8 | Link [outside](../../std/foo/bar.html). 9 | 10 | ![Some image](../images/picture.png) 11 | 12 | HTML Link 13 | 14 | raw html 15 | 16 | ## Some section 17 | -------------------------------------------------------------------------------- /ci/install-rust.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Install/update rust. 3 | # The first argument should be the toolchain to install. 4 | 5 | set -ex 6 | if [ -z "$1" ] 7 | then 8 | echo "First parameter must be toolchain to install." 9 | exit 1 10 | fi 11 | TOOLCHAIN="$1" 12 | 13 | rustup set profile minimal 14 | rustup component remove --toolchain=$TOOLCHAIN rust-docs || echo "already removed" 15 | rustup update --no-self-update $TOOLCHAIN 16 | rustup default $TOOLCHAIN 17 | rustup -V 18 | rustc -Vv 19 | cargo -V 20 | -------------------------------------------------------------------------------- /test_book/src/individual/code.md: -------------------------------------------------------------------------------- 1 | # Code 2 | 3 | This section only does simple code blocks and inline code, detailed syntax highlight and stuff is in the languages section 4 | 5 | --- 6 | 7 | ``` 8 | This is a codeblock 9 | ``` 10 | 11 | --- 12 | 13 | This line contains `inline code` 14 | 15 | --- 16 | 17 | ```` 18 | escaping ``` in ```, fun, isn't is? 19 | ```` 20 | 21 | --- 22 | 23 | ```bash,editable 24 | This is an editable codeblock 25 | ``` 26 | 27 | --- 28 | 29 | ```rust 30 | // This links to a playpen 31 | ``` 32 | -------------------------------------------------------------------------------- /tests/dummy_book/src/first/markdown.md: -------------------------------------------------------------------------------- 1 | # Markdown tests 2 | 3 | Tests for some markdown output. 4 | 5 | ## Tables 6 | 7 | | foo | bar | 8 | | --- | --- | 9 | | baz | bim | 10 | 11 | ## Footnotes 12 | 13 | Footnote example[^1], or with a word[^word]. 14 | 15 | [^1]: This is a footnote. 16 | 17 | [^word]: A longer footnote. 18 | With multiple lines. 19 | Third line. 20 | 21 | ## Strikethrough 22 | 23 | ~~strikethrough example~~ 24 | 25 | ## Tasklisks 26 | 27 | - [X] Apples 28 | - [X] Broccoli 29 | - [ ] Carrots 30 | -------------------------------------------------------------------------------- /tests/dummy_book/src/first/nested-test-with-anchors.rs: -------------------------------------------------------------------------------- 1 | // The next line will cause a `testing` test to fail if the anchor feature is broken in such a way 2 | // that the whole file gets mistakenly included. 3 | assert!(!$TEST_STATUS); 4 | 5 | // ANCHOR: myanchor 6 | // ANCHOR: unendinganchor 7 | // The next line will cause a `rendered_output` test to fail if the anchor feature is broken in 8 | // such a way that the content between anchors isn't included. 9 | // unique-string-for-anchor-test 10 | assert!($TEST_STATUS); 11 | // ANCHOR_END: myanchor 12 | -------------------------------------------------------------------------------- /tests/dummy_book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Dummy Book](README.md) 4 | 5 | --- 6 | 7 | [Introduction](intro.md) 8 | 9 | - [First Chapter](first/index.md) 10 | - [Nested Chapter](first/nested.md) 11 | - [Includes](first/includes.md) 12 | - [Recursive](first/recursive.md) 13 | - [Markdown](first/markdown.md) 14 | - [Unicode](first/unicode.md) 15 | - [No Headers](first/no-headers.md) 16 | - [Duplicate Headers](first/duplicate-headers.md) 17 | - [Second Chapter](second.md) 18 | - [Nested Chapter](second/nested.md) 19 | 20 | --- 21 | 22 | [Conclusion](conclusion.md) 23 | -------------------------------------------------------------------------------- /test_book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | title = "mdBook test book" 3 | description = "A demo book to test and validate changes" 4 | authors = ["YJDoc2"] 5 | language = "en" 6 | 7 | [rust] 8 | edition = "2018" 9 | 10 | [output.html] 11 | mathjax-support = true 12 | 13 | [output.html.playground] 14 | editable = true 15 | line-numbers = true 16 | 17 | [output.html.search] 18 | limit-results = 20 19 | use-boolean-and = true 20 | boost-title = 2 21 | boost-hierarchy = 2 22 | boost-paragraph = 1 23 | expand = true 24 | heading-split-level = 2 25 | 26 | [output.html.redirect] 27 | "/format/config.html" = "configuration/index.html" 28 | -------------------------------------------------------------------------------- /guide/src/format/configuration/README.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | This section details the configuration options available in the ***book.toml***: 4 | - **[General]** configuration including the `book`, `rust`, `build` sections 5 | - **[Preprocessor]** configuration for default and custom book preprocessors 6 | - **[Renderer]** configuration for the HTML, Markdown and custom renderers 7 | - **[Environment Variable]** configuration for overriding configuration options in your environment 8 | 9 | [General]: general.md 10 | [Preprocessor]: preprocessors.md 11 | [Renderer]: renderers.md 12 | [Environment Variable]: environment-variables.md -------------------------------------------------------------------------------- /test_book/src/languages/README.md: -------------------------------------------------------------------------------- 1 | # Syntax Highlighting 2 | 3 | This Currently contains following languages 4 | 5 | - apache 6 | - armasm 7 | - bash 8 | - c 9 | - coffeescript 10 | - cpp 11 | - csharp 12 | - css 13 | - d 14 | - diff 15 | - go 16 | - handlebars 17 | - haskell 18 | - http 19 | - ini 20 | - java 21 | - javascript 22 | - json 23 | - julia 24 | - kotlin 25 | - less 26 | - lua 27 | - makefile 28 | - markdown 29 | - nginx 30 | - objectivec 31 | - perl 32 | - php 33 | - plaintext 34 | - properties 35 | - python 36 | - r 37 | - ruby 38 | - rust 39 | - scala 40 | - scss 41 | - shell 42 | - sql 43 | - swift 44 | - typescript 45 | - x86asm 46 | - xml 47 | - yaml 48 | -------------------------------------------------------------------------------- /tests/summary_md_files/rust_ffi_guide.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Overview](./overview.md) 4 | - [Setting Up](./setting_up.md) 5 | - [Core Client Library](./client.md) 6 | - [Constructing a Basic Request](./basic_request.md) 7 | - [Sending the Request](./send_basic.md) 8 | - [Generating a Header File](./cbindgen.md) 9 | - [Better Error Handling](./error_handling.md) 10 | - [Asynchronous Operations](./async.md) 11 | - [More Complex Requests](./complex_request.md) 12 | - [Testing](./testing.md) 13 | - [Dynamic Loading & Plugins](./dynamic_loading.md) 14 | 15 | --- 16 | 17 | - [Break All The Things!!1!](./fun/index.md) 18 | - [Problems](./fun/problems.md) 19 | - [Solutions](./fun/solutions.md) -------------------------------------------------------------------------------- /tests/dummy_book/src/first/nested.md: -------------------------------------------------------------------------------- 1 | # Nested Chapter 2 | 3 | This file has some testable code. 4 | 5 | ```rust 6 | assert!($TEST_STATUS); 7 | ``` 8 | 9 | ## Some Section 10 | 11 | ```rust 12 | {{#include nested-test.rs}} 13 | ``` 14 | 15 | ## Anchors include the part of a file between special comments 16 | 17 | ```rust 18 | {{#include nested-test-with-anchors.rs:myanchor}} 19 | ``` 20 | 21 | ## Rustdoc include adds the rest of the file as hidden 22 | 23 | ```rust 24 | {{#rustdoc_include partially-included-test.rs:5:7}} 25 | ``` 26 | 27 | ## Rustdoc include works with anchors too 28 | 29 | ```rust 30 | {{#rustdoc_include partially-included-test-with-anchors.rs:rustdoc-include-anchor}} 31 | ``` 32 | -------------------------------------------------------------------------------- /tests/testing.rs: -------------------------------------------------------------------------------- 1 | mod dummy_book; 2 | 3 | use crate::dummy_book::DummyBook; 4 | 5 | use mdbook::MDBook; 6 | 7 | #[test] 8 | fn mdbook_can_correctly_test_a_passing_book() { 9 | let temp = DummyBook::new().with_passing_test(true).build().unwrap(); 10 | let mut md = MDBook::load(temp.path()).unwrap(); 11 | 12 | let result = md.test(vec![]); 13 | assert!( 14 | result.is_ok(), 15 | "Tests failed with {}", 16 | result.err().unwrap() 17 | ); 18 | } 19 | 20 | #[test] 21 | fn mdbook_detects_book_with_failing_tests() { 22 | let temp = DummyBook::new().with_passing_test(false).build().unwrap(); 23 | let mut md = MDBook::load(temp.path()).unwrap(); 24 | 25 | assert!(md.test(vec![]).is_err()); 26 | } 27 | -------------------------------------------------------------------------------- /tests/summary_md_files/example_book.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [mdBook](README.md) 4 | - [Command Line Tool](cli/cli-tool.md) 5 | - [init](cli/init.md) 6 | - [build](cli/build.md) 7 | - [watch](cli/watch.md) 8 | - [serve](cli/serve.md) 9 | - [test](cli/test.md) 10 | - [Format](format/format.md) 11 | - [SUMMARY.md](format/summary.md) 12 | - [Configuration](format/config.md) 13 | - [Theme](format/theme/theme.md) 14 | - [index.hbs](format/theme/index-hbs.md) 15 | - [Syntax highlighting](format/theme/syntax-highlighting.md) 16 | - [MathJax Support](format/mathjax.md) 17 | - [Rust code specific features](format/rust.md) 18 | - [Rust Library](lib/lib.md) 19 | ----------- 20 | [Contributors](misc/contributors.md) 21 | -------------------------------------------------------------------------------- /test_book/src/README.md: -------------------------------------------------------------------------------- 1 | # Demo Book 2 | 3 | This is a simple demo book, which is intended to be used for verifying and validating style changes in mdBook. 4 | This contains dummy examples of various markdown elements and code languages, so that one can check changes made in mdBook styles. 5 | 6 | This rough outline is : 7 | 8 | - individual : contains basic markdown elements such as headings, paragraphs, links etc. 9 | - languages : contains a `hello world` in each of supported language to see changes in syntax highlighting 10 | - rust : contains language examples specific to rust, such as play pen, runnable examples etc. 11 | 12 | This is more for checking and fixing style, rather than verifying that correct code is generated for given markdown, that is better handled in tests. 13 | -------------------------------------------------------------------------------- /ci/install-hub.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Installs the `hub` executable into hub/bin 3 | set -ex 4 | case $1 in 5 | ubuntu*) 6 | curl -LsSf https://github.com/github/hub/releases/download/v2.12.8/hub-linux-amd64-2.12.8.tgz -o hub.tgz 7 | mkdir hub 8 | tar -xzvf hub.tgz --strip=1 -C hub 9 | ;; 10 | macos*) 11 | curl -LsSf https://github.com/github/hub/releases/download/v2.12.8/hub-darwin-amd64-2.12.8.tgz -o hub.tgz 12 | mkdir hub 13 | tar -xzvf hub.tgz --strip=1 -C hub 14 | ;; 15 | windows*) 16 | curl -LsSf https://github.com/github/hub/releases/download/v2.12.8/hub-windows-amd64-2.12.8.zip -o hub.zip 17 | 7z x hub.zip -ohub 18 | ;; 19 | *) 20 | echo "OS should be first parameter, was: $1" 21 | ;; 22 | esac 23 | 24 | echo "$PWD/hub/bin" >> $GITHUB_PATH 25 | -------------------------------------------------------------------------------- /guide/src/cli/completions.md: -------------------------------------------------------------------------------- 1 | # The completions command 2 | 3 | The completions command is used to generate auto-completions for some common shells. 4 | This means when you type `mdbook` in your shell, you can then press your shell's auto-complete key (usually the Tab key) and it may display what the valid options are, or finish partial input. 5 | 6 | The completions first need to be installed for your shell: 7 | 8 | ```bash 9 | mdbook completions bash > ~/.local/share/bash-completion/completions/mdbook 10 | ``` 11 | 12 | The command prints a completion script for the given shell. 13 | Run `mdbook completions --help` for a list of supported shells. 14 | 15 | Where to place the completions depend on which shell you are using and your operating system. 16 | Consult your shell's documentation for more information one where to place the script. 17 | -------------------------------------------------------------------------------- /guide/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | title = "mdBook Documentation" 3 | description = "Create book from markdown files. Like Gitbook but implemented in Rust" 4 | authors = ["Mathieu David", "Michael-F-Bryan"] 5 | language = "en" 6 | 7 | [rust] 8 | edition = "2018" 9 | 10 | [output.html] 11 | mathjax-support = true 12 | site-url = "/mdBook/" 13 | git-repository-url = "https://github.com/rust-lang/mdBook/tree/master/guide" 14 | edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}" 15 | 16 | [output.html.playground] 17 | editable = true 18 | line-numbers = true 19 | 20 | [output.html.search] 21 | limit-results = 20 22 | use-boolean-and = true 23 | boost-title = 2 24 | boost-hierarchy = 2 25 | boost-paragraph = 1 26 | expand = true 27 | heading-split-level = 2 28 | 29 | [output.html.redirect] 30 | "/format/config.html" = "configuration/index.html" 31 | -------------------------------------------------------------------------------- /guide/src/cli/clean.md: -------------------------------------------------------------------------------- 1 | # The clean command 2 | 3 | The clean command is used to delete the generated book and any other build 4 | artifacts. 5 | 6 | ```bash 7 | mdbook clean 8 | ``` 9 | 10 | #### Specify a directory 11 | 12 | The `clean` command can take a directory as an argument to use as the book's 13 | root instead of the current working directory. 14 | 15 | ```bash 16 | mdbook clean path/to/book 17 | ``` 18 | 19 | #### --dest-dir 20 | 21 | The `--dest-dir` (`-d`) option allows you to override the book's output 22 | directory, which will be deleted by this command. Relative paths are interpreted 23 | relative to the book's root directory. If not specified it will default to the 24 | value of the `build.build-dir` key in `book.toml`, or to `./book`. 25 | 26 | ```bash 27 | mdbook clean --dest-dir=path/to/book 28 | ``` 29 | 30 | `path/to/book` could be absolute or relative. -------------------------------------------------------------------------------- /guide/src/cli/README.md: -------------------------------------------------------------------------------- 1 | # Command Line Tool 2 | 3 | The `mdbook` command-line tool is used to create and build books. 4 | After you have [installed](../guide/installation.md) `mdbook`, you can run the `mdbook help` command in your terminal to view the available commands. 5 | 6 | This following sections provide in-depth information on the different commands available. 7 | 8 | * [`mdbook init `](init.md) — Creates a new book with minimal boilerplate to start with. 9 | * [`mdbook build`](build.md) — Renders the book. 10 | * [`mdbook watch`](watch.md) — Rebuilds the book any time a source file changes. 11 | * [`mdbook serve`](serve.md) — Runs a web server to view the book, and rebuilds on changes. 12 | * [`mdbook test`](test.md) — Tests Rust code samples. 13 | * [`mdbook clean`](clean.md) — Deletes the rendered output. 14 | * [`mdbook completions`](completions.md) — Support for shell auto-completion. 15 | -------------------------------------------------------------------------------- /tests/cli/build.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::cmd::mdbook_cmd; 2 | use crate::dummy_book::DummyBook; 3 | 4 | #[test] 5 | fn mdbook_cli_dummy_book_generates_index_html() { 6 | let temp = DummyBook::new().build().unwrap(); 7 | 8 | // doesn't exist before 9 | assert!(!temp.path().join("book").exists()); 10 | 11 | let mut cmd = mdbook_cmd(); 12 | cmd.arg("build").current_dir(temp.path()); 13 | cmd.assert() 14 | .success() 15 | .stderr( 16 | predicates::str::is_match(r##"Stack depth exceeded in first[\\/]recursive.md."##) 17 | .unwrap(), 18 | ) 19 | .stderr(predicates::str::contains( 20 | r##"[INFO] (mdbook::book): Running the html backend"##, 21 | )); 22 | 23 | // exists afterward 24 | assert!(temp.path().join("book").exists()); 25 | 26 | let index_file = temp.path().join("book/index.html"); 27 | assert!(index_file.exists()); 28 | } 29 | -------------------------------------------------------------------------------- /src/theme/css/print.css: -------------------------------------------------------------------------------- 1 | 2 | #sidebar, 3 | #menu-bar, 4 | .nav-chapters, 5 | .mobile-nav-chapters { 6 | display: none; 7 | } 8 | 9 | #page-wrapper.page-wrapper { 10 | transform: none; 11 | margin-left: 0px; 12 | overflow-y: initial; 13 | } 14 | 15 | #content { 16 | max-width: none; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | .page { 22 | overflow-y: initial; 23 | } 24 | 25 | code { 26 | background-color: #666666; 27 | border-radius: 5px; 28 | 29 | /* Force background to be printed in Chrome */ 30 | -webkit-print-color-adjust: exact; 31 | } 32 | 33 | pre > .buttons { 34 | z-index: 2; 35 | } 36 | 37 | a, a:visited, a:active, a:hover { 38 | color: #4183c4; 39 | text-decoration: none; 40 | } 41 | 42 | h1, h2, h3, h4, h5, h6 { 43 | page-break-inside: avoid; 44 | page-break-after: avoid; 45 | } 46 | 47 | pre, code { 48 | page-break-inside: avoid; 49 | white-space: pre-wrap; 50 | } 51 | 52 | .fa { 53 | display: none !important; 54 | } 55 | -------------------------------------------------------------------------------- /src/theme/playground_editor/editor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | window.editors = []; 3 | (function(editors) { 4 | if (typeof(ace) === 'undefined' || !ace) { 5 | return; 6 | } 7 | 8 | Array.from(document.querySelectorAll('.editable')).forEach(function(editable) { 9 | let display_line_numbers = window.playground_line_numbers || false; 10 | 11 | let editor = ace.edit(editable); 12 | editor.setOptions({ 13 | highlightActiveLine: false, 14 | showPrintMargin: false, 15 | showLineNumbers: display_line_numbers, 16 | showGutter: display_line_numbers, 17 | maxLines: Infinity, 18 | fontSize: "0.875em" // please adjust the font size of the code in general.css 19 | }); 20 | 21 | editor.$blockScrolling = Infinity; 22 | 23 | editor.getSession().setMode("ace/mode/rust"); 24 | 25 | editor.originalCode = editor.getValue(); 26 | 27 | editors.push(editor); 28 | }); 29 | })(window.editors); 30 | -------------------------------------------------------------------------------- /src/renderer/html_handlebars/helpers/theme.rs: -------------------------------------------------------------------------------- 1 | use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError}; 2 | 3 | pub fn theme_option( 4 | h: &Helper<'_, '_>, 5 | _r: &Handlebars<'_>, 6 | ctx: &Context, 7 | rc: &mut RenderContext<'_, '_>, 8 | out: &mut dyn Output, 9 | ) -> Result<(), RenderError> { 10 | trace!("theme_option (handlebars helper)"); 11 | 12 | let param = h.param(0).and_then(|v| v.value().as_str()).ok_or_else(|| { 13 | RenderError::new("Param 0 with String type is required for theme_option helper.") 14 | })?; 15 | 16 | let default_theme = rc.evaluate(ctx, "@root/default_theme")?; 17 | let default_theme_name = default_theme 18 | .as_json() 19 | .as_str() 20 | .ok_or_else(|| RenderError::new("Type error for `default_theme`, string expected"))?; 21 | 22 | out.write(param)?; 23 | if param.to_lowercase() == default_theme_name.to_lowercase() { 24 | out.write(" (default)")?; 25 | } 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /test_book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Prefix Chapter](prefix.md) 4 | 5 | --- 6 | 7 | - [Introduction](README.md) 8 | - [Draft Chapter]() 9 | 10 | # Actual Markdown Tag Examples 11 | 12 | - [Markdown Individual tags](individual/README.md) 13 | - [Heading](individual/heading.md) 14 | - [Paragraphs](individual/paragraph.md) 15 | - [Line Break](individual/linebreak.md) 16 | - [Emphasis](individual/emphasis.md) 17 | - [Blockquote](individual/blockquote.md) 18 | - [List](individual/list.md) 19 | - [Code](individual/code.md) 20 | - [Image](individual/image.md) 21 | - [Links and Horizontal Rule](individual/link_hr.md) 22 | - [Tables](individual/table.md) 23 | - [Tasks](individual/task.md) 24 | - [Strikethrough](individual/strikethrough.md) 25 | - [Mixed](individual/mixed.md) 26 | - [Languages](languages/README.md) 27 | - [Syntax Highlight](languages/highlight.md) 28 | - [Rust Specific](rust/README.md) 29 | - [Rust Codeblocks](rust/rust_codeblock.md) 30 | 31 | --- 32 | 33 | [Suffix Chapter](suffix.md) 34 | -------------------------------------------------------------------------------- /test_book/src/individual/image.md: -------------------------------------------------------------------------------- 1 | # Images 2 | 3 | For copyright and trademark information on these images, please check [rust-artwork repository](https://github.com/rust-lang/rust-artworkhttps://github.com/rust-lang/rust-artwork) 4 | 5 | ## A 16x16 image 6 | 7 | ![16x16 rust-lang logo](http://rust-lang.org/logos/rust-logo-16x16.png) 8 | 9 | ## A 32x32 image 10 | 11 | ![32x32 rust-lang logo](http://rust-lang.org/logos/rust-logo-32x32-blk.png) 12 | 13 | ## A 256x256 image 14 | 15 | ![256x256 rust-lang logo](http://rust-lang.org/logos/rust-logo-256x256.png) 16 | 17 | ## A 512x512 image 18 | 19 | ![512x512 rust-lang logo](http://rust-lang.org/logos/rust-logo-512x512-blk.png) 20 | 21 | ## A large image 22 | 23 | ![2018 rust-conf art](https://raw.githubusercontent.com/rust-lang/rust-artwork/master/2018-RustConf/lucy-mountain-climber.png) 24 | 25 | ## A SVG image 26 | 27 | ![2018 rust-conf art svg](https://raw.githubusercontent.com/rust-lang/rust-artwork/461afe27d8e02451cf9f46e507f2c2a71d2b276b/2018-RustConf/lucy-mountain-climber.svg) 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mdBook 2 | 3 | [![Build Status](https://github.com/rust-lang/mdBook/workflows/CI/badge.svg?event=push)](https://github.com/rust-lang/mdBook/actions?workflow=CI) 4 | [![crates.io](https://img.shields.io/crates/v/mdbook.svg)](https://crates.io/crates/mdbook) 5 | [![LICENSE](https://img.shields.io/github/license/rust-lang/mdBook.svg)](LICENSE) 6 | 7 | mdBook is a utility to create modern online books from Markdown files. 8 | 9 | Check out the **[User Guide]** for a list of features and installation and usage information. 10 | The User Guide also serves as a demonstration to showcase what a book looks like. 11 | 12 | If you are interested in contributing to the development of mdBook, check out the [Contribution Guide]. 13 | 14 | ## License 15 | 16 | All the code in this repository is released under the ***Mozilla Public License v2.0***, for more information take a look at the [LICENSE] file. 17 | 18 | [User Guide]: https://rust-lang.github.io/mdBook/ 19 | [contribution guide]: https://github.com/rust-lang/mdBook/blob/master/CONTRIBUTING.md 20 | [LICENSE]: https://github.com/rust-lang/mdBook/blob/master/LICENSE 21 | -------------------------------------------------------------------------------- /guide/src/misc/contributors.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | Here is a list of the contributors who have helped improving mdBook. Big 4 | shout-out to them! 5 | 6 | - [mdinger](https://github.com/mdinger) 7 | - Kevin ([kbknapp](https://github.com/kbknapp)) 8 | - Steve Klabnik ([steveklabnik](https://github.com/steveklabnik)) 9 | - Adam Solove ([asolove](https://github.com/asolove)) 10 | - Wayne Nilsen ([waynenilsen](https://github.com/waynenilsen)) 11 | - [funnkill](https://github.com/funkill) 12 | - Fu Gangqiang ([FuGangqiang](https://github.com/FuGangqiang)) 13 | - [Michael-F-Bryan](https://github.com/Michael-F-Bryan) 14 | - Chris Spiegel ([cspiegel](https://github.com/cspiegel)) 15 | - [projektir](https://github.com/projektir) 16 | - [Phaiax](https://github.com/Phaiax) 17 | - Matt Ickstadt ([mattico](https://github.com/mattico)) 18 | - Weihang Lo ([weihanglo](https://github.com/weihanglo)) 19 | - Avision Ho ([avisionh](https://github.com/avisionh)) 20 | - Vivek Akupatni ([apatniv](https://github.com/apatniv)) 21 | - Eric Huss ([ehuss](https://github.com/ehuss)) 22 | - Josh Rotenberg ([joshrotenberg](https://github.com/joshrotenberg)) 23 | 24 | If you feel you're missing from this list, feel free to add yourself in a PR. 25 | -------------------------------------------------------------------------------- /guide/src/format/theme/editor.md: -------------------------------------------------------------------------------- 1 | # Editor 2 | 3 | In addition to providing runnable code playgrounds, mdBook optionally allows them 4 | to be editable. In order to enable editable code blocks, the following needs to 5 | be added to the ***book.toml***: 6 | 7 | ```toml 8 | [output.html.playground] 9 | editable = true 10 | ``` 11 | 12 | To make a specific block available for editing, the attribute `editable` needs 13 | to be added to it: 14 | 15 | ~~~markdown 16 | ```rust,editable 17 | fn main() { 18 | let number = 5; 19 | print!("{}", number); 20 | } 21 | ``` 22 | ~~~ 23 | 24 | The above will result in this editable playground: 25 | 26 | ```rust,editable 27 | fn main() { 28 | let number = 5; 29 | print!("{}", number); 30 | } 31 | ``` 32 | 33 | Note the new `Undo Changes` button in the editable playgrounds. 34 | 35 | ## Customizing the Editor 36 | 37 | By default, the editor is the [Ace](https://ace.c9.io/) editor, but, if desired, 38 | the functionality may be overridden by providing a different folder: 39 | 40 | ```toml 41 | [output.html.playground] 42 | editable = true 43 | editor = "/path/to/editor" 44 | ``` 45 | 46 | Note that for the editor changes to function correctly, the `book.js` inside of 47 | the `theme` folder will need to be overridden as it has some couplings with the 48 | default Ace editor. 49 | -------------------------------------------------------------------------------- /src/theme/ayu-highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | Based off of the Ayu theme 3 | Original by Dempfi (https://github.com/dempfi/ayu) 4 | */ 5 | 6 | .hljs { 7 | display: block; 8 | overflow-x: auto; 9 | background: #191f26; 10 | color: #e6e1cf; 11 | padding: 0.5em; 12 | } 13 | 14 | .hljs-comment, 15 | .hljs-quote { 16 | color: #5c6773; 17 | font-style: italic; 18 | } 19 | 20 | .hljs-variable, 21 | .hljs-template-variable, 22 | .hljs-attribute, 23 | .hljs-attr, 24 | .hljs-regexp, 25 | .hljs-link, 26 | .hljs-selector-id, 27 | .hljs-selector-class { 28 | color: #ff7733; 29 | } 30 | 31 | .hljs-number, 32 | .hljs-meta, 33 | .hljs-builtin-name, 34 | .hljs-literal, 35 | .hljs-type, 36 | .hljs-params { 37 | color: #ffee99; 38 | } 39 | 40 | .hljs-string, 41 | .hljs-bullet { 42 | color: #b8cc52; 43 | } 44 | 45 | .hljs-title, 46 | .hljs-built_in, 47 | .hljs-section { 48 | color: #ffb454; 49 | } 50 | 51 | .hljs-keyword, 52 | .hljs-selector-tag, 53 | .hljs-symbol { 54 | color: #ff7733; 55 | } 56 | 57 | .hljs-name { 58 | color: #36a3d9; 59 | } 60 | 61 | .hljs-tag { 62 | color: #00568d; 63 | } 64 | 65 | .hljs-emphasis { 66 | font-style: italic; 67 | } 68 | 69 | .hljs-strong { 70 | font-weight: bold; 71 | } 72 | 73 | .hljs-addition { 74 | color: #91b362; 75 | } 76 | 77 | .hljs-deletion { 78 | color: #d96c75; 79 | } 80 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | release: 4 | types: [created] 5 | 6 | jobs: 7 | release: 8 | name: Deploy Release 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, macos-latest, windows-latest] 13 | steps: 14 | - uses: actions/checkout@master 15 | - name: Install hub 16 | run: ci/install-hub.sh ${{ matrix.os }} 17 | shell: bash 18 | - name: Install Rust 19 | run: ci/install-rust.sh stable 20 | shell: bash 21 | - name: Build and deploy artifacts 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | run: ci/make-release.sh ${{ matrix.os }} 25 | shell: bash 26 | pages: 27 | name: GitHub Pages 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@master 31 | - name: Install Rust (rustup) 32 | run: rustup update stable --no-self-update && rustup default stable 33 | - name: Build book 34 | run: cargo run -- build guide 35 | - name: Deploy to GitHub 36 | env: 37 | GITHUB_DEPLOY_KEY: ${{ secrets.GITHUB_DEPLOY_KEY }} 38 | run: | 39 | touch guide/book/.nojekyll 40 | curl -LsSf https://raw.githubusercontent.com/rust-lang/simpleinfra/master/setup-deploy-keys/src/deploy.rs | rustc - -o /tmp/deploy 41 | cd guide/book 42 | /tmp/deploy 43 | -------------------------------------------------------------------------------- /guide/src/cli/build.md: -------------------------------------------------------------------------------- 1 | # The build command 2 | 3 | The build command is used to render your book: 4 | 5 | ```bash 6 | mdbook build 7 | ``` 8 | 9 | It will try to parse your `SUMMARY.md` file to understand the structure of your 10 | book and fetch the corresponding files. Note that files mentioned in `SUMMARY.md` 11 | but not present will be created. 12 | 13 | The rendered output will maintain the same directory structure as the source for 14 | convenience. Large books will therefore remain structured when rendered. 15 | 16 | #### Specify a directory 17 | 18 | The `build` command can take a directory as an argument to use as the book's 19 | root instead of the current working directory. 20 | 21 | ```bash 22 | mdbook build path/to/book 23 | ``` 24 | 25 | #### --open 26 | 27 | When you use the `--open` (`-o`) flag, mdbook will open the rendered book in 28 | your default web browser after building it. 29 | 30 | #### --dest-dir 31 | 32 | The `--dest-dir` (`-d`) option allows you to change the output directory for the 33 | book. Relative paths are interpreted relative to the book's root directory. If 34 | not specified it will default to the value of the `build.build-dir` key in 35 | `book.toml`, or to `./book`. 36 | 37 | ------------------- 38 | 39 | ***Note:*** *The build command copies all files (excluding files with `.md` extension) from the source directory 40 | into the build directory.* 41 | -------------------------------------------------------------------------------- /guide/src/format/mathjax.md: -------------------------------------------------------------------------------- 1 | # MathJax Support 2 | 3 | mdBook has optional support for math equations through 4 | [MathJax](https://www.mathjax.org/). 5 | 6 | To enable MathJax, you need to add the `mathjax-support` key to your `book.toml` 7 | under the `output.html` section. 8 | 9 | ```toml 10 | [output.html] 11 | mathjax-support = true 12 | ``` 13 | 14 | >**Note:** The usual delimiters MathJax uses are not yet supported. You can't 15 | currently use `$$ ... $$` as delimiters and the `\[ ... \]` delimiters need an 16 | extra backslash to work. Hopefully this limitation will be lifted soon. 17 | 18 | >**Note:** When you use double backslashes in MathJax blocks (for example in 19 | > commands such as `\begin{cases} \frac 1 2 \\ \frac 3 4 \end{cases}`) you need 20 | > to add _two extra_ backslashes (e.g., `\begin{cases} \frac 1 2 \\\\ \frac 3 4 21 | > \end{cases}`). 22 | 23 | 24 | ### Inline equations 25 | Inline equations are delimited by `\\(` and `\\)`. So for example, to render the 26 | following inline equation \\( \int x dx = \frac{x^2}{2} + C \\) you would write 27 | the following: 28 | ``` 29 | \\( \int x dx = \frac{x^2}{2} + C \\) 30 | ``` 31 | 32 | ### Block equations 33 | Block equations are delimited by `\\[` and `\\]`. To render the following 34 | equation 35 | 36 | \\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\] 37 | 38 | 39 | you would write: 40 | 41 | ```bash 42 | \\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\] 43 | ``` 44 | -------------------------------------------------------------------------------- /ci/make-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Builds the release and creates an archive and optionally deploys to GitHub. 3 | set -ex 4 | 5 | if [[ -z "$GITHUB_REF" ]] 6 | then 7 | echo "GITHUB_REF must be set" 8 | exit 1 9 | fi 10 | # Strip mdbook-refs/tags/ from the start of the ref. 11 | TAG=${GITHUB_REF#*/tags/} 12 | 13 | host=$(rustc -Vv | grep ^host: | sed -e "s/host: //g") 14 | export CARGO_PROFILE_RELEASE_LTO=true 15 | cargo build --bin mdbook --release 16 | cd target/release 17 | case $1 in 18 | ubuntu*) 19 | asset="mdbook-$TAG-$host.tar.gz" 20 | tar czf ../../$asset mdbook 21 | ;; 22 | macos*) 23 | asset="mdbook-$TAG-$host.tar.gz" 24 | # There is a bug with BSD tar on macOS where the first 8MB of the file are 25 | # sometimes all NUL bytes. See https://github.com/actions/cache/issues/403 26 | # and https://github.com/rust-lang/cargo/issues/8603 for some more 27 | # information. An alternative solution here is to install GNU tar, but 28 | # flushing the disk cache seems to work, too. 29 | sudo /usr/sbin/purge 30 | tar czf ../../$asset mdbook 31 | ;; 32 | windows*) 33 | asset="mdbook-$TAG-$host.zip" 34 | 7z a ../../$asset mdbook.exe 35 | ;; 36 | *) 37 | echo "OS should be first parameter, was: $1" 38 | ;; 39 | esac 40 | cd ../.. 41 | 42 | if [[ -z "$GITHUB_TOKEN" ]] 43 | then 44 | echo "$GITHUB_TOKEN not set, skipping deploy." 45 | else 46 | hub release edit -m "" --attach $asset $TAG 47 | fi 48 | -------------------------------------------------------------------------------- /tests/custom_preprocessors.rs: -------------------------------------------------------------------------------- 1 | mod dummy_book; 2 | 3 | use crate::dummy_book::DummyBook; 4 | use mdbook::preprocess::{CmdPreprocessor, Preprocessor}; 5 | use mdbook::MDBook; 6 | 7 | fn example() -> CmdPreprocessor { 8 | CmdPreprocessor::new( 9 | "nop-preprocessor".to_string(), 10 | "cargo run --example nop-preprocessor --".to_string(), 11 | ) 12 | } 13 | 14 | #[test] 15 | fn example_supports_whatever() { 16 | let cmd = example(); 17 | 18 | let got = cmd.supports_renderer("whatever"); 19 | 20 | assert_eq!(got, true); 21 | } 22 | 23 | #[test] 24 | fn example_doesnt_support_not_supported() { 25 | let cmd = example(); 26 | 27 | let got = cmd.supports_renderer("not-supported"); 28 | 29 | assert_eq!(got, false); 30 | } 31 | 32 | #[test] 33 | fn ask_the_preprocessor_to_blow_up() { 34 | let dummy_book = DummyBook::new(); 35 | let temp = dummy_book.build().unwrap(); 36 | let mut md = MDBook::load(temp.path()).unwrap(); 37 | md.with_preprocessor(example()); 38 | 39 | md.config 40 | .set("preprocessor.nop-preprocessor.blow-up", true) 41 | .unwrap(); 42 | 43 | let got = md.build(); 44 | 45 | assert!(got.is_err()); 46 | } 47 | 48 | #[test] 49 | fn process_the_dummy_book() { 50 | let dummy_book = DummyBook::new(); 51 | let temp = dummy_book.build().unwrap(); 52 | let mut md = MDBook::load(temp.path()).unwrap(); 53 | md.with_preprocessor(example()); 54 | 55 | md.build().unwrap(); 56 | } 57 | -------------------------------------------------------------------------------- /tests/parse_existing_summary_files.rs: -------------------------------------------------------------------------------- 1 | //! Some integration tests to make sure the `SUMMARY.md` parser can deal with 2 | //! some real-life examples. 3 | 4 | use mdbook::book; 5 | use std::fs::File; 6 | use std::io::Read; 7 | use std::path::Path; 8 | 9 | macro_rules! summary_md_test { 10 | ($name:ident, $filename:expr) => { 11 | #[test] 12 | fn $name() { 13 | env_logger::try_init().ok(); 14 | 15 | let filename = Path::new(env!("CARGO_MANIFEST_DIR")) 16 | .join("tests") 17 | .join("summary_md_files") 18 | .join($filename); 19 | 20 | if !filename.exists() { 21 | panic!("{} Doesn't exist", filename.display()); 22 | } 23 | 24 | let mut content = String::new(); 25 | File::open(&filename) 26 | .unwrap() 27 | .read_to_string(&mut content) 28 | .unwrap(); 29 | 30 | if let Err(e) = book::parse_summary(&content) { 31 | eprintln!("Error parsing {}", filename.display()); 32 | eprintln!(); 33 | eprintln!("{:?}", e); 34 | panic!(); 35 | } 36 | } 37 | }; 38 | } 39 | 40 | summary_md_test!(rust_by_example, "rust_by_example.md"); 41 | summary_md_test!(rust_ffi_guide, "rust_ffi_guide.md"); 42 | summary_md_test!(example_book, "example_book.md"); 43 | summary_md_test!(the_book_2nd_edition, "the_book-2nd_edition.md"); 44 | -------------------------------------------------------------------------------- /src/cmd/clean.rs: -------------------------------------------------------------------------------- 1 | use crate::get_book_dir; 2 | use anyhow::Context; 3 | use clap::{arg, App, Arg, ArgMatches}; 4 | use mdbook::MDBook; 5 | use std::fs; 6 | 7 | // Create clap subcommand arguments 8 | pub fn make_subcommand<'help>() -> App<'help> { 9 | App::new("clean") 10 | .about("Deletes a built book") 11 | .arg( 12 | Arg::new("dest-dir") 13 | .short('d') 14 | .long("dest-dir") 15 | .value_name("dest-dir") 16 | .help( 17 | "Output directory for the book{n}\ 18 | Relative paths are interpreted relative to the book's root directory.{n}\ 19 | If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.", 20 | ), 21 | ) 22 | .arg(arg!([dir] 23 | "Root directory for the book{n}\ 24 | (Defaults to the Current Directory when omitted)" 25 | )) 26 | } 27 | 28 | // Clean command implementation 29 | pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> { 30 | let book_dir = get_book_dir(args); 31 | let book = MDBook::load(&book_dir)?; 32 | 33 | let dir_to_remove = match args.value_of("dest-dir") { 34 | Some(dest_dir) => dest_dir.into(), 35 | None => book.root.join(&book.config.build.build_dir), 36 | }; 37 | 38 | if dir_to_remove.exists() { 39 | fs::remove_dir_all(&dir_to_remove) 40 | .with_context(|| "Unable to remove the build directory")?; 41 | } 42 | 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | # Only run when merging to master, or open/synchronize/reopen a PR. 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | name: Test 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | build: [stable, beta, nightly, macos, windows, msrv] 16 | include: 17 | - build: stable 18 | os: ubuntu-latest 19 | rust: stable 20 | - build: beta 21 | os: ubuntu-latest 22 | rust: beta 23 | - build: nightly 24 | os: ubuntu-latest 25 | rust: nightly 26 | - build: macos 27 | os: macos-latest 28 | rust: stable 29 | - build: windows 30 | os: windows-latest 31 | rust: stable 32 | - build: msrv 33 | os: ubuntu-latest 34 | # sync MSRV with docs: guide/src/guide/installation.md 35 | rust: 1.54.0 36 | steps: 37 | - uses: actions/checkout@master 38 | - name: Install Rust 39 | run: bash ci/install-rust.sh ${{ matrix.rust }} 40 | - name: Build and run tests 41 | run: cargo test 42 | - name: Test no default 43 | run: cargo test --no-default-features 44 | 45 | rustfmt: 46 | name: Rustfmt 47 | runs-on: ubuntu-latest 48 | steps: 49 | - uses: actions/checkout@master 50 | - name: Install Rust 51 | run: rustup update stable && rustup default stable && rustup component add rustfmt 52 | - run: cargo fmt --check 53 | -------------------------------------------------------------------------------- /src/theme/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | * An increased contrast highlighting scheme loosely based on the 3 | * "Base16 Atelier Dune Light" theme by Bram de Haan 4 | * (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) 5 | * Original Base16 color scheme by Chris Kempson 6 | * (https://github.com/chriskempson/base16) 7 | */ 8 | 9 | /* Comment */ 10 | .hljs-comment, 11 | .hljs-quote { 12 | color: #575757; 13 | } 14 | 15 | /* Red */ 16 | .hljs-variable, 17 | .hljs-template-variable, 18 | .hljs-attribute, 19 | .hljs-tag, 20 | .hljs-name, 21 | .hljs-regexp, 22 | .hljs-link, 23 | .hljs-name, 24 | .hljs-selector-id, 25 | .hljs-selector-class { 26 | color: #d70025; 27 | } 28 | 29 | /* Orange */ 30 | .hljs-number, 31 | .hljs-meta, 32 | .hljs-built_in, 33 | .hljs-builtin-name, 34 | .hljs-literal, 35 | .hljs-type, 36 | .hljs-params { 37 | color: #b21e00; 38 | } 39 | 40 | /* Green */ 41 | .hljs-string, 42 | .hljs-symbol, 43 | .hljs-bullet { 44 | color: #008200; 45 | } 46 | 47 | /* Blue */ 48 | .hljs-title, 49 | .hljs-section { 50 | color: #0030f2; 51 | } 52 | 53 | /* Purple */ 54 | .hljs-keyword, 55 | .hljs-selector-tag { 56 | color: #9d00ec; 57 | } 58 | 59 | .hljs { 60 | display: block; 61 | overflow-x: auto; 62 | background: #f6f7f6; 63 | color: #000; 64 | padding: 0.5em; 65 | } 66 | 67 | .hljs-emphasis { 68 | font-style: italic; 69 | } 70 | 71 | .hljs-strong { 72 | font-weight: bold; 73 | } 74 | 75 | .hljs-addition { 76 | color: #22863a; 77 | background-color: #f0fff4; 78 | } 79 | 80 | .hljs-deletion { 81 | color: #b31d28; 82 | background-color: #ffeef0; 83 | } 84 | -------------------------------------------------------------------------------- /guide/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](README.md) 4 | 5 | # User Guide 6 | 7 | - [Installation](guide/installation.md) 8 | - [Reading Books](guide/reading.md) 9 | - [Creating a Book](guide/creating.md) 10 | 11 | # Reference Guide 12 | 13 | - [Command Line Tool](cli/README.md) 14 | - [init](cli/init.md) 15 | - [build](cli/build.md) 16 | - [watch](cli/watch.md) 17 | - [serve](cli/serve.md) 18 | - [test](cli/test.md) 19 | - [clean](cli/clean.md) 20 | - [completions](cli/completions.md) 21 | - [Format](format/README.md) 22 | - [SUMMARY.md](format/summary.md) 23 | - [Draft chapter]() 24 | - [Configuration](format/configuration/README.md) 25 | - [General](format/configuration/general.md) 26 | - [Preprocessors](format/configuration/preprocessors.md) 27 | - [Renderers](format/configuration/renderers.md) 28 | - [Environment Variables](format/configuration/environment-variables.md) 29 | - [Theme](format/theme/README.md) 30 | - [index.hbs](format/theme/index-hbs.md) 31 | - [Syntax highlighting](format/theme/syntax-highlighting.md) 32 | - [Editor](format/theme/editor.md) 33 | - [MathJax Support](format/mathjax.md) 34 | - [mdBook-specific features](format/mdbook.md) 35 | - [Markdown](format/markdown.md) 36 | - [Continuous Integration](continuous-integration.md) 37 | - [For Developers](for_developers/README.md) 38 | - [Preprocessors](for_developers/preprocessors.md) 39 | - [Alternative Backends](for_developers/backends.md) 40 | 41 | ----------- 42 | 43 | [Contributors](misc/contributors.md) 44 | -------------------------------------------------------------------------------- /guide/src/for_developers/mdbook-wordcount/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate mdbook; 2 | extern crate serde; 3 | #[macro_use] 4 | extern crate serde_derive; 5 | 6 | use std::process; 7 | use std::fs::{self, File}; 8 | use std::io::{self, Write}; 9 | use mdbook::renderer::RenderContext; 10 | use mdbook::book::{BookItem, Chapter}; 11 | 12 | fn main() { 13 | let mut stdin = io::stdin(); 14 | let ctx = RenderContext::from_json(&mut stdin).unwrap(); 15 | let cfg: WordcountConfig = ctx.config 16 | .get_deserialized("output.wordcount") 17 | .unwrap_or_default(); 18 | 19 | let _ = fs::create_dir_all(&ctx.destination); 20 | let mut f = File::create(ctx.destination.join("wordcounts.txt")).unwrap(); 21 | 22 | for item in ctx.book.iter() { 23 | if let BookItem::Chapter(ref ch) = *item { 24 | if cfg.ignores.contains(&ch.name) { 25 | continue; 26 | } 27 | 28 | let num_words = count_words(ch); 29 | println!("{}: {}", ch.name, num_words); 30 | writeln!(f, "{}: {}", ch.name, num_words).unwrap(); 31 | 32 | if cfg.deny_odds && num_words % 2 == 1 { 33 | eprintln!("{} has an odd number of words!", ch.name); 34 | process::exit(1); 35 | } 36 | } 37 | } 38 | } 39 | 40 | fn count_words(ch: &Chapter) -> usize { 41 | ch.content.split_whitespace().count() 42 | } 43 | 44 | #[derive(Debug, Default, Serialize, Deserialize)] 45 | #[serde(default, rename_all = "kebab-case")] 46 | pub struct WordcountConfig { 47 | pub ignores: Vec, 48 | pub deny_odds: bool, 49 | } 50 | -------------------------------------------------------------------------------- /guide/src/cli/watch.md: -------------------------------------------------------------------------------- 1 | # The watch command 2 | 3 | The `watch` command is useful when you want your book to be rendered on every 4 | file change. You could repeatedly issue `mdbook build` every time a file is 5 | changed. But using `mdbook watch` once will watch your files and will trigger a 6 | build automatically whenever you modify a file; this includes re-creating 7 | deleted files still mentioned in `SUMMARY.md`! 8 | 9 | #### Specify a directory 10 | 11 | The `watch` command can take a directory as an argument to use as the book's 12 | root instead of the current working directory. 13 | 14 | ```bash 15 | mdbook watch path/to/book 16 | ``` 17 | 18 | #### --open 19 | 20 | When you use the `--open` (`-o`) option, mdbook will open the rendered book in 21 | your default web browser. 22 | 23 | #### --dest-dir 24 | 25 | The `--dest-dir` (`-d`) option allows you to change the output directory for the 26 | book. Relative paths are interpreted relative to the book's root directory. If 27 | not specified it will default to the value of the `build.build-dir` key in 28 | `book.toml`, or to `./book`. 29 | 30 | 31 | #### Specify exclude patterns 32 | 33 | The `watch` command will not automatically trigger a build for files listed in 34 | the `.gitignore` file in the book root directory. The `.gitignore` file may 35 | contain file patterns described in the [gitignore 36 | documentation](https://git-scm.com/docs/gitignore). This can be useful for 37 | ignoring temporary files created by some editors. 38 | 39 | _Note: Only `.gitignore` from book root directory is used. Global 40 | `$HOME/.gitignore` or `.gitignore` files in parent directories are not used._ 41 | -------------------------------------------------------------------------------- /src/cmd/build.rs: -------------------------------------------------------------------------------- 1 | use crate::{get_book_dir, open}; 2 | use clap::{arg, App, Arg, ArgMatches}; 3 | use mdbook::errors::Result; 4 | use mdbook::MDBook; 5 | 6 | // Create clap subcommand arguments 7 | pub fn make_subcommand<'help>() -> App<'help> { 8 | App::new("build") 9 | .about("Builds a book from its markdown files") 10 | .arg( 11 | Arg::new("dest-dir") 12 | .short('d') 13 | .long("dest-dir") 14 | .value_name("dest-dir") 15 | .help( 16 | "Output directory for the book{n}\ 17 | Relative paths are interpreted relative to the book's root directory.{n}\ 18 | If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.", 19 | ), 20 | ) 21 | .arg(arg!([dir] 22 | "Root directory for the book{n}\ 23 | (Defaults to the Current Directory when omitted)" 24 | )) 25 | .arg(arg!(-o --open "Opens the compiled book in a web browser")) 26 | } 27 | 28 | // Build command implementation 29 | pub fn execute(args: &ArgMatches) -> Result<()> { 30 | let book_dir = get_book_dir(args); 31 | let mut book = MDBook::load(&book_dir)?; 32 | 33 | if let Some(dest_dir) = args.value_of("dest-dir") { 34 | book.config.build.build_dir = dest_dir.into(); 35 | } 36 | 37 | book.build()?; 38 | 39 | if args.is_present("open") { 40 | // FIXME: What's the right behaviour if we don't use the HTML renderer? 41 | open(book.build_dir_for("html").join("index.html")); 42 | } 43 | 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /src/renderer/markdown_renderer.rs: -------------------------------------------------------------------------------- 1 | use crate::book::BookItem; 2 | use crate::errors::*; 3 | use crate::renderer::{RenderContext, Renderer}; 4 | use crate::utils; 5 | 6 | use std::fs; 7 | 8 | #[derive(Default)] 9 | /// A renderer to output the Markdown after the preprocessors have run. Mostly useful 10 | /// when debugging preprocessors. 11 | pub struct MarkdownRenderer; 12 | 13 | impl MarkdownRenderer { 14 | /// Create a new `MarkdownRenderer` instance. 15 | pub fn new() -> Self { 16 | MarkdownRenderer 17 | } 18 | } 19 | 20 | impl Renderer for MarkdownRenderer { 21 | fn name(&self) -> &str { 22 | "markdown" 23 | } 24 | 25 | fn render(&self, ctx: &RenderContext) -> Result<()> { 26 | let destination = &ctx.destination; 27 | let book = &ctx.book; 28 | 29 | if destination.exists() { 30 | utils::fs::remove_dir_content(destination) 31 | .with_context(|| "Unable to remove stale Markdown output")?; 32 | } 33 | 34 | trace!("markdown render"); 35 | for item in book.iter() { 36 | if let BookItem::Chapter(ref ch) = *item { 37 | if !ch.is_draft_chapter() { 38 | utils::fs::write_file( 39 | &ctx.destination, 40 | &ch.path.as_ref().expect("Checked path exists before"), 41 | ch.content.as_bytes(), 42 | )?; 43 | } 44 | } 45 | } 46 | 47 | fs::create_dir_all(&destination) 48 | .with_context(|| "Unexpected error when constructing destination path")?; 49 | 50 | Ok(()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test_book/src/individual/mixed.md: -------------------------------------------------------------------------------- 1 | # Mixed 2 | 3 | This contains all tags randomly mixed together, to make sure style changes in one does not affect others. 4 | 5 | ### A heading 6 | 7 | **Quite a Strong statement , to make** 8 | 9 | ~~No, cross that~~ 10 | 11 | > Whose **quote** is this 12 | > 13 | > > And ~~this~~ 14 | > > 15 | > > > - and 16 | > > > - this 17 | > > > - also 18 | 19 | ``` 20 | You encountered a wild codepen 21 | ``` 22 | 23 | ```rust,editable 24 | // The codepen is editable and runnable 25 | fn main(){ 26 | println!("Hello world!"); 27 | } 28 | ``` 29 | 30 | A random image sprinkled in between 31 | 32 | ![16x16 rust-lang logo](http://rust-lang.org/logos/rust-logo-16x16.png) 33 | 34 | --- 35 | 36 | - ~~An unordered list~~ 37 | - **Hello** 38 | - _World_ 39 | - What 40 | 1. Should 41 | 2. be 42 | 3. `put` 43 | 4. here? 44 | 45 | | col1 | col2 | col 3 | col 4 | col 5 | col 6 | 46 | | ---- | ---- | ----- | ----- | ----- | ----- | 47 | | val1 | val2 | val3 | val5 | val4 | val6 | 48 | 49 | | col1 | col2 | col 3 | An Questionable table header | col 5 | col 6 | 50 | | ---- | ---- | ----- | ---------------------------- | ----- | ---------------------------------------- | 51 | | val1 | val2 | val3 | val5 | val4 | An equally Questionable long table value | 52 | 53 | ### Things to do 54 | 55 | - [x] Add individual tags 56 | - [ ] Add language examples 57 | - [ ] Add rust specific examples 58 | 59 | And another image 60 | 61 | ![2018 rust-conf art svg](https://raw.githubusercontent.com/rust-lang/rust-artwork/461afe27d8e02451cf9f46e507f2c2a71d2b276b/2018-RustConf/lucy-mountain-climber.svg) 62 | -------------------------------------------------------------------------------- /guide/src/format/configuration/environment-variables.md: -------------------------------------------------------------------------------- 1 | # Environment Variables 2 | 3 | All configuration values can be overridden from the command line by setting the 4 | corresponding environment variable. Because many operating systems restrict 5 | environment variables to be alphanumeric characters or `_`, the configuration 6 | key needs to be formatted slightly differently to the normal `foo.bar.baz` form. 7 | 8 | Variables starting with `MDBOOK_` are used for configuration. The key is created 9 | by removing the `MDBOOK_` prefix and turning the resulting string into 10 | `kebab-case`. Double underscores (`__`) separate nested keys, while a single 11 | underscore (`_`) is replaced with a dash (`-`). 12 | 13 | For example: 14 | 15 | - `MDBOOK_foo` -> `foo` 16 | - `MDBOOK_FOO` -> `foo` 17 | - `MDBOOK_FOO__BAR` -> `foo.bar` 18 | - `MDBOOK_FOO_BAR` -> `foo-bar` 19 | - `MDBOOK_FOO_bar__baz` -> `foo-bar.baz` 20 | 21 | So by setting the `MDBOOK_BOOK__TITLE` environment variable you can override the 22 | book's title without needing to touch your `book.toml`. 23 | 24 | > **Note:** To facilitate setting more complex config items, the value of an 25 | > environment variable is first parsed as JSON, falling back to a string if the 26 | > parse fails. 27 | > 28 | > This means, if you so desired, you could override all book metadata when 29 | > building the book with something like 30 | > 31 | > ```shell 32 | > $ export MDBOOK_BOOK="{'title': 'My Awesome Book', authors: ['Michael-F-Bryan']}" 33 | > $ mdbook build 34 | > ``` 35 | 36 | The latter case may be useful in situations where `mdbook` is invoked from a 37 | script or CI, where it sometimes isn't possible to update the `book.toml` before 38 | building. 39 | -------------------------------------------------------------------------------- /src/theme/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/cli/test.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::cmd::mdbook_cmd; 2 | use crate::dummy_book::DummyBook; 3 | 4 | use predicates::boolean::PredicateBooleanExt; 5 | 6 | #[test] 7 | fn mdbook_cli_can_correctly_test_a_passing_book() { 8 | let temp = DummyBook::new().with_passing_test(true).build().unwrap(); 9 | 10 | let mut cmd = mdbook_cmd(); 11 | cmd.arg("test").current_dir(temp.path()); 12 | cmd.assert().success() 13 | .stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]README.md""##).unwrap()) 14 | .stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]intro.md""##).unwrap()) 15 | .stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]index.md""##).unwrap()) 16 | .stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]nested.md""##).unwrap()) 17 | .stderr(predicates::str::is_match(r##"rustdoc returned an error:\n\n"##).unwrap().not()) 18 | .stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap().not()); 19 | } 20 | 21 | #[test] 22 | fn mdbook_cli_detects_book_with_failing_tests() { 23 | let temp = DummyBook::new().with_passing_test(false).build().unwrap(); 24 | 25 | let mut cmd = mdbook_cmd(); 26 | cmd.arg("test").current_dir(temp.path()); 27 | cmd.assert().failure() 28 | .stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]README.md""##).unwrap()) 29 | .stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]intro.md""##).unwrap()) 30 | .stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]index.md""##).unwrap()) 31 | .stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]nested.md""##).unwrap()) 32 | .stderr(predicates::str::is_match(r##"rustdoc returned an error:\n\n"##).unwrap()) 33 | .stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap()); 34 | } 35 | -------------------------------------------------------------------------------- /guide/src/for_developers/README.md: -------------------------------------------------------------------------------- 1 | # For Developers 2 | 3 | While `mdbook` is mainly used as a command line tool, you can also import the 4 | underlying library directly and use that to manage a book. It also has a fairly 5 | flexible plugin mechanism, allowing you to create your own custom tooling and 6 | consumers (often referred to as *backends*) if you need to do some analysis of 7 | the book or render it in a different format. 8 | 9 | The *For Developers* chapters are here to show you the more advanced usage of 10 | `mdbook`. 11 | 12 | The two main ways a developer can hook into the book's build process is via, 13 | 14 | - [Preprocessors](preprocessors.md) 15 | - [Alternative Backends](backends.md) 16 | 17 | 18 | ## The Build Process 19 | 20 | The process of rendering a book project goes through several steps. 21 | 22 | 1. Load the book 23 | - Parse the `book.toml`, falling back to the default `Config` if it doesn't 24 | exist 25 | - Load the book chapters into memory 26 | - Discover which preprocessors/backends should be used 27 | 2. For each backend: 28 | 1. Run all the preprocessors. 29 | 2. Call the backend to render the processed result. 30 | 31 | 32 | ## Using `mdbook` as a Library 33 | 34 | The `mdbook` binary is just a wrapper around the `mdbook` crate, exposing its 35 | functionality as a command-line program. As such it is quite easy to create your 36 | own programs which use `mdbook` internally, adding your own functionality (e.g. 37 | a custom preprocessor) or tweaking the build process. 38 | 39 | The easiest way to find out how to use the `mdbook` crate is by looking at the 40 | [API Docs]. The top level documentation explains how one would use the 41 | [`MDBook`] type to load and build a book, while the [config] module gives a good 42 | explanation on the configuration system. 43 | 44 | 45 | [`MDBook`]: https://docs.rs/mdbook/*/mdbook/book/struct.MDBook.html 46 | [API Docs]: https://docs.rs/mdbook/*/mdbook/ 47 | [config]: https://docs.rs/mdbook/*/mdbook/config/index.html 48 | -------------------------------------------------------------------------------- /src/cmd/test.rs: -------------------------------------------------------------------------------- 1 | use crate::get_book_dir; 2 | use clap::{arg, App, Arg, ArgMatches}; 3 | use mdbook::errors::Result; 4 | use mdbook::MDBook; 5 | 6 | // Create clap subcommand arguments 7 | pub fn make_subcommand<'help>() -> App<'help> { 8 | App::new("test") 9 | .about("Tests that a book's Rust code samples compile") 10 | .arg( 11 | Arg::new("dest-dir") 12 | .short('d') 13 | .long("dest-dir") 14 | .value_name("dest-dir") 15 | .help( 16 | "Output directory for the book{n}\ 17 | Relative paths are interpreted relative to the book's root directory.{n}\ 18 | If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.", 19 | ), 20 | ) 21 | .arg(arg!([dir] 22 | "Root directory for the book{n}\ 23 | (Defaults to the Current Directory when omitted)" 24 | )) 25 | .arg(Arg::new("library-path") 26 | .short('L') 27 | .long("library-path") 28 | .value_name("dir") 29 | .takes_value(true) 30 | .use_delimiter(true) 31 | .require_delimiter(true) 32 | .multiple_values(true) 33 | .multiple_occurrences(true) 34 | .forbid_empty_values(true) 35 | .help("A comma-separated list of directories to add to {n}the crate search path when building tests")) 36 | } 37 | 38 | // test command implementation 39 | pub fn execute(args: &ArgMatches) -> Result<()> { 40 | let library_paths: Vec<&str> = args 41 | .values_of("library-path") 42 | .map(std::iter::Iterator::collect) 43 | .unwrap_or_default(); 44 | let book_dir = get_book_dir(args); 45 | let mut book = MDBook::load(&book_dir)?; 46 | 47 | if let Some(dest_dir) = args.value_of("dest-dir") { 48 | book.config.build.build_dir = dest_dir.into(); 49 | } 50 | 51 | book.test(library_paths)?; 52 | 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mdbook" 3 | version = "0.4.17" 4 | authors = [ 5 | "Mathieu David ", 6 | "Michael-F-Bryan ", 7 | "Matt Ickstadt " 8 | ] 9 | documentation = "http://rust-lang.github.io/mdBook/index.html" 10 | edition = "2018" 11 | exclude = ["/guide/*"] 12 | keywords = ["book", "gitbook", "rustbook", "markdown"] 13 | license = "MPL-2.0" 14 | readme = "README.md" 15 | repository = "https://github.com/rust-lang/mdBook" 16 | description = "Creates a book from markdown files" 17 | 18 | [dependencies] 19 | anyhow = "1.0.28" 20 | chrono = "0.4" 21 | clap = { version = "3.0", features = ["cargo"] } 22 | clap_complete = "3.0" 23 | env_logger = "0.7.1" 24 | handlebars = "4.0" 25 | lazy_static = "1.0" 26 | log = "0.4" 27 | memchr = "2.0" 28 | opener = "0.5" 29 | pulldown-cmark = { version = "0.9.1", default-features = false } 30 | regex = "1.5.5" 31 | serde = "1.0" 32 | serde_derive = "1.0" 33 | serde_json = "1.0" 34 | shlex = "1" 35 | tempfile = "3.0" 36 | toml = "0.5.1" 37 | topological-sort = "0.1.0" 38 | 39 | # Watch feature 40 | notify = { version = "4.0", optional = true } 41 | gitignore = { version = "1.0", optional = true } 42 | 43 | # Serve feature 44 | futures-util = { version = "0.3.4", optional = true } 45 | tokio = { version = "1", features = ["macros", "rt-multi-thread"], optional = true } 46 | warp = { version = "0.3.1", default-features = false, features = ["websocket"], optional = true } 47 | 48 | # Search feature 49 | elasticlunr-rs = { version = "2.3", optional = true, default-features = false } 50 | ammonia = { version = "3", optional = true } 51 | 52 | [dev-dependencies] 53 | assert_cmd = "1" 54 | predicates = "2" 55 | select = "0.5" 56 | semver = "1.0" 57 | pretty_assertions = "0.6" 58 | walkdir = "2.0" 59 | 60 | [features] 61 | default = ["watch", "serve", "search"] 62 | watch = ["notify", "gitignore"] 63 | serve = ["futures-util", "tokio", "warp"] 64 | search = ["elasticlunr-rs", "ammonia"] 65 | 66 | [[bin]] 67 | doc = false 68 | name = "mdbook" 69 | -------------------------------------------------------------------------------- /guide/src/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | **mdBook** is a command line tool to create books with Markdown. 4 | It is ideal for creating product or API documentation, tutorials, course materials or anything that requires a clean, 5 | easily navigable and customizable presentation. 6 | 7 | * Lightweight [Markdown] syntax helps you focus more on your content 8 | * Integrated [search] support 9 | * Color [syntax highlighting] for code blocks for many different languages 10 | * [Theme] files allow customizing the formatting of the output 11 | * [Preprocessors] can provide extensions for custom syntax and modifying content 12 | * [Backends] can render the output to multiple formats 13 | * Written in [Rust] for speed, safety, and simplicity 14 | * Automated testing of [Rust code samples] 15 | 16 | This guide is an example of what mdBook produces. 17 | mdBook is used by the Rust programming language project, and [The Rust Programming Language][trpl] book is another fine example of mdBook in action. 18 | 19 | [Markdown]: format/markdown.md 20 | [search]: guide/reading.md#search 21 | [syntax highlighting]: format/theme/syntax-highlighting.md 22 | [theme]: format/theme/index.html 23 | [preprocessors]: format/configuration/preprocessors.md 24 | [backends]: format/configuration/renderers.md 25 | [Rust]: https://www.rust-lang.org/ 26 | [trpl]: https://doc.rust-lang.org/book/ 27 | [Rust code samples]: cli/test.md 28 | 29 | ## Contributing 30 | 31 | mdBook is free and open source. You can find the source code on 32 | [GitHub](https://github.com/rust-lang/mdBook) and issues and feature requests can be posted on 33 | the [GitHub issue tracker](https://github.com/rust-lang/mdBook/issues). mdBook relies on the community to fix bugs and 34 | add features: if you'd like to contribute, please read 35 | the [CONTRIBUTING](https://github.com/rust-lang/mdBook/blob/master/CONTRIBUTING.md) guide and consider opening 36 | a [pull request](https://github.com/rust-lang/mdBook/pulls). 37 | 38 | ## License 39 | 40 | The mdBook source and documentation are released under 41 | the [Mozilla Public License v2.0](https://www.mozilla.org/MPL/2.0/). 42 | -------------------------------------------------------------------------------- /guide/src/cli/serve.md: -------------------------------------------------------------------------------- 1 | # The serve command 2 | 3 | The serve command is used to preview a book by serving it via HTTP at 4 | `localhost:3000` by default: 5 | 6 | ```bash 7 | mdbook serve 8 | ``` 9 | 10 | The `serve` command watches the book's `src` directory for 11 | changes, rebuilding the book and refreshing clients for each change; this includes 12 | re-creating deleted files still mentioned in `SUMMARY.md`! A websocket 13 | connection is used to trigger the client-side refresh. 14 | 15 | ***Note:*** *The `serve` command is for testing a book's HTML output, and is not 16 | intended to be a complete HTTP server for a website.* 17 | 18 | #### Specify a directory 19 | 20 | The `serve` command can take a directory as an argument to use as the book's 21 | root instead of the current working directory. 22 | 23 | ```bash 24 | mdbook serve path/to/book 25 | ``` 26 | 27 | ### Server options 28 | 29 | The `serve` hostname defaults to `localhost`, and the port defaults to `3000`. Either option can be specified on the command line: 30 | 31 | ```bash 32 | mdbook serve path/to/book -p 8000 -n 127.0.0.1 33 | ``` 34 | 35 | #### --open 36 | 37 | When you use the `--open` (`-o`) flag, mdbook will open the book in your 38 | default web browser after starting the server. 39 | 40 | #### --dest-dir 41 | 42 | The `--dest-dir` (`-d`) option allows you to change the output directory for the 43 | book. Relative paths are interpreted relative to the book's root directory. If 44 | not specified it will default to the value of the `build.build-dir` key in 45 | `book.toml`, or to `./book`. 46 | 47 | #### Specify exclude patterns 48 | 49 | The `serve` command will not automatically trigger a build for files listed in 50 | the `.gitignore` file in the book root directory. The `.gitignore` file may 51 | contain file patterns described in the [gitignore 52 | documentation](https://git-scm.com/docs/gitignore). This can be useful for 53 | ignoring temporary files created by some editors. 54 | 55 | ***Note:*** *Only the `.gitignore` from the book root directory is used. Global 56 | `$HOME/.gitignore` or `.gitignore` files in parent directories are not used.* 57 | -------------------------------------------------------------------------------- /guide/src/guide/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | There are multiple ways to install the mdBook CLI tool. 4 | Choose any one of the methods below that best suit your needs. 5 | If you are installing mdBook for automatic deployment, check out the [continuous integration] chapter for more examples on how to install. 6 | 7 | [continuous integration]: ../continuous-integration.md 8 | 9 | ## Pre-compiled binaries 10 | 11 | Executable binaries are available for download on the [GitHub Releases page][releases]. 12 | Download the binary for your platform (Windows, macOS, or Linux) and extract the archive. 13 | The archive contains an `mdbook` executable which you can run to build your books. 14 | 15 | To make it easier to run, put the path to the binary into your `PATH`. 16 | 17 | [releases]: https://github.com/rust-lang/mdBook/releases 18 | 19 | ## Build from source using Rust 20 | 21 | To build the `mdbook` executable from source, you will first need to install Rust and Cargo. 22 | Follow the instructions on the [Rust installation page]. 23 | mdBook currently requires at least Rust version 1.54. 24 | 25 | Once you have installed Rust, the following command can be used to build and install mdBook: 26 | 27 | ```sh 28 | cargo install mdbook 29 | ``` 30 | 31 | This will automatically download mdBook from [crates.io], build it, and install it in Cargo's global binary directory (`~/.cargo/bin/` by default). 32 | 33 | [Rust installation page]: https://www.rust-lang.org/tools/install 34 | [crates.io]: https://crates.io/ 35 | 36 | ### Installing the latest master version 37 | 38 | The version published to crates.io will ever so slightly be behind the version hosted on GitHub. 39 | If you need the latest version you can build the git version of mdBook yourself. 40 | Cargo makes this ***super easy***! 41 | 42 | ```sh 43 | cargo install --git https://github.com/rust-lang/mdBook.git mdbook 44 | ``` 45 | 46 | Again, make sure to add the Cargo bin directory to your `PATH`. 47 | 48 | If you are interested in making modifications to mdBook itself, check out the [Contributing Guide] for more information. 49 | 50 | [Contributing Guide]: https://github.com/rust-lang/mdBook/blob/master/CONTRIBUTING.md 51 | -------------------------------------------------------------------------------- /src/preprocess/mod.rs: -------------------------------------------------------------------------------- 1 | //! Book preprocessing. 2 | 3 | pub use self::cmd::CmdPreprocessor; 4 | pub use self::index::IndexPreprocessor; 5 | pub use self::links::LinkPreprocessor; 6 | 7 | mod cmd; 8 | mod index; 9 | mod links; 10 | 11 | use crate::book::Book; 12 | use crate::config::Config; 13 | use crate::errors::*; 14 | 15 | use std::cell::RefCell; 16 | use std::collections::HashMap; 17 | use std::path::PathBuf; 18 | 19 | /// Extra information for a `Preprocessor` to give them more context when 20 | /// processing a book. 21 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 22 | pub struct PreprocessorContext { 23 | /// The location of the book directory on disk. 24 | pub root: PathBuf, 25 | /// The book configuration (`book.toml`). 26 | pub config: Config, 27 | /// The `Renderer` this preprocessor is being used with. 28 | pub renderer: String, 29 | /// The calling `mdbook` version. 30 | pub mdbook_version: String, 31 | #[serde(skip)] 32 | pub(crate) chapter_titles: RefCell>, 33 | #[serde(skip)] 34 | __non_exhaustive: (), 35 | } 36 | 37 | impl PreprocessorContext { 38 | /// Create a new `PreprocessorContext`. 39 | pub(crate) fn new(root: PathBuf, config: Config, renderer: String) -> Self { 40 | PreprocessorContext { 41 | root, 42 | config, 43 | renderer, 44 | mdbook_version: crate::MDBOOK_VERSION.to_string(), 45 | chapter_titles: RefCell::new(HashMap::new()), 46 | __non_exhaustive: (), 47 | } 48 | } 49 | } 50 | 51 | /// An operation which is run immediately after loading a book into memory and 52 | /// before it gets rendered. 53 | pub trait Preprocessor { 54 | /// Get the `Preprocessor`'s name. 55 | fn name(&self) -> &str; 56 | 57 | /// Run this `Preprocessor`, allowing it to update the book before it is 58 | /// given to a renderer. 59 | fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result; 60 | 61 | /// A hint to `MDBook` whether this preprocessor is compatible with a 62 | /// particular renderer. 63 | /// 64 | /// By default, always returns `true`. 65 | fn supports_renderer(&self, _renderer: &str) -> bool { 66 | true 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /guide/src/cli/init.md: -------------------------------------------------------------------------------- 1 | # The init command 2 | 3 | There is some minimal boilerplate that is the same for every new book. It's for 4 | this purpose that mdBook includes an `init` command. 5 | 6 | The `init` command is used like this: 7 | 8 | ```bash 9 | mdbook init 10 | ``` 11 | 12 | When using the `init` command for the first time, a couple of files will be set 13 | up for you: 14 | ```bash 15 | book-test/ 16 | ├── book 17 | └── src 18 | ├── chapter_1.md 19 | └── SUMMARY.md 20 | ``` 21 | 22 | - The `src` directory is where you write your book in markdown. It contains all 23 | the source files, configuration files, etc. 24 | 25 | - The `book` directory is where your book is rendered. All the output is ready 26 | to be uploaded to a server to be seen by your audience. 27 | 28 | - The `SUMMARY.md` is the skeleton of your 29 | book, and is discussed in more detail [in another 30 | chapter](../format/summary.md). 31 | 32 | #### Tip: Generate chapters from SUMMARY.md 33 | 34 | When a `SUMMARY.md` file already exists, the `init` command will first parse it 35 | and generate the missing files according to the paths used in the `SUMMARY.md`. 36 | This allows you to think and create the whole structure of your book and then 37 | let mdBook generate it for you. 38 | 39 | #### Specify a directory 40 | 41 | The `init` command can take a directory as an argument to use as the book's root 42 | instead of the current working directory. 43 | 44 | ```bash 45 | mdbook init path/to/book 46 | ``` 47 | 48 | #### --theme 49 | 50 | When you use the `--theme` flag, the default theme will be copied into a 51 | directory called `theme` in your source directory so that you can modify it. 52 | 53 | The theme is selectively overwritten, this means that if you don't want to 54 | overwrite a specific file, just delete it and the default file will be used. 55 | 56 | #### --title 57 | 58 | Specify a title for the book. If not supplied, an interactive prompt will ask for 59 | a title. 60 | 61 | ```bash 62 | mdbook init --title="my amazing book" 63 | ``` 64 | 65 | #### --ignore 66 | 67 | Create a `.gitignore` file configured to ignore the `book` directory created when [building] a book. 68 | If not supplied, an interactive prompt will ask whether it should be created. 69 | 70 | [building]: build.md 71 | -------------------------------------------------------------------------------- /tests/build_process.rs: -------------------------------------------------------------------------------- 1 | mod dummy_book; 2 | 3 | use crate::dummy_book::DummyBook; 4 | use mdbook::book::Book; 5 | use mdbook::config::Config; 6 | use mdbook::errors::*; 7 | use mdbook::preprocess::{Preprocessor, PreprocessorContext}; 8 | use mdbook::renderer::{RenderContext, Renderer}; 9 | use mdbook::MDBook; 10 | use std::sync::{Arc, Mutex}; 11 | 12 | struct Spy(Arc>); 13 | 14 | #[derive(Debug, Default)] 15 | struct Inner { 16 | run_count: usize, 17 | rendered_with: Vec, 18 | } 19 | 20 | impl Preprocessor for Spy { 21 | fn name(&self) -> &str { 22 | "dummy" 23 | } 24 | 25 | fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result { 26 | let mut inner = self.0.lock().unwrap(); 27 | inner.run_count += 1; 28 | inner.rendered_with.push(ctx.renderer.clone()); 29 | Ok(book) 30 | } 31 | } 32 | 33 | impl Renderer for Spy { 34 | fn name(&self) -> &str { 35 | "dummy" 36 | } 37 | 38 | fn render(&self, _ctx: &RenderContext) -> Result<()> { 39 | let mut inner = self.0.lock().unwrap(); 40 | inner.run_count += 1; 41 | Ok(()) 42 | } 43 | } 44 | 45 | #[test] 46 | fn mdbook_runs_preprocessors() { 47 | let spy: Arc> = Default::default(); 48 | 49 | let temp = DummyBook::new().build().unwrap(); 50 | let cfg = Config::default(); 51 | 52 | let mut book = MDBook::load_with_config(temp.path(), cfg).unwrap(); 53 | book.with_preprocessor(Spy(Arc::clone(&spy))); 54 | book.build().unwrap(); 55 | 56 | let inner = spy.lock().unwrap(); 57 | assert_eq!(inner.run_count, 1); 58 | assert_eq!(inner.rendered_with.len(), 1); 59 | assert_eq!( 60 | "html", inner.rendered_with[0], 61 | "We should have been run with the default HTML renderer" 62 | ); 63 | } 64 | 65 | #[test] 66 | fn mdbook_runs_renderers() { 67 | let spy: Arc> = Default::default(); 68 | 69 | let temp = DummyBook::new().build().unwrap(); 70 | let cfg = Config::default(); 71 | 72 | let mut book = MDBook::load_with_config(temp.path(), cfg).unwrap(); 73 | book.with_renderer(Spy(Arc::clone(&spy))); 74 | book.build().unwrap(); 75 | 76 | let inner = spy.lock().unwrap(); 77 | assert_eq!(inner.run_count, 1); 78 | } 79 | -------------------------------------------------------------------------------- /src/theme/fonts/mod.rs: -------------------------------------------------------------------------------- 1 | pub static CSS: &[u8] = include_bytes!("fonts.css"); 2 | // An array of (file_name, file_contents) pairs 3 | pub static LICENSES: [(&str, &[u8]); 2] = [ 4 | ( 5 | "fonts/OPEN-SANS-LICENSE.txt", 6 | include_bytes!("OPEN-SANS-LICENSE.txt"), 7 | ), 8 | ( 9 | "fonts/SOURCE-CODE-PRO-LICENSE.txt", 10 | include_bytes!("SOURCE-CODE-PRO-LICENSE.txt"), 11 | ), 12 | ]; 13 | // An array of (file_name, file_contents) pairs 14 | pub static OPEN_SANS: [(&str, &[u8]); 10] = [ 15 | ( 16 | "fonts/open-sans-v17-all-charsets-300.woff2", 17 | include_bytes!("open-sans-v17-all-charsets-300.woff2"), 18 | ), 19 | ( 20 | "fonts/open-sans-v17-all-charsets-300italic.woff2", 21 | include_bytes!("open-sans-v17-all-charsets-300italic.woff2"), 22 | ), 23 | ( 24 | "fonts/open-sans-v17-all-charsets-regular.woff2", 25 | include_bytes!("open-sans-v17-all-charsets-regular.woff2"), 26 | ), 27 | ( 28 | "fonts/open-sans-v17-all-charsets-italic.woff2", 29 | include_bytes!("open-sans-v17-all-charsets-italic.woff2"), 30 | ), 31 | ( 32 | "fonts/open-sans-v17-all-charsets-600.woff2", 33 | include_bytes!("open-sans-v17-all-charsets-600.woff2"), 34 | ), 35 | ( 36 | "fonts/open-sans-v17-all-charsets-600italic.woff2", 37 | include_bytes!("open-sans-v17-all-charsets-600italic.woff2"), 38 | ), 39 | ( 40 | "fonts/open-sans-v17-all-charsets-700.woff2", 41 | include_bytes!("open-sans-v17-all-charsets-700.woff2"), 42 | ), 43 | ( 44 | "fonts/open-sans-v17-all-charsets-700italic.woff2", 45 | include_bytes!("open-sans-v17-all-charsets-700italic.woff2"), 46 | ), 47 | ( 48 | "fonts/open-sans-v17-all-charsets-800.woff2", 49 | include_bytes!("open-sans-v17-all-charsets-800.woff2"), 50 | ), 51 | ( 52 | "fonts/open-sans-v17-all-charsets-800italic.woff2", 53 | include_bytes!("open-sans-v17-all-charsets-800italic.woff2"), 54 | ), 55 | ]; 56 | 57 | // A (file_name, file_contents) pair 58 | pub static SOURCE_CODE_PRO: (&str, &[u8]) = ( 59 | "fonts/source-code-pro-v11-all-charsets-500.woff2", 60 | include_bytes!("source-code-pro-v11-all-charsets-500.woff2"), 61 | ); 62 | -------------------------------------------------------------------------------- /src/theme/tomorrow-night.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Night Theme */ 2 | /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ 3 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 4 | /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ 5 | 6 | /* Tomorrow Comment */ 7 | .hljs-comment { 8 | color: #969896; 9 | } 10 | 11 | /* Tomorrow Red */ 12 | .hljs-variable, 13 | .hljs-attribute, 14 | .hljs-tag, 15 | .hljs-regexp, 16 | .ruby .hljs-constant, 17 | .xml .hljs-tag .hljs-title, 18 | .xml .hljs-pi, 19 | .xml .hljs-doctype, 20 | .html .hljs-doctype, 21 | .css .hljs-id, 22 | .css .hljs-class, 23 | .css .hljs-pseudo { 24 | color: #cc6666; 25 | } 26 | 27 | /* Tomorrow Orange */ 28 | .hljs-number, 29 | .hljs-preprocessor, 30 | .hljs-pragma, 31 | .hljs-built_in, 32 | .hljs-literal, 33 | .hljs-params, 34 | .hljs-constant { 35 | color: #de935f; 36 | } 37 | 38 | /* Tomorrow Yellow */ 39 | .ruby .hljs-class .hljs-title, 40 | .css .hljs-rule .hljs-attribute { 41 | color: #f0c674; 42 | } 43 | 44 | /* Tomorrow Green */ 45 | .hljs-string, 46 | .hljs-value, 47 | .hljs-inheritance, 48 | .hljs-header, 49 | .hljs-name, 50 | .ruby .hljs-symbol, 51 | .xml .hljs-cdata { 52 | color: #b5bd68; 53 | } 54 | 55 | /* Tomorrow Aqua */ 56 | .hljs-title, 57 | .css .hljs-hexcolor { 58 | color: #8abeb7; 59 | } 60 | 61 | /* Tomorrow Blue */ 62 | .hljs-function, 63 | .python .hljs-decorator, 64 | .python .hljs-title, 65 | .ruby .hljs-function .hljs-title, 66 | .ruby .hljs-title .hljs-keyword, 67 | .perl .hljs-sub, 68 | .javascript .hljs-title, 69 | .coffeescript .hljs-title { 70 | color: #81a2be; 71 | } 72 | 73 | /* Tomorrow Purple */ 74 | .hljs-keyword, 75 | .javascript .hljs-function { 76 | color: #b294bb; 77 | } 78 | 79 | .hljs { 80 | display: block; 81 | overflow-x: auto; 82 | background: #1d1f21; 83 | color: #c5c8c6; 84 | padding: 0.5em; 85 | -webkit-text-size-adjust: none; 86 | } 87 | 88 | .coffeescript .javascript, 89 | .javascript .xml, 90 | .tex .hljs-formula, 91 | .xml .javascript, 92 | .xml .vbscript, 93 | .xml .css, 94 | .xml .hljs-cdata { 95 | opacity: 0.5; 96 | } 97 | 98 | .hljs-addition { 99 | color: #718c00; 100 | } 101 | 102 | .hljs-deletion { 103 | color: #c82829; 104 | } 105 | -------------------------------------------------------------------------------- /guide/src/format/images/rust-logo-blk.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /guide/src/cli/test.md: -------------------------------------------------------------------------------- 1 | # The test command 2 | 3 | When writing a book, you sometimes need to automate some tests. For example, 4 | [The Rust Programming Book](https://doc.rust-lang.org/stable/book/) uses a lot 5 | of code examples that could get outdated. Therefore it is very important for 6 | them to be able to automatically test these code examples. 7 | 8 | mdBook supports a `test` command that will run all available tests in a book. At 9 | the moment, only rustdoc tests are supported, but this may be expanded upon in 10 | the future. 11 | 12 | #### Disable tests on a code block 13 | 14 | rustdoc doesn't test code blocks which contain the `ignore` attribute: 15 | 16 | ```rust,ignore 17 | fn main() {} 18 | ``` 19 | 20 | rustdoc also doesn't test code blocks which specify a language other than Rust: 21 | 22 | ```markdown 23 | **Foo**: _bar_ 24 | ``` 25 | 26 | rustdoc *does* test code blocks which have no language specified: 27 | 28 | ``` 29 | This is going to cause an error! 30 | ``` 31 | 32 | #### Specify a directory 33 | 34 | The `test` command can take a directory as an argument to use as the book's root 35 | instead of the current working directory. 36 | 37 | ```bash 38 | mdbook test path/to/book 39 | ``` 40 | 41 | #### --library-path 42 | 43 | The `--library-path` (`-L`) option allows you to add directories to the library 44 | search path used by `rustdoc` when it builds and tests the examples. Multiple 45 | directories can be specified with multiple options (`-L foo -L bar`) or with a 46 | comma-delimited list (`-L foo,bar`). The path should point to the Cargo 47 | [build cache](https://doc.rust-lang.org/cargo/guide/build-cache.html) `deps` directory that 48 | contains the build output of your project. For example, if your Rust project's book is in a directory 49 | named `my-book`, the following command would include the crate's dependencies when running `test`: 50 | 51 | ```shell 52 | mdbook test my-book -L target/debug/deps/ 53 | ``` 54 | 55 | See the `rustdoc` command-line [documentation](https://doc.rust-lang.org/rustdoc/command-line-arguments.html#-l--library-path-where-to-look-for-dependencies) 56 | for more information. 57 | 58 | #### --dest-dir 59 | 60 | The `--dest-dir` (`-d`) option allows you to change the output directory for the 61 | book. Relative paths are interpreted relative to the book's root directory. If 62 | not specified it will default to the value of the `build.build-dir` key in 63 | `book.toml`, or to `./book`. 64 | -------------------------------------------------------------------------------- /src/theme/playground_editor/theme-dawn.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/theme/dawn",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-dawn",t.cssText=".ace-dawn .ace_gutter {background: #ebebeb;color: #333}.ace-dawn .ace_print-margin {width: 1px;background: #e8e8e8}.ace-dawn {background-color: #F9F9F9;color: #080808}.ace-dawn .ace_cursor {color: #000000}.ace-dawn .ace_marker-layer .ace_selection {background: rgba(39, 95, 255, 0.30)}.ace-dawn.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #F9F9F9;}.ace-dawn .ace_marker-layer .ace_step {background: rgb(255, 255, 0)}.ace-dawn .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgba(75, 75, 126, 0.50)}.ace-dawn .ace_marker-layer .ace_active-line {background: rgba(36, 99, 180, 0.12)}.ace-dawn .ace_gutter-active-line {background-color : #dcdcdc}.ace-dawn .ace_marker-layer .ace_selected-word {border: 1px solid rgba(39, 95, 255, 0.30)}.ace-dawn .ace_invisible {color: rgba(75, 75, 126, 0.50)}.ace-dawn .ace_keyword,.ace-dawn .ace_meta {color: #794938}.ace-dawn .ace_constant,.ace-dawn .ace_constant.ace_character,.ace-dawn .ace_constant.ace_character.ace_escape,.ace-dawn .ace_constant.ace_other {color: #811F24}.ace-dawn .ace_invalid.ace_illegal {text-decoration: underline;font-style: italic;color: #F8F8F8;background-color: #B52A1D}.ace-dawn .ace_invalid.ace_deprecated {text-decoration: underline;font-style: italic;color: #B52A1D}.ace-dawn .ace_support {color: #691C97}.ace-dawn .ace_support.ace_constant {color: #B4371F}.ace-dawn .ace_fold {background-color: #794938;border-color: #080808}.ace-dawn .ace_list,.ace-dawn .ace_markup.ace_list,.ace-dawn .ace_support.ace_function {color: #693A17}.ace-dawn .ace_storage {font-style: italic;color: #A71D5D}.ace-dawn .ace_string {color: #0B6125}.ace-dawn .ace_string.ace_regexp {color: #CF5628}.ace-dawn .ace_comment {font-style: italic;color: #5A525F}.ace-dawn .ace_heading,.ace-dawn .ace_markup.ace_heading {color: #19356D}.ace-dawn .ace_variable {color: #234A97}.ace-dawn .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGBgYLh/5+x/AAizA4hxNNsZAAAAAElFTkSuQmCC) right repeat-y}";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}); (function() { 2 | ace.require(["ace/theme/dawn"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /guide/src/format/theme/README.md: -------------------------------------------------------------------------------- 1 | # Theme 2 | 3 | The default renderer uses a [handlebars](http://handlebarsjs.com/) template to 4 | render your markdown files and comes with a default theme included in the mdBook 5 | binary. 6 | 7 | The theme is totally customizable, you can selectively replace every file from 8 | the theme by your own by adding a `theme` directory next to `src` folder in your 9 | project root. Create a new file with the name of the file you want to override 10 | and now that file will be used instead of the default file. 11 | 12 | Here are the files you can override: 13 | 14 | - **_index.hbs_** is the handlebars template. 15 | - **_head.hbs_** is appended to the HTML `` section. 16 | - **_header.hbs_** content is appended on top of every book page. 17 | - **_css/_** contains the CSS files for styling the book. 18 | - **_css/chrome.css_** is for UI elements. 19 | - **_css/general.css_** is the base styles. 20 | - **_css/print.css_** is the style for printer output. 21 | - **_css/variables.css_** contains variables used in other CSS files. 22 | - **_book.js_** is mostly used to add client side functionality, like hiding / 23 | un-hiding the sidebar, changing the theme, ... 24 | - **_highlight.js_** is the JavaScript that is used to highlight code snippets, 25 | you should not need to modify this. 26 | - **_highlight.css_** is the theme used for the code highlighting. 27 | - **_favicon.svg_** and **_favicon.png_** the favicon that will be used. The SVG 28 | version is used by [newer browsers]. 29 | 30 | Generally, when you want to tweak the theme, you don't need to override all the 31 | files. If you only need changes in the stylesheet, there is no point in 32 | overriding all the other files. Because custom files take precedence over 33 | built-in ones, they will not get updated with new fixes / features. 34 | 35 | **Note:** When you override a file, it is possible that you break some 36 | functionality. Therefore I recommend to use the file from the default theme as 37 | template and only add / modify what you need. You can copy the default theme 38 | into your source directory automatically by using `mdbook init --theme` and just 39 | remove the files you don't want to override. 40 | 41 | `mdbook init --theme` will not create every file listed above. 42 | Some files, such as `head.hbs`, do not have built-in equivalents. 43 | Just create the file if you need it. 44 | 45 | If you completely replace all built-in themes, be sure to also set 46 | [`output.html.preferred-dark-theme`] in the config, which defaults to the 47 | built-in `navy` theme. 48 | 49 | [`output.html.preferred-dark-theme`]: ../configuration/renderers.md#html-renderer-options 50 | [newer browsers]: https://caniuse.com/#feat=link-icon-svg 51 | -------------------------------------------------------------------------------- /guide/src/format/theme/syntax-highlighting.md: -------------------------------------------------------------------------------- 1 | # Syntax Highlighting 2 | 3 | mdBook uses [Highlight.js](https://highlightjs.org) with a custom theme 4 | for syntax highlighting. 5 | 6 | Automatic language detection has been turned off, so you will probably want to 7 | specify the programming language you use like this: 8 | 9 | ~~~markdown 10 | ```rust 11 | fn main() { 12 | // Some code 13 | } 14 | ``` 15 | ~~~ 16 | 17 | ## Supported languages 18 | 19 | These languages are supported by default, but you can add more by supplying 20 | your own `highlight.js` file: 21 | 22 | - apache 23 | - armasm 24 | - bash 25 | - c 26 | - coffeescript 27 | - cpp 28 | - csharp 29 | - css 30 | - d 31 | - diff 32 | - go 33 | - handlebars 34 | - haskell 35 | - http 36 | - ini 37 | - java 38 | - javascript 39 | - json 40 | - julia 41 | - kotlin 42 | - less 43 | - lua 44 | - makefile 45 | - markdown 46 | - nginx 47 | - objectivec 48 | - perl 49 | - php 50 | - plaintext 51 | - properties 52 | - python 53 | - r 54 | - ruby 55 | - rust 56 | - scala 57 | - scss 58 | - shell 59 | - sql 60 | - swift 61 | - typescript 62 | - x86asm 63 | - xml 64 | - yaml 65 | 66 | ## Custom theme 67 | Like the rest of the theme, the files used for syntax highlighting can be 68 | overridden with your own. 69 | 70 | - ***highlight.js*** normally you shouldn't have to overwrite this file, unless 71 | you want to use a more recent version. 72 | - ***highlight.css*** theme used by highlight.js for syntax highlighting. 73 | 74 | If you want to use another theme for `highlight.js` download it from their 75 | website, or make it yourself, rename it to `highlight.css` and put it in 76 | the `theme` folder of your book. 77 | 78 | Now your theme will be used instead of the default theme. 79 | 80 | ## Hiding code lines 81 | 82 | There is a feature in mdBook that lets you hide code lines by prepending them 83 | with a `#`. 84 | 85 | 86 | ```bash 87 | # fn main() { 88 | let x = 5; 89 | let y = 6; 90 | 91 | println!("{}", x + y); 92 | # } 93 | ``` 94 | 95 | Will render as 96 | 97 | ```rust 98 | # fn main() { 99 | let x = 5; 100 | let y = 7; 101 | 102 | println!("{}", x + y); 103 | # } 104 | ``` 105 | 106 | **At the moment, this only works for code examples that are annotated with 107 | `rust`. Because it would collide with semantics of some programming languages. 108 | In the future, we want to make this configurable through the `book.toml` so that 109 | everyone can benefit from it.** 110 | 111 | 112 | ## Improve default theme 113 | 114 | If you think the default theme doesn't look quite right for a specific language, 115 | or could be improved, feel free to [submit a new 116 | issue](https://github.com/rust-lang/mdBook/issues) explaining what you 117 | have in mind and I will take a look at it. 118 | 119 | You could also create a pull-request with the proposed improvements. 120 | 121 | Overall the theme should be light and sober, without too many flashy colors. 122 | -------------------------------------------------------------------------------- /src/theme/playground_editor/theme-tomorrow_night.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/theme/tomorrow_night",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!0,t.cssClass="ace-tomorrow-night",t.cssText=".ace-tomorrow-night .ace_gutter {background: #25282c;color: #C5C8C6}.ace-tomorrow-night .ace_print-margin {width: 1px;background: #25282c}.ace-tomorrow-night {background-color: #1D1F21;color: #C5C8C6}.ace-tomorrow-night .ace_cursor {color: #AEAFAD}.ace-tomorrow-night .ace_marker-layer .ace_selection {background: #373B41}.ace-tomorrow-night.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #1D1F21;}.ace-tomorrow-night .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-tomorrow-night .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #4B4E55}.ace-tomorrow-night .ace_marker-layer .ace_active-line {background: #282A2E}.ace-tomorrow-night .ace_gutter-active-line {background-color: #282A2E}.ace-tomorrow-night .ace_marker-layer .ace_selected-word {border: 1px solid #373B41}.ace-tomorrow-night .ace_invisible {color: #4B4E55}.ace-tomorrow-night .ace_keyword,.ace-tomorrow-night .ace_meta,.ace-tomorrow-night .ace_storage,.ace-tomorrow-night .ace_storage.ace_type,.ace-tomorrow-night .ace_support.ace_type {color: #B294BB}.ace-tomorrow-night .ace_keyword.ace_operator {color: #8ABEB7}.ace-tomorrow-night .ace_constant.ace_character,.ace-tomorrow-night .ace_constant.ace_language,.ace-tomorrow-night .ace_constant.ace_numeric,.ace-tomorrow-night .ace_keyword.ace_other.ace_unit,.ace-tomorrow-night .ace_support.ace_constant,.ace-tomorrow-night .ace_variable.ace_parameter {color: #DE935F}.ace-tomorrow-night .ace_constant.ace_other {color: #CED1CF}.ace-tomorrow-night .ace_invalid {color: #CED2CF;background-color: #DF5F5F}.ace-tomorrow-night .ace_invalid.ace_deprecated {color: #CED2CF;background-color: #B798BF}.ace-tomorrow-night .ace_fold {background-color: #81A2BE;border-color: #C5C8C6}.ace-tomorrow-night .ace_entity.ace_name.ace_function,.ace-tomorrow-night .ace_support.ace_function,.ace-tomorrow-night .ace_variable {color: #81A2BE}.ace-tomorrow-night .ace_support.ace_class,.ace-tomorrow-night .ace_support.ace_type {color: #F0C674}.ace-tomorrow-night .ace_heading,.ace-tomorrow-night .ace_markup.ace_heading,.ace-tomorrow-night .ace_string {color: #B5BD68}.ace-tomorrow-night .ace_entity.ace_name.ace_tag,.ace-tomorrow-night .ace_entity.ace_other.ace_attribute-name,.ace-tomorrow-night .ace_meta.ace_tag,.ace-tomorrow-night .ace_string.ace_regexp,.ace-tomorrow-night .ace_variable {color: #CC6666}.ace-tomorrow-night .ace_comment {color: #969896}.ace-tomorrow-night .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGBgYHB3d/8PAAOIAdULw8qMAAAAAElFTkSuQmCC) right repeat-y}";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}); (function() { 2 | ace.require(["ace/theme/tomorrow_night"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /test_book/src/individual/table.md: -------------------------------------------------------------------------------- 1 | # Tables 2 | 3 | | col1 | col2 | 4 | | ---- | ---- | 5 | 6 | --- 7 | 8 | | col1 | col2 | 9 | | ---- | ---- | 10 | | val1 | val2 | 11 | 12 | --- 13 | 14 | | col1 | col2 | col 3 | col 4 | col 5 | col 6 | 15 | | ---- | ---- | ----- | ----- | ----- | ----- | 16 | | val1 | val2 | val3 | val5 | val4 | val6 | 17 | | val1 | val2 | val3 | val5 | val4 | val6 | 18 | | val1 | val2 | val3 | val5 | val4 | val6 | 19 | | val1 | val2 | val3 | val5 | val4 | val6 | 20 | 21 | --- 22 | 23 | | col1 | col2 | col 3 | col 4 | col 5 | col 6 | 24 | | -------------------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ----- | -------------------------------------------------------------------------------------------------------------- | 25 | | This is a simple demo book, which is intended to be used for verifying and validating style changes in mdBook. | val2 | val3 | val5 | val4 | val6 | 26 | | val1 | val2 | val3 | val5 | val4 | val6 | 27 | | val1 | val2 | val3 | val5 | val4 | This is a simple demo book, which is intended to be used for verifying and validating style changes in mdBook. | 28 | | val1 | val2 | This is a simple demo book, which is intended to be used for verifying and validating style changes in mdBook. | This is a simple demo book, which is intended to be used for verifying and validating style changes in mdBook. | val4 | val6 | 29 | -------------------------------------------------------------------------------- /guide/src/format/configuration/preprocessors.md: -------------------------------------------------------------------------------- 1 | # Configuring Preprocessors 2 | 3 | Preprocessors are extensions that can modify the raw Markdown source before it gets sent to the renderer. 4 | 5 | The following preprocessors are built-in and included by default: 6 | 7 | - `links`: Expands the `{{ #playground }}`, `{{ #include }}`, and `{{ #rustdoc_include }}` handlebars 8 | helpers in a chapter to include the contents of a file. 9 | See [Including files] for more. 10 | - `index`: Convert all chapter files named `README.md` into `index.md`. That is 11 | to say, all `README.md` would be rendered to an index file `index.html` in the 12 | rendered book. 13 | 14 | The built-in preprocessors can be disabled with the [`build.use-default-preprocessors`] config option. 15 | 16 | The community has developed several preprocessors. 17 | See the [Third Party Plugins] wiki page for a list of available preprocessors. 18 | 19 | For information on how to create a new preprocessor, see the [Preprocessors for Developers] chapter. 20 | 21 | [Including files]: ../mdbook.md#including-files 22 | [`build.use-default-preprocessors`]: general.md#build-options 23 | [Third Party Plugins]: https://github.com/rust-lang/mdBook/wiki/Third-party-plugins 24 | [Preprocessors for Developers]: ../../for_developers/preprocessors.md 25 | 26 | ## Custom Preprocessor Configuration 27 | 28 | Preprocessors can be added by including a `preprocessor` table in `book.toml` with the name of the preprocessor. 29 | For example, if you have a preprocessor called `mdbook-example`, then you can include it with: 30 | 31 | ```toml 32 | [preprocessor.example] 33 | ``` 34 | 35 | With this table, mdBook will execute the `mdbook-example` preprocessor. 36 | 37 | This table can include additional key-value pairs that are specific to the preprocessor. 38 | For example, if our example prepocessor needed some extra configuration options: 39 | 40 | ```toml 41 | [preprocessor.example] 42 | some-extra-feature = true 43 | ``` 44 | 45 | ## Locking a Preprocessor dependency to a renderer 46 | 47 | You can explicitly specify that a preprocessor should run for a renderer by 48 | binding the two together. 49 | 50 | ```toml 51 | [preprocessor.example] 52 | renderers = ["html"] # example preprocessor only runs with the HTML renderer 53 | ``` 54 | 55 | ## Provide Your Own Command 56 | 57 | By default when you add a `[preprocessor.foo]` table to your `book.toml` file, 58 | `mdbook` will try to invoke the `mdbook-foo` executable. If you want to use a 59 | different program name or pass in command-line arguments, this behaviour can 60 | be overridden by adding a `command` field. 61 | 62 | ```toml 63 | [preprocessor.random] 64 | command = "python random.py" 65 | ``` 66 | 67 | ## Require A Certain Order 68 | 69 | The order in which preprocessors are run can be controlled with the `before` and `after` fields. 70 | For example, suppose you want your `linenos` preprocessor to process lines that may have been `{{#include}}`d; then you want it to run after the built-in `links` preprocessor, which you can require using either the `before` or `after` field: 71 | 72 | ```toml 73 | [preprocessor.linenos] 74 | after = [ "links" ] 75 | ``` 76 | 77 | or 78 | 79 | ```toml 80 | [preprocessor.links] 81 | before = [ "linenos" ] 82 | ``` 83 | 84 | It would also be possible, though redundant, to specify both of the above in the same config file. 85 | 86 | Preprocessors having the same priority specified through `before` and `after` are sorted by name. 87 | Any infinite loops will be detected and produce an error. 88 | -------------------------------------------------------------------------------- /src/preprocess/index.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | use std::path::Path; 3 | 4 | use crate::errors::*; 5 | 6 | use super::{Preprocessor, PreprocessorContext}; 7 | use crate::book::{Book, BookItem}; 8 | 9 | /// A preprocessor for converting file name `README.md` to `index.md` since 10 | /// `README.md` is the de facto index file in markdown-based documentation. 11 | #[derive(Default)] 12 | pub struct IndexPreprocessor; 13 | 14 | impl IndexPreprocessor { 15 | pub(crate) const NAME: &'static str = "index"; 16 | 17 | /// Create a new `IndexPreprocessor`. 18 | pub fn new() -> Self { 19 | IndexPreprocessor 20 | } 21 | } 22 | 23 | impl Preprocessor for IndexPreprocessor { 24 | fn name(&self) -> &str { 25 | Self::NAME 26 | } 27 | 28 | fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result { 29 | let source_dir = ctx.root.join(&ctx.config.book.src); 30 | book.for_each_mut(|section: &mut BookItem| { 31 | if let BookItem::Chapter(ref mut ch) = *section { 32 | if let Some(ref mut path) = ch.path { 33 | if is_readme_file(&path) { 34 | let mut index_md = source_dir.join(path.with_file_name("index.md")); 35 | if index_md.exists() { 36 | warn_readme_name_conflict(&path, &&mut index_md); 37 | } 38 | 39 | path.set_file_name("index.md"); 40 | } 41 | } 42 | } 43 | }); 44 | 45 | Ok(book) 46 | } 47 | } 48 | 49 | fn warn_readme_name_conflict>(readme_path: P, index_path: P) { 50 | let file_name = readme_path.as_ref().file_name().unwrap_or_default(); 51 | let parent_dir = index_path 52 | .as_ref() 53 | .parent() 54 | .unwrap_or_else(|| index_path.as_ref()); 55 | warn!( 56 | "It seems that there are both {:?} and index.md under \"{}\".", 57 | file_name, 58 | parent_dir.display() 59 | ); 60 | warn!( 61 | "mdbook converts {:?} into index.html by default. It may cause", 62 | file_name 63 | ); 64 | warn!("unexpected behavior if putting both files under the same directory."); 65 | warn!("To solve the warning, try to rearrange the book structure or disable"); 66 | warn!("\"index\" preprocessor to stop the conversion."); 67 | } 68 | 69 | fn is_readme_file>(path: P) -> bool { 70 | lazy_static! { 71 | static ref RE: Regex = Regex::new(r"(?i)^readme$").unwrap(); 72 | } 73 | RE.is_match( 74 | path.as_ref() 75 | .file_stem() 76 | .and_then(std::ffi::OsStr::to_str) 77 | .unwrap_or_default(), 78 | ) 79 | } 80 | 81 | #[cfg(test)] 82 | mod tests { 83 | use super::*; 84 | 85 | #[test] 86 | fn file_stem_exactly_matches_readme_case_insensitively() { 87 | let path = "path/to/Readme.md"; 88 | assert!(is_readme_file(path)); 89 | 90 | let path = "path/to/README.md"; 91 | assert!(is_readme_file(path)); 92 | 93 | let path = "path/to/rEaDmE.md"; 94 | assert!(is_readme_file(path)); 95 | 96 | let path = "path/to/README.markdown"; 97 | assert!(is_readme_file(path)); 98 | 99 | let path = "path/to/README"; 100 | assert!(is_readme_file(path)); 101 | 102 | let path = "path/to/README-README.md"; 103 | assert!(!is_readme_file(path)); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /examples/nop-preprocessor.rs: -------------------------------------------------------------------------------- 1 | use crate::nop_lib::Nop; 2 | use clap::{App, Arg, ArgMatches}; 3 | use mdbook::book::Book; 4 | use mdbook::errors::Error; 5 | use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext}; 6 | use semver::{Version, VersionReq}; 7 | use std::io; 8 | use std::process; 9 | 10 | pub fn make_app() -> App<'static> { 11 | App::new("nop-preprocessor") 12 | .about("A mdbook preprocessor which does precisely nothing") 13 | .subcommand( 14 | App::new("supports") 15 | .arg(Arg::new("renderer").required(true)) 16 | .about("Check whether a renderer is supported by this preprocessor"), 17 | ) 18 | } 19 | 20 | fn main() { 21 | let matches = make_app().get_matches(); 22 | 23 | // Users will want to construct their own preprocessor here 24 | let preprocessor = Nop::new(); 25 | 26 | if let Some(sub_args) = matches.subcommand_matches("supports") { 27 | handle_supports(&preprocessor, sub_args); 28 | } else if let Err(e) = handle_preprocessing(&preprocessor) { 29 | eprintln!("{}", e); 30 | process::exit(1); 31 | } 32 | } 33 | 34 | fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> { 35 | let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?; 36 | 37 | let book_version = Version::parse(&ctx.mdbook_version)?; 38 | let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?; 39 | 40 | if !version_req.matches(&book_version) { 41 | eprintln!( 42 | "Warning: The {} plugin was built against version {} of mdbook, \ 43 | but we're being called from version {}", 44 | pre.name(), 45 | mdbook::MDBOOK_VERSION, 46 | ctx.mdbook_version 47 | ); 48 | } 49 | 50 | let processed_book = pre.run(&ctx, book)?; 51 | serde_json::to_writer(io::stdout(), &processed_book)?; 52 | 53 | Ok(()) 54 | } 55 | 56 | fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! { 57 | let renderer = sub_args.value_of("renderer").expect("Required argument"); 58 | let supported = pre.supports_renderer(renderer); 59 | 60 | // Signal whether the renderer is supported by exiting with 1 or 0. 61 | if supported { 62 | process::exit(0); 63 | } else { 64 | process::exit(1); 65 | } 66 | } 67 | 68 | /// The actual implementation of the `Nop` preprocessor. This would usually go 69 | /// in your main `lib.rs` file. 70 | mod nop_lib { 71 | use super::*; 72 | 73 | /// A no-op preprocessor. 74 | pub struct Nop; 75 | 76 | impl Nop { 77 | pub fn new() -> Nop { 78 | Nop 79 | } 80 | } 81 | 82 | impl Preprocessor for Nop { 83 | fn name(&self) -> &str { 84 | "nop-preprocessor" 85 | } 86 | 87 | fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result { 88 | // In testing we want to tell the preprocessor to blow up by setting a 89 | // particular config value 90 | if let Some(nop_cfg) = ctx.config.get_preprocessor(self.name()) { 91 | if nop_cfg.contains_key("blow-up") { 92 | anyhow::bail!("Boom!!1!"); 93 | } 94 | } 95 | 96 | // we *are* a no-op preprocessor after all 97 | Ok(book) 98 | } 99 | 100 | fn supports_renderer(&self, renderer: &str) -> bool { 101 | renderer != "not-supported" 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /guide/src/format/theme/index-hbs.md: -------------------------------------------------------------------------------- 1 | # index.hbs 2 | 3 | `index.hbs` is the handlebars template that is used to render the book. The 4 | markdown files are processed to html and then injected in that template. 5 | 6 | If you want to change the layout or style of your book, chances are that you 7 | will have to modify this template a little bit. Here is what you need to know. 8 | 9 | ## Data 10 | 11 | A lot of data is exposed to the handlebars template with the "context". In the 12 | handlebars template you can access this information by using 13 | 14 | ```handlebars 15 | {{name_of_property}} 16 | ``` 17 | 18 | Here is a list of the properties that are exposed: 19 | 20 | - ***language*** Language of the book in the form `en`, as specified in `book.toml` (if not specified, defaults to `en`). To use in \ for example. 22 | - ***title*** Title used for the current page. This is identical to `{{ chapter_title }} - {{ book_title }}` unless `book_title` is not set in which case it just defaults to the `chapter_title`. 23 | - ***book_title*** Title of the book, as specified in `book.toml` 24 | - ***chapter_title*** Title of the current chapter, as listed in `SUMMARY.md` 25 | 26 | - ***path*** Relative path to the original markdown file from the source 27 | directory 28 | - ***content*** This is the rendered markdown. 29 | - ***path_to_root*** This is a path containing exclusively `../`'s that points 30 | to the root of the book from the current file. Since the original directory 31 | structure is maintained, it is useful to prepend relative links with this 32 | `path_to_root`. 33 | 34 | - ***chapters*** Is an array of dictionaries of the form 35 | ```json 36 | {"section": "1.2.1", "name": "name of this chapter", "path": "dir/markdown.md"} 37 | ``` 38 | containing all the chapters of the book. It is used for example to construct 39 | the table of contents (sidebar). 40 | 41 | ## Handlebars Helpers 42 | 43 | In addition to the properties you can access, there are some handlebars helpers 44 | at your disposal. 45 | 46 | ### 1. toc 47 | 48 | The toc helper is used like this 49 | 50 | ```handlebars 51 | {{#toc}}{{/toc}} 52 | ``` 53 | 54 | and outputs something that looks like this, depending on the structure of your 55 | book 56 | 57 | ```html 58 | 66 | ``` 67 | 68 | If you would like to make a toc with another structure, you have access to the 69 | chapters property containing all the data. The only limitation at the moment 70 | is that you would have to do it with JavaScript instead of with a handlebars 71 | helper. 72 | 73 | ```html 74 | 78 | ``` 79 | 80 | ### 2. previous / next 81 | 82 | The previous and next helpers expose a `link` and `name` property to the 83 | previous and next chapters. 84 | 85 | They are used like this 86 | 87 | ```handlebars 88 | {{#previous}} 89 | 92 | {{/previous}} 93 | ``` 94 | 95 | The inner html will only be rendered if the previous / next chapter exists. 96 | Of course the inner html can be changed to your liking. 97 | 98 | ------ 99 | 100 | *If you would like other properties or helpers exposed, please [create a new 101 | issue](https://github.com/rust-lang/mdBook/issues)* 102 | -------------------------------------------------------------------------------- /guide/src/format/configuration/general.md: -------------------------------------------------------------------------------- 1 | # General Configuration 2 | 3 | You can configure the parameters for your book in the ***book.toml*** file. 4 | 5 | Here is an example of what a ***book.toml*** file might look like: 6 | 7 | ```toml 8 | [book] 9 | title = "Example book" 10 | author = "John Doe" 11 | description = "The example book covers examples." 12 | 13 | [rust] 14 | edition = "2018" 15 | 16 | [build] 17 | build-dir = "my-example-book" 18 | create-missing = false 19 | 20 | [preprocessor.index] 21 | 22 | [preprocessor.links] 23 | 24 | [output.html] 25 | additional-css = ["custom.css"] 26 | 27 | [output.html.search] 28 | limit-results = 15 29 | ``` 30 | 31 | ## Supported configuration options 32 | 33 | It is important to note that **any** relative path specified in the 34 | configuration will always be taken relative from the root of the book where the 35 | configuration file is located. 36 | 37 | ### General metadata 38 | 39 | This is general information about your book. 40 | 41 | - **title:** The title of the book 42 | - **authors:** The author(s) of the book 43 | - **description:** A description for the book, which is added as meta 44 | information in the html `` of each page 45 | - **src:** By default, the source directory is found in the directory named 46 | `src` directly under the root folder. But this is configurable with the `src` 47 | key in the configuration file. 48 | - **language:** The main language of the book, which is used as a language attribute `` for example. 49 | 50 | **book.toml** 51 | ```toml 52 | [book] 53 | title = "Example book" 54 | authors = ["John Doe", "Jane Doe"] 55 | description = "The example book covers examples." 56 | src = "my-src" # the source files will be found in `root/my-src` instead of `root/src` 57 | language = "en" 58 | ``` 59 | 60 | ### Rust options 61 | 62 | Options for the Rust language, relevant to running tests and playground 63 | integration. 64 | 65 | ```toml 66 | [rust] 67 | edition = "2015" # the default edition for code blocks 68 | ``` 69 | 70 | - **edition**: Rust edition to use by default for the code snippets. Default 71 | is "2015". Individual code blocks can be controlled with the `edition2015`, 72 | `edition2018` or `edition2021` annotations, such as: 73 | 74 | ~~~text 75 | ```rust,edition2015 76 | // This only works in 2015. 77 | let try = true; 78 | ``` 79 | ~~~ 80 | 81 | ### Build options 82 | 83 | This controls the build process of your book. 84 | 85 | ```toml 86 | [build] 87 | build-dir = "book" # the directory where the output is placed 88 | create-missing = true # whether or not to create missing pages 89 | use-default-preprocessors = true # use the default preprocessors 90 | ``` 91 | 92 | - **build-dir:** The directory to put the rendered book in. By default this is 93 | `book/` in the book's root directory. 94 | This can overridden with the `--dest-dir` CLI option. 95 | - **create-missing:** By default, any missing files specified in `SUMMARY.md` 96 | will be created when the book is built (i.e. `create-missing = true`). If this 97 | is `false` then the build process will instead exit with an error if any files 98 | do not exist. 99 | - **use-default-preprocessors:** Disable the default preprocessors of (`links` & 100 | `index`) by setting this option to `false`. 101 | 102 | If you have the same, and/or other preprocessors declared via their table 103 | of configuration, they will run instead. 104 | 105 | - For clarity, with no preprocessor configuration, the default `links` and 106 | `index` will run. 107 | - Setting `use-default-preprocessors = false` will disable these 108 | default preprocessors from running. 109 | - Adding `[preprocessor.links]`, for example, will ensure, regardless of 110 | `use-default-preprocessors` that `links` it will run. 111 | -------------------------------------------------------------------------------- /guide/src/guide/reading.md: -------------------------------------------------------------------------------- 1 | # Reading Books 2 | 3 | This chapter gives an introduction on how to interact with a book produced by mdBook. 4 | This assumes you are reading an HTML book. 5 | The options and formatting will be different for other output formats such as PDF. 6 | 7 | A book is organized into *chapters*. 8 | Each chapter is a separate page. 9 | Chapters can be nested into a hierarchy of sub-chapters. 10 | Typically, each chapter will be organized into a series of *headings* to subdivide a chapter. 11 | 12 | ## Navigation 13 | 14 | There are several methods for navigating through the chapters of a book. 15 | 16 | The **sidebar** on the left provides a list of all chapters. 17 | Clicking on any of the chapter titles will load that page. 18 | 19 | The sidebar may not automatically appear if the window is too narrow, particularly on mobile displays. 20 | In that situation, the menu icon (three horizontal bars) at the top-left of the page can be pressed to open and close the sidebar. 21 | 22 | The **arrow buttons** at the bottom of the page can be used to navigate to the previous or the next chapter. 23 | 24 | The **left and right arrow keys** on the keyboard can be used to navigate to the previous or the next chapter. 25 | 26 | ## Top menu bar 27 | 28 | The menu bar at the top of the page provides some icons for interacting with the book. 29 | The icons displayed will depend on the settings of how the book was generated. 30 | 31 | | Icon | Description | 32 | |------|-------------| 33 | | | Opens and closes the chapter listing sidebar. | 34 | | | Opens a picker to choose a different color theme. | 35 | | | Opens a search bar for searching within the book. | 36 | | | Instructs the web browser to print the entire book. | 37 | | | Opens a link to the website that hosts the source code of the book. | 38 | | | Opens a page to directly edit the source of the page you are currently reading. | 39 | 40 | Tapping the menu bar will scroll the page to the top. 41 | 42 | ## Search 43 | 44 | Each book has a built-in search system. 45 | Pressing the search icon () in the menu bar, or pressing the `S` key on the keyboard will open an input box for entering search terms. 46 | Typing some terms will show matching chapters and sections in real time. 47 | 48 | Clicking any of the results will jump to that section. 49 | The up and down arrow keys can be used to navigate the results, and enter will open the highlighted section. 50 | 51 | After loading a search result, the matching search terms will be highlighted in the text. 52 | Clicking a highlighted word or pressing the `Esc` key will remove the highlighting. 53 | 54 | ## Code blocks 55 | 56 | mdBook books are often used for programming projects, and thus support highlighting code blocks and samples. 57 | Code blocks may contain several different icons for interacting with them: 58 | 59 | | Icon | Description | 60 | |------|-------------| 61 | | | Copies the code block into your local clipboard, to allow pasting into another application. | 62 | | | For Rust code examples, this will execute the sample code and display the compiler output just below the example (see [playground]). | 63 | | | For Rust code examples, this will toggle visibility of "hidden" lines. Sometimes, larger examples will hide lines which are not particularly relevant to what is being illustrated (see [hiding code lines]). | 64 | | | For [editable code examples][editor], this will undo any changes you have made. | 65 | 66 | Here's an example: 67 | 68 | ```rust 69 | println!("Hello, World!"); 70 | ``` 71 | 72 | [editor]: ../format/theme/editor.md 73 | [playground]: ../format/mdbook.md#rust-playground 74 | [hiding code lines]: ../format/mdbook.md#hiding-code-lines 75 | -------------------------------------------------------------------------------- /src/utils/toml_ext.rs: -------------------------------------------------------------------------------- 1 | use toml::value::{Table, Value}; 2 | 3 | pub(crate) trait TomlExt { 4 | fn read(&self, key: &str) -> Option<&Value>; 5 | fn read_mut(&mut self, key: &str) -> Option<&mut Value>; 6 | fn insert(&mut self, key: &str, value: Value); 7 | fn delete(&mut self, key: &str) -> Option; 8 | } 9 | 10 | impl TomlExt for Value { 11 | fn read(&self, key: &str) -> Option<&Value> { 12 | if let Some((head, tail)) = split(key) { 13 | self.get(head)?.read(tail) 14 | } else { 15 | self.get(key) 16 | } 17 | } 18 | 19 | fn read_mut(&mut self, key: &str) -> Option<&mut Value> { 20 | if let Some((head, tail)) = split(key) { 21 | self.get_mut(head)?.read_mut(tail) 22 | } else { 23 | self.get_mut(key) 24 | } 25 | } 26 | 27 | fn insert(&mut self, key: &str, value: Value) { 28 | if !self.is_table() { 29 | *self = Value::Table(Table::new()); 30 | } 31 | 32 | let table = self.as_table_mut().expect("unreachable"); 33 | 34 | if let Some((head, tail)) = split(key) { 35 | table 36 | .entry(head) 37 | .or_insert_with(|| Value::Table(Table::new())) 38 | .insert(tail, value); 39 | } else { 40 | table.insert(key.to_string(), value); 41 | } 42 | } 43 | 44 | fn delete(&mut self, key: &str) -> Option { 45 | if let Some((head, tail)) = split(key) { 46 | self.get_mut(head)?.delete(tail) 47 | } else if let Some(table) = self.as_table_mut() { 48 | table.remove(key) 49 | } else { 50 | None 51 | } 52 | } 53 | } 54 | 55 | fn split(key: &str) -> Option<(&str, &str)> { 56 | let ix = key.find('.')?; 57 | 58 | let (head, tail) = key.split_at(ix); 59 | // splitting will leave the "." 60 | let tail = &tail[1..]; 61 | 62 | Some((head, tail)) 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::*; 68 | use std::str::FromStr; 69 | 70 | #[test] 71 | fn read_simple_table() { 72 | let src = "[table]"; 73 | let value = Value::from_str(src).unwrap(); 74 | 75 | let got = value.read("table").unwrap(); 76 | 77 | assert!(got.is_table()); 78 | } 79 | 80 | #[test] 81 | fn read_nested_item() { 82 | let src = "[table]\nnested=true"; 83 | let value = Value::from_str(src).unwrap(); 84 | 85 | let got = value.read("table.nested").unwrap(); 86 | 87 | assert_eq!(got, &Value::Boolean(true)); 88 | } 89 | 90 | #[test] 91 | fn insert_item_at_top_level() { 92 | let mut value = Value::Table(Table::default()); 93 | let item = Value::Boolean(true); 94 | 95 | value.insert("first", item.clone()); 96 | 97 | assert_eq!(value.get("first").unwrap(), &item); 98 | } 99 | 100 | #[test] 101 | fn insert_nested_item() { 102 | let mut value = Value::Table(Table::default()); 103 | let item = Value::Boolean(true); 104 | 105 | value.insert("first.second", item.clone()); 106 | 107 | let inserted = value.read("first.second").unwrap(); 108 | assert_eq!(inserted, &item); 109 | } 110 | 111 | #[test] 112 | fn delete_a_top_level_item() { 113 | let src = "top = true"; 114 | let mut value = Value::from_str(src).unwrap(); 115 | 116 | let got = value.delete("top").unwrap(); 117 | 118 | assert_eq!(got, Value::Boolean(true)); 119 | } 120 | 121 | #[test] 122 | fn delete_a_nested_item() { 123 | let src = "[table]\n nested = true"; 124 | let mut value = Value::from_str(src).unwrap(); 125 | 126 | let got = value.delete("table.nested").unwrap(); 127 | 128 | assert_eq!(got, Value::Boolean(true)); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/theme/fonts/fonts.css: -------------------------------------------------------------------------------- 1 | /* Open Sans is licensed under the Apache License, Version 2.0. See http://www.apache.org/licenses/LICENSE-2.0 */ 2 | /* Source Code Pro is under the Open Font License. See https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL */ 3 | 4 | /* open-sans-300 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 5 | @font-face { 6 | font-family: 'Open Sans'; 7 | font-style: normal; 8 | font-weight: 300; 9 | src: local('Open Sans Light'), local('OpenSans-Light'), 10 | url('open-sans-v17-all-charsets-300.woff2') format('woff2'); 11 | } 12 | 13 | /* open-sans-300italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 14 | @font-face { 15 | font-family: 'Open Sans'; 16 | font-style: italic; 17 | font-weight: 300; 18 | src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'), 19 | url('open-sans-v17-all-charsets-300italic.woff2') format('woff2'); 20 | } 21 | 22 | /* open-sans-regular - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 23 | @font-face { 24 | font-family: 'Open Sans'; 25 | font-style: normal; 26 | font-weight: 400; 27 | src: local('Open Sans Regular'), local('OpenSans-Regular'), 28 | url('open-sans-v17-all-charsets-regular.woff2') format('woff2'); 29 | } 30 | 31 | /* open-sans-italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 32 | @font-face { 33 | font-family: 'Open Sans'; 34 | font-style: italic; 35 | font-weight: 400; 36 | src: local('Open Sans Italic'), local('OpenSans-Italic'), 37 | url('open-sans-v17-all-charsets-italic.woff2') format('woff2'); 38 | } 39 | 40 | /* open-sans-600 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 41 | @font-face { 42 | font-family: 'Open Sans'; 43 | font-style: normal; 44 | font-weight: 600; 45 | src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), 46 | url('open-sans-v17-all-charsets-600.woff2') format('woff2'); 47 | } 48 | 49 | /* open-sans-600italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 50 | @font-face { 51 | font-family: 'Open Sans'; 52 | font-style: italic; 53 | font-weight: 600; 54 | src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'), 55 | url('open-sans-v17-all-charsets-600italic.woff2') format('woff2'); 56 | } 57 | 58 | /* open-sans-700 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 59 | @font-face { 60 | font-family: 'Open Sans'; 61 | font-style: normal; 62 | font-weight: 700; 63 | src: local('Open Sans Bold'), local('OpenSans-Bold'), 64 | url('open-sans-v17-all-charsets-700.woff2') format('woff2'); 65 | } 66 | 67 | /* open-sans-700italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 68 | @font-face { 69 | font-family: 'Open Sans'; 70 | font-style: italic; 71 | font-weight: 700; 72 | src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), 73 | url('open-sans-v17-all-charsets-700italic.woff2') format('woff2'); 74 | } 75 | 76 | /* open-sans-800 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 77 | @font-face { 78 | font-family: 'Open Sans'; 79 | font-style: normal; 80 | font-weight: 800; 81 | src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'), 82 | url('open-sans-v17-all-charsets-800.woff2') format('woff2'); 83 | } 84 | 85 | /* open-sans-800italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 86 | @font-face { 87 | font-family: 'Open Sans'; 88 | font-style: italic; 89 | font-weight: 800; 90 | src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'), 91 | url('open-sans-v17-all-charsets-800italic.woff2') format('woff2'); 92 | } 93 | 94 | /* source-code-pro-500 - latin_vietnamese_latin-ext_greek_cyrillic-ext_cyrillic */ 95 | @font-face { 96 | font-family: 'Source Code Pro'; 97 | font-style: normal; 98 | font-weight: 500; 99 | src: url('source-code-pro-v11-all-charsets-500.woff2') format('woff2'); 100 | } 101 | -------------------------------------------------------------------------------- /guide/src/format/summary.md: -------------------------------------------------------------------------------- 1 | # SUMMARY.md 2 | 3 | The summary file is used by mdBook to know what chapters to include, in what 4 | order they should appear, what their hierarchy is and where the source files 5 | are. Without this file, there is no book. 6 | 7 | This markdown file must be named `SUMMARY.md`. Its formatting 8 | is very strict and must follow the structure outlined below to allow for easy 9 | parsing. Any element not specified below, be it formatting or textual, is likely 10 | to be ignored at best, or may cause an error when attempting to build the book. 11 | 12 | ### Structure 13 | 14 | 1. ***Title*** - While optional, it's common practice to begin with a title, generally # Summary. This is ignored by the parser however, and 16 | can be omitted. 17 | ```markdown 18 | # Summary 19 | ``` 20 | 21 | 1. ***Prefix Chapter*** - Before the main numbered chapters, prefix chapters can be added 22 | that will not be numbered. This is useful for forewords, 23 | introductions, etc. There are, however, some constraints. Prefix chapters cannot be 24 | nested; they should all be on the root level. And you cannot add 25 | prefix chapters once you have added numbered chapters. 26 | ```markdown 27 | [A Prefix Chapter](relative/path/to/markdown.md) 28 | 29 | - [First Chapter](relative/path/to/markdown2.md) 30 | ``` 31 | 32 | 1. ***Part Title*** - Headers can be used as a title for the following numbered 33 | chapters. This can be used to logically separate different sections 34 | of the book. The title is rendered as unclickable text. 35 | Titles are optional, and the numbered chapters can be broken into as many 36 | parts as desired. 37 | ```markdown 38 | # My Part Title 39 | 40 | - [First Chapter](relative/path/to/markdown.md) 41 | ``` 42 | 43 | 1. ***Numbered Chapter*** - Numbered chapters outline the main content of the book 44 | and can be nested, resulting in a nice hierarchy 45 | (chapters, sub-chapters, etc.). 46 | ```markdown 47 | # Title of Part 48 | 49 | - [First Chapter](relative/path/to/markdown.md) 50 | - [Second Chapter](relative/path/to/markdown2.md) 51 | - [Sub Chapter](relative/path/to/markdown3.md) 52 | 53 | # Title of Another Part 54 | 55 | - [Another Chapter](relative/path/to/markdown4.md) 56 | ``` 57 | Numbered chapters can be denoted with either `-` or `*` (do not mix delimiters). 58 | 59 | 1. ***Suffix Chapter*** - Like prefix chapters, suffix chapters are unnumbered, but they come after 60 | numbered chapters. 61 | ```markdown 62 | - [Last Chapter](relative/path/to/markdown.md) 63 | 64 | [Title of Suffix Chapter](relative/path/to/markdown2.md) 65 | ``` 66 | 67 | 1. ***Draft chapters*** - Draft chapters are chapters without a file and thus content. 68 | The purpose of a draft chapter is to signal future chapters still to be written. 69 | Or when still laying out the structure of the book to avoid creating the files 70 | while you are still changing the structure of the book a lot. 71 | Draft chapters will be rendered in the HTML renderer as disabled links in the table 72 | of contents, as you can see for the next chapter in the table of contents on the left. 73 | Draft chapters are written like normal chapters but without writing the path to the file. 74 | ```markdown 75 | - [Draft Chapter]() 76 | ``` 77 | 78 | 1. ***Separators*** - Separators can be added before, in between, and after any other element. They result 79 | in an HTML rendered line in the built table of contents. A separator is 80 | a line containing exclusively dashes and at least three of them: `---`. 81 | ```markdown 82 | # My Part Title 83 | 84 | [A Prefix Chapter](relative/path/to/markdown.md) 85 | 86 | --- 87 | 88 | - [First Chapter](relative/path/to/markdown2.md) 89 | ``` 90 | 91 | 92 | ### Example 93 | 94 | Below is the markdown source for the `SUMMARY.md` for this guide, with the resulting table 95 | of contents as rendered to the left. 96 | 97 | ```markdown 98 | {{#include ../SUMMARY.md}} 99 | ``` 100 | -------------------------------------------------------------------------------- /guide/src/guide/creating.md: -------------------------------------------------------------------------------- 1 | # Creating a Book 2 | 3 | Once you have the `mdbook` CLI tool installed, you can use it to create and render a book. 4 | 5 | ## Initializing a book 6 | 7 | The `mdbook init` command will create a new directory containing an empty book for you to get started. 8 | Give it the name of the directory that you want to create: 9 | 10 | ```sh 11 | mdbook init my-first-book 12 | ``` 13 | 14 | It will ask a few questions before generating the book. 15 | After answering the questions, you can change the current directory into the new book: 16 | 17 | ```sh 18 | cd my-first-book 19 | ``` 20 | 21 | There are several ways to render a book, but one of the easiest methods is to use the `serve` command, which will build your book and start a local webserver: 22 | 23 | ```sh 24 | mdbook serve --open 25 | ``` 26 | 27 | The `--open` option will open your default web browser to view your new book. 28 | You can leave the server running even while you edit the content of the book, and `mdbook` will automatically rebuild the output *and* automatically refresh your web browser. 29 | 30 | Check out the [CLI Guide](../cli/index.html) for more information about other `mdbook` commands and CLI options. 31 | 32 | ## Anatomy of a book 33 | 34 | A book is built from several files which define the settings and layout of the book. 35 | 36 | ### `book.toml` 37 | 38 | In the root of your book, there is a `book.toml` file which contains settings for describing how to build your book. 39 | This is written in the [TOML markup language](https://toml.io/). 40 | The default settings are usually good enough to get you started. 41 | When you are interested in exploring more features and options that mdBook provides, check out the [Configuration chapter](../format/configuration/index.html) for more details. 42 | 43 | A very basic `book.toml` can be as simple as this: 44 | 45 | ```toml 46 | [book] 47 | title = "My First Book" 48 | ``` 49 | 50 | ### `SUMMARY.md` 51 | 52 | The next major part of a book is the summary file located at `src/SUMMARY.md`. 53 | This file contains a list of all the chapters in the book. 54 | Before a chapter can be viewed, it must be added to this list. 55 | 56 | Here's a basic summary file with a few chapters: 57 | 58 | ```md 59 | # Summary 60 | 61 | [Introduction](README.md) 62 | 63 | - [My First Chapter](my-first-chapter.md) 64 | - [Nested example](nested/README.md) 65 | - [Sub-chapter](nested/sub-chapter.md) 66 | ``` 67 | 68 | Try opening up `src/SUMMARY.md` in your editor and adding a few chapters. 69 | If any of the chapter files do not exist, `mdbook` will automatically create them for you. 70 | 71 | For more details on other formatting options for the summary file, check out the [Summary chapter](../format/summary.md). 72 | 73 | ### Source files 74 | 75 | The content of your book is all contained in the `src` directory. 76 | Each chapter is a separate Markdown file. 77 | Typically, each chapter starts with a level 1 heading with the title of the chapter. 78 | 79 | ```md 80 | # My First Chapter 81 | 82 | Fill out your content here. 83 | ``` 84 | 85 | The precise layout of the files is up to you. 86 | The organization of the files will correspond to the HTML files generated, so keep in mind that the file layout is part of the URL of each chapter. 87 | 88 | While the `mdbook serve` command is running, you can open any of the chapter files and start editing them. 89 | Each time you save the file, `mdbook` will rebuild the book and refresh your web browser. 90 | 91 | Check out the [Markdown chapter](../format/markdown.md) for more information on formatting the content of your chapters. 92 | 93 | All other files in the `src` directory will be included in the output. 94 | So if you have images or other static files, just include them somewhere in the `src` directory. 95 | 96 | ## Publishing a book 97 | 98 | Once you've written your book, you may want to host it somewhere for others to view. 99 | The first step is to build the output of the book. 100 | This can be done with the `mdbook build` command in the same directory where the `book.toml` file is located: 101 | 102 | ```sh 103 | mdbook build 104 | ``` 105 | 106 | This will generate a directory named `book` which contains the HTML content of your book. 107 | You can then place this directory on any web server to host it. 108 | 109 | For more information about publishing and deploying, check out the [Continuous Integration chapter](../continuous-integration.md) for more. -------------------------------------------------------------------------------- /src/cmd/init.rs: -------------------------------------------------------------------------------- 1 | use crate::get_book_dir; 2 | use clap::{arg, App, Arg, ArgMatches}; 3 | use mdbook::config; 4 | use mdbook::errors::Result; 5 | use mdbook::MDBook; 6 | use std::io; 7 | use std::io::Write; 8 | use std::process::Command; 9 | 10 | // Create clap subcommand arguments 11 | pub fn make_subcommand<'help>() -> App<'help> { 12 | App::new("init") 13 | .about("Creates the boilerplate structure and files for a new book") 14 | // the {n} denotes a newline which will properly aligned in all help messages 15 | .arg(arg!([dir] 16 | "Directory to create the book in{n}\ 17 | (Defaults to the Current Directory when omitted)" 18 | )) 19 | .arg(arg!(--theme "Copies the default theme into your source folder")) 20 | .arg(arg!(--force "Skips confirmation prompts")) 21 | .arg( 22 | Arg::new("title") 23 | .long("title") 24 | .takes_value(true) 25 | .help("Sets the book title") 26 | .required(false), 27 | ) 28 | .arg( 29 | Arg::new("ignore") 30 | .long("ignore") 31 | .takes_value(true) 32 | .possible_values(&["none", "git"]) 33 | .help("Creates a VCS ignore file (i.e. .gitignore)") 34 | .required(false), 35 | ) 36 | } 37 | 38 | // Init command implementation 39 | pub fn execute(args: &ArgMatches) -> Result<()> { 40 | let book_dir = get_book_dir(args); 41 | let mut builder = MDBook::init(&book_dir); 42 | let mut config = config::Config::default(); 43 | // If flag `--theme` is present, copy theme to src 44 | if args.is_present("theme") { 45 | let theme_dir = book_dir.join("theme"); 46 | println!(); 47 | println!("Copying the default theme to {}", theme_dir.display()); 48 | // Skip this if `--force` is present 49 | if !args.is_present("force") && theme_dir.exists() { 50 | println!("This could potentially overwrite files already present in that directory."); 51 | print!("\nAre you sure you want to continue? (y/n) "); 52 | 53 | // Read answer from user and exit if it's not 'yes' 54 | if confirm() { 55 | builder.copy_theme(true); 56 | } 57 | } else { 58 | builder.copy_theme(true); 59 | } 60 | } 61 | 62 | if let Some(ignore) = args.value_of("ignore") { 63 | match ignore { 64 | "git" => builder.create_gitignore(true), 65 | _ => builder.create_gitignore(false), 66 | }; 67 | } else { 68 | println!("\nDo you want a .gitignore to be created? (y/n)"); 69 | if confirm() { 70 | builder.create_gitignore(true); 71 | } 72 | } 73 | 74 | config.book.title = if args.is_present("title") { 75 | args.value_of("title").map(String::from) 76 | } else { 77 | request_book_title() 78 | }; 79 | 80 | if let Some(author) = get_author_name() { 81 | debug!("Obtained user name from gitconfig: {:?}", author); 82 | config.book.authors.push(author); 83 | builder.with_config(config); 84 | } 85 | 86 | builder.build()?; 87 | println!("\nAll done, no errors..."); 88 | 89 | Ok(()) 90 | } 91 | 92 | /// Obtains author name from git config file by running the `git config` command. 93 | fn get_author_name() -> Option { 94 | let output = Command::new("git") 95 | .args(&["config", "--get", "user.name"]) 96 | .output() 97 | .ok()?; 98 | 99 | if output.status.success() { 100 | Some(String::from_utf8_lossy(&output.stdout).trim().to_owned()) 101 | } else { 102 | None 103 | } 104 | } 105 | 106 | /// Request book title from user and return if provided. 107 | fn request_book_title() -> Option { 108 | println!("What title would you like to give the book? "); 109 | io::stdout().flush().unwrap(); 110 | let mut resp = String::new(); 111 | io::stdin().read_line(&mut resp).unwrap(); 112 | let resp = resp.trim(); 113 | if resp.is_empty() { 114 | None 115 | } else { 116 | Some(resp.into()) 117 | } 118 | } 119 | 120 | // Simple function for user confirmation 121 | fn confirm() -> bool { 122 | io::stdout().flush().unwrap(); 123 | let mut s = String::new(); 124 | io::stdin().read_line(&mut s).ok(); 125 | match &*s.trim() { 126 | "Y" | "y" | "yes" | "Yes" => true, 127 | _ => false, 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # mdBook 2 | //! 3 | //! **mdBook** is a tool for rendering a collection of markdown documents into 4 | //! a form more suitable for end users like HTML or EPUB. It offers a command 5 | //! line interface, but this crate can be used if more control is required. 6 | //! 7 | //! This is the API doc, the [user guide] is also available if you want 8 | //! information about the command line tool, format, structure etc. It is also 9 | //! rendered with mdBook to showcase the features and default theme. 10 | //! 11 | //! Some reasons why you would want to use the crate (over the cli): 12 | //! 13 | //! - Integrate mdbook in a current project 14 | //! - Extend the capabilities of mdBook 15 | //! - Do some processing or test before building your book 16 | //! - Accessing the public API to help create a new Renderer 17 | //! - ... 18 | //! 19 | //! > **Note:** While we try to ensure `mdbook`'s command-line interface and 20 | //! > behaviour are backwards compatible, the tool's internals are still 21 | //! > evolving and being iterated on. If you wish to prevent accidental 22 | //! > breakages it is recommended to pin any tools building on top of the 23 | //! > `mdbook` crate to a specific release. 24 | //! 25 | //! # Examples 26 | //! 27 | //! If creating a new book from scratch, you'll want to get a `BookBuilder` via 28 | //! the `MDBook::init()` method. 29 | //! 30 | //! ```rust,no_run 31 | //! use mdbook::MDBook; 32 | //! use mdbook::config::Config; 33 | //! 34 | //! let root_dir = "/path/to/book/root"; 35 | //! 36 | //! // create a default config and change a couple things 37 | //! let mut cfg = Config::default(); 38 | //! cfg.book.title = Some("My Book".to_string()); 39 | //! cfg.book.authors.push("Michael-F-Bryan".to_string()); 40 | //! 41 | //! MDBook::init(root_dir) 42 | //! .create_gitignore(true) 43 | //! .with_config(cfg) 44 | //! .build() 45 | //! .expect("Book generation failed"); 46 | //! ``` 47 | //! 48 | //! You can also load an existing book and build it. 49 | //! 50 | //! ```rust,no_run 51 | //! use mdbook::MDBook; 52 | //! 53 | //! let root_dir = "/path/to/book/root"; 54 | //! 55 | //! let mut md = MDBook::load(root_dir) 56 | //! .expect("Unable to load the book"); 57 | //! md.build().expect("Building failed"); 58 | //! ``` 59 | //! 60 | //! ## Implementing a new Backend 61 | //! 62 | //! `mdbook` has a fairly flexible mechanism for creating additional backends 63 | //! for your book. The general idea is you'll add an extra table in the book's 64 | //! `book.toml` which specifies an executable to be invoked by `mdbook`. This 65 | //! executable will then be called during a build, with an in-memory 66 | //! representation ([`RenderContext`]) of the book being passed to the 67 | //! subprocess via `stdin`. 68 | //! 69 | //! The [`RenderContext`] gives the backend access to the contents of 70 | //! `book.toml` and lets it know which directory all generated artefacts should 71 | //! be placed in. For a much more in-depth explanation, consult the [relevant 72 | //! chapter] in the *For Developers* section of the user guide. 73 | //! 74 | //! To make creating a backend easier, the `mdbook` crate can be imported 75 | //! directly, making deserializing the `RenderContext` easy and giving you 76 | //! access to the various methods for working with the [`Config`]. 77 | //! 78 | //! [user guide]: https://rust-lang.github.io/mdBook/ 79 | //! [`RenderContext`]: renderer::RenderContext 80 | //! [relevant chapter]: https://rust-lang.github.io/mdBook/for_developers/backends.html 81 | //! [`Config`]: config::Config 82 | 83 | #![deny(missing_docs)] 84 | #![deny(rust_2018_idioms)] 85 | #![allow(clippy::comparison_chain)] 86 | 87 | #[macro_use] 88 | extern crate lazy_static; 89 | #[macro_use] 90 | extern crate log; 91 | #[macro_use] 92 | extern crate serde_derive; 93 | #[macro_use] 94 | extern crate serde_json; 95 | 96 | #[cfg(test)] 97 | #[macro_use] 98 | extern crate pretty_assertions; 99 | 100 | pub mod book; 101 | pub mod config; 102 | pub mod preprocess; 103 | pub mod renderer; 104 | pub mod theme; 105 | pub mod utils; 106 | 107 | /// The current version of `mdbook`. 108 | /// 109 | /// This is provided as a way for custom preprocessors and renderers to do 110 | /// compatibility checks. 111 | pub const MDBOOK_VERSION: &str = env!("CARGO_PKG_VERSION"); 112 | 113 | pub use crate::book::BookItem; 114 | pub use crate::book::MDBook; 115 | pub use crate::config::Config; 116 | pub use crate::renderer::Renderer; 117 | 118 | /// The error types used through out this crate. 119 | pub mod errors { 120 | pub(crate) use anyhow::{bail, ensure, Context}; 121 | pub use anyhow::{Error, Result}; 122 | } 123 | -------------------------------------------------------------------------------- /src/theme/fonts/SOURCE-CODE-PRO-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /src/theme/css/general.css: -------------------------------------------------------------------------------- 1 | /* Base styles and content styles */ 2 | 3 | @import 'variables.css'; 4 | 5 | :root { 6 | /* Browser default font-size is 16px, this way 1 rem = 10px */ 7 | font-size: 62.5%; 8 | } 9 | 10 | html { 11 | font-family: "Open Sans", sans-serif; 12 | color: var(--fg); 13 | background-color: var(--bg); 14 | text-size-adjust: none; 15 | -webkit-text-size-adjust: none; 16 | } 17 | 18 | body { 19 | margin: 0; 20 | font-size: 1.6rem; 21 | overflow-x: hidden; 22 | } 23 | 24 | code { 25 | font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important; 26 | font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */ 27 | } 28 | 29 | /* Don't change font size in headers. */ 30 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 31 | font-size: unset; 32 | } 33 | 34 | .left { float: left; } 35 | .right { float: right; } 36 | .boring { opacity: 0.6; } 37 | .hide-boring .boring { display: none; } 38 | .hidden { display: none !important; } 39 | 40 | h2, h3 { margin-top: 2.5em; } 41 | h4, h5 { margin-top: 2em; } 42 | 43 | .header + .header h3, 44 | .header + .header h4, 45 | .header + .header h5 { 46 | margin-top: 1em; 47 | } 48 | 49 | h1:target::before, 50 | h2:target::before, 51 | h3:target::before, 52 | h4:target::before, 53 | h5:target::before, 54 | h6:target::before { 55 | display: inline-block; 56 | content: "»"; 57 | margin-left: -30px; 58 | width: 30px; 59 | } 60 | 61 | /* This is broken on Safari as of version 14, but is fixed 62 | in Safari Technology Preview 117 which I think will be Safari 14.2. 63 | https://bugs.webkit.org/show_bug.cgi?id=218076 64 | */ 65 | :target { 66 | scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); 67 | } 68 | 69 | .page { 70 | outline: 0; 71 | padding: 0 var(--page-padding); 72 | margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ 73 | } 74 | .page-wrapper { 75 | box-sizing: border-box; 76 | } 77 | .js:not(.sidebar-resizing) .page-wrapper { 78 | transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ 79 | } 80 | 81 | .content { 82 | overflow-y: auto; 83 | padding: 0 15px; 84 | padding-bottom: 50px; 85 | } 86 | .content main { 87 | margin-left: auto; 88 | margin-right: auto; 89 | max-width: var(--content-max-width); 90 | } 91 | .content p { line-height: 1.45em; } 92 | .content ol { line-height: 1.45em; } 93 | .content ul { line-height: 1.45em; } 94 | .content a { text-decoration: none; } 95 | .content a:hover { text-decoration: underline; } 96 | .content img, .content video { max-width: 100%; } 97 | .content .header:link, 98 | .content .header:visited { 99 | color: var(--fg); 100 | } 101 | .content .header:link, 102 | .content .header:visited:hover { 103 | text-decoration: none; 104 | } 105 | 106 | table { 107 | margin: 0 auto; 108 | border-collapse: collapse; 109 | } 110 | table td { 111 | padding: 3px 20px; 112 | border: 1px var(--table-border-color) solid; 113 | } 114 | table thead { 115 | background: var(--table-header-bg); 116 | } 117 | table thead td { 118 | font-weight: 700; 119 | border: none; 120 | } 121 | table thead th { 122 | padding: 3px 20px; 123 | } 124 | table thead tr { 125 | border: 1px var(--table-header-bg) solid; 126 | } 127 | /* Alternate background colors for rows */ 128 | table tbody tr:nth-child(2n) { 129 | background: var(--table-alternate-bg); 130 | } 131 | 132 | 133 | blockquote { 134 | margin: 20px 0; 135 | padding: 0 20px; 136 | color: var(--fg); 137 | background-color: var(--quote-bg); 138 | border-top: .1em solid var(--quote-border); 139 | border-bottom: .1em solid var(--quote-border); 140 | } 141 | 142 | 143 | :not(.footnote-definition) + .footnote-definition, 144 | .footnote-definition + :not(.footnote-definition) { 145 | margin-top: 2em; 146 | } 147 | .footnote-definition { 148 | font-size: 0.9em; 149 | margin: 0.5em 0; 150 | } 151 | .footnote-definition p { 152 | display: inline; 153 | } 154 | 155 | .tooltiptext { 156 | position: absolute; 157 | visibility: hidden; 158 | color: #fff; 159 | background-color: #333; 160 | transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ 161 | left: -8px; /* Half of the width of the icon */ 162 | top: -35px; 163 | font-size: 0.8em; 164 | text-align: center; 165 | border-radius: 6px; 166 | padding: 5px 8px; 167 | margin: 5px; 168 | z-index: 1000; 169 | } 170 | .tooltipped .tooltiptext { 171 | visibility: visible; 172 | } 173 | 174 | .chapter li.part-title { 175 | color: var(--sidebar-fg); 176 | margin: 5px 0px; 177 | font-weight: bold; 178 | } 179 | 180 | .result-no-output { 181 | font-style: italic; 182 | } 183 | -------------------------------------------------------------------------------- /tests/init.rs: -------------------------------------------------------------------------------- 1 | use mdbook::config::Config; 2 | use mdbook::MDBook; 3 | use std::fs; 4 | use std::fs::File; 5 | use std::io::prelude::*; 6 | use std::path::PathBuf; 7 | use tempfile::Builder as TempFileBuilder; 8 | 9 | /// Run `mdbook init` in an empty directory and make sure the default files 10 | /// are created. 11 | #[test] 12 | fn base_mdbook_init_should_create_default_content() { 13 | let created_files = vec!["book", "src", "src/SUMMARY.md", "src/chapter_1.md"]; 14 | 15 | let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap(); 16 | for file in &created_files { 17 | assert!(!temp.path().join(file).exists()); 18 | } 19 | 20 | MDBook::init(temp.path()).build().unwrap(); 21 | 22 | for file in &created_files { 23 | let target = temp.path().join(file); 24 | println!("{}", target.display()); 25 | assert!(target.exists(), "{} doesn't exist", file); 26 | } 27 | 28 | let contents = fs::read_to_string(temp.path().join("book.toml")).unwrap(); 29 | assert_eq!( 30 | contents, 31 | "[book]\nauthors = []\nlanguage = \"en\"\nmultilingual = false\nsrc = \"src\"\n" 32 | ); 33 | } 34 | 35 | /// Run `mdbook init` in a directory containing a SUMMARY.md should create the 36 | /// files listed in the summary. 37 | #[test] 38 | fn run_mdbook_init_should_create_content_from_summary() { 39 | let created_files = vec!["intro.md", "first.md", "outro.md"]; 40 | 41 | let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap(); 42 | let src_dir = temp.path().join("src"); 43 | fs::create_dir_all(src_dir.clone()).unwrap(); 44 | static SUMMARY: &str = r#"# Summary 45 | 46 | [intro](intro.md) 47 | 48 | - [First chapter](first.md) 49 | 50 | [outro](outro.md) 51 | 52 | "#; 53 | 54 | let mut summary = File::create(src_dir.join("SUMMARY.md")).unwrap(); 55 | summary.write_all(SUMMARY.as_bytes()).unwrap(); 56 | MDBook::init(temp.path()).build().unwrap(); 57 | 58 | for file in &created_files { 59 | let target = src_dir.join(file); 60 | println!("{}", target.display()); 61 | assert!(target.exists(), "{} doesn't exist", file); 62 | } 63 | } 64 | 65 | /// Set some custom arguments for where to place the source and destination 66 | /// files, then call `mdbook init`. 67 | #[test] 68 | fn run_mdbook_init_with_custom_book_and_src_locations() { 69 | let created_files = vec!["out", "in", "in/SUMMARY.md", "in/chapter_1.md"]; 70 | 71 | let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap(); 72 | for file in &created_files { 73 | assert!( 74 | !temp.path().join(file).exists(), 75 | "{} shouldn't exist yet!", 76 | file 77 | ); 78 | } 79 | 80 | let mut cfg = Config::default(); 81 | cfg.book.src = PathBuf::from("in"); 82 | cfg.build.build_dir = PathBuf::from("out"); 83 | 84 | MDBook::init(temp.path()).with_config(cfg).build().unwrap(); 85 | 86 | for file in &created_files { 87 | let target = temp.path().join(file); 88 | assert!( 89 | target.exists(), 90 | "{} should have been created by `mdbook init`", 91 | file 92 | ); 93 | } 94 | 95 | let contents = fs::read_to_string(temp.path().join("book.toml")).unwrap(); 96 | assert_eq!( 97 | contents, 98 | "[book]\nauthors = []\nlanguage = \"en\"\nmultilingual = false\nsrc = \"in\"\n\n[build]\nbuild-dir = \"out\"\ncreate-missing = true\nuse-default-preprocessors = true\n" 99 | ); 100 | } 101 | 102 | #[test] 103 | fn book_toml_isnt_required() { 104 | let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap(); 105 | let md = MDBook::init(temp.path()).build().unwrap(); 106 | 107 | let _ = fs::remove_file(temp.path().join("book.toml")); 108 | 109 | md.build().unwrap(); 110 | } 111 | 112 | #[test] 113 | fn copy_theme() { 114 | let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap(); 115 | MDBook::init(temp.path()).copy_theme(true).build().unwrap(); 116 | let expected = vec![ 117 | "book.js", 118 | "css/chrome.css", 119 | "css/general.css", 120 | "css/print.css", 121 | "css/variables.css", 122 | "favicon.png", 123 | "favicon.svg", 124 | "highlight.css", 125 | "highlight.js", 126 | "index.hbs", 127 | ]; 128 | let theme_dir = temp.path().join("theme"); 129 | let mut actual: Vec<_> = walkdir::WalkDir::new(&theme_dir) 130 | .into_iter() 131 | .filter_map(|e| e.ok()) 132 | .filter(|e| !e.file_type().is_dir()) 133 | .map(|e| { 134 | e.path() 135 | .strip_prefix(&theme_dir) 136 | .unwrap() 137 | .to_str() 138 | .unwrap() 139 | .replace('\\', "/") 140 | }) 141 | .collect(); 142 | actual.sort(); 143 | assert_eq!(actual, expected); 144 | } 145 | -------------------------------------------------------------------------------- /tests/alternative_backends.rs: -------------------------------------------------------------------------------- 1 | //! Integration tests to make sure alternative backends work. 2 | 3 | use mdbook::config::Config; 4 | use mdbook::MDBook; 5 | use std::fs; 6 | use std::path::Path; 7 | use tempfile::{Builder as TempFileBuilder, TempDir}; 8 | 9 | #[test] 10 | fn passing_alternate_backend() { 11 | let (md, _temp) = dummy_book_with_backend("passing", success_cmd(), false); 12 | 13 | md.build().unwrap(); 14 | } 15 | 16 | #[test] 17 | fn failing_alternate_backend() { 18 | let (md, _temp) = dummy_book_with_backend("failing", fail_cmd(), false); 19 | 20 | md.build().unwrap_err(); 21 | } 22 | 23 | #[test] 24 | fn missing_backends_are_fatal() { 25 | let (md, _temp) = dummy_book_with_backend("missing", "trduyvbhijnorgevfuhn", false); 26 | assert!(md.build().is_err()); 27 | } 28 | 29 | #[test] 30 | fn missing_optional_backends_are_not_fatal() { 31 | let (md, _temp) = dummy_book_with_backend("missing", "trduyvbhijnorgevfuhn", true); 32 | assert!(md.build().is_ok()); 33 | } 34 | 35 | #[test] 36 | fn alternate_backend_with_arguments() { 37 | let (md, _temp) = dummy_book_with_backend("arguments", "echo Hello World!", false); 38 | 39 | md.build().unwrap(); 40 | } 41 | 42 | /// Get a command which will pipe `stdin` to the provided file. 43 | #[cfg(not(windows))] 44 | fn tee_command>(out_file: P) -> String { 45 | let out_file = out_file.as_ref(); 46 | 47 | if cfg!(windows) { 48 | format!("cmd.exe /c \"type > {}\"", out_file.display()) 49 | } else { 50 | format!("tee {}", out_file.display()) 51 | } 52 | } 53 | 54 | #[test] 55 | #[cfg(not(windows))] 56 | fn backends_receive_render_context_via_stdin() { 57 | use mdbook::renderer::RenderContext; 58 | use std::fs::File; 59 | 60 | let temp = TempFileBuilder::new().prefix("output").tempdir().unwrap(); 61 | let out_file = temp.path().join("out.txt"); 62 | let cmd = tee_command(&out_file); 63 | 64 | let (md, _temp) = dummy_book_with_backend("cat-to-file", &cmd, false); 65 | 66 | assert!(!out_file.exists()); 67 | md.build().unwrap(); 68 | assert!(out_file.exists()); 69 | 70 | let got = RenderContext::from_json(File::open(&out_file).unwrap()); 71 | assert!(got.is_ok()); 72 | } 73 | 74 | #[test] 75 | fn relative_command_path() { 76 | // Checks behavior of relative paths for the `command` setting. 77 | let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap(); 78 | let renderers = temp.path().join("renderers"); 79 | fs::create_dir(&renderers).unwrap(); 80 | rust_exe( 81 | &renderers, 82 | "myrenderer", 83 | r#"fn main() { 84 | std::fs::write("output", "test").unwrap(); 85 | }"#, 86 | ); 87 | let do_test = |cmd_path| { 88 | let mut config = Config::default(); 89 | config 90 | .set("output.html", toml::value::Table::new()) 91 | .unwrap(); 92 | config.set("output.myrenderer.command", cmd_path).unwrap(); 93 | let md = MDBook::init(&temp.path()) 94 | .with_config(config) 95 | .build() 96 | .unwrap(); 97 | let output = temp.path().join("book/myrenderer/output"); 98 | assert!(!output.exists()); 99 | md.build().unwrap(); 100 | assert!(output.exists()); 101 | fs::remove_file(output).unwrap(); 102 | }; 103 | // Legacy paths work, relative to the output directory. 104 | if cfg!(windows) { 105 | do_test("../../renderers/myrenderer.exe"); 106 | } else { 107 | do_test("../../renderers/myrenderer"); 108 | } 109 | // Modern path, relative to the book directory. 110 | do_test("renderers/myrenderer"); 111 | } 112 | 113 | fn dummy_book_with_backend( 114 | name: &str, 115 | command: &str, 116 | backend_is_optional: bool, 117 | ) -> (MDBook, TempDir) { 118 | let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap(); 119 | 120 | let mut config = Config::default(); 121 | config 122 | .set(format!("output.{}.command", name), command) 123 | .unwrap(); 124 | 125 | if backend_is_optional { 126 | config 127 | .set(format!("output.{}.optional", name), true) 128 | .unwrap(); 129 | } 130 | 131 | let md = MDBook::init(temp.path()) 132 | .with_config(config) 133 | .build() 134 | .unwrap(); 135 | 136 | (md, temp) 137 | } 138 | 139 | fn fail_cmd() -> &'static str { 140 | if cfg!(windows) { 141 | r#"cmd.exe /c "exit 1""# 142 | } else { 143 | "false" 144 | } 145 | } 146 | 147 | fn success_cmd() -> &'static str { 148 | if cfg!(windows) { 149 | r#"cmd.exe /c "exit 0""# 150 | } else { 151 | "true" 152 | } 153 | } 154 | 155 | fn rust_exe(temp: &Path, name: &str, src: &str) { 156 | let rs = temp.join(name).with_extension("rs"); 157 | fs::write(&rs, src).unwrap(); 158 | let status = std::process::Command::new("rustc") 159 | .arg(rs) 160 | .current_dir(temp) 161 | .status() 162 | .expect("rustc should run"); 163 | assert!(status.success()); 164 | } 165 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate clap; 3 | #[macro_use] 4 | extern crate log; 5 | 6 | use anyhow::anyhow; 7 | use chrono::Local; 8 | use clap::{App, AppSettings, Arg, ArgMatches}; 9 | use clap_complete::Shell; 10 | use env_logger::Builder; 11 | use log::LevelFilter; 12 | use mdbook::utils; 13 | use std::env; 14 | use std::ffi::OsStr; 15 | use std::io::Write; 16 | use std::path::{Path, PathBuf}; 17 | 18 | mod cmd; 19 | 20 | const VERSION: &str = concat!("v", crate_version!()); 21 | 22 | fn main() { 23 | init_logger(); 24 | 25 | let app = create_clap_app(); 26 | 27 | // Check which subcomamnd the user ran... 28 | let res = match app.get_matches().subcommand() { 29 | Some(("init", sub_matches)) => cmd::init::execute(sub_matches), 30 | Some(("build", sub_matches)) => cmd::build::execute(sub_matches), 31 | Some(("clean", sub_matches)) => cmd::clean::execute(sub_matches), 32 | #[cfg(feature = "watch")] 33 | Some(("watch", sub_matches)) => cmd::watch::execute(sub_matches), 34 | #[cfg(feature = "serve")] 35 | Some(("serve", sub_matches)) => cmd::serve::execute(sub_matches), 36 | Some(("test", sub_matches)) => cmd::test::execute(sub_matches), 37 | Some(("completions", sub_matches)) => (|| { 38 | let shell: Shell = sub_matches 39 | .value_of("shell") 40 | .ok_or_else(|| anyhow!("Shell name missing."))? 41 | .parse() 42 | .map_err(|s| anyhow!("Invalid shell: {}", s))?; 43 | 44 | let mut complete_app = create_clap_app(); 45 | clap_complete::generate( 46 | shell, 47 | &mut complete_app, 48 | "mdbook", 49 | &mut std::io::stdout().lock(), 50 | ); 51 | Ok(()) 52 | })(), 53 | _ => unreachable!(), 54 | }; 55 | 56 | if let Err(e) = res { 57 | utils::log_backtrace(&e); 58 | 59 | std::process::exit(101); 60 | } 61 | } 62 | 63 | /// Create a list of valid arguments and sub-commands 64 | fn create_clap_app() -> App<'static> { 65 | let app = App::new(crate_name!()) 66 | .about(crate_description!()) 67 | .author("Mathieu David ") 68 | .version(VERSION) 69 | .setting(AppSettings::PropagateVersion) 70 | .setting(AppSettings::ArgRequiredElseHelp) 71 | .after_help( 72 | "For more information about a specific command, try `mdbook --help`\n\ 73 | The source code for mdBook is available at: https://github.com/rust-lang/mdBook", 74 | ) 75 | .subcommand(cmd::init::make_subcommand()) 76 | .subcommand(cmd::build::make_subcommand()) 77 | .subcommand(cmd::test::make_subcommand()) 78 | .subcommand(cmd::clean::make_subcommand()) 79 | .subcommand( 80 | App::new("completions") 81 | .about("Generate shell completions for your shell to stdout") 82 | .arg( 83 | Arg::new("shell") 84 | .takes_value(true) 85 | .possible_values(Shell::possible_values()) 86 | .help("the shell to generate completions for") 87 | .value_name("SHELL") 88 | .required(true), 89 | ), 90 | ); 91 | 92 | #[cfg(feature = "watch")] 93 | let app = app.subcommand(cmd::watch::make_subcommand()); 94 | #[cfg(feature = "serve")] 95 | let app = app.subcommand(cmd::serve::make_subcommand()); 96 | 97 | app 98 | } 99 | 100 | fn init_logger() { 101 | let mut builder = Builder::new(); 102 | 103 | builder.format(|formatter, record| { 104 | writeln!( 105 | formatter, 106 | "{} [{}] ({}): {}", 107 | Local::now().format("%Y-%m-%d %H:%M:%S"), 108 | record.level(), 109 | record.target(), 110 | record.args() 111 | ) 112 | }); 113 | 114 | if let Ok(var) = env::var("RUST_LOG") { 115 | builder.parse_filters(&var); 116 | } else { 117 | // if no RUST_LOG provided, default to logging at the Info level 118 | builder.filter(None, LevelFilter::Info); 119 | // Filter extraneous html5ever not-implemented messages 120 | builder.filter(Some("html5ever"), LevelFilter::Error); 121 | } 122 | 123 | builder.init(); 124 | } 125 | 126 | fn get_book_dir(args: &ArgMatches) -> PathBuf { 127 | if let Some(dir) = args.value_of("dir") { 128 | // Check if path is relative from current dir, or absolute... 129 | let p = Path::new(dir); 130 | if p.is_relative() { 131 | env::current_dir().unwrap().join(dir) 132 | } else { 133 | p.to_path_buf() 134 | } 135 | } else { 136 | env::current_dir().expect("Unable to determine the current directory") 137 | } 138 | } 139 | 140 | fn open>(path: P) { 141 | info!("Opening web browser"); 142 | if let Err(e) = opener::open(path) { 143 | error!("Error opening web browser: {}", e); 144 | } 145 | } 146 | 147 | #[test] 148 | fn verify_app() { 149 | create_clap_app().debug_assert(); 150 | } 151 | -------------------------------------------------------------------------------- /tests/dummy_book/mod.rs: -------------------------------------------------------------------------------- 1 | //! This will create an entire book in a temporary directory using some 2 | //! dummy contents from the `tests/dummy-book/` directory. 3 | 4 | // Not all features are used in all test crates, so... 5 | #![allow(dead_code, unused_variables, unused_imports, unused_extern_crates)] 6 | 7 | use anyhow::Context; 8 | use mdbook::errors::*; 9 | use mdbook::MDBook; 10 | use std::fs::{self, File}; 11 | use std::io::{Read, Write}; 12 | use std::path::Path; 13 | use tempfile::{Builder as TempFileBuilder, TempDir}; 14 | use walkdir::WalkDir; 15 | 16 | /// Create a dummy book in a temporary directory, using the contents of 17 | /// `SUMMARY_MD` as a guide. 18 | /// 19 | /// The "Nested Chapter" file contains a code block with a single 20 | /// `assert!($TEST_STATUS)`. If you want to check MDBook's testing 21 | /// functionality, `$TEST_STATUS` can be substitute for either `true` or 22 | /// `false`. This is done using the `passing_test` parameter. 23 | #[derive(Clone, Debug, PartialEq)] 24 | pub struct DummyBook { 25 | passing_test: bool, 26 | } 27 | 28 | impl DummyBook { 29 | /// Create a new `DummyBook` with all the defaults. 30 | pub fn new() -> DummyBook { 31 | DummyBook { passing_test: true } 32 | } 33 | 34 | /// Whether the doc-test included in the "Nested Chapter" should pass or 35 | /// fail (it passes by default). 36 | pub fn with_passing_test(&mut self, test_passes: bool) -> &mut DummyBook { 37 | self.passing_test = test_passes; 38 | self 39 | } 40 | 41 | /// Write a book to a temporary directory using the provided settings. 42 | pub fn build(&self) -> Result { 43 | let temp = TempFileBuilder::new() 44 | .prefix("dummy_book-") 45 | .tempdir() 46 | .with_context(|| "Unable to create temp directory")?; 47 | 48 | let dummy_book_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/dummy_book"); 49 | recursive_copy(&dummy_book_root, temp.path()).with_context(|| { 50 | "Couldn't copy files into a \ 51 | temporary directory" 52 | })?; 53 | 54 | let sub_pattern = if self.passing_test { "true" } else { "false" }; 55 | let files_containing_tests = [ 56 | "src/first/nested.md", 57 | "src/first/nested-test.rs", 58 | "src/first/nested-test-with-anchors.rs", 59 | "src/first/partially-included-test.rs", 60 | "src/first/partially-included-test-with-anchors.rs", 61 | ]; 62 | for file in &files_containing_tests { 63 | let path_containing_tests = temp.path().join(file); 64 | replace_pattern_in_file(&path_containing_tests, "$TEST_STATUS", sub_pattern)?; 65 | } 66 | 67 | Ok(temp) 68 | } 69 | } 70 | 71 | fn replace_pattern_in_file(filename: &Path, from: &str, to: &str) -> Result<()> { 72 | let contents = fs::read_to_string(filename)?; 73 | File::create(filename)?.write_all(contents.replace(from, to).as_bytes())?; 74 | 75 | Ok(()) 76 | } 77 | 78 | /// Read the contents of the provided file into memory and then iterate through 79 | /// the list of strings asserting that the file contains all of them. 80 | pub fn assert_contains_strings>(filename: P, strings: &[&str]) { 81 | let filename = filename.as_ref(); 82 | let content = fs::read_to_string(filename).expect("Couldn't read the file's contents"); 83 | 84 | for s in strings { 85 | assert!( 86 | content.contains(s), 87 | "Searching for {:?} in {}\n\n{}", 88 | s, 89 | filename.display(), 90 | content 91 | ); 92 | } 93 | } 94 | 95 | pub fn assert_doesnt_contain_strings>(filename: P, strings: &[&str]) { 96 | let filename = filename.as_ref(); 97 | let content = fs::read_to_string(filename).expect("Couldn't read the file's contents"); 98 | 99 | for s in strings { 100 | assert!( 101 | !content.contains(s), 102 | "Found {:?} in {}\n\n{}", 103 | s, 104 | filename.display(), 105 | content 106 | ); 107 | } 108 | } 109 | 110 | /// Recursively copy an entire directory tree to somewhere else (a la `cp -r`). 111 | fn recursive_copy, B: AsRef>(from: A, to: B) -> Result<()> { 112 | let from = from.as_ref(); 113 | let to = to.as_ref(); 114 | 115 | for entry in WalkDir::new(&from) { 116 | let entry = entry.with_context(|| "Unable to inspect directory entry")?; 117 | 118 | let original_location = entry.path(); 119 | let relative = original_location 120 | .strip_prefix(&from) 121 | .expect("`original_location` is inside the `from` directory"); 122 | let new_location = to.join(relative); 123 | 124 | if original_location.is_file() { 125 | if let Some(parent) = new_location.parent() { 126 | fs::create_dir_all(parent).with_context(|| "Couldn't create directory")?; 127 | } 128 | 129 | fs::copy(&original_location, &new_location) 130 | .with_context(|| "Unable to copy file contents")?; 131 | } 132 | } 133 | 134 | Ok(()) 135 | } 136 | 137 | pub fn new_copy_of_example_book() -> Result { 138 | let temp = TempFileBuilder::new().prefix("guide").tempdir()?; 139 | 140 | let guide = Path::new(env!("CARGO_MANIFEST_DIR")).join("guide"); 141 | 142 | recursive_copy(guide, temp.path())?; 143 | 144 | Ok(temp) 145 | } 146 | -------------------------------------------------------------------------------- /src/cmd/watch.rs: -------------------------------------------------------------------------------- 1 | use crate::{get_book_dir, open}; 2 | use clap::{arg, App, Arg, ArgMatches}; 3 | use mdbook::errors::Result; 4 | use mdbook::utils; 5 | use mdbook::MDBook; 6 | use notify::Watcher; 7 | use std::path::{Path, PathBuf}; 8 | use std::sync::mpsc::channel; 9 | use std::thread::sleep; 10 | use std::time::Duration; 11 | 12 | // Create clap subcommand arguments 13 | pub fn make_subcommand<'help>() -> App<'help> { 14 | App::new("watch") 15 | .about("Watches a book's files and rebuilds it on changes") 16 | .arg( 17 | Arg::new("dest-dir") 18 | .short('d') 19 | .long("dest-dir") 20 | .value_name("dest-dir") 21 | .help( 22 | "Output directory for the book{n}\ 23 | Relative paths are interpreted relative to the book's root directory.{n}\ 24 | If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.", 25 | ), 26 | ) 27 | .arg(arg!([dir] 28 | "Root directory for the book{n}\ 29 | (Defaults to the Current Directory when omitted)" 30 | )) 31 | .arg(arg!(-o --open "Opens the compiled book in a web browser")) 32 | } 33 | 34 | // Watch command implementation 35 | pub fn execute(args: &ArgMatches) -> Result<()> { 36 | let book_dir = get_book_dir(args); 37 | let mut book = MDBook::load(&book_dir)?; 38 | 39 | let update_config = |book: &mut MDBook| { 40 | if let Some(dest_dir) = args.value_of("dest-dir") { 41 | book.config.build.build_dir = dest_dir.into(); 42 | } 43 | }; 44 | update_config(&mut book); 45 | 46 | if args.is_present("open") { 47 | book.build()?; 48 | open(book.build_dir_for("html").join("index.html")); 49 | } 50 | 51 | trigger_on_change(&book, |paths, book_dir| { 52 | info!("Files changed: {:?}\nBuilding book...\n", paths); 53 | let result = MDBook::load(&book_dir).and_then(|mut b| { 54 | update_config(&mut b); 55 | b.build() 56 | }); 57 | 58 | if let Err(e) = result { 59 | error!("Unable to build the book"); 60 | utils::log_backtrace(&e); 61 | } 62 | }); 63 | 64 | Ok(()) 65 | } 66 | 67 | fn remove_ignored_files(book_root: &Path, paths: &[PathBuf]) -> Vec { 68 | if paths.is_empty() { 69 | return vec![]; 70 | } 71 | 72 | match find_gitignore(book_root) { 73 | Some(gitignore_path) => { 74 | match gitignore::File::new(gitignore_path.as_path()) { 75 | Ok(exclusion_checker) => filter_ignored_files(exclusion_checker, paths), 76 | Err(_) => { 77 | // We're unable to read the .gitignore file, so we'll silently allow everything. 78 | // Please see discussion: https://github.com/rust-lang/mdBook/pull/1051 79 | paths.iter().map(|path| path.to_path_buf()).collect() 80 | } 81 | } 82 | } 83 | None => { 84 | // There is no .gitignore file. 85 | paths.iter().map(|path| path.to_path_buf()).collect() 86 | } 87 | } 88 | } 89 | 90 | fn find_gitignore(book_root: &Path) -> Option { 91 | book_root 92 | .ancestors() 93 | .map(|p| p.join(".gitignore")) 94 | .find(|p| p.exists()) 95 | } 96 | 97 | fn filter_ignored_files(exclusion_checker: gitignore::File, paths: &[PathBuf]) -> Vec { 98 | paths 99 | .iter() 100 | .filter(|path| match exclusion_checker.is_excluded(path) { 101 | Ok(exclude) => !exclude, 102 | Err(error) => { 103 | warn!( 104 | "Unable to determine if {:?} is excluded: {:?}. Including it.", 105 | &path, error 106 | ); 107 | true 108 | } 109 | }) 110 | .map(|path| path.to_path_buf()) 111 | .collect() 112 | } 113 | 114 | /// Calls the closure when a book source file is changed, blocking indefinitely. 115 | pub fn trigger_on_change(book: &MDBook, closure: F) 116 | where 117 | F: Fn(Vec, &Path), 118 | { 119 | use notify::DebouncedEvent::*; 120 | use notify::RecursiveMode::*; 121 | 122 | // Create a channel to receive the events. 123 | let (tx, rx) = channel(); 124 | 125 | let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) { 126 | Ok(w) => w, 127 | Err(e) => { 128 | error!("Error while trying to watch the files:\n\n\t{:?}", e); 129 | std::process::exit(1) 130 | } 131 | }; 132 | 133 | // Add the source directory to the watcher 134 | if let Err(e) = watcher.watch(book.source_dir(), Recursive) { 135 | error!("Error while watching {:?}:\n {:?}", book.source_dir(), e); 136 | std::process::exit(1); 137 | }; 138 | 139 | let _ = watcher.watch(book.theme_dir(), Recursive); 140 | 141 | // Add the book.toml file to the watcher if it exists 142 | let _ = watcher.watch(book.root.join("book.toml"), NonRecursive); 143 | 144 | info!("Listening for changes..."); 145 | 146 | loop { 147 | let first_event = rx.recv().unwrap(); 148 | sleep(Duration::from_millis(50)); 149 | let other_events = rx.try_iter(); 150 | 151 | let all_events = std::iter::once(first_event).chain(other_events); 152 | 153 | let paths = all_events 154 | .filter_map(|event| { 155 | debug!("Received filesystem event: {:?}", event); 156 | 157 | match event { 158 | Create(path) | Write(path) | Remove(path) | Rename(_, path) => Some(path), 159 | _ => None, 160 | } 161 | }) 162 | .collect::>(); 163 | 164 | let paths = remove_ignored_files(&book.root, &paths[..]); 165 | 166 | if !paths.is_empty() { 167 | closure(paths, &book.root); 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /test_book/src/individual/paragraph.md: -------------------------------------------------------------------------------- 1 | Just a simple paragraph. 2 | 3 | Let's stress test this. 4 | 5 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer elit lorem, eleifend eu leo sit amet, suscipit feugiat libero. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Proin congue lectus sit amet lacus venenatis, ac sollicitudin purus condimentum. Suspendisse pretium volutpat sapien at gravida. In tincidunt, sem non accumsan consectetur, leo libero porttitor dolor, at imperdiet erat nibh quis leo. Cras dictum erat augue, quis pharetra justo porttitor posuere. Aenean sed lacinia justo, vel suscipit nisl. Etiam eleifend id mauris at gravida. Aliquam molestie cursus lorem pulvinar sollicitudin. Nam et ex dignissim, posuere sem non, pellentesque lacus. Morbi vulputate sed lorem et convallis. Duis non turpis eget elit posuere volutpat. Donec accumsan euismod enim, id consequat ex rhoncus ac. Pellentesque ac felis nisl. Duis imperdiet vel tellus ac iaculis. 6 | 7 | Vivamus nec tempus enim. Integer in ligula eget elit ornare vulputate id et est. Proin mi elit, sagittis nec urna et, iaculis imperdiet neque. Vestibulum placerat cursus dolor. Donec eu sodales nulla. Praesent ac tellus eros. Donec venenatis ligula id ex porttitor malesuada. Aliquam maximus, nisi in fringilla finibus, ante elit rhoncus dui, placerat semper nisl tellus quis odio. Cras luctus magna ultrices dolor pharetra volutpat. Maecenas non enim vitae ligula efficitur aliquet id quis quam. In sagittis mollis magna eu porta. Morbi at nulla et ante elementum pharetra in sed est. Nam commodo purus enim. 8 | 9 | Ut non elit sit amet urna luctus facilisis vel et sapien. Morbi nec metus at libero imperdiet sollicitudin eget quis lacus. Donec in ipsum at enim accumsan tempor vel sed magna. Aliquam non imperdiet neque. Etiam pharetra neque sed pretium interdum. Suspendisse potenti. Phasellus varius, lectus quis dapibus faucibus, purus mauris accumsan nibh, vel tempor quam metus nec sem. Nunc sagittis suscipit lorem eu finibus. Nullam augue leo, imperdiet vel diam et, vulputate scelerisque turpis. Nullam ut volutpat diam. Praesent cursus accumsan dui a commodo. Vivamus sed libero sed turpis facilisis rutrum id sed ligula. Ut id sollicitudin dui. Nulla pulvinar commodo lectus. Cras ut quam congue, consectetur dolor ac, consequat ante. 10 | 11 | Curabitur scelerisque sed leo eu facilisis. Nam faucibus neque eget dictum hendrerit. Duis efficitur ex sed vulputate volutpat. Praesent condimentum nisl ac sapien efficitur laoreet. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut ut nibh elit. Nunc a neque lobortis, tempus diam vitae, interdum magna. Aenean eget nisl sed justo volutpat interdum. Mauris malesuada ex nisl, a dignissim dui elementum eget. Suspendisse potenti. 12 | 13 | Praesent congue fringilla sem sed faucibus. Vivamus malesuada eget mauris at molestie. In sed faucibus nulla. Vivamus elementum accumsan metus quis suscipit. Maecenas interdum est nulla. Cras volutpat cursus nibh quis sollicitudin. Morbi vitae massa laoreet, aliquet tellus quis, consectetur ipsum. Mauris euismod congue purus non condimentum. Etiam laoreet mi vel sem consectetur gravida. Vestibulum volutpat magna nunc, vitae ultrices risus commodo eu. 14 | 15 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer elit lorem, eleifend eu leo sit amet, suscipit feugiat libero. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Proin congue lectus sit amet lacus venenatis, ac sollicitudin purus condimentum. Suspendisse pretium volutpat sapien at gravida. In tincidunt, sem non accumsan consectetur, leo libero porttitor dolor, at imperdiet erat nibh quis leo. Cras dictum erat augue, quis pharetra justo porttitor posuere. Aenean sed lacinia justo, vel suscipit nisl. Etiam eleifend id mauris at gravida. Aliquam molestie cursus lorem pulvinar sollicitudin. Nam et ex dignissim, posuere sem non, pellentesque lacus. Morbi vulputate sed lorem et convallis. Duis non turpis eget elit posuere volutpat. Donec accumsan euismod enim, id consequat ex rhoncus ac. Pellentesque ac felis nisl. Duis imperdiet vel tellus ac iaculis. 16 | 17 | Vivamus nec tempus enim. Integer in ligula eget elit ornare vulputate id et est. Proin mi elit, sagittis nec urna et, iaculis imperdiet neque. Vestibulum placerat cursus dolor. Donec eu sodales nulla. Praesent ac tellus eros. Donec venenatis ligula id ex porttitor malesuada. Aliquam maximus, nisi in fringilla finibus, ante elit rhoncus dui, placerat semper nisl tellus quis odio. Cras luctus magna ultrices dolor pharetra volutpat. Maecenas non enim vitae ligula efficitur aliquet id quis quam. In sagittis mollis magna eu porta. Morbi at nulla et ante elementum pharetra in sed est. Nam commodo purus enim. 18 | 19 | Ut non elit sit amet urna luctus facilisis vel et sapien. Morbi nec metus at libero imperdiet sollicitudin eget quis lacus. Donec in ipsum at enim accumsan tempor vel sed magna. Aliquam non imperdiet neque. Etiam pharetra neque sed pretium interdum. Suspendisse potenti. Phasellus varius, lectus quis dapibus faucibus, purus mauris accumsan nibh, vel tempor quam metus nec sem. Nunc sagittis suscipit lorem eu finibus. Nullam augue leo, imperdiet vel diam et, vulputate scelerisque turpis. Nullam ut volutpat diam. Praesent cursus accumsan dui a commodo. Vivamus sed libero sed turpis facilisis rutrum id sed ligula. Ut id sollicitudin dui. Nulla pulvinar commodo lectus. Cras ut quam congue, consectetur dolor ac, consequat ante. 20 | 21 | Curabitur scelerisque sed leo eu facilisis. Nam faucibus neque eget dictum hendrerit. Duis efficitur ex sed vulputate volutpat. Praesent condimentum nisl ac sapien efficitur laoreet. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut ut nibh elit. Nunc a neque lobortis, tempus diam vitae, interdum magna. Aenean eget nisl sed justo volutpat interdum. Mauris malesuada ex nisl, a dignissim dui elementum eget. Suspendisse potenti. 22 | 23 | Praesent congue fringilla sem sed faucibus. Vivamus malesuada eget mauris at molestie. In sed faucibus nulla. Vivamus elementum accumsan metus quis suscipit. Maecenas interdum est nulla. Cras volutpat cursus nibh quis sollicitudin. Morbi vitae massa laoreet, aliquet tellus quis, consectetur ipsum. Mauris euismod congue purus non condimentum. Etiam laoreet mi vel sem consectetur gravida. Vestibulum volutpat magna nunc, vitae ultrices risus commodo eu. 24 | 25 | Hopefully everything above was rendered nicely, on both desktop and mobile. 26 | -------------------------------------------------------------------------------- /guide/src/continuous-integration.md: -------------------------------------------------------------------------------- 1 | # Running `mdbook` in Continuous Integration 2 | 3 | There are a variety of services such as [GitHub Actions] or [GitLab CI/CD] which can be used to test and deploy your book automatically. 4 | 5 | The following provides some general guidelines on how to configure your service to run mdBook. 6 | Specific recipes can be found at the [Automated Deployment] wiki page. 7 | 8 | [GitHub Actions]: https://docs.github.com/en/actions 9 | [GitLab CI/CD]: https://docs.gitlab.com/ee/ci/ 10 | [Automated Deployment]: https://github.com/rust-lang/mdBook/wiki/Automated-Deployment 11 | 12 | ## Installing mdBook 13 | 14 | There are several different strategies for installing mdBook. 15 | The particular method depends on your needs and preferences. 16 | 17 | ### Pre-compiled binaries 18 | 19 | Perhaps the easiest method is to use the pre-compiled binaries found on the [GitHub Releases page][releases]. 20 | A simple approach would be to use the popular `curl` CLI tool to download the executable: 21 | 22 | ```sh 23 | mkdir bin 24 | 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 25 | bin/mdbook build 26 | ``` 27 | 28 | Some considerations for this approach: 29 | 30 | * This is relatively fast, and does not necessarily require dealing with caching. 31 | * This does not require installing Rust. 32 | * Specifying a specific URL means you have to manually update your script to get a new version. 33 | This may be a benefit if you want to lock to a specific version. 34 | However, some users prefer to automatically get a newer version when they are published. 35 | * You are reliant on the GitHub CDN being available. 36 | 37 | [releases]: https://github.com/rust-lang/mdBook/releases 38 | 39 | ### Building from source 40 | 41 | Building from source will require having Rust installed. 42 | Some services have Rust pre-installed, but if your service does not, you will need to add a step to install it. 43 | 44 | After Rust is installed, `cargo install` can be used to build and install mdBook. 45 | We recommend using a SemVer version specifier so that you get the latest **non-breaking** version of mdBook. 46 | For example: 47 | 48 | ```sh 49 | cargo install mdbook --no-default-features --features search --vers "^0.4" --locked 50 | ``` 51 | 52 | This includes several recommended options: 53 | 54 | * `--no-default-features` — Disables features like the HTTP server used by `mdbook serve` that is likely not needed on CI. 55 | This will speed up the build time significantly. 56 | * `--features search` — Disabling default features means you should then manually enable features that you want, such as the built-in [search] capability. 57 | * `--vers "^0.4"` — This will install the most recent version of the `0.4` series. 58 | However, versions after like `0.5.0` won't be installed, as they may break your build. 59 | Cargo will automatically upgrade mdBook if you have an older version already installed. 60 | * `--locked` — This will use the dependencies that were used when mdBook was released. 61 | Without `--locked`, it will use the latest version of all dependencies, which may include some fixes since the last release, but may also (rarely) cause build problems. 62 | 63 | You will likely want to investigate caching options, as building mdBook can be somewhat slow. 64 | 65 | [search]: guide/reading.md#search 66 | 67 | ## Running tests 68 | 69 | You may want to run tests using [`mdbook test`] every time you push a change or create a pull request. 70 | This can be used to validate Rust code examples in the book. 71 | 72 | This will require having Rust installed. 73 | Some services have Rust pre-installed, but if your service does not, you will need to add a step to install it. 74 | 75 | Other than making sure the appropriate version of Rust is installed, there's not much more than just running `mdbook test` from the book directory. 76 | 77 | You may also want to consider running other kinds of tests, like [mdbook-linkcheck] which will check for broken links. 78 | Or if you have your own style checks, spell checker, or any other tests it might be good to run them in CI. 79 | 80 | [`mdbook test`]: cli/test.md 81 | [mdbook-linkcheck]: https://github.com/Michael-F-Bryan/mdbook-linkcheck#continuous-integration 82 | 83 | ## Deploying 84 | 85 | You may want to automatically deploy your book. 86 | Some may want to do this with every time a change is pushed, and others may want to only deploy when a specific release is tagged. 87 | 88 | You'll also need to understand the specifics on how to push a change to your web service. 89 | For example, [GitHub Pages] just requires committing the output onto a specific git branch. 90 | Other services may require using something like SSH to connect to a remote server. 91 | 92 | The basic outline is that you need to run `mdbook build` to generate the output, and then transfer the files (which are in the `book` directory) to the correct location. 93 | 94 | You may then want to consider if you need to invalidate any caches on your web service. 95 | 96 | See the [Automated Deployment] wiki page for examples of various different services. 97 | 98 | [GitHub Pages]: https://docs.github.com/en/pages 99 | 100 | ### 404 handling 101 | 102 | mdBook automatically generates a 404 page to be used for broken links. 103 | The default output is a file named `404.html` at the root of the book. 104 | Some services like [GitHub Pages] will automatically use this page for broken links. 105 | For other services, you may want to consider configuring the web server to use this page as it will provide the reader navigation to get back to the book. 106 | 107 | If your book is not deployed at the root of the domain, then you should set the [`output.html.site-url`] setting so that the 404 page works correctly. 108 | It needs to know where the book is deployed in order to load the static files (like CSS) correctly. 109 | For example, this guide is deployed at , and the `site-url` setting is configured like this: 110 | 111 | ```toml 112 | # book.toml 113 | [output.html] 114 | site-url = "/mdBook/" 115 | ``` 116 | 117 | You can customize the look of the 404 page by creating a file named `src/404.md` in your book. 118 | If you want to use a different filename, you can set [`output.html.input-404`] to a different filename. 119 | 120 | [`output.html.site-url`]: format/configuration/renderers.md#html-renderer-options 121 | [`output.html.input-404`]: format/configuration/renderers.md#html-renderer-options 122 | -------------------------------------------------------------------------------- /guide/src/for_developers/preprocessors.md: -------------------------------------------------------------------------------- 1 | # Preprocessors 2 | 3 | A *preprocessor* is simply a bit of code which gets run immediately after the 4 | book is loaded and before it gets rendered, allowing you to update and mutate 5 | the book. Possible use cases are: 6 | 7 | - Creating custom helpers like `\{{#include /path/to/file.md}}` 8 | - Substituting in latex-style expressions (`$$ \frac{1}{3} $$`) with their 9 | mathjax equivalents 10 | 11 | See [Configuring Preprocessors](../format/configuration/preprocessors.md) for more information about using preprocessors. 12 | 13 | ## Hooking Into MDBook 14 | 15 | MDBook uses a fairly simple mechanism for discovering third party plugins. 16 | A new table is added to `book.toml` (e.g. `[preprocessor.foo]` for the `foo` 17 | preprocessor) and then `mdbook` will try to invoke the `mdbook-foo` program as 18 | part of the build process. 19 | 20 | Once the preprocessor has been defined and the build process starts, mdBook executes the command defined in the `preprocessor.foo.command` key twice. 21 | The first time it runs the preprocessor to determine if it supports the given renderer. 22 | mdBook passes two arguments to the process: the first argument is the string `supports` and the second argument is the renderer name. 23 | The preprocessor should exit with a status code 0 if it supports the given renderer, or return a non-zero exit code if it does not. 24 | 25 | If the preprocessor supports the renderer, then mdbook runs it a second time, passing JSON data into stdin. 26 | The JSON consists of an array of `[context, book]` where `context` is the serialized object [`PreprocessorContext`] and `book` is a [`Book`] object containing the content of the book. 27 | 28 | The preprocessor should return the JSON format of the [`Book`] object to stdout, with any modifications it wishes to perform. 29 | 30 | The easiest way to get started is by creating your own implementation of the 31 | `Preprocessor` trait (e.g. in `lib.rs`) and then creating a shell binary which 32 | translates inputs to the correct `Preprocessor` method. For convenience, there 33 | is [an example no-op preprocessor] in the `examples/` directory which can easily 34 | be adapted for other preprocessors. 35 | 36 |
37 | Example no-op preprocessor 38 | 39 | ```rust 40 | // nop-preprocessors.rs 41 | 42 | {{#include ../../../examples/nop-preprocessor.rs}} 43 | ``` 44 |
45 | 46 | ## Hints For Implementing A Preprocessor 47 | 48 | By pulling in `mdbook` as a library, preprocessors can have access to the 49 | existing infrastructure for dealing with books. 50 | 51 | For example, a custom preprocessor could use the 52 | [`CmdPreprocessor::parse_input()`] function to deserialize the JSON written to 53 | `stdin`. Then each chapter of the `Book` can be mutated in-place via 54 | [`Book::for_each_mut()`], and then written to `stdout` with the `serde_json` 55 | crate. 56 | 57 | Chapters can be accessed either directly (by recursively iterating over 58 | chapters) or via the `Book::for_each_mut()` convenience method. 59 | 60 | The `chapter.content` is just a string which happens to be markdown. While it's 61 | entirely possible to use regular expressions or do a manual find & replace, 62 | you'll probably want to process the input into something more computer-friendly. 63 | The [`pulldown-cmark`][pc] crate implements a production-quality event-based 64 | Markdown parser, with the [`pulldown-cmark-to-cmark`][pctc] crate allowing you to 65 | translate events back into markdown text. 66 | 67 | The following code block shows how to remove all emphasis from markdown, 68 | without accidentally breaking the document. 69 | 70 | ```rust 71 | fn remove_emphasis( 72 | num_removed_items: &mut usize, 73 | chapter: &mut Chapter, 74 | ) -> Result { 75 | let mut buf = String::with_capacity(chapter.content.len()); 76 | 77 | let events = Parser::new(&chapter.content).filter(|e| { 78 | let should_keep = match *e { 79 | Event::Start(Tag::Emphasis) 80 | | Event::Start(Tag::Strong) 81 | | Event::End(Tag::Emphasis) 82 | | Event::End(Tag::Strong) => false, 83 | _ => true, 84 | }; 85 | if !should_keep { 86 | *num_removed_items += 1; 87 | } 88 | should_keep 89 | }); 90 | 91 | cmark(events, &mut buf, None).map(|_| buf).map_err(|err| { 92 | Error::from(format!("Markdown serialization failed: {}", err)) 93 | }) 94 | } 95 | ``` 96 | 97 | For everything else, have a look [at the complete example][example]. 98 | 99 | ## Implementing a preprocessor with a different language 100 | 101 | The fact that mdBook utilizes stdin and stdout to communicate with the preprocessors makes it easy to implement them in a language other than Rust. 102 | The following code shows how to implement a simple preprocessor in Python, which will modify the content of the first chapter. 103 | The example below follows the configuration shown above with `preprocessor.foo.command` actually pointing to a Python script. 104 | 105 | ```python 106 | import json 107 | import sys 108 | 109 | 110 | if __name__ == '__main__': 111 | if len(sys.argv) > 1: # we check if we received any argument 112 | if sys.argv[1] == "supports": 113 | # then we are good to return an exit status code of 0, since the other argument will just be the renderer's name 114 | sys.exit(0) 115 | 116 | # load both the context and the book representations from stdin 117 | context, book = json.load(sys.stdin) 118 | # and now, we can just modify the content of the first chapter 119 | book['sections'][0]['Chapter']['content'] = '# Hello' 120 | # we are done with the book's modification, we can just print it to stdout, 121 | print(json.dumps(book)) 122 | ``` 123 | 124 | 125 | 126 | [preprocessor-docs]: https://docs.rs/mdbook/latest/mdbook/preprocess/trait.Preprocessor.html 127 | [pc]: https://crates.io/crates/pulldown-cmark 128 | [pctc]: https://crates.io/crates/pulldown-cmark-to-cmark 129 | [example]: https://github.com/rust-lang/mdBook/blob/master/examples/nop-preprocessor.rs 130 | [an example no-op preprocessor]: https://github.com/rust-lang/mdBook/blob/master/examples/nop-preprocessor.rs 131 | [`CmdPreprocessor::parse_input()`]: https://docs.rs/mdbook/latest/mdbook/preprocess/trait.Preprocessor.html#method.parse_input 132 | [`Book::for_each_mut()`]: https://docs.rs/mdbook/latest/mdbook/book/struct.Book.html#method.for_each_mut 133 | [`PreprocessorContext`]: https://docs.rs/mdbook/latest/mdbook/preprocess/struct.PreprocessorContext.html 134 | [`Book`]: https://docs.rs/mdbook/latest/mdbook/book/struct.Book.html 135 | --------------------------------------------------------------------------------