Compare commits

..

2 Commits

Author SHA1 Message Date
Eric Huss
f66df09324 Bump 0.4.5 2021-01-04 07:03:20 -08:00
Pietro Albini
648c9ae772 fix xss in the search page
Thanks to Kamil Vavra for responsibly disclosing the vulnerability
according to Rust's Security Policy.
2021-01-04 07:01:49 -08:00
111 changed files with 2073 additions and 6314 deletions

View File

@@ -31,8 +31,7 @@ jobs:
rust: stable
- build: msrv
os: ubuntu-latest
# sync MSRV with docs: guide/src/guide/installation.md
rust: 1.54.0
rust: 1.39.0
steps:
- uses: actions/checkout@master
- name: Install Rust
@@ -49,4 +48,4 @@ jobs:
- uses: actions/checkout@master
- name: Install Rust
run: rustup update stable && rustup default stable && rustup component add rustfmt
- run: cargo fmt --check
- run: cargo fmt -- --check

1
.gitignore vendored
View File

@@ -8,7 +8,6 @@ guide/book
.vscode
tests/dummy_book/book/
test_book/book/
# Ignore Jetbrains specific files.
.idea/

View File

@@ -1,235 +1,5 @@
# Changelog
## mdBook 0.4.17
[a5fddfa...981b79b](https://github.com/rust-lang/mdBook/compare/a5fddfa...981b79b)
### Fixed
- Fixed parsing of `output.html.print` configuration table.
[#1775](https://github.com/rust-lang/mdBook/pull/1775)
## mdBook 0.4.16
[68a5c09...a5fddfa](https://github.com/rust-lang/mdBook/compare/68a5c09...a5fddfa)
### Added
- Added `output.html.print.page-break` config option to control whether or not
there is a page break between chapters in the print output.
[#1728](https://github.com/rust-lang/mdBook/pull/1728)
- Added `output.html.playground.runnable` config option to globally disable
the run button in code blocks.
[#1546](https://github.com/rust-lang/mdBook/pull/1546)
### Changed
- The `cargo serve` live reload websocket now uses the protocol, host, and
port of the current page, allowing access through a proxy.
[#1771](https://github.com/rust-lang/mdBook/pull/1771)
- The 404 not-found page now includes the books title in the HTML title tag.
[#1693](https://github.com/rust-lang/mdBook/pull/1693)
- Migrated to clap 3.0 which which handles CLI option parsing.
[#1731](https://github.com/rust-lang/mdBook/pull/1731)
### Fixed
- Minor fixes to the markdown parser.
[#1729](https://github.com/rust-lang/mdBook/pull/1729)
- Fixed incorrect parsing in `SUMMARY.md` when it didn't start with a title.
[#1744](https://github.com/rust-lang/mdBook/pull/1744)
- Fixed duplicate anchor IDs for links in search results.
[#1749](https://github.com/rust-lang/mdBook/pull/1749)
## mdBook 0.4.15
[5eb7d46...68a5c09](https://github.com/rust-lang/mdBook/compare/5eb7d46...68a5c09)
### Changed
- Major update to expand the documentation located at <https://rust-lang.github.io/mdBook/>.
[#1709](https://github.com/rust-lang/mdBook/pull/1709)
[#1710](https://github.com/rust-lang/mdBook/pull/1710)
- Updated the markdown parser with various fixes for common-mark compliance.
[#1712](https://github.com/rust-lang/mdBook/pull/1712)
## mdBook 0.4.14
[ffa8284...c9b6be8](https://github.com/rust-lang/mdBook/compare/ffa8284...c9b6be8)
### Added
- The 2021 Rust edition option has been stabilized.
[#1642](https://github.com/rust-lang/mdBook/pull/1642)
### Changed
- Header anchors no longer include any HTML tags. Previously only a small
subset were excluded.
[#1683](https://github.com/rust-lang/mdBook/pull/1683)
- Deprecated the google-analytics option. Books using this option should place
the appropriate code in the `theme/head.hbs` file instead.
[#1675](https://github.com/rust-lang/mdBook/pull/1675)
### Fixed
- Updated the markdown parser which brings in a few small fixes and removes
the custom smart quote handling.
[#1668](https://github.com/rust-lang/mdBook/pull/1668)
- Fixed iOS Safari enlarging text when going into landscape mode.
[#1685](https://github.com/rust-lang/mdBook/pull/1685)
## mdBook 0.4.13
[e6629cd...f55028b](https://github.com/rust-lang/mdBook/compare/e6629cd...f55028b)
### Added
- Added the ability to specify the preprocessor order.
[#1607](https://github.com/rust-lang/mdBook/pull/1607)
### Fixed
- Include chapters with no headers in the search index
[#1637](https://github.com/rust-lang/mdBook/pull/1637)
- Switched to the `opener` crate for opening a web browser, which should fix
some issues with blocking.
[#1656](https://github.com/rust-lang/mdBook/pull/1656)
- Fixed clicking the border of the theme switcher breaking the theme selection.
[#1651](https://github.com/rust-lang/mdBook/pull/1651)
## mdBook 0.4.12
[14add9c...8b4e488](https://github.com/rust-lang/mdBook/compare/14add9c...8b4e488)
### Changed
- Reverted the change to update to highlight.js 11, as it broke hidden code lines.
[#1597](https://github.com/rust-lang/mdBook/pull/1621)
## mdBook 0.4.11
[e440094...2cf00d0](https://github.com/rust-lang/mdBook/compare/e440094...2cf00d0)
### Added
- Added support for Rust 2021 edition.
[#1596](https://github.com/rust-lang/mdBook/pull/1596)
- Added `mdbook completions` subcommand which provides shell completions.
[#1425](https://github.com/rust-lang/mdBook/pull/1425)
- Added `--title` and `--ignore` flags to `mdbook init` to avoid the
interactive input.
[#1559](https://github.com/rust-lang/mdBook/pull/1559)
### Changed
- If running a Rust example does not have any output, it now displays the text
"No output" instead of not showing anything.
[#1599](https://github.com/rust-lang/mdBook/pull/1599)
- Code block language tags can now be separated by space or tab (along with
commas) to match the behavior of other sites like GitHub and rustdoc.
[#1469](https://github.com/rust-lang/mdBook/pull/1469)
- Updated `warp` (the web server) to the latest version.
This also updates the minimum supported Rust version to 1.46.
[#1612](https://github.com/rust-lang/mdBook/pull/1612)
- Updated to highlight.js 11. This has various highlighting improvements.
[#1597](https://github.com/rust-lang/mdBook/pull/1597)
### Fixed
- Inline code blocks inside a header are no longer highlighted when
`output.html.playground.editable` is `true`.
[#1613](https://github.com/rust-lang/mdBook/pull/1613)
## mdBook 0.4.10
[2f7293a...dc2062a](https://github.com/rust-lang/mdBook/compare/2f7293a...dc2062a)
### Changed
- Reverted breaking change in 0.4.9 that removed the `__non_exhaustive` marker
on the `Book` struct.
[#1572](https://github.com/rust-lang/mdBook/pull/1572)
- Updated handlebars to 4.0.
[#1550](https://github.com/rust-lang/mdBook/pull/1550)
- Removed the `chapter_begin` id on the print page's chapter separators.
[#1541](https://github.com/rust-lang/mdBook/pull/1541)
## mdBook 0.4.9
[7e01cf9...d325c60](https://github.com/rust-lang/mdBook/compare/7e01cf9...d325c60)
### Changed
- Updated all dependencies and raised the minimum Rust version to 1.42.
[#1528](https://github.com/rust-lang/mdBook/pull/1528)
- Added more detail to error message when a preprocessor fails.
[#1526](https://github.com/rust-lang/mdBook/pull/1526)
- Set max-width of HTML video tags to 100% to match img tags.
[#1542](https://github.com/rust-lang/mdBook/pull/1542)
### Fixed
- Type errors when parsing `book.toml` are no longer ignored.
[#1539](https://github.com/rust-lang/mdBook/pull/1539)
- Better handling if `mdbook serve` fails to start the http server.
[#1555](https://github.com/rust-lang/mdBook/pull/1555)
- Fixed the path for `edit-url-template` if the book used a source directory
other than `src`.
[#1554](https://github.com/rust-lang/mdBook/pull/1554)
## mdBook 0.4.8
[fcceee4...b592b10](https://github.com/rust-lang/mdBook/compare/fcceee4...b592b10)
### Added
- Added the option `output.html.edit-url-template` which can be a URL which is
linked on each page to direct the user to a site (such as GitHub) where the
user can directly suggest an edit for the page they are currently reading.
[#1506](https://github.com/rust-lang/mdBook/pull/1506)
### Changed
- Printed output now includes a page break between chapters.
[#1485](https://github.com/rust-lang/mdBook/pull/1485)
### Fixed
- HTML, such as HTML comments, is now ignored if it appears above the title line
in `SUMMARY.md`.
[#1437](https://github.com/rust-lang/mdBook/pull/1437)
## mdBook 0.4.7
[9a9eb01...c83bbd6](https://github.com/rust-lang/mdBook/compare/9a9eb01...c83bbd6)
### Changed
- Updated shlex parser to fix a minor parsing issue (used by the
preprocessor/backend custom command config).
[#1471](https://github.com/rust-lang/mdBook/pull/1471)
- Enhanced text contrast of `light` theme to improve accessibility.
[#1470](https://github.com/rust-lang/mdBook/pull/1470)
### Fixed
- Fixed some issues with fragment scrolling and linking.
[#1463](https://github.com/rust-lang/mdBook/pull/1463)
## mdBook 0.4.6
[eaa6914...1a0c296](https://github.com/rust-lang/mdBook/compare/eaa6914...1a0c296)
### Changed
- The chapter name is now included in the search breadcrumbs.
[#1389](https://github.com/rust-lang/mdBook/pull/1389)
- Pressing Escape will remove the `?highlight` argument from the URL.
[#1427](https://github.com/rust-lang/mdBook/pull/1427)
- `mdbook init --theme` will now place the theme in the root of the book
directory instead of in the `src` directory.
[#1432](https://github.com/rust-lang/mdBook/pull/1432)
- A custom renderer that sets the `command` to a relative path now interprets
the relative path relative to the book root. Previously it was inconsistent
based on the platform (either relative to the current directory, or relative
to the renderer output directory). Paths relative to the output directory
are still supported with a deprecation warning.
[#1418](https://github.com/rust-lang/mdBook/pull/1418)
- The `theme` directory in the config is now interpreted as relative to the
book root, instead of the current directory.
[#1405](https://github.com/rust-lang/mdBook/pull/1405)
- Handle UTF-8 BOM for chapter sources.
[#1285](https://github.com/rust-lang/mdBook/pull/1285)
- Removed extra whitespace added to `{{#playground}}` snippets.
[#1375](https://github.com/rust-lang/mdBook/pull/1375)
### Fixed
- Clicking on a search result with multiple search words will now correctly
highlight all of the words.
[#1426](https://github.com/rust-lang/mdBook/pull/1426)
- Properly handle `<` and `>` characters in the table of contents.
[#1376](https://github.com/rust-lang/mdBook/pull/1376)
- Fixed to properly serialize the `build` table in the config, which prevented
setting it in the API.
[#1378](https://github.com/rust-lang/mdBook/pull/1378)
## mdBook 0.4.5
[eaa6914...f66df09](https://github.com/rust-lang/mdBook/compare/eaa6914...f66df09)
### Fixed
- Fixed XSS in the search page.
[CVE-2020-26297](https://groups.google.com/g/rustlang-security-announcements/c/3-sO6of29O0)
[648c9ae](https://github.com/rust-lang/mdBook/commit/648c9ae772bec83f0a5954d17b4287d5bb1d6606)
## mdBook 0.4.4
[4df9ec9...01836ba](https://github.com/rust-lang/mdBook/compare/4df9ec9...01836ba)

View File

@@ -1,3 +0,0 @@
# The Rust Code of Conduct
The Code of Conduct for this repository [can be found online](https://www.rust-lang.org/conduct.html).

View File

@@ -6,6 +6,7 @@ If you have come here to learn how to contribute to mdBook, we have some tips fo
First of all, don't hesitate to ask questions!
Use the [issue tracker](https://github.com/rust-lang/mdBook/issues), no question is too simple.
If we don't respond in a couple of days, ping us @Michael-F-Bryan, @budziq, @steveklabnik, @frewsxcv it might just be that we forgot. :wink:
### Issues to work on
@@ -45,7 +46,7 @@ mdBook builds on stable Rust, if you want to build mdBook from source, here are
0. Navigate into the newly created `mdBook` directory
0. Run `cargo build`
The resulting binary can be found in `mdBook/target/debug/` under the name `mdbook` or `mdbook.exe`.
The resulting binary can be found in `mdBook/target/debug/` under the name `mdBook` or `mdBook.exe`.
### Code Quality
@@ -78,7 +79,7 @@ For more information, such as running it from your favourite editor, please see
#### Finding Issues with Clippy
Clippy is a code analyser/linter detecting mistakes, and therefore helps to improve your code.
Clippy is a code analyser/linter detecting mistakes, and therfore helps to improve your code.
Like formatting your code with `rustfmt`, running clippy regularly and before your Pull Request will
help us maintain awesome code.
@@ -105,26 +106,3 @@ and [rustfmt](https://github.com/rust-lang/rustfmt) on the code first.
This is not a requirement though and will never block a pull-request from being merged.
That's it, happy contributions! :tada: :tada: :tada:
## Browser compatibility and testing
Currently we don't have a strict browser compatibility matrix due to our limited resources.
We generally strive to keep mdBook compatible with a relatively recent browser on all of the most major platforms.
That is, supporting Chrome, Safari, Firefox, Edge on Windows, macOS, Linux, iOS, and Android.
If possible, do your best to avoid breaking older browser releases.
Any change to the HTML or styling is encouraged to manually check on as many browsers and platforms that you can.
Unfortunately at this time we don't have any automated UI or browser testing, so your assistance in testing is appreciated.
## Updating higlight.js
The following are instructions for updating [highlight.js](https://highlightjs.org/).
1. Clone the repository at <https://github.com/highlightjs/highlight.js>
1. Check out a tagged release (like `10.1.1`).
1. Run `npm install`
1. Run `node tools/build.js :common apache armasm coffeescript d handlebars haskell http julia nginx properties r scala x86asm yaml`
1. Compare the language list that it spits out to the one in [`syntax-highlighting.md`](https://github.com/camelid/mdBook/blob/master/guide/src/format/theme/syntax-highlighting.md). If any are missing, add them to the list and rebuild (and update these docs). If any are added to the common set, add them to `syntax-highlighting.md`.
1. Copy `build/highlight.min.js` to mdbook's directory [`highlight.js`](https://github.com/rust-lang/mdBook/blob/master/src/theme/highlight.js).
1. Be sure to check the highlight.js [CHANGES](https://github.com/highlightjs/highlight.js/blob/main/CHANGES.md) for any breaking changes. Breaking changes that would affect users will need to wait until the next major release.
1. Build mdbook with the new file and build some books with the new version and compare the output with a variety of languages to see if anything changes. The [test_book](https://github.com/rust-lang/mdBook/tree/master/test_book) contains a chapter with many languages to examine.

1033
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "mdbook"
version = "0.4.17"
version = "0.4.5"
authors = [
"Mathieu David <mathieudavid@mathieudavid.org>",
"Michael-F-Bryan <michaelfbryan@gmail.com>",
@@ -18,23 +18,21 @@ description = "Creates a book from markdown files"
[dependencies]
anyhow = "1.0.28"
chrono = "0.4"
clap = { version = "3.0", features = ["cargo"] }
clap_complete = "3.0"
clap = "2.24"
env_logger = "0.7.1"
handlebars = "4.0"
handlebars = "3.0"
lazy_static = "1.0"
log = "0.4"
memchr = "2.0"
opener = "0.5"
pulldown-cmark = { version = "0.9.1", default-features = false }
regex = "1.5.5"
open = "1.1"
pulldown-cmark = "0.7.0"
regex = "1.0.0"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
shlex = "1"
shlex = "0.1"
tempfile = "3.0"
toml = "0.5.1"
topological-sort = "0.1.0"
# Watch feature
notify = { version = "4.0", optional = true }
@@ -42,18 +40,15 @@ gitignore = { version = "1.0", optional = true }
# Serve feature
futures-util = { version = "0.3.4", optional = true }
tokio = { version = "1", features = ["macros", "rt-multi-thread"], optional = true }
warp = { version = "0.3.1", default-features = false, features = ["websocket"], optional = true }
tokio = { version = "0.2.18", features = ["macros"], optional = true }
warp = { version = "0.2.2", default-features = false, features = ["websocket"], optional = true }
# Search feature
elasticlunr-rs = { version = "2.3", optional = true, default-features = false }
ammonia = { version = "3", optional = true }
[dev-dependencies]
assert_cmd = "1"
predicates = "2"
select = "0.5"
semver = "1.0"
pretty_assertions = "0.6"
walkdir = "2.0"

220
README.md
View File

@@ -6,15 +6,229 @@
mdBook is a utility to create modern online books from Markdown files.
Check out the **[User Guide]** for a list of features and installation and usage information.
The User Guide also serves as a demonstration to showcase what a book looks like.
If you are interested in contributing to the development of mdBook, check out the [Contribution Guide].
## What does it look like?
The [User Guide] for mdBook has been written in Markdown and is using mdBook to
generate the online book-like website you can read. The documentation uses the
latest version on GitHub and showcases the available features.
## Installation
There are multiple ways to install mdBook.
1. **Binaries**
Binaries are available for download [here][releases]. Make sure to put the
path to the binary into your `PATH`.
2. **From Crates.io**
This requires at least [Rust] 1.39 and Cargo to be installed. Once you have installed
Rust, type the following in the terminal:
```
cargo install mdbook
```
This will download and compile mdBook for you, the only thing left to do is
to add the Cargo bin directory to your `PATH`.
**Note for automatic deployment**
If you are using a script to do automatic deployments using Travis or
another CI server, we recommend that you specify a semver version range for
mdBook when you install it through your script!
This will constrain the server to install the latest **non-breaking**
version of mdBook and will prevent your books from failing to build because
we released a new version.
You can also disable default features to speed up compile time.
Example:
```
cargo install mdbook --no-default-features --features output --vers "^0.1.0"
```
3. **From Git**
The version published to crates.io will ever so slightly be behind the
version hosted here on GitHub. If you need the latest version you can build
the git version of mdBook yourself. Cargo makes this ***super easy***!
```
cargo install --git https://github.com/rust-lang/mdBook.git mdbook
```
Again, make sure to add the Cargo bin directory to your `PATH`.
4. **For Contributions**
If you want to contribute to mdBook you will have to clone the repository on
your local machine:
```
git clone https://github.com/rust-lang/mdBook.git
```
`cd` into `mdBook/` and run
```
cargo build
```
The resulting binary can be found in `mdBook/target/debug/` under the name
`mdBook` or `mdBook.exe`.
## Usage
mdBook is primarily used as a command line tool, even though it exposes
all its functionality as a Rust crate for integration in other projects.
Here are the main commands you will want to run. For a more exhaustive
explanation, check out the [User Guide].
- `mdbook init`
The init command will create a directory with the minimal boilerplate to
start with.
```
book-test/
├── book
└── src
├── chapter_1.md
└── SUMMARY.md
```
`book` and `src` are both directories. `src` contains the markdown files
that will be used to render the output to the `book` directory.
Please, take a look at the [CLI docs] for more information and some neat tricks.
- `mdbook build`
This is the command you will run to render your book, it reads the
`SUMMARY.md` file to understand the structure of your book, takes the
markdown files in the source directory as input and outputs static html
pages that you can upload to a server.
- `mdbook watch`
When you run this command, mdbook will watch your markdown files to rebuild
the book on every change. This avoids having to come back to the terminal
to type `mdbook build` over and over again.
- `mdbook serve`
Does the same thing as `mdbook watch` but additionally serves the book at
`http://localhost:3000` (port is changeable) and reloads the browser when a
change occurs.
- `mdbook clean`
Delete directory in which generated book is located.
### 3rd Party Plugins
The way a book is loaded and rendered can be configured by the user via third
party plugins. These plugins are just programs which will be invoked during the
build process and are split into roughly two categories, *preprocessors* and
*renderers*.
Preprocessors are used to transform a book before it is sent to a renderer.
One example would be to replace all occurrences of
`{{#include some_file.ext}}` with the contents of that file. Some existing
preprocessors are:
- `index` - a built-in preprocessor (enabled by default) which will transform
all `README.md` chapters to `index.md` so `foo/README.md` can be accessed via
the url `foo/` when published to a browser
- `links` - a built-in preprocessor (enabled by default) for expanding the
`{{# playground}}` and `{{# include}}` helpers in a chapter.
Renderers are given the final book so they can do something with it. This is
typically used for, as the name suggests, rendering the document in a particular
format, however there's nothing stopping a renderer from doing static analysis
of a book in order to validate links or run tests. Some existing renderers are:
- `html` - the built-in renderer which will generate a HTML version of the book
- `markdown` - the built-in renderer (disabled by default) which will run
preprocessors then output the resulting Markdown. Useful for debugging
preprocessors.
- [`linkcheck`] - a backend which will check that all links are valid
- [`epub`] - an experimental EPUB generator
> **Note for Developers:** Feel free to send us a PR if you've developed your
> own plugin and want it mentioned here.
A preprocessor or renderer is enabled by installing the appropriate program and
then mentioning it in the book's `book.toml` file.
```console
$ cargo install mdbook-linkcheck
$ edit book.toml && cat book.toml
[book]
title = "My Awesome Book"
authors = ["Michael-F-Bryan"]
[output.html]
[output.linkcheck] # enable the "mdbook-linkcheck" renderer
$ mdbook build
2018-10-20 13:57:51 [INFO] (mdbook::book): Book building has started
2018-10-20 13:57:51 [INFO] (mdbook::book): Running the html backend
2018-10-20 13:57:53 [INFO] (mdbook::book): Running the linkcheck backend
```
For more information on the plugin system, consult the [User Guide].
### As a library
Aside from the command line interface, this crate can also be used as a
library. This means that you could integrate it in an existing project, like a
web-app for example. Since the command line interface is just a wrapper around
the library functionality, when you use this crate as a library you have full
access to all the functionality of the command line interface with an easy to
use API and more!
See the [User Guide] and the [API docs] for more information.
## Contributions
Contributions are highly appreciated and encouraged! Don't hesitate to
participate to discussions in the issues, propose new features and ask for
help.
If you are just starting out with Rust, there are a series of issues that are
tagged [E-Easy] and **we will gladly mentor you** so that you can successfully
go through the process of fixing a bug or adding a new feature! Let us know if
you need any help.
For more info about contributing, check out our [contribution guide] which helps
you go through the build and contribution process!
There is also a [rendered version][master-docs] of the latest API docs
available, for those hacking on `master`.
## License
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.
[User Guide]: https://rust-lang.github.io/mdBook/
[API docs]: https://docs.rs/mdbook/*/mdbook/
[E-Easy]: https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AE-Easy
[contribution guide]: https://github.com/rust-lang/mdBook/blob/master/CONTRIBUTING.md
[LICENSE]: https://github.com/rust-lang/mdBook/blob/master/LICENSE
[releases]: https://github.com/rust-lang/mdBook/releases
[Rust]: https://www.rust-lang.org/
[CLI docs]: http://rust-lang.github.io/mdBook/cli/init.html
[master-docs]: http://rust-lang.github.io/mdBook/
[`linkcheck`]: https://crates.io/crates/mdbook-linkcheck
[`epub`]: https://crates.io/crates/mdbook-epub

View File

@@ -1,18 +1,17 @@
use crate::nop_lib::Nop;
use clap::{App, Arg, ArgMatches};
use clap::{App, Arg, ArgMatches, SubCommand};
use mdbook::book::Book;
use mdbook::errors::Error;
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
use semver::{Version, VersionReq};
use std::io;
use std::process;
pub fn make_app() -> App<'static> {
pub fn make_app() -> App<'static, 'static> {
App::new("nop-preprocessor")
.about("A mdbook preprocessor which does precisely nothing")
.subcommand(
App::new("supports")
.arg(Arg::new("renderer").required(true))
SubCommand::with_name("supports")
.arg(Arg::with_name("renderer").required(true))
.about("Check whether a renderer is supported by this preprocessor"),
)
}
@@ -34,10 +33,9 @@ fn main() {
fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
let book_version = Version::parse(&ctx.mdbook_version)?;
let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?;
if !version_req.matches(&book_version) {
if ctx.mdbook_version != mdbook::MDBOOK_VERSION {
// We should probably use the `semver` crate to check compatibility
// here...
eprintln!(
"Warning: The {} plugin was built against version {} of mdbook, \
but we're being called from version {}",
@@ -55,7 +53,7 @@ fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! {
let renderer = sub_args.value_of("renderer").expect("Required argument");
let supported = pre.supports_renderer(renderer);
let supported = pre.supports_renderer(&renderer);
// Signal whether the renderer is supported by exiting with 1 or 0.
if supported {

View File

@@ -10,8 +10,6 @@ edition = "2018"
[output.html]
mathjax-support = true
site-url = "/mdBook/"
git-repository-url = "https://github.com/rust-lang/mdBook/tree/master/guide"
edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
[output.html.playground]
editable = true
@@ -25,6 +23,3 @@ boost-hierarchy = 2
boost-paragraph = 1
expand = true
heading-split-level = 2
[output.html.redirect]
"/format/config.html" = "configuration/index.html"

View File

@@ -1,41 +1,25 @@
# Introduction
# mdBook
**mdBook** is a command line tool to create books with Markdown.
It is ideal for creating product or API documentation, tutorials, course materials or anything that requires a clean,
easily navigable and customizable presentation.
**mdBook** is a command line tool and Rust crate to create books using Markdown
(as by the [CommonMark](https://commonmark.org/) specification) files. It's very
similar to Gitbook but written in [Rust](http://www.rust-lang.org).
* Lightweight [Markdown] syntax helps you focus more on your content
* Integrated [search] support
* Color [syntax highlighting] for code blocks for many different languages
* [Theme] files allow customizing the formatting of the output
* [Preprocessors] can provide extensions for custom syntax and modifying content
* [Backends] can render the output to multiple formats
* Written in [Rust] for speed, safety, and simplicity
* Automated testing of [Rust code samples]
What you are reading serves as an example of the output of mdBook and at the
same time as a high-level documentation.
This guide is an example of what mdBook produces.
mdBook is used by the Rust programming language project, and [The Rust Programming Language][trpl] book is another fine example of mdBook in action.
mdBook is free and open source, you can find the source code on
[GitHub](https://github.com/rust-lang/mdBook). Issues and feature
requests can be posted on the [GitHub issue
tracker](https://github.com/rust-lang/mdBook/issues).
[Markdown]: format/markdown.md
[search]: guide/reading.md#search
[syntax highlighting]: format/theme/syntax-highlighting.md
[theme]: format/theme/index.html
[preprocessors]: format/configuration/preprocessors.md
[backends]: format/configuration/renderers.md
[Rust]: https://www.rust-lang.org/
[trpl]: https://doc.rust-lang.org/book/
[Rust code samples]: cli/test.md
## API docs
## Contributing
mdBook is free and open source. You can find the source code on
[GitHub](https://github.com/rust-lang/mdBook) and issues and feature requests can be posted on
the [GitHub issue tracker](https://github.com/rust-lang/mdBook/issues). mdBook relies on the community to fix bugs and
add features: if you'd like to contribute, please read
the [CONTRIBUTING](https://github.com/rust-lang/mdBook/blob/master/CONTRIBUTING.md) guide and consider opening
a [pull request](https://github.com/rust-lang/mdBook/pulls).
Alongside this book you can also read the [API
docs](https://docs.rs/mdbook/*/mdbook/) generated by Rustdoc if you would like
to use mdBook as a crate or write a new renderer and need a more low-level
overview.
## License
The mdBook source and documentation are released under
the [Mozilla Public License v2.0](https://www.mozilla.org/MPL/2.0/).
mdBook, all the source code, is released under the [Mozilla Public License
v2.0](https://www.mozilla.org/MPL/2.0/).

View File

@@ -1,15 +1,6 @@
# Summary
[Introduction](README.md)
# User Guide
- [Installation](guide/installation.md)
- [Reading Books](guide/reading.md)
- [Creating a Book](guide/creating.md)
# Reference Guide
- [mdBook](README.md)
- [Command Line Tool](cli/README.md)
- [init](cli/init.md)
- [build](cli/build.md)
@@ -17,22 +8,16 @@
- [serve](cli/serve.md)
- [test](cli/test.md)
- [clean](cli/clean.md)
- [completions](cli/completions.md)
- [Format](format/README.md)
- [SUMMARY.md](format/summary.md)
- [Draft chapter]()
- [Configuration](format/configuration/README.md)
- [General](format/configuration/general.md)
- [Preprocessors](format/configuration/preprocessors.md)
- [Renderers](format/configuration/renderers.md)
- [Environment Variables](format/configuration/environment-variables.md)
- [Configuration](format/config.md)
- [Theme](format/theme/README.md)
- [index.hbs](format/theme/index-hbs.md)
- [Syntax highlighting](format/theme/syntax-highlighting.md)
- [Editor](format/theme/editor.md)
- [MathJax Support](format/mathjax.md)
- [mdBook-specific features](format/mdbook.md)
- [Markdown](format/markdown.md)
- [Continuous Integration](continuous-integration.md)
- [For Developers](for_developers/README.md)
- [Preprocessors](for_developers/preprocessors.md)

View File

@@ -1,14 +1,55 @@
# Command Line Tool
The `mdbook` command-line tool is used to create and build books.
After you have [installed](../guide/installation.md) `mdbook`, you can run the `mdbook help` command in your terminal to view the available commands.
mdBook can be used either as a command line tool or a [Rust
crate](https://crates.io/crates/mdbook). Let's focus on the command line tool
capabilities first.
This following sections provide in-depth information on the different commands available.
## Install From Binaries
* [`mdbook init <directory>`](init.md) — Creates a new book with minimal boilerplate to start with.
* [`mdbook build`](build.md) — Renders the book.
* [`mdbook watch`](watch.md) — Rebuilds the book any time a source file changes.
* [`mdbook serve`](serve.md) — Runs a web server to view the book, and rebuilds on changes.
* [`mdbook test`](test.md) — Tests Rust code samples.
* [`mdbook clean`](clean.md) — Deletes the rendered output.
* [`mdbook completions`](completions.md) — Support for shell auto-completion.
Precompiled binaries are provided for major platforms on a best-effort basis.
Visit [the releases page](https://github.com/rust-lang/mdBook/releases)
to download the appropriate version for your platform.
## Install From Source
mdBook can also be installed from source
### Pre-requisite
mdBook is written in **[Rust](https://www.rust-lang.org/)** and therefore needs
to be compiled with **Cargo**. If you haven't already installed Rust, please go
ahead and [install it](https://www.rust-lang.org/tools/install) now.
### Install Crates.io version
Installing mdBook is relatively easy if you already have Rust and Cargo
installed. You just have to type this snippet in your terminal:
```bash
cargo install mdbook
```
This will fetch the source code for the latest release from
[Crates.io](https://crates.io/) and compile it. You will have to add Cargo's
`bin` directory to your `PATH`.
Run `mdbook help` in your terminal to verify if it works. Congratulations, you
have installed mdBook!
### Install Git version
The **[git version](https://github.com/rust-lang/mdBook)** contains all
the latest bug-fixes and features, that will be released in the next version on
**Crates.io**, if you can't wait until the next release. You can build the git
version yourself. Open your terminal and navigate to the directory of you
choice. We need to clone the git repository and then build it with Cargo.
```bash
git clone --depth=1 https://github.com/rust-lang/mdBook.git
cd mdBook
cargo build --release
```
The executable `mdbook` will be in the `./target/release` folder, this should be
added to the path.

View File

@@ -7,8 +7,7 @@ mdbook build
```
It will try to parse your `SUMMARY.md` file to understand the structure of your
book and fetch the corresponding files. Note that files mentioned in `SUMMARY.md`
but not present will be created.
book and fetch the corresponding files.
The rendered output will maintain the same directory structure as the source for
convenience. Large books will therefore remain structured when rendered.

View File

@@ -1,16 +0,0 @@
# The completions command
The completions command is used to generate auto-completions for some common shells.
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.
The completions first need to be installed for your shell:
```bash
mdbook completions bash > ~/.local/share/bash-completion/completions/mdbook
```
The command prints a completion script for the given shell.
Run `mdbook completions --help` for a list of supported shells.
Where to place the completions depend on which shell you are using and your operating system.
Consult your shell's documentation for more information one where to place the script.

View File

@@ -25,9 +25,9 @@ book-test/
- The `book` directory is where your book is rendered. All the output is ready
to be uploaded to a server to be seen by your audience.
- The `SUMMARY.md` is the skeleton of your
book, and is discussed in more detail [in another
chapter](../format/summary.md).
- The `SUMMARY.md` file is the most important file, it's the skeleton of your
book and is discussed in more detail [in another
chapter](../format/summary.md)
#### Tip: Generate chapters from SUMMARY.md
@@ -52,19 +52,3 @@ directory called `theme` in your source directory so that you can modify it.
The theme is selectively overwritten, this means that if you don't want to
overwrite a specific file, just delete it and the default file will be used.
#### --title
Specify a title for the book. If not supplied, an interactive prompt will ask for
a title.
```bash
mdbook init --title="my amazing book"
```
#### --ignore
Create a `.gitignore` file configured to ignore the `book` directory created when [building] a book.
If not supplied, an interactive prompt will ask whether it should be created.
[building]: build.md

View File

@@ -1,15 +1,8 @@
# The serve command
The serve command is used to preview a book by serving it via HTTP at
`localhost:3000` by default:
```bash
mdbook serve
```
The `serve` command watches the book's `src` directory for
changes, rebuilding the book and refreshing clients for each change; this includes
re-creating deleted files still mentioned in `SUMMARY.md`! A websocket
The serve command is used to preview a book by serving it over HTTP at
`localhost:3000` by default. Additionally it watches the book's directory for
changes, rebuilding the book and refreshing clients for each change. A websocket
connection is used to trigger the client-side refresh.
***Note:*** *The `serve` command is for testing a book's HTML output, and is not
@@ -24,14 +17,24 @@ root instead of the current working directory.
mdbook serve path/to/book
```
### Server options
#### Server options
The `serve` hostname defaults to `localhost`, and the port defaults to `3000`. Either option can be specified on the command line:
`serve` has four options: the HTTP port, the WebSocket port, the HTTP hostname
to listen on, and the hostname for the browser to connect to for WebSockets.
For example: suppose you have an nginx server for SSL termination which has a
public address of 192.168.1.100 on port 80 and proxied that to 127.0.0.1 on port
8000\. To run use the nginx proxy do:
```bash
mdbook serve path/to/book -p 8000 -n 127.0.0.1
mdbook serve path/to/book -p 8000 -n 127.0.0.1 --websocket-hostname 192.168.1.100
```
If you were to want live reloading for this you would need to proxy the
websocket calls through nginx as well from `192.168.1.100:<WS_PORT>` to
`127.0.0.1:<WS_PORT>`. The `-w` flag allows for the websocket port to be
configured.
#### --open
When you use the `--open` (`-o`) flag, mdbook will open the book in your
@@ -52,5 +55,5 @@ contain file patterns described in the [gitignore
documentation](https://git-scm.com/docs/gitignore). This can be useful for
ignoring temporary files created by some editors.
***Note:*** *Only the `.gitignore` from the book root directory is used. Global
`$HOME/.gitignore` or `.gitignore` files in parent directories are not used.*
_Note: Only `.gitignore` from book root directory is used. Global
`$HOME/.gitignore` or `.gitignore` files in parent directories are not used._

View File

@@ -43,17 +43,7 @@ mdbook test path/to/book
The `--library-path` (`-L`) option allows you to add directories to the library
search path used by `rustdoc` when it builds and tests the examples. Multiple
directories can be specified with multiple options (`-L foo -L bar`) or with a
comma-delimited list (`-L foo,bar`). The path should point to the Cargo
[build cache](https://doc.rust-lang.org/cargo/guide/build-cache.html) `deps` directory that
contains the build output of your project. For example, if your Rust project's book is in a directory
named `my-book`, the following command would include the crate's dependencies when running `test`:
```shell
mdbook test my-book -L target/debug/deps/
```
See the `rustdoc` command-line [documentation](https://doc.rust-lang.org/rustdoc/command-line-arguments.html#-l--library-path-where-to-look-for-dependencies)
for more information.
comma-delimited list (`-L foo,bar`).
#### --dest-dir

View File

@@ -3,8 +3,7 @@
The `watch` command is useful when you want your book to be rendered on every
file change. You could repeatedly issue `mdbook build` every time a file is
changed. But using `mdbook watch` once will watch your files and will trigger a
build automatically whenever you modify a file; this includes re-creating
deleted files still mentioned in `SUMMARY.md`!
build automatically whenever you modify a file.
#### Specify a directory

View File

@@ -1,121 +1,89 @@
# Running `mdbook` in Continuous Integration
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.
While the following examples use Travis CI, their principles should
straightforwardly transfer to other continuous integration providers as well.
The following provides some general guidelines on how to configure your service to run mdBook.
Specific recipes can be found at the [Automated Deployment] wiki page.
## Ensuring Your Book Builds and Tests Pass
[GitHub Actions]: https://docs.github.com/en/actions
[GitLab CI/CD]: https://docs.gitlab.com/ee/ci/
[Automated Deployment]: https://github.com/rust-lang/mdBook/wiki/Automated-Deployment
Here is a sample Travis CI `.travis.yml` configuration that ensures `mdbook
build` and `mdbook test` run successfully. The key to fast CI turnaround times
is caching `mdbook` installs, so that you aren't compiling `mdbook` on every CI
run.
## Installing mdBook
```yaml
language: rust
sudo: false
There are several different strategies for installing mdBook.
The particular method depends on your needs and preferences.
cache:
- cargo
### Pre-compiled binaries
rust:
- stable
Perhaps the easiest method is to use the pre-compiled binaries found on the [GitHub Releases page][releases].
A simple approach would be to use the popular `curl` CLI tool to download the executable:
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.3" mdbook)
- cargo install-update -a
```sh
mkdir bin
curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.17/mdbook-v0.4.17-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
bin/mdbook build
script:
- mdbook build path/to/mybook && mdbook test path/to/mybook
```
Some considerations for this approach:
## Deploying Your Book to GitHub Pages
* This is relatively fast, and does not necessarily require dealing with caching.
* This does not require installing Rust.
* Specifying a specific URL means you have to manually update your script to get a new version.
This may be a benefit if you want to lock to a specific version.
However, some users prefer to automatically get a newer version when they are published.
* You are reliant on the GitHub CDN being available.
Following these instructions will result in your book being published to GitHub
pages after a successful CI run on your repository's `master` branch.
[releases]: https://github.com/rust-lang/mdBook/releases
First, create a new GitHub "Personal Access Token" with the "public_repo"
permissions (or "repo" for private repositories). Go to your repository's Travis
CI settings page and add an environment variable named `GITHUB_TOKEN` that is
marked secure and *not* shown in the logs.
### Building from source
Then, append this snippet to your `.travis.yml` and update the path to the
`book` directory:
Building from source will require having Rust installed.
Some services have Rust pre-installed, but if your service does not, you will need to add a step to install it.
After Rust is installed, `cargo install` can be used to build and install mdBook.
We recommend using a SemVer version specifier so that you get the latest **non-breaking** version of mdBook.
For example:
```sh
cargo install mdbook --no-default-features --features search --vers "^0.4" --locked
```yaml
deploy:
provider: pages
skip-cleanup: true
github-token: $GITHUB_TOKEN
local-dir: path/to/mybook/book
keep-history: false
on:
branch: master
```
This includes several recommended options:
That's it!
* `--no-default-features` — Disables features like the HTTP server used by `mdbook serve` that is likely not needed on CI.
This will speed up the build time significantly.
* `--features search` — Disabling default features means you should then manually enable features that you want, such as the built-in [search] capability.
* `--vers "^0.4"` — This will install the most recent version of the `0.4` series.
However, versions after like `0.5.0` won't be installed, as they may break your build.
Cargo will automatically upgrade mdBook if you have an older version already installed.
* `--locked` — This will use the dependencies that were used when mdBook was released.
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.
### Deploying to GitHub Pages manually
You will likely want to investigate caching options, as building mdBook can be somewhat slow.
If your CI doesn't support GitHub pages, or you're deploying somewhere else
with integrations such as Github Pages:
*note: you may want to use different tmp dirs*:
[search]: guide/reading.md#search
## Running tests
You may want to run tests using [`mdbook test`] every time you push a change or create a pull request.
This can be used to validate Rust code examples in the book.
This will require having Rust installed.
Some services have Rust pre-installed, but if your service does not, you will need to add a step to install it.
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.
You may also want to consider running other kinds of tests, like [mdbook-linkcheck] which will check for broken links.
Or if you have your own style checks, spell checker, or any other tests it might be good to run them in CI.
[`mdbook test`]: cli/test.md
[mdbook-linkcheck]: https://github.com/Michael-F-Bryan/mdbook-linkcheck#continuous-integration
## Deploying
You may want to automatically deploy your book.
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.
You'll also need to understand the specifics on how to push a change to your web service.
For example, [GitHub Pages] just requires committing the output onto a specific git branch.
Other services may require using something like SSH to connect to a remote server.
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.
You may then want to consider if you need to invalidate any caches on your web service.
See the [Automated Deployment] wiki page for examples of various different services.
[GitHub Pages]: https://docs.github.com/en/pages
### 404 handling
mdBook automatically generates a 404 page to be used for broken links.
The default output is a file named `404.html` at the root of the book.
Some services like [GitHub Pages] will automatically use this page for broken links.
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.
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.
It needs to know where the book is deployed in order to load the static files (like CSS) correctly.
For example, this guide is deployed at <https://rust-lang.github.io/mdBook/>, and the `site-url` setting is configured like this:
```toml
# book.toml
[output.html]
site-url = "/mdBook/"
```console
$> git worktree add /tmp/book gh-pages
$> mdbook build
$> rm -rf /tmp/book/* # this won't delete the .git directory
$> cp -rp book/* /tmp/book/
$> cd /tmp/book
$> git add -A
$> git commit 'new book message'
$> git push origin gh-pages
$> cd -
```
You can customize the look of the 404 page by creating a file named `src/404.md` in your book.
If you want to use a different filename, you can set [`output.html.input-404`] to a different filename.
Or put this into a Makefile rule:
[`output.html.site-url`]: format/configuration/renderers.md#html-renderer-options
[`output.html.input-404`]: format/configuration/renderers.md#html-renderer-options
```makefile
.PHONY: deploy
deploy: book
@echo "====> deploying to github"
git worktree add /tmp/book gh-pages
rm -rf /tmp/book/*
cp -rp book/* /tmp/book/
cd /tmp/book && \
git add -A && \
git commit -m "deployed on $(shell date) by ${USER}" && \
git push origin gh-pages
```

View File

@@ -24,9 +24,8 @@ The process of rendering a book project goes through several steps.
exist
- Load the book chapters into memory
- Discover which preprocessors/backends should be used
2. For each backend:
1. Run all the preprocessors.
2. Call the backend to render the processed result.
2. Run the preprocessors
3. Call each backend in turn
## Using `mdbook` as a Library

View File

@@ -5,17 +5,22 @@ rendering process. This program is passed a JSON representation of the book and
configuration information via `stdin`. Once the backend receives this
information it is free to do whatever it wants.
See [Configuring Renderers](../format/configuration/renderers.md) for more information about using backends.
There are already several alternative backends on GitHub which can be used as a
rough example of how this is accomplished in practice.
The community has developed several backends.
See the [Third Party Plugins] wiki page for a list of available backends.
## Setting Up
- [mdbook-linkcheck] - a simple program for verifying the book doesn't contain
any broken links
- [mdbook-epub] - an EPUB renderer
- [mdbook-test] - a program to run the book's contents through [rust-skeptic] to
verify everything compiles and runs correctly (similar to `rustdoc --test`)
This page will step you through creating your own alternative backend in the form
of a simple word counting program. Although it will be written in Rust, there's
no reason why it couldn't be accomplished using something like Python or Ruby.
## Setting Up
First you'll want to create a new binary program and add `mdbook` as a
dependency.
@@ -324,6 +329,39 @@ generation or a warning).
All environment variables are passed through to the backend, allowing you to use
the usual `RUST_LOG` to control logging verbosity.
## Handling missing backends
If you enable a backend that isn't installed, the default behavior is to throw an error:
```text
The command `mdbook-wordcount` wasn't found, is the "wordcount" backend installed?
If you want to ignore this error when the "wordcount" backend is not installed,
set `optional = true` in the `[output.wordcount]` section of the book.toml configuration file.
```
This behavior can be changed by marking the backend as optional.
```diff
[book]
title = "mdBook Documentation"
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
authors = ["Mathieu David", "Michael-F-Bryan"]
[output.html]
[output.wordcount]
command = "python /path/to/wordcount.py"
+ optional = true
```
This demotes the error to a warning, and it will instead look like this:
```text
The command was not found, but was marked as optional.
Command: wordcount
```
## Wrapping Up
Although contrived, hopefully this example was enough to show how you'd create
@@ -336,7 +374,10 @@ as a good example of how it's done in real life, so feel free to skim through
the source code or ask questions.
[Third Party Plugins]: https://github.com/rust-lang/mdBook/wiki/Third-party-plugins
[mdbook-linkcheck]: https://github.com/Michael-F-Bryan/mdbook-linkcheck
[mdbook-epub]: https://github.com/Michael-F-Bryan/mdbook-epub
[mdbook-test]: https://github.com/Michael-F-Bryan/mdbook-test
[rust-skeptic]: https://github.com/budziq/rust-skeptic
[`RenderContext`]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html
[`RenderContext::from_json()`]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html#method.from_json
[`semver`]: https://crates.io/crates/semver

View File

@@ -5,27 +5,37 @@ book is loaded and before it gets rendered, allowing you to update and mutate
the book. Possible use cases are:
- Creating custom helpers like `\{{#include /path/to/file.md}}`
- Updating links so `[some chapter](some_chapter.md)` is automatically changed
to `[some chapter](some_chapter.html)` for the HTML renderer
- Substituting in latex-style expressions (`$$ \frac{1}{3} $$`) with their
mathjax equivalents
See [Configuring Preprocessors](../format/configuration/preprocessors.md) for more information about using preprocessors.
## Hooking Into MDBook
MDBook uses a fairly simple mechanism for discovering third party plugins.
A new table is added to `book.toml` (e.g. `[preprocessor.foo]` for the `foo`
A new table is added to `book.toml` (e.g. `preprocessor.foo` for the `foo`
preprocessor) and then `mdbook` will try to invoke the `mdbook-foo` program as
part of the build process.
Once the preprocessor has been defined and the build process starts, mdBook executes the command defined in the `preprocessor.foo.command` key twice.
The first time it runs the preprocessor to determine if it supports the given renderer.
mdBook passes two arguments to the process: the first argument is the string `supports` and the second argument is the renderer name.
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.
While preprocessors can be hard-coded to specify which backend it should be run
for (e.g. it doesn't make sense for MathJax to be used for non-HTML renderers)
with the `preprocessor.foo.renderer` key.
If the preprocessor supports the renderer, then mdbook runs it a second time, passing JSON data into stdin.
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.
```toml
[book]
title = "My Book"
authors = ["Michael-F-Bryan"]
The preprocessor should return the JSON format of the [`Book`] object to stdout, with any modifications it wishes to perform.
[preprocessor.foo]
# The command can also be specified manually
command = "python3 /path/to/foo.py"
# Only run the `foo` preprocessor for the HTML and EPUB renderer
renderer = ["html", "epub"]
```
In typical unix style, all inputs to the plugin will be written to `stdin` as
JSON and `mdbook` will read from `stdout` if it is expecting output.
The easiest way to get started is by creating your own implementation of the
`Preprocessor` trait (e.g. in `lib.rs`) and then creating a shell binary which
@@ -61,7 +71,7 @@ The `chapter.content` is just a string which happens to be markdown. While it's
entirely possible to use regular expressions or do a manual find & replace,
you'll probably want to process the input into something more computer-friendly.
The [`pulldown-cmark`][pc] crate implements a production-quality event-based
Markdown parser, with the [`pulldown-cmark-to-cmark`][pctc] crate allowing you to
Markdown parser, with the [`pulldown-cmark-to-cmark`][pctc] allowing you to
translate events back into markdown text.
The following code block shows how to remove all emphasis from markdown,
@@ -96,33 +106,6 @@ fn remove_emphasis(
For everything else, have a look [at the complete example][example].
## Implementing a preprocessor with a different language
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.
The following code shows how to implement a simple preprocessor in Python, which will modify the content of the first chapter.
The example below follows the configuration shown above with `preprocessor.foo.command` actually pointing to a Python script.
```python
import json
import sys
if __name__ == '__main__':
if len(sys.argv) > 1: # we check if we received any argument
if sys.argv[1] == "supports":
# then we are good to return an exit status code of 0, since the other argument will just be the renderer's name
sys.exit(0)
# load both the context and the book representations from stdin
context, book = json.load(sys.stdin)
# and now, we can just modify the content of the first chapter
book['sections'][0]['Chapter']['content'] = '# Hello'
# we are done with the book's modification, we can just print it to stdout,
print(json.dumps(book))
```
[preprocessor-docs]: https://docs.rs/mdbook/latest/mdbook/preprocess/trait.Preprocessor.html
[pc]: https://crates.io/crates/pulldown-cmark
[pctc]: https://crates.io/crates/pulldown-cmark-to-cmark
@@ -130,5 +113,3 @@ if __name__ == '__main__':
[an example no-op preprocessor]: https://github.com/rust-lang/mdBook/blob/master/examples/nop-preprocessor.rs
[`CmdPreprocessor::parse_input()`]: https://docs.rs/mdbook/latest/mdbook/preprocess/trait.Preprocessor.html#method.parse_input
[`Book::for_each_mut()`]: https://docs.rs/mdbook/latest/mdbook/book/struct.Book.html#method.for_each_mut
[`PreprocessorContext`]: https://docs.rs/mdbook/latest/mdbook/preprocess/struct.PreprocessorContext.html
[`Book`]: https://docs.rs/mdbook/latest/mdbook/book/struct.Book.html

396
guide/src/format/config.md Normal file
View File

@@ -0,0 +1,396 @@
# Configuration
You can configure the parameters for your book in the ***book.toml*** file.
Here is an example of what a ***book.toml*** file might look like:
```toml
[book]
title = "Example book"
author = "John Doe"
description = "The example book covers examples."
[rust]
edition = "2018"
[build]
build-dir = "my-example-book"
create-missing = false
[preprocessor.index]
[preprocessor.links]
[output.html]
additional-css = ["custom.css"]
[output.html.search]
limit-results = 15
```
## Supported configuration options
It is important to note that **any** relative path specified in the
configuration will always be taken relative from the root of the book where the
configuration file is located.
### General metadata
This is general information about your book.
- **title:** The title of the book
- **authors:** The author(s) of the book
- **description:** A description for the book, which is added as meta
information in the html `<head>` of each page
- **src:** By default, the source directory is found in the directory named
`src` directly under the root folder. But this is configurable with the `src`
key in the configuration file.
- **language:** The main language of the book, which is used as a language attribute `<html lang="en">` for example.
**book.toml**
```toml
[book]
title = "Example book"
authors = ["John Doe", "Jane Doe"]
description = "The example book covers examples."
src = "my-src" # the source files will be found in `root/my-src` instead of `root/src`
language = "en"
```
### Rust options
Options for the Rust language, relevant to running tests and playground
integration.
- **edition**: Rust edition to use by default for the code snippets. Default
is "2015". Individual code blocks can be controlled with the `edition2015`
or `edition2018` annotations, such as:
~~~text
```rust,edition2015
// This only works in 2015.
let try = true;
```
~~~
### Build options
This controls the build process of your book.
- **build-dir:** The directory to put the rendered book in. By default this is
`book/` in the book's root directory.
- **create-missing:** By default, any missing files specified in `SUMMARY.md`
will be created when the book is built (i.e. `create-missing = true`). If this
is `false` then the build process will instead exit with an error if any files
do not exist.
- **use-default-preprocessors:** Disable the default preprocessors of (`links` &
`index`) by setting this option to `false`.
If you have the same, and/or other preprocessors declared via their table
of configuration, they will run instead.
- For clarity, with no preprocessor configuration, the default `links` and
`index` will run.
- Setting `use-default-preprocessors = false` will disable these
default preprocessors from running.
- Adding `[preprocessor.links]`, for example, will ensure, regardless of
`use-default-preprocessors` that `links` it will run.
## Configuring Preprocessors
The following preprocessors are available and included by default:
- `links`: Expand the `{{ #playground }}`, `{{ #include }}`, and `{{ #rustdoc_include }}` handlebars
helpers in a chapter to include the contents of a file.
- `index`: Convert all chapter files named `README.md` into `index.md`. That is
to say, all `README.md` would be rendered to an index file `index.html` in the
rendered book.
**book.toml**
```toml
[build]
build-dir = "build"
create-missing = false
[preprocessor.links]
[preprocessor.index]
```
### Custom Preprocessor Configuration
Like renderers, preprocessor will need to be given its own table (e.g.
`[preprocessor.mathjax]`). In the section, you may then pass extra
configuration to the preprocessor by adding key-value pairs to the table.
For example
```toml
[preprocessor.links]
# set the renderers this preprocessor will run for
renderers = ["html"]
some_extra_feature = true
```
#### Locking a Preprocessor dependency to a renderer
You can explicitly specify that a preprocessor should run for a renderer by
binding the two together.
```toml
[preprocessor.mathjax]
renderers = ["html"] # mathjax only makes sense with the HTML renderer
```
### Provide Your Own Command
By default when you add a `[preprocessor.foo]` table to your `book.toml` file,
`mdbook` will try to invoke the `mdbook-foo` executable. If you want to use a
different program name or pass in command-line arguments, this behaviour can
be overridden by adding a `command` field.
```toml
[preprocessor.random]
command = "python random.py"
```
## Configuring Renderers
### HTML renderer options
The HTML renderer has a couple of options as well. All the options for the
renderer need to be specified under the TOML table `[output.html]`.
The following configuration options are available:
- **theme:** mdBook comes with a default theme and all the resource files needed
for it. But if this option is set, mdBook will selectively overwrite the theme
files with the ones found in the specified folder.
- **default-theme:** The theme color scheme to select by default in the
'Change Theme' dropdown. Defaults to `light`.
- **preferred-dark-theme:** The default dark theme. This theme will be used if
the browser requests the dark version of the site via the
['prefers-color-scheme'](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)
CSS media query. Defaults to `navy`.
- **curly-quotes:** Convert straight quotes to curly quotes, except for those
that occur in code blocks and code spans. Defaults to `false`.
- **mathjax-support:** Adds support for [MathJax](mathjax.md). Defaults to
`false`.
- **copy-fonts:** Copies fonts.css and respective font files to the output directory and use them in the default theme. Defaults to `true`.
- **google-analytics:** If you use Google Analytics, this option lets you enable
it by simply specifying your ID in the configuration file.
- **additional-css:** If you need to slightly change the appearance of your book
without overwriting the whole style, you can specify a set of stylesheets that
will be loaded after the default ones where you can surgically change the
style.
- **additional-js:** If you need to add some behaviour to your book without
removing the current behaviour, you can specify a set of JavaScript files that
will be loaded alongside the default one.
- **print:** A subtable for configuration print settings. mdBook by default adds
support for printing out the book as a single page. This is accessed using the
print icon on the top right of the book.
- **no-section-label:** mdBook by defaults adds section label in table of
contents column. For example, "1.", "2.1". Set this option to true to disable
those labels. Defaults to `false`.
- **fold:** A subtable for configuring sidebar section-folding behavior.
- **playground:** A subtable for configuring various playground settings.
- **search:** A subtable for configuring the in-browser search functionality.
mdBook must be compiled with the `search` feature enabled (on by default).
- **git-repository-url:** A url to the git repository for the book. If provided
an icon link will be output in the menu bar of the book.
- **git-repository-icon:** The FontAwesome icon class to use for the git
repository link. Defaults to `fa-github`.
- **redirect:** A subtable used for generating redirects when a page is moved.
The table contains key-value pairs where the key is where the redirect file
needs to be created, as an absolute path from the build directory, (e.g.
`/appendices/bibliography.html`). The value can be any valid URI the
browser should navigate to (e.g. `https://rust-lang.org/`,
`/overview.html`, or `../bibliography.html`).
- **input-404:** The name of the markdown file used for misssing files.
The corresponding output file will be the same, with the extension replaced with `html`.
Defaults to `404.md`.
- **site-url:** The url where the book will be hosted. This is required to ensure
navigation links and script/css imports in the 404 file work correctly, even when accessing
urls in subdirectories. Defaults to `/`.
- **cname:** The DNS subdomain or apex domain at which your book will be hosted.
This string will be written to a file named CNAME in the root of your site, as
required by GitHub Pages (see [*Managing a custom domain for your GitHub Pages
site*][custom domain]).
[custom domain]: https://docs.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site
Available configuration options for the `[output.html.print]` table:
- **enable:** Enable print support. When `false`, all print support will not be
rendered. Defaults to `true`.
Available configuration options for the `[output.html.fold]` table:
- **enable:** Enable section-folding. When off, all folds are open.
Defaults to `false`.
- **level:** The higher the more folded regions are open. When level is 0, all
folds are closed. Defaults to `0`.
Available configuration options for the `[output.html.playground]` table:
- **editable:** Allow editing the source code. Defaults to `false`.
- **copyable:** Display the copy button on code snippets. Defaults to `true`.
- **copy-js:** Copy JavaScript files for the editor to the output directory.
Defaults to `true`.
- **line-numbers** Display line numbers on editable sections of code. Requires both `editable` and `copy-js` to be `true`. Defaults to `false`.
[Ace]: https://ace.c9.io/
Available configuration options for the `[output.html.search]` table:
- **enable:** Enables the search feature. Defaults to `true`.
- **limit-results:** The maximum number of search results. Defaults to `30`.
- **teaser-word-count:** The number of words used for a search result teaser.
Defaults to `30`.
- **use-boolean-and:** Define the logical link between multiple search words. If
true, all search words must appear in each result. Defaults to `false`.
- **boost-title:** Boost factor for the search result score if a search word
appears in the header. Defaults to `2`.
- **boost-hierarchy:** Boost factor for the search result score if a search word
appears in the hierarchy. The hierarchy contains all titles of the parent
documents and all parent headings. Defaults to `1`.
- **boost-paragraph:** Boost factor for the search result score if a search word
appears in the text. Defaults to `1`.
- **expand:** True if search should match longer results e.g. search `micro`
should match `microwave`. Defaults to `true`.
- **heading-split-level:** Search results will link to a section of the document
which contains the result. Documents are split into sections by headings this
level or less. Defaults to `3`. (`### This is a level 3 heading`)
- **copy-js:** Copy JavaScript files for the search implementation to the output
directory. Defaults to `true`.
This shows all available HTML output options in the **book.toml**:
```toml
[book]
title = "Example book"
authors = ["John Doe", "Jane Doe"]
description = "The example book covers examples."
[output.html]
theme = "my-theme"
default-theme = "light"
preferred-dark-theme = "navy"
curly-quotes = true
mathjax-support = false
copy-fonts = true
google-analytics = "UA-123456-7"
additional-css = ["custom.css", "custom2.css"]
additional-js = ["custom.js"]
no-section-label = false
git-repository-url = "https://github.com/rust-lang/mdBook"
git-repository-icon = "fa-github"
site-url = "/example-book/"
cname = "myproject.rs"
input-404 = "not-found.md"
[output.html.print]
enable = true
[output.html.fold]
enable = false
level = 0
[output.html.playground]
editable = false
copy-js = true
line-numbers = false
[output.html.search]
enable = true
limit-results = 30
teaser-word-count = 30
use-boolean-and = true
boost-title = 2
boost-hierarchy = 1
boost-paragraph = 1
expand = true
heading-split-level = 3
copy-js = true
[output.html.redirect]
"/appendices/bibliography.html" = "https://rustc-dev-guide.rust-lang.org/appendix/bibliography.html"
"/other-installation-methods.html" = "../infra/other-installation-methods.html"
```
### Markdown Renderer
The Markdown renderer will run preprocessors and then output the resulting
Markdown. This is mostly useful for debugging preprocessors, especially in
conjunction with `mdbook test` to see the Markdown that `mdbook` is passing
to `rustdoc`.
The Markdown renderer is included with `mdbook` but disabled by default.
Enable it by adding an empty table to your `book.toml` as follows:
```toml
[output.markdown]
```
There are no configuration options for the Markdown renderer at this time;
only whether it is enabled or disabled.
See [the preprocessors documentation](#configuring-preprocessors) for how to
specify which preprocessors should run before the Markdown renderer.
### Custom Renderers
A custom renderer can be enabled by adding a `[output.foo]` table to your
`book.toml`. Similar to [preprocessors](#configuring-preprocessors) this will
instruct `mdbook` to pass a representation of the book to `mdbook-foo` for
rendering. See the [alternative backends] chapter for more detail.
The custom renderer has access to all the fields within its table (i.e.
anything under `[output.foo]`). mdBook checks for two common fields:
- **command:** The command to execute for this custom renderer. Defaults to
the name of the renderer with the `mdbook-` prefix (such as `mdbook-foo`).
- **optional:** If `true`, then the command will be ignored if it is not
installed, otherwise mdBook will fail with an error. Defaults to `false`.
[alternative backends]: ../for_developers/backends.md
## Environment Variables
All configuration values can be overridden from the command line by setting the
corresponding environment variable. Because many operating systems restrict
environment variables to be alphanumeric characters or `_`, the configuration
key needs to be formatted slightly differently to the normal `foo.bar.baz` form.
Variables starting with `MDBOOK_` are used for configuration. The key is created
by removing the `MDBOOK_` prefix and turning the resulting string into
`kebab-case`. Double underscores (`__`) separate nested keys, while a single
underscore (`_`) is replaced with a dash (`-`).
For example:
- `MDBOOK_foo` -> `foo`
- `MDBOOK_FOO` -> `foo`
- `MDBOOK_FOO__BAR` -> `foo.bar`
- `MDBOOK_FOO_BAR` -> `foo-bar`
- `MDBOOK_FOO_bar__baz` -> `foo-bar.baz`
So by setting the `MDBOOK_BOOK__TITLE` environment variable you can override the
book's title without needing to touch your `book.toml`.
> **Note:** To facilitate setting more complex config items, the value of an
> environment variable is first parsed as JSON, falling back to a string if the
> parse fails.
>
> This means, if you so desired, you could override all book metadata when
> building the book with something like
>
> ```shell
> $ export MDBOOK_BOOK="{'title': 'My Awesome Book', authors: ['Michael-F-Bryan']}"
> $ mdbook build
> ```
The latter case may be useful in situations where `mdbook` is invoked from a
script or CI, where it sometimes isn't possible to update the `book.toml` before
building.

View File

@@ -1,12 +0,0 @@
# Configuration
This section details the configuration options available in the ***book.toml***:
- **[General]** configuration including the `book`, `rust`, `build` sections
- **[Preprocessor]** configuration for default and custom book preprocessors
- **[Renderer]** configuration for the HTML, Markdown and custom renderers
- **[Environment Variable]** configuration for overriding configuration options in your environment
[General]: general.md
[Preprocessor]: preprocessors.md
[Renderer]: renderers.md
[Environment Variable]: environment-variables.md

View File

@@ -1,38 +0,0 @@
# Environment Variables
All configuration values can be overridden from the command line by setting the
corresponding environment variable. Because many operating systems restrict
environment variables to be alphanumeric characters or `_`, the configuration
key needs to be formatted slightly differently to the normal `foo.bar.baz` form.
Variables starting with `MDBOOK_` are used for configuration. The key is created
by removing the `MDBOOK_` prefix and turning the resulting string into
`kebab-case`. Double underscores (`__`) separate nested keys, while a single
underscore (`_`) is replaced with a dash (`-`).
For example:
- `MDBOOK_foo` -> `foo`
- `MDBOOK_FOO` -> `foo`
- `MDBOOK_FOO__BAR` -> `foo.bar`
- `MDBOOK_FOO_BAR` -> `foo-bar`
- `MDBOOK_FOO_bar__baz` -> `foo-bar.baz`
So by setting the `MDBOOK_BOOK__TITLE` environment variable you can override the
book's title without needing to touch your `book.toml`.
> **Note:** To facilitate setting more complex config items, the value of an
> environment variable is first parsed as JSON, falling back to a string if the
> parse fails.
>
> This means, if you so desired, you could override all book metadata when
> building the book with something like
>
> ```shell
> $ export MDBOOK_BOOK="{'title': 'My Awesome Book', authors: ['Michael-F-Bryan']}"
> $ mdbook build
> ```
The latter case may be useful in situations where `mdbook` is invoked from a
script or CI, where it sometimes isn't possible to update the `book.toml` before
building.

View File

@@ -1,110 +0,0 @@
# General Configuration
You can configure the parameters for your book in the ***book.toml*** file.
Here is an example of what a ***book.toml*** file might look like:
```toml
[book]
title = "Example book"
author = "John Doe"
description = "The example book covers examples."
[rust]
edition = "2018"
[build]
build-dir = "my-example-book"
create-missing = false
[preprocessor.index]
[preprocessor.links]
[output.html]
additional-css = ["custom.css"]
[output.html.search]
limit-results = 15
```
## Supported configuration options
It is important to note that **any** relative path specified in the
configuration will always be taken relative from the root of the book where the
configuration file is located.
### General metadata
This is general information about your book.
- **title:** The title of the book
- **authors:** The author(s) of the book
- **description:** A description for the book, which is added as meta
information in the html `<head>` of each page
- **src:** By default, the source directory is found in the directory named
`src` directly under the root folder. But this is configurable with the `src`
key in the configuration file.
- **language:** The main language of the book, which is used as a language attribute `<html lang="en">` for example.
**book.toml**
```toml
[book]
title = "Example book"
authors = ["John Doe", "Jane Doe"]
description = "The example book covers examples."
src = "my-src" # the source files will be found in `root/my-src` instead of `root/src`
language = "en"
```
### Rust options
Options for the Rust language, relevant to running tests and playground
integration.
```toml
[rust]
edition = "2015" # the default edition for code blocks
```
- **edition**: Rust edition to use by default for the code snippets. Default
is "2015". Individual code blocks can be controlled with the `edition2015`,
`edition2018` or `edition2021` annotations, such as:
~~~text
```rust,edition2015
// This only works in 2015.
let try = true;
```
~~~
### Build options
This controls the build process of your book.
```toml
[build]
build-dir = "book" # the directory where the output is placed
create-missing = true # whether or not to create missing pages
use-default-preprocessors = true # use the default preprocessors
```
- **build-dir:** The directory to put the rendered book in. By default this is
`book/` in the book's root directory.
This can overridden with the `--dest-dir` CLI option.
- **create-missing:** By default, any missing files specified in `SUMMARY.md`
will be created when the book is built (i.e. `create-missing = true`). If this
is `false` then the build process will instead exit with an error if any files
do not exist.
- **use-default-preprocessors:** Disable the default preprocessors of (`links` &
`index`) by setting this option to `false`.
If you have the same, and/or other preprocessors declared via their table
of configuration, they will run instead.
- For clarity, with no preprocessor configuration, the default `links` and
`index` will run.
- Setting `use-default-preprocessors = false` will disable these
default preprocessors from running.
- Adding `[preprocessor.links]`, for example, will ensure, regardless of
`use-default-preprocessors` that `links` it will run.

View File

@@ -1,87 +0,0 @@
# Configuring Preprocessors
Preprocessors are extensions that can modify the raw Markdown source before it gets sent to the renderer.
The following preprocessors are built-in and included by default:
- `links`: Expands the `{{ #playground }}`, `{{ #include }}`, and `{{ #rustdoc_include }}` handlebars
helpers in a chapter to include the contents of a file.
See [Including files] for more.
- `index`: Convert all chapter files named `README.md` into `index.md`. That is
to say, all `README.md` would be rendered to an index file `index.html` in the
rendered book.
The built-in preprocessors can be disabled with the [`build.use-default-preprocessors`] config option.
The community has developed several preprocessors.
See the [Third Party Plugins] wiki page for a list of available preprocessors.
For information on how to create a new preprocessor, see the [Preprocessors for Developers] chapter.
[Including files]: ../mdbook.md#including-files
[`build.use-default-preprocessors`]: general.md#build-options
[Third Party Plugins]: https://github.com/rust-lang/mdBook/wiki/Third-party-plugins
[Preprocessors for Developers]: ../../for_developers/preprocessors.md
## Custom Preprocessor Configuration
Preprocessors can be added by including a `preprocessor` table in `book.toml` with the name of the preprocessor.
For example, if you have a preprocessor called `mdbook-example`, then you can include it with:
```toml
[preprocessor.example]
```
With this table, mdBook will execute the `mdbook-example` preprocessor.
This table can include additional key-value pairs that are specific to the preprocessor.
For example, if our example prepocessor needed some extra configuration options:
```toml
[preprocessor.example]
some-extra-feature = true
```
## Locking a Preprocessor dependency to a renderer
You can explicitly specify that a preprocessor should run for a renderer by
binding the two together.
```toml
[preprocessor.example]
renderers = ["html"] # example preprocessor only runs with the HTML renderer
```
## Provide Your Own Command
By default when you add a `[preprocessor.foo]` table to your `book.toml` file,
`mdbook` will try to invoke the `mdbook-foo` executable. If you want to use a
different program name or pass in command-line arguments, this behaviour can
be overridden by adding a `command` field.
```toml
[preprocessor.random]
command = "python random.py"
```
## Require A Certain Order
The order in which preprocessors are run can be controlled with the `before` and `after` fields.
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:
```toml
[preprocessor.linenos]
after = [ "links" ]
```
or
```toml
[preprocessor.links]
before = [ "linenos" ]
```
It would also be possible, though redundant, to specify both of the above in the same config file.
Preprocessors having the same priority specified through `before` and `after` are sorted by name.
Any infinite loops will be detected and produce an error.

View File

@@ -1,299 +0,0 @@
# Configuring Renderers
Renderers (also called "backends") are responsible for creating the output of the book.
The following backends are built-in:
* [`html`](#html-renderer-options) — This renders the book to HTML.
This is enabled by default if no other `[output]` tables are defined in `book.toml`.
* [`markdown`](#markdown-renderer) — This outputs the book as markdown after running the preprocessors.
This is useful for debugging preprocessors.
The community has developed several backends.
See the [Third Party Plugins] wiki page for a list of available backends.
For information on how to create a new backend, see the [Backends for Developers] chapter.
[Third Party Plugins]: https://github.com/rust-lang/mdBook/wiki/Third-party-plugins
[Backends for Developers]: ../../for_developers/backends.md
## Output tables
Backends can be added by including a `output` table in `book.toml` with the name of the backend.
For example, if you have a backend called `mdbook-wordcount`, then you can include it with:
```toml
[output.wordcount]
```
With this table, mdBook will execute the `mdbook-wordcount` backend.
This table can include additional key-value pairs that are specific to the backend.
For example, if our example backend needed some extra configuration options:
```toml
[output.wordcount]
ignores = ["Example Chapter"]
```
If you define any `[output]` tables, then the `html` backend is not enabled by default.
If you want to keep the `html` backend running, then just include it in the `book.toml` file.
For example:
```toml
[book]
title = "My Awesome Book"
[output.wordcount]
[output.html]
```
If more than one `output` table is included, this changes the behavior for the layout of the output directory.
If there is only one backend, then it places its output directly in the `book` directory (see [`build.build-dir`] to override this location).
If there is more than one backend, then each backend is placed in a separate directory underneath `book`.
For example, the above would have directories `book/html` and `book/wordcount`.
[`build.build-dir`]: general.md#build-options
### Custom backend commands
By default when you add an `[output.foo]` table to your `book.toml` file,
`mdbook` will try to invoke the `mdbook-foo` executable.
If you want to use a different program name or pass in command-line arguments,
this behaviour can be overridden by adding a `command` field.
```toml
[output.random]
command = "python random.py"
```
### Optional backends
If you enable a backend that isn't installed, the default behavior is to throw an error.
This behavior can be changed by marking the backend as optional:
```toml
[output.wordcount]
optional = true
```
This demotes the error to a warning.
## HTML renderer options
The HTML renderer has a variety of options detailed below.
They should be specified in the `[output.html]` table of the `book.toml` file.
```toml
# Example book.toml file with all output options.
[book]
title = "Example book"
authors = ["John Doe", "Jane Doe"]
description = "The example book covers examples."
[output.html]
theme = "my-theme"
default-theme = "light"
preferred-dark-theme = "navy"
curly-quotes = true
mathjax-support = false
copy-fonts = true
additional-css = ["custom.css", "custom2.css"]
additional-js = ["custom.js"]
no-section-label = false
git-repository-url = "https://github.com/rust-lang/mdBook"
git-repository-icon = "fa-github"
edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
site-url = "/example-book/"
cname = "myproject.rs"
input-404 = "not-found.md"
```
The following configuration options are available:
- **theme:** mdBook comes with a default theme and all the resource files needed
for it. But if this option is set, mdBook will selectively overwrite the theme
files with the ones found in the specified folder.
- **default-theme:** The theme color scheme to select by default in the
'Change Theme' dropdown. Defaults to `light`.
- **preferred-dark-theme:** The default dark theme. This theme will be used if
the browser requests the dark version of the site via the
['prefers-color-scheme'](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)
CSS media query. Defaults to `navy`.
- **curly-quotes:** Convert straight quotes to curly quotes, except for those
that occur in code blocks and code spans. Defaults to `false`.
- **mathjax-support:** Adds support for [MathJax](../mathjax.md). Defaults to
`false`.
- **copy-fonts:** Copies fonts.css and respective font files to the output directory and use them in the default theme. Defaults to `true`.
- **google-analytics:** This field has been deprecated and will be removed in a future release.
Use the `theme/head.hbs` file to add the appropriate Google Analytics code instead.
- **additional-css:** If you need to slightly change the appearance of your book
without overwriting the whole style, you can specify a set of stylesheets that
will be loaded after the default ones where you can surgically change the
style.
- **additional-js:** If you need to add some behaviour to your book without
removing the current behaviour, you can specify a set of JavaScript files that
will be loaded alongside the default one.
- **no-section-label:** mdBook by defaults adds numeric section labels in the table of
contents column. For example, "1.", "2.1". Set this option to true to disable
those labels. Defaults to `false`.
- **git-repository-url:** A url to the git repository for the book. If provided
an icon link will be output in the menu bar of the book.
- **git-repository-icon:** The FontAwesome icon class to use for the git
repository link. Defaults to `fa-github` which looks like <i class="fa fa-github"></i>.
If you are not using GitHub, another option to consider is `fa-code-fork` which looks like <i class="fa fa-code-fork"></i>.
- **edit-url-template:** Edit url template, when provided shows a
"Suggest an edit" button (which looks like <i class="fa fa-edit"></i>) for directly jumping to editing the currently
viewed page. For e.g. GitHub projects set this to
`https://github.com/<owner>/<repo>/edit/master/{path}` or for
Bitbucket projects set it to
`https://bitbucket.org/<owner>/<repo>/src/master/{path}?mode=edit`
where {path} will be replaced with the full path of the file in the
repository.
- **input-404:** The name of the markdown file used for missing files.
The corresponding output file will be the same, with the extension replaced with `html`.
Defaults to `404.md`.
- **site-url:** The url where the book will be hosted. This is required to ensure
navigation links and script/css imports in the 404 file work correctly, even when accessing
urls in subdirectories. Defaults to `/`.
- **cname:** The DNS subdomain or apex domain at which your book will be hosted.
This string will be written to a file named CNAME in the root of your site, as
required by GitHub Pages (see [*Managing a custom domain for your GitHub Pages
site*][custom domain]).
[custom domain]: https://docs.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site
### `[output.html.print]`
The `[output.html.print]` table provides options for controlling the printable output.
By default, mdBook will include an icon on the top right of the book (which looks like <i class="fa fa-print"></i>) that will print the book as a single page.
```toml
[output.html.print]
enable = true # include support for printable output
page-break = true # insert page-break after each chapter
```
- **enable:** Enable print support. When `false`, all print support will not be
rendered. Defaults to `true`.
- **page-break** Insert page breaks between chapters. Defaults to `true`.
### `[output.html.fold]`
The `[output.html.fold]` table provides options for controlling folding of the chapter listing in the navigation sidebar.
```toml
[output.html.fold]
enable = false # whether or not to enable section folding
level = 0 # the depth to start folding
```
- **enable:** Enable section-folding. When off, all folds are open.
Defaults to `false`.
- **level:** The higher the more folded regions are open. When level is 0, all
folds are closed. Defaults to `0`.
### `[output.html.playground]`
The `[output.html.playground]` table provides options for controlling Rust sample code blocks, and their integration with the [Rust Playground].
[Rust Playground]: https://play.rust-lang.org/
```toml
[output.html.playground]
editable = false # allows editing the source code
copyable = true # include the copy button for copying code snippets
copy-js = true # includes the JavaScript for the code editor
line-numbers = false # displays line numbers for editable code
runnable = true # displays a run button for rust code
```
- **editable:** Allow editing the source code. Defaults to `false`.
- **copyable:** Display the copy button on code snippets. Defaults to `true`.
- **copy-js:** Copy JavaScript files for the editor to the output directory.
Defaults to `true`.
- **line-numbers** Display line numbers on editable sections of code. Requires both `editable` and `copy-js` to be `true`. Defaults to `false`.
- **runnable** Displays a run button for rust code snippets. Changing this to `false` will disable the run in playground feature globally. Defaults to `true`.
[Ace]: https://ace.c9.io/
### `[output.html.search]`
The `[output.html.search]` table provides options for controlling the built-in text [search].
mdBook must be compiled with the `search` feature enabled (on by default).
[search]: ../../guide/reading.md#search
```toml
[output.html.search]
enable = true # enables the search feature
limit-results = 30 # maximum number of search results
teaser-word-count = 30 # number of words used for a search result teaser
use-boolean-and = true # multiple search terms must all match
boost-title = 2 # ranking boost factor for matches in headers
boost-hierarchy = 1 # ranking boost factor for matches in page names
boost-paragraph = 1 # ranking boost factor for matches in text
expand = true # partial words will match longer terms
heading-split-level = 3 # link results to heading levels
copy-js = true # include Javascript code for search
```
- **enable:** Enables the search feature. Defaults to `true`.
- **limit-results:** The maximum number of search results. Defaults to `30`.
- **teaser-word-count:** The number of words used for a search result teaser.
Defaults to `30`.
- **use-boolean-and:** Define the logical link between multiple search words. If
true, all search words must appear in each result. Defaults to `false`.
- **boost-title:** Boost factor for the search result score if a search word
appears in the header. Defaults to `2`.
- **boost-hierarchy:** Boost factor for the search result score if a search word
appears in the hierarchy. The hierarchy contains all titles of the parent
documents and all parent headings. Defaults to `1`.
- **boost-paragraph:** Boost factor for the search result score if a search word
appears in the text. Defaults to `1`.
- **expand:** True if search should match longer results e.g. search `micro`
should match `microwave`. Defaults to `true`.
- **heading-split-level:** Search results will link to a section of the document
which contains the result. Documents are split into sections by headings this
level or less. Defaults to `3`. (`### This is a level 3 heading`)
- **copy-js:** Copy JavaScript files for the search implementation to the output
directory. Defaults to `true`.
### `[output.html.redirect]`
The `[output.html.redirect]` table provides a way to add redirects.
This is useful when you move, rename, or remove a page to ensure that links to the old URL will go to the new location.
```toml
[output.html.redirect]
"/appendices/bibliography.html" = "https://rustc-dev-guide.rust-lang.org/appendix/bibliography.html"
"/other-installation-methods.html" = "../infra/other-installation-methods.html"
```
The table contains key-value pairs where the key is where the redirect file needs to be created, as an absolute path from the build directory, (e.g. `/appendices/bibliography.html`).
The value can be any valid URI the browser should navigate to (e.g. `https://rust-lang.org/`, `/overview.html`, or `../bibliography.html`).
This will generate an HTML page which will automatically redirect to the given location.
Note that the source location does not support `#` anchor redirects.
## Markdown Renderer
The Markdown renderer will run preprocessors and then output the resulting
Markdown. This is mostly useful for debugging preprocessors, especially in
conjunction with `mdbook test` to see the Markdown that `mdbook` is passing
to `rustdoc`.
The Markdown renderer is included with `mdbook` but disabled by default.
Enable it by adding an empty table to your `book.toml` as follows:
```toml
[output.markdown]
```
There are no configuration options for the Markdown renderer at this time;
only whether it is enabled or disabled.
See [the preprocessors documentation](preprocessors.md) for how to
specify which preprocessors should run before the Markdown renderer.

View File

@@ -1,6 +1,6 @@
fn main() {
println!("Hello World!");
#
# // You can even hide lines! :D
# println!("I am hidden! Expand the code snippet to see me");
# // You can even hide lines! :D
# println!("I am hidden! Expand the code snippet to see me");
}

View File

@@ -1 +0,0 @@
<svg height="144" width="144" xmlns="http://www.w3.org/2000/svg"><path d="m71.05 23.68c-26.06 0-47.27 21.22-47.27 47.27s21.22 47.27 47.27 47.27 47.27-21.22 47.27-47.27-21.22-47.27-47.27-47.27zm-.07 4.2a3.1 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11zm7.12 5.12a38.27 38.27 0 0 1 26.2 18.66l-3.67 8.28c-.63 1.43.02 3.11 1.44 3.75l7.06 3.13a38.27 38.27 0 0 1 .08 6.64h-3.93c-.39 0-.55.26-.55.64v1.8c0 4.24-2.39 5.17-4.49 5.4-2 .23-4.21-.84-4.49-2.06-1.18-6.63-3.14-8.04-6.24-10.49 3.85-2.44 7.85-6.05 7.85-10.87 0-5.21-3.57-8.49-6-10.1-3.42-2.25-7.2-2.7-8.22-2.7h-40.6a38.27 38.27 0 0 1 21.41-12.08l4.79 5.02c1.08 1.13 2.87 1.18 4 .09zm-44.2 23.02a3.11 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11zm74.15.14a3.11 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11zm-68.29.5h5.42v24.44h-10.94a38.27 38.27 0 0 1 -1.24-14.61l6.7-2.98c1.43-.64 2.08-2.31 1.44-3.74zm22.62.26h12.91c.67 0 4.71.77 4.71 3.8 0 2.51-3.1 3.41-5.65 3.41h-11.98zm0 17.56h9.89c.9 0 4.83.26 6.08 5.28.39 1.54 1.26 6.56 1.85 8.17.59 1.8 2.98 5.4 5.53 5.4h16.14a38.27 38.27 0 0 1 -3.54 4.1l-6.57-1.41c-1.53-.33-3.04.65-3.37 2.18l-1.56 7.28a38.27 38.27 0 0 1 -31.91-.15l-1.56-7.28c-.33-1.53-1.83-2.51-3.36-2.18l-6.43 1.38a38.27 38.27 0 0 1 -3.32-3.92h31.27c.35 0 .59-.06.59-.39v-11.06c0-.32-.24-.39-.59-.39h-9.15zm-14.43 25.33a3.11 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11zm46.05.14a3.11 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11z"/><path d="m115.68 70.95a44.63 44.63 0 0 1 -44.63 44.63 44.63 44.63 0 0 1 -44.63-44.63 44.63 44.63 0 0 1 44.63-44.63 44.63 44.63 0 0 1 44.63 44.63zm-.84-4.31 6.96 4.31-6.96 4.31 5.98 5.59-7.66 2.87 4.78 6.65-8.09 1.32 3.4 7.46-8.19-.29 1.88 7.98-7.98-1.88.29 8.19-7.46-3.4-1.32 8.09-6.65-4.78-2.87 7.66-5.59-5.98-4.31 6.96-4.31-6.96-5.59 5.98-2.87-7.66-6.65 4.78-1.32-8.09-7.46 3.4.29-8.19-7.98 1.88 1.88-7.98-8.19.29 3.4-7.46-8.09-1.32 4.78-6.65-7.66-2.87 5.98-5.59-6.96-4.31 6.96-4.31-5.98-5.59 7.66-2.87-4.78-6.65 8.09-1.32-3.4-7.46 8.19.29-1.88-7.98 7.98 1.88-.29-8.19 7.46 3.4 1.32-8.09 6.65 4.78 2.87-7.66 5.59 5.98 4.31-6.96 4.31 6.96 5.59-5.98 2.87 7.66 6.65-4.78 1.32 8.09 7.46-3.4-.29 8.19 7.98-1.88-1.88 7.98 8.19-.29-3.4 7.46 8.09 1.32-4.78 6.65 7.66 2.87z" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/></svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,222 +0,0 @@
# Markdown
mdBook's [parser](https://github.com/raphlinus/pulldown-cmark) adheres to the [CommonMark](https://commonmark.org/) specification with some extensions described below.
You can take a quick [tutorial](https://commonmark.org/help/tutorial/),
or [try out](https://spec.commonmark.org/dingus/) CommonMark in real time. A complete Markdown overview is out of scope for
this documentation, but below is a high level overview of some of the basics. For a more in-depth experience, check out the
[Markdown Guide](https://www.markdownguide.org).
## Text and Paragraphs
Text is rendered relatively predictably:
```markdown
Here is a line of text.
This is a new line.
```
Will look like you might expect:
Here is a line of text.
This is a new line.
## Headings
Headings use the `#` marker and should be on a line by themselves. More `#` mean smaller headings:
```markdown
### A heading
Some text.
#### A smaller heading
More text.
```
### A heading
Some text.
#### A smaller heading
More text.
## Lists
Lists can be unordered or ordered. Ordered lists will order automatically:
```markdown
* milk
* eggs
* butter
1. carrots
1. celery
1. radishes
```
* milk
* eggs
* butter
1. carrots
1. celery
1. radishes
## Links
Linking to a URL or local file is easy:
```markdown
Use [mdBook](https://github.com/rust-lang/mdBook).
Read about [mdBook](mdBook.md).
A bare url: <https://www.rust-lang.org>.
```
Use [mdBook](https://github.com/rust-lang/mdBook).
Read about [mdBook](mdBook.md).
A bare url: <https://www.rust-lang.org>.
----
Relative links that end with `.md` will be converted to the `.html` extension.
It is recommended to use `.md` links when possible.
This is useful when viewing the Markdown file outside of mdBook, for example on GitHub or GitLab which render Markdown automatically.
Links to `README.md` will be converted to `index.html`.
This is done since some services like GitHub render README files automatically, but web servers typically expect the root file to be called `index.html`.
You can link to individual headings with `#` fragments.
For example, `mdbook.md#text-and-paragraphs` would link to the [Text and Paragraphs](#text-and-paragraphs) section above.
The ID is created by transforming the heading such as converting to lowercase and replacing spaces with dashes.
You can click on any heading and look at the URL in your browser to see what the fragment looks like.
## Images
Including images is simply a matter of including a link to them, much like in the _Links_ section above. The following markdown
includes the Rust logo SVG image found in the `images` directory at the same level as this file:
```markdown
![The Rust Logo](images/rust-logo-blk.svg)
```
Produces the following HTML when built with mdBook:
```html
<p><img src="images/rust-logo-blk.svg" alt="The Rust Logo" /></p>
```
Which, of course displays the image like so:
![The Rust Logo](images/rust-logo-blk.svg)
## Extensions
mdBook has several extensions beyond the standard CommonMark specification.
### Strikethrough
Text may be rendered with a horizontal line through the center by wrapping the
text with two tilde characters on each side:
```text
An example of ~~strikethrough text~~.
```
This example will render as:
> An example of ~~strikethrough text~~.
This follows the [GitHub Strikethrough extension][strikethrough].
### Footnotes
A footnote generates a small numbered link in the text which when clicked
takes the reader to the footnote text at the bottom of the item. The footnote
label is written similarly to a link reference with a caret at the front. The
footnote text is written like a link reference definition, with the text
following the label. Example:
```text
This is an example of a footnote[^note].
[^note]: This text is the contents of the footnote, which will be rendered
towards the bottom.
```
This example will render as:
> This is an example of a footnote[^note].
>
> [^note]: This text is the contents of the footnote, which will be rendered
> towards the bottom.
The footnotes are automatically numbered based on the order the footnotes are
written.
### Tables
Tables can be written using pipes and dashes to draw the rows and columns of
the table. These will be translated to HTML table matching the shape. Example:
```text
| Header1 | Header2 |
|---------|---------|
| abc | def |
```
This example will render similarly to this:
| Header1 | Header2 |
|---------|---------|
| abc | def |
See the specification for the [GitHub Tables extension][tables] for more
details on the exact syntax supported.
### Task lists
Task lists can be used as a checklist of items that have been completed.
Example:
```md
- [x] Complete task
- [ ] Incomplete task
```
This will render as:
> - [x] Complete task
> - [ ] Incomplete task
See the specification for the [task list extension] for more details.
### Smart punctuation
Some ASCII punctuation sequences will be automatically turned into fancy Unicode
characters:
| ASCII sequence | Unicode |
|----------------|---------|
| `--` | |
| `---` | — |
| `...` | … |
| `"` | “ or ”, depending on context |
| `'` | or , depending on context |
So, no need to manually enter those Unicode characters!
This feature is disabled by default.
To enable it, see the [`output.html.curly-quotes`] config option.
[strikethrough]: https://github.github.com/gfm/#strikethrough-extension-
[tables]: https://github.github.com/gfm/#tables-extension-
[task list extension]: https://github.github.com/gfm/#task-list-items-extension-
[`output.html.curly-quotes`]: configuration/renderers.md#html-renderer-options

View File

@@ -4,7 +4,6 @@
There is a feature in mdBook that lets you hide code lines by prepending them
with a `#` [like you would with Rustdoc][rustdoc-hide].
This currently only works with Rust language code blocks.
[rustdoc-hide]: https://doc.rust-lang.org/stable/rustdoc/documentation-tests.html#hiding-portions-of-the-example
@@ -22,73 +21,12 @@ Will render as
```rust
# fn main() {
let x = 5;
let y = 6;
let y = 7;
println!("{}", x + y);
# }
```
The code block has an eyeball icon (<i class="fa fa-eye"></i>) which will toggle the visibility of the hidden lines.
## Rust Playground
Rust language code blocks will automatically get a play button (<i class="fa fa-play"></i>) which will execute the code and display the output just below the code block.
This works by sending the code to the [Rust Playground].
```rust
println!("Hello, World!");
```
If there is no `main` function, then the code is automatically wrapped inside one.
If you wish to disable the play button for a code block, you can include the `noplayground` option on the code block like this:
~~~markdown
```rust,noplayground
let mut name = String::new();
std::io::stdin().read_line(&mut name).expect("failed to read line");
println!("Hello {}!", name);
```
~~~
Or, if you wish to disable the play button for all code blocks in your book, you can write the config to the `book.toml` like this.
```toml
[output.html.playground]
runnable = false
```
## Rust code block attributes
Additional attributes can be included in Rust code blocks with comma, space, or tab-separated terms just after the language term. For example:
~~~markdown
```rust,ignore
# This example won't be tested.
panic!("oops!");
```
~~~
These are particularly important when using [`mdbook test`] to test Rust examples.
These use the same attributes as [rustdoc attributes], with a few additions:
* `editable` — Enables the [editor].
* `noplayground` — Removes the play button, but will still be tested.
* `mdbook-runnable` — Forces the play button to be displayed.
This is intended to be combined with the `ignore` attribute for examples that should not be tested, but you want to allow the reader to run.
* `ignore` — Will not be tested and no play button is shown, but it is still highlighted as Rust syntax.
* `should_panic` — When executed, it should produce a panic.
* `no_run` — The code is compiled when tested, but it is not run.
The play button is also not shown.
* `compile_fail` — The code should fail to compile.
* `edition2015`, `edition2018`, `edition2021` — Forces the use of a specific Rust edition.
See [`rust.edition`] to set this globally.
[`mdbook test`]: ../cli/test.md
[rustdoc attributes]: https://doc.rust-lang.org/rustdoc/documentation-tests.html#attributes
[editor]: theme/editor.md
[`rust.edition`]: configuration/general.md#rust-options
## Including files
With the following syntax, you can include files into your book:
@@ -102,7 +40,7 @@ The path to the file has to be relative from the current source file.
mdBook will interpret included files as Markdown. Since the include command
is usually used for inserting code snippets and examples, you will often
wrap the command with ```` ``` ```` to display the file contents without
interpreting them.
interpretting them.
````hbs
```
@@ -111,7 +49,7 @@ interpreting them.
````
## Including portions of a file
Often you only need a specific part of the file, e.g. relevant lines for an
Often you only need a specific part of the file e.g. relevant lines for an
example. We support four different modes of partial includes:
```hbs
@@ -130,8 +68,8 @@ consisting of lines 2 to 10.
To avoid breaking your book when modifying included files, you can also
include a specific section using anchors instead of line numbers.
An anchor is a pair of matching lines. The line beginning an anchor must
match the regex `ANCHOR:\s*[\w_-]+` and similarly the ending line must match
the regex `ANCHOR_END:\s*[\w_-]+`. This allows you to put anchors in
match the regex "ANCHOR:\s*[\w_-]+" and similarly the ending line must match
the regex "ANCHOR_END:\s*[\w_-]+". This allows you to put anchors in
any kind of commented line.
Consider the following file to include:
@@ -218,7 +156,7 @@ To call the `add_one` function, we pass it an `i32` and bind the returned value
#
# fn add_one(num: i32) -> i32 {
# num + 1
# }
#}
```
````
@@ -232,7 +170,7 @@ That is, it looks like this (click the "expand" icon to see the rest of the file
#
# fn add_one(num: i32) -> i32 {
# num + 1
# }
#}
```
## Inserting runnable Rust files
@@ -253,24 +191,4 @@ Here is what a rendered code snippet looks like:
{{#playground example.rs}}
Any additional values passed after the filename will be included as attributes of the code block.
For example `\{{#playground example.rs editable}}` will create the code block like the following:
~~~markdown
```rust,editable
# Contents of example.rs here.
```
~~~
And the `editable` attribute will enable the [editor] as described at [Rust code block attributes](#rust-code-block-attributes).
[Rust Playground]: https://play.rust-lang.org/
## Controlling page \<title\>
A chapter can set a \<title\> that is different from its entry in the table of
contents (sidebar) by including a `\{{#title ...}}` near the top of the page.
```hbs
\{{#title My Title}}
```

View File

@@ -4,96 +4,63 @@ The summary file is used by mdBook to know what chapters to include, in what
order they should appear, what their hierarchy is and where the source files
are. Without this file, there is no book.
This markdown file must be named `SUMMARY.md`. Its formatting
is very strict and must follow the structure outlined below to allow for easy
parsing. Any element not specified below, be it formatting or textual, is likely
to be ignored at best, or may cause an error when attempting to build the book.
Even though `SUMMARY.md` is a markdown file, the formatting is very strict to
allow for easy parsing. Let's see how you should format your `SUMMARY.md` file.
### Structure
#### Structure
1. ***Title*** - While optional, it's common practice to begin with a title, generally <code
class="language-markdown"># Summary</code>. This is ignored by the parser however, and
can be omitted.
```markdown
# Summary
```
1. ***Title*** It's common practice to begin with a title, generally <code
class="language-markdown"># Summary</code>. But it is not mandatory, the
parser just ignores it. So you can too if you feel like it.
1. ***Prefix Chapter*** - Before the main numbered chapters, prefix chapters can be added
that will not be numbered. This is useful for forewords,
introductions, etc. There are, however, some constraints. Prefix chapters cannot be
nested; they should all be on the root level. And you cannot add
2. ***Prefix Chapter*** Before the main numbered chapters you can add a couple
of elements that will not be numbered. This is useful for forewords,
introductions, etc. There are however some constraints. You can not nest
prefix chapters, they should all be on the root level. And you can not add
prefix chapters once you have added numbered chapters.
```markdown
[A Prefix Chapter](relative/path/to/markdown.md)
- [First Chapter](relative/path/to/markdown2.md)
[Title of prefix element](relative/path/to/markdown.md)
```
1. ***Part Title*** - Headers can be used as a title for the following numbered
3. ***Part Title:*** Headers can be used as a title for the following numbered
chapters. This can be used to logically separate different sections
of the book. The title is rendered as unclickable text.
of book. The title is rendered as unclickable text.
Titles are optional, and the numbered chapters can be broken into as many
parts as desired.
```markdown
# My Part Title
- [First Chapter](relative/path/to/markdown.md)
```
1. ***Numbered Chapter*** - Numbered chapters outline the main content of the book
and can be nested, resulting in a nice hierarchy
(chapters, sub-chapters, etc.).
4. ***Numbered Chapter*** Numbered chapters are the main content of the book,
they will be numbered and can be nested, resulting in a nice hierarchy
(chapters, sub-chapters, etc.)
```markdown
# Title of Part
- [First Chapter](relative/path/to/markdown.md)
- [Second Chapter](relative/path/to/markdown2.md)
- [Sub Chapter](relative/path/to/markdown3.md)
- [Title of the Chapter](relative/path/to/markdown.md)
# Title of Another Part
- [Another Chapter](relative/path/to/markdown4.md)
- [More Chapters](relative/path/to/markdown2.md)
```
Numbered chapters can be denoted with either `-` or `*` (do not mix delimiters).
1. ***Suffix Chapter*** - Like prefix chapters, suffix chapters are unnumbered, but they come after
numbered chapters.
```markdown
- [Last Chapter](relative/path/to/markdown.md)
You can either use `-` or `*` to indicate a numbered chapter.
[Title of Suffix Chapter](relative/path/to/markdown2.md)
```
5. ***Suffix Chapter*** After the numbered chapters you can add a couple of
non-numbered chapters. They are the same as prefix chapters but come after
the numbered chapters instead of before.
1. ***Draft chapters*** - Draft chapters are chapters without a file and thus content.
The purpose of a draft chapter is to signal future chapters still to be written.
Or when still laying out the structure of the book to avoid creating the files
while you are still changing the structure of the book a lot.
Draft chapters will be rendered in the HTML renderer as disabled links in the table
of contents, as you can see for the next chapter in the table of contents on the left.
Draft chapters are written like normal chapters but without writing the path to the file.
```markdown
- [Draft Chapter]()
```
All other elements are unsupported and will be ignored at best or result in an
error.
1. ***Separators*** - Separators can be added before, in between, and after any other element. They result
in an HTML rendered line in the built table of contents. A separator is
a line containing exclusively dashes and at least three of them: `---`.
```markdown
# My Part Title
[A Prefix Chapter](relative/path/to/markdown.md)
#### Other elements
---
- [First Chapter](relative/path/to/markdown2.md)
```
### Example
Below is the markdown source for the `SUMMARY.md` for this guide, with the resulting table
of contents as rendered to the left.
```markdown
{{#include ../SUMMARY.md}}
```
- ***Separators*** In between chapters you can add a separator. In the HTML renderer
this will result in a line being rendered in the table of contents. A separator is
a line containing exclusively dashes and at least three of them: `---`.
- ***Draft chapters*** Draft chapters are chapters without a file and thus content.
The purpose of a draft chapter is to signal future chapters still to be written.
Or when still laying out the structure of the book to avoid creating the files
while you are still changing the structure of the book a lot.
Draft chapters will be rendered in the HTML renderer as disabled links in the table
of contents, as you can see for the next chapter in the table of contents on the left.
Draft chapters are written like normal chapters but without writing the path to the file
```markdown
- [Draft chapter]()
```

View File

@@ -14,11 +14,9 @@ Here are the files you can override:
- **_index.hbs_** is the handlebars template.
- **_head.hbs_** is appended to the HTML `<head>` section.
- **_header.hbs_** content is appended on top of every book page.
- **_css/_** contains the CSS files for styling the book.
- **_css/chrome.css_** is for UI elements.
- **_css/general.css_** is the base styles.
- **_css/print.css_** is the style for printer output.
- **_css/variables.css_** contains variables used in other CSS files.
- **_book.css_** is the style used in the output. If you want to change the
design of your book, this is probably the file you want to modify. Sometimes
in conjunction with `index.hbs` when you want to radically change the layout.
- **_book.js_** is mostly used to add client side functionality, like hiding /
un-hiding the sidebar, changing the theme, ...
- **_highlight.js_** is the JavaScript that is used to highlight code snippets,
@@ -35,16 +33,12 @@ built-in ones, they will not get updated with new fixes / features.
**Note:** When you override a file, it is possible that you break some
functionality. Therefore I recommend to use the file from the default theme as
template and only add / modify what you need. You can copy the default theme
into your source directory automatically by using `mdbook init --theme` and just
into your source directory automatically by using `mdbook init --theme` just
remove the files you don't want to override.
`mdbook init --theme` will not create every file listed above.
Some files, such as `head.hbs`, do not have built-in equivalents.
Just create the file if you need it.
If you completely replace all built-in themes, be sure to also set
[`output.html.preferred-dark-theme`] in the config, which defaults to the
built-in `navy` theme.
[`output.html.preferred-dark-theme`]: ../configuration/renderers.md#html-renderer-options
[`output.html.preferred-dark-theme`]: ../config.md#html-renderer-options
[newer browsers]: https://caniuse.com/#feat=link-icon-svg

View File

@@ -12,14 +12,12 @@ editable = true
To make a specific block available for editing, the attribute `editable` needs
to be added to it:
~~~markdown
```rust,editable
<pre><code class="language-markdown">```rust,editable
fn main() {
let number = 5;
print!("{}", number);
}
```
~~~
```</code></pre>
The above will result in this editable playground:
@@ -35,7 +33,7 @@ Note the new `Undo Changes` button in the editable playgrounds.
## Customizing the Editor
By default, the editor is the [Ace](https://ace.c9.io/) editor, but, if desired,
the functionality may be overridden by providing a different folder:
the functionality may be overriden by providing a different folder:
```toml
[output.html.playground]
@@ -44,5 +42,5 @@ editor = "/path/to/editor"
```
Note that for the editor changes to function correctly, the `book.js` inside of
the `theme` folder will need to be overridden as it has some couplings with the
the `theme` folder will need to be overriden as it has some couplings with the
default Ace editor.

View File

@@ -19,7 +19,7 @@ Here is a list of the properties that are exposed:
- ***language*** Language of the book in the form `en`, as specified in `book.toml` (if not specified, defaults to `en`). To use in <code
class="language-html">\<html lang="{{ language }}"></code> for example.
- ***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`.
- ***title*** Title used for the current page. This is identical to `{{ book_title }} - {{ chapter_title }}` unless `book_title` is not set in which case it just defaults to the `chapter_title`.
- ***book_title*** Title of the book, as specified in `book.toml`
- ***chapter_title*** Title of the current chapter, as listed in `SUMMARY.md`

View File

@@ -112,10 +112,10 @@ everyone can benefit from it.**
## Improve default theme
If you think the default theme doesn't look quite right for a specific language,
or could be improved, feel free to [submit a new
or could be improved. Feel free to [submit a new
issue](https://github.com/rust-lang/mdBook/issues) explaining what you
have in mind and I will take a look at it.
You could also create a pull-request with the proposed improvements.
Overall the theme should be light and sober, without too many flashy colors.
Overall the theme should be light and sober, without to many flashy colors.

View File

@@ -1,7 +0,0 @@
# User Guide
This user guide provides an introduction to basic concepts of using mdBook.
- [Installation](installation.md)
- [Reading Books](reading.md)
- [Creating a Book](creating.md)

View File

@@ -1,109 +0,0 @@
# Creating a Book
Once you have the `mdbook` CLI tool installed, you can use it to create and render a book.
## Initializing a book
The `mdbook init` command will create a new directory containing an empty book for you to get started.
Give it the name of the directory that you want to create:
```sh
mdbook init my-first-book
```
It will ask a few questions before generating the book.
After answering the questions, you can change the current directory into the new book:
```sh
cd my-first-book
```
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:
```sh
mdbook serve --open
```
The `--open` option will open your default web browser to view your new book.
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.
Check out the [CLI Guide](../cli/index.html) for more information about other `mdbook` commands and CLI options.
## Anatomy of a book
A book is built from several files which define the settings and layout of the book.
### `book.toml`
In the root of your book, there is a `book.toml` file which contains settings for describing how to build your book.
This is written in the [TOML markup language](https://toml.io/).
The default settings are usually good enough to get you started.
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.
A very basic `book.toml` can be as simple as this:
```toml
[book]
title = "My First Book"
```
### `SUMMARY.md`
The next major part of a book is the summary file located at `src/SUMMARY.md`.
This file contains a list of all the chapters in the book.
Before a chapter can be viewed, it must be added to this list.
Here's a basic summary file with a few chapters:
```md
# Summary
[Introduction](README.md)
- [My First Chapter](my-first-chapter.md)
- [Nested example](nested/README.md)
- [Sub-chapter](nested/sub-chapter.md)
```
Try opening up `src/SUMMARY.md` in your editor and adding a few chapters.
If any of the chapter files do not exist, `mdbook` will automatically create them for you.
For more details on other formatting options for the summary file, check out the [Summary chapter](../format/summary.md).
### Source files
The content of your book is all contained in the `src` directory.
Each chapter is a separate Markdown file.
Typically, each chapter starts with a level 1 heading with the title of the chapter.
```md
# My First Chapter
Fill out your content here.
```
The precise layout of the files is up to you.
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.
While the `mdbook serve` command is running, you can open any of the chapter files and start editing them.
Each time you save the file, `mdbook` will rebuild the book and refresh your web browser.
Check out the [Markdown chapter](../format/markdown.md) for more information on formatting the content of your chapters.
All other files in the `src` directory will be included in the output.
So if you have images or other static files, just include them somewhere in the `src` directory.
## Publishing a book
Once you've written your book, you may want to host it somewhere for others to view.
The first step is to build the output of the book.
This can be done with the `mbdook build` command in the same directory where the `book.toml` file is located:
```sh
mdbook build
```
This will generate a directory named `book` which contains the HTML content of your book.
You can then place this directory on any web server to host it.
For more information about publishing and deploying, check out the [Continuous Integration chapter](../continuous-integration.md) for more.

View File

@@ -1,50 +0,0 @@
# Installation
There are multiple ways to install the mdBook CLI tool.
Choose any one of the methods below that best suit your needs.
If you are installing mdBook for automatic deployment, check out the [continuous integration] chapter for more examples on how to install.
[continuous integration]: ../continuous-integration.md
## Pre-compiled binaries
Executable binaries are available for download on the [GitHub Releases page][releases].
Download the binary for your platform (Windows, macOS, or Linux) and extract the archive.
The archive contains an `mdbook` executable which you can run to build your books.
To make it easier to run, put the path to the binary into your `PATH`.
[releases]: https://github.com/rust-lang/mdBook/releases
## Build from source using Rust
To build the `mdbook` executable from source, you will first need to install Rust and Cargo.
Follow the instructions on the [Rust installation page].
mdBook currently requires at least Rust version 1.54.
Once you have installed Rust, the following command can be used to build and install mdBook:
```sh
cargo install mdbook
```
This will automatically download mdBook from [crates.io], build it, and install it in Cargo's global binary directory (`~/.cargo/bin/` by default).
[Rust installation page]: https://www.rust-lang.org/tools/install
[crates.io]: https://crates.io/
### Installing the latest master version
The version published to crates.io will ever so slightly be behind the version hosted on GitHub.
If you need the latest version you can build the git version of mdBook yourself.
Cargo makes this ***super easy***!
```sh
cargo install --git https://github.com/rust-lang/mdBook.git mdbook
```
Again, make sure to add the Cargo bin directory to your `PATH`.
If you are interested in making modifications to mdBook itself, check out the [Contributing Guide] for more information.
[Contributing Guide]: https://github.com/rust-lang/mdBook/blob/master/CONTRIBUTING.md

View File

@@ -1,74 +0,0 @@
# Reading Books
This chapter gives an introduction on how to interact with a book produced by mdBook.
This assumes you are reading an HTML book.
The options and formatting will be different for other output formats such as PDF.
A book is organized into *chapters*.
Each chapter is a separate page.
Chapters can be nested into a hierarchy of sub-chapters.
Typically, each chapter will be organized into a series of *headings* to subdivide a chapter.
## Navigation
There are several methods for navigating through the chapters of a book.
The **sidebar** on the left provides a list of all chapters.
Clicking on any of the chapter titles will load that page.
The sidebar may not automatically appear if the window is too narrow, particularly on mobile displays.
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.
The **arrow buttons** at the bottom of the page can be used to navigate to the previous or the next chapter.
The **left and right arrow keys** on the keyboard can be used to navigate to the previous or the next chapter.
## Top menu bar
The menu bar at the top of the page provides some icons for interacting with the book.
The icons displayed will depend on the settings of how the book was generated.
| Icon | Description |
|------|-------------|
| <i class="fa fa-bars"></i> | Opens and closes the chapter listing sidebar. |
| <i class="fa fa-paint-brush"></i> | Opens a picker to choose a different color theme. |
| <i class="fa fa-search"></i> | Opens a search bar for searching within the book. |
| <i class="fa fa-print"></i> | Instructs the web browser to print the entire book. |
| <i class="fa fa-github"></i> | Opens a link to the website that hosts the source code of the book. |
| <i class="fa fa-edit"></i> | Opens a page to directly edit the source of the page you are currently reading. |
Tapping the menu bar will scroll the page to the top.
## Search
Each book has a built-in search system.
Pressing the search icon (<i class="fa fa-search"></i>) in the menu bar, or pressing the `S` key on the keyboard will open an input box for entering search terms.
Typing some terms will show matching chapters and sections in real time.
Clicking any of the results will jump to that section.
The up and down arrow keys can be used to navigate the results, and enter will open the highlighted section.
After loading a search result, the matching search terms will be highlighted in the text.
Clicking a highlighted word or pressing the `Esc` key will remove the highlighting.
## Code blocks
mdBook books are often used for programming projects, and thus support highlighting code blocks and samples.
Code blocks may contain several different icons for interacting with them:
| Icon | Description |
|------|-------------|
| <i class="fa fa-copy"></i> | Copies the code block into your local clipboard, to allow pasting into another application. |
| <i class="fa fa-play"></i> | For Rust code examples, this will execute the sample code and display the compiler output just below the example (see [playground]). |
| <i class="fa fa-eye"></i> | 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]). |
| <i class="fa fa-history"></i> | For [editable code examples][editor], this will undo any changes you have made. |
Here's an example:
```rust
println!("Hello, World!");
```
[editor]: ../format/theme/editor.md
[playground]: ../format/mdbook.md#rust-playground
[hiding code lines]: ../format/mdbook.md#hiding-code-lines

View File

@@ -15,10 +15,6 @@ shout-out to them!
- [projektir](https://github.com/projektir)
- [Phaiax](https://github.com/Phaiax)
- Matt Ickstadt ([mattico](https://github.com/mattico))
- Weihang Lo ([weihanglo](https://github.com/weihanglo))
- Avision Ho ([avisionh](https://github.com/avisionh))
- Vivek Akupatni ([apatniv](https://github.com/apatniv))
- Eric Huss ([ehuss](https://github.com/ehuss))
- Josh Rotenberg ([joshrotenberg](https://github.com/joshrotenberg))
- Weihang Lo ([@weihanglo](https://github.com/weihanglo))
If you feel you're missing from this list, feel free to add yourself in a PR.
If you feel you're missing from this list, feel free to add yourself in a PR.

View File

@@ -0,0 +1,3 @@
# Introduction
A frontmatter chapter.

View File

@@ -14,15 +14,14 @@ pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book>
let summary_md = src_dir.join("SUMMARY.md");
let mut summary_content = String::new();
File::open(&summary_md)
.with_context(|| format!("Couldn't open SUMMARY.md in {:?} directory", src_dir))?
File::open(summary_md)
.with_context(|| "Couldn't open SUMMARY.md")?
.read_to_string(&mut summary_content)?;
let summary = parse_summary(&summary_content)
.with_context(|| format!("Summary parsing failed for file={:?}", summary_md))?;
let summary = parse_summary(&summary_content).with_context(|| "Summary parsing failed")?;
if cfg.create_missing {
create_missing(src_dir, &summary).with_context(|| "Unable to create missing chapters")?;
create_missing(&src_dir, &summary).with_context(|| "Unable to create missing chapters")?;
}
load_book_from_disk(&summary, src_dir)
@@ -50,9 +49,7 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
}
debug!("Creating missing file {}", filename.display());
let mut f = File::create(&filename).with_context(|| {
format!("Unable to create missing file: {}", filename.display())
})?;
let mut f = File::create(&filename)?;
writeln!(f, "# {}", link.name)?;
}
}
@@ -66,7 +63,7 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
/// A dumb tree structure representing a book.
///
/// For the moment a book is just a collection of [`BookItems`] which are
/// For the moment a book is just a collection of `BookItems` which are
/// accessible by either iterating (immutably) over the book with [`iter()`], or
/// recursively applying a closure to each section to mutate the chapters, using
/// [`for_each_mut()`].
@@ -160,9 +157,7 @@ pub struct Chapter {
pub sub_items: Vec<BookItem>,
/// The chapter's location, relative to the `SUMMARY.md` file.
pub path: Option<PathBuf>,
/// The chapter's source file, relative to the `SUMMARY.md` file.
pub source_path: Option<PathBuf>,
/// An ordered list of the names of each chapter above this one in the hierarchy.
/// An ordered list of the names of each chapter above this one, in the hierarchy.
pub parent_names: Vec<String>,
}
@@ -171,36 +166,36 @@ impl Chapter {
pub fn new<P: Into<PathBuf>>(
name: &str,
content: String,
p: P,
path: P,
parent_names: Vec<String>,
) -> Chapter {
let path: PathBuf = p.into();
Chapter {
name: name.to_string(),
content,
path: Some(path.clone()),
source_path: Some(path),
path: Some(path.into()),
parent_names,
..Default::default()
}
}
/// Create a new draft chapter that is not attached to a source markdown file (and thus
/// has no content).
/// Create a new draft chapter that is not attached to a source markdown file and has
/// thus no content.
pub fn new_draft(name: &str, parent_names: Vec<String>) -> Self {
Chapter {
name: name.to_string(),
content: String::new(),
path: None,
source_path: None,
parent_names,
..Default::default()
}
}
/// Check if the chapter is a draft chapter, meaning it has no path to a source markdown file.
/// Check if the chapter is a draft chapter, meaning it has no path to a source markdown file
pub fn is_draft_chapter(&self) -> bool {
self.path.is_none()
match self.path {
Some(_) => false,
None => true,
}
}
}
@@ -269,10 +264,6 @@ fn load_chapter<P: AsRef<Path>>(
format!("Unable to read \"{}\" ({})", link.name, location.display())
})?;
if content.as_bytes().starts_with(b"\xef\xbb\xbf") {
content.replace_range(..3, "");
}
let stripped = location
.strip_prefix(&src_dir)
.expect("Chapters are always inside a book");
@@ -282,7 +273,7 @@ fn load_chapter<P: AsRef<Path>>(
Chapter::new_draft(&link.name, parent_names.clone())
};
let mut sub_item_parents = parent_names;
let mut sub_item_parents = parent_names.clone();
ch.number = link.number.clone();
@@ -304,6 +295,8 @@ fn load_chapter<P: AsRef<Path>>(
///
/// This struct shouldn't be created directly, instead prefer the
/// [`Book::iter()`] method.
///
/// [`Book::iter()`]: struct.Book.html#method.iter
pub struct BookItems<'a> {
items: VecDeque<&'a BookItem>,
}
@@ -381,7 +374,7 @@ And here is some \
root.nested_items.push(second.clone().into());
root.nested_items.push(SummaryItem::Separator);
root.nested_items.push(second.into());
root.nested_items.push(second.clone().into());
(root, temp_dir)
}
@@ -400,29 +393,6 @@ And here is some \
assert_eq!(got, should_be);
}
#[test]
fn load_a_single_chapter_with_utf8_bom_from_disk() {
let temp_dir = TempFileBuilder::new().prefix("book").tempdir().unwrap();
let chapter_path = temp_dir.path().join("chapter_1.md");
File::create(&chapter_path)
.unwrap()
.write_all(("\u{feff}".to_owned() + DUMMY_SRC).as_bytes())
.unwrap();
let link = Link::new("Chapter 1", chapter_path);
let should_be = Chapter::new(
"Chapter 1",
DUMMY_SRC.to_string(),
"chapter_1.md",
Vec::new(),
);
let got = load_chapter(&link, temp_dir.path(), Vec::new()).unwrap();
assert_eq!(got, should_be);
}
#[test]
fn cant_load_a_nonexistent_chapter() {
let link = Link::new("Chapter 1", "/foo/bar/baz.md");
@@ -440,7 +410,6 @@ And here is some \
content: String::from("Hello World!"),
number: Some(SectionNumber(vec![1, 2])),
path: Some(PathBuf::from("second.md")),
source_path: Some(PathBuf::from("second.md")),
parent_names: vec![String::from("Chapter 1")],
sub_items: Vec::new(),
};
@@ -449,12 +418,11 @@ And here is some \
content: String::from(DUMMY_SRC),
number: None,
path: Some(PathBuf::from("chapter_1.md")),
source_path: Some(PathBuf::from("chapter_1.md")),
parent_names: Vec::new(),
sub_items: vec![
BookItem::Chapter(nested.clone()),
BookItem::Separator,
BookItem::Chapter(nested),
BookItem::Chapter(nested.clone()),
],
});
@@ -474,7 +442,6 @@ And here is some \
name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC),
path: Some(PathBuf::from("chapter_1.md")),
source_path: Some(PathBuf::from("chapter_1.md")),
..Default::default()
})],
..Default::default()
@@ -515,7 +482,6 @@ And here is some \
content: String::from(DUMMY_SRC),
number: None,
path: Some(PathBuf::from("Chapter_1/index.md")),
source_path: Some(PathBuf::from("Chapter_1/index.md")),
parent_names: Vec::new(),
sub_items: vec![
BookItem::Chapter(Chapter::new(
@@ -568,7 +534,6 @@ And here is some \
content: String::from(DUMMY_SRC),
number: None,
path: Some(PathBuf::from("Chapter_1/index.md")),
source_path: Some(PathBuf::from("Chapter_1/index.md")),
parent_names: Vec::new(),
sub_items: vec![
BookItem::Chapter(Chapter::new(

View File

@@ -28,7 +28,7 @@ impl BookBuilder {
}
}
/// Set the [`Config`] to be used.
/// Set the `Config` to be used.
pub fn with_config(&mut self, cfg: Config) -> &mut BookBuilder {
self.config = cfg;
self
@@ -110,7 +110,10 @@ impl BookBuilder {
debug!("Copying theme");
let html_config = self.config.html_config().unwrap_or_default();
let themedir = html_config.theme_dir(&self.root);
let themedir = html_config
.theme
.unwrap_or_else(|| self.config.book.src.join("theme"));
let themedir = self.root.join(themedir);
if !themedir.exists() {
debug!(
@@ -124,9 +127,7 @@ impl BookBuilder {
index.write_all(theme::INDEX)?;
let cssdir = themedir.join("css");
if !cssdir.exists() {
fs::create_dir(&cssdir)?;
}
fs::create_dir(&cssdir)?;
let mut general_css = File::create(cssdir.join("general.css"))?;
general_css.write_all(theme::GENERAL_CSS)?;

View File

@@ -20,7 +20,6 @@ use std::process::Command;
use std::string::ToString;
use tempfile::Builder as TempFileBuilder;
use toml::Value;
use topological_sort::TopologicalSort;
use crate::errors::*;
use crate::preprocess::{
@@ -41,7 +40,7 @@ pub struct MDBook {
pub book: Book,
renderers: Vec<Box<dyn Renderer>>,
/// List of pre-processors to be run on the book.
/// List of pre-processors to be run on the book
preprocessors: Vec<Box<dyn Preprocessor>>,
}
@@ -70,20 +69,6 @@ impl MDBook {
config.update_from_env();
if config
.html_config()
.map_or(false, |html| html.google_analytics.is_some())
{
warn!(
"The output.html.google-analytics field has been deprecated; \
it will be removed in a future release.\n\
Consider placing the appropriate site tag code into the \
theme/head.hbs file instead.\n\
The tracking code may be found in the Google Analytics Admin page.\n\
"
);
}
if log_enabled!(log::Level::Trace) {
for line in format!("Config: {:#?}", config).lines() {
trace!("{}", line);
@@ -93,7 +78,7 @@ impl MDBook {
MDBook::load_with_config(book_root, config)
}
/// Load a book from its root directory using a custom `Config`.
/// Load a book from its root directory using a custom config.
pub fn load_with_config<P: Into<PathBuf>>(book_root: P, config: Config) -> Result<MDBook> {
let root = book_root.into();
@@ -112,7 +97,7 @@ impl MDBook {
})
}
/// Load a book from its root directory using a custom `Config` and a custom summary.
/// Load a book from its root directory using a custom config and a custom summary.
pub fn load_with_config_and_summary<P: Into<PathBuf>>(
book_root: P,
config: Config,
@@ -136,7 +121,7 @@ impl MDBook {
}
/// Returns a flat depth-first iterator over the elements of the book,
/// it returns a [`BookItem`] enum:
/// it returns an [BookItem enum](bookitem.html):
/// `(section: String, bookitem: &BookItem)`
///
/// ```no_run
@@ -195,7 +180,7 @@ impl MDBook {
Ok(())
}
/// Run the entire build process for a particular [`Renderer`].
/// Run the entire build process for a particular `Renderer`.
pub fn execute_build_process(&self, renderer: &dyn Renderer) -> Result<()> {
let mut preprocessed_book = self.book.clone();
let preprocess_ctx = PreprocessorContext::new(
@@ -211,34 +196,37 @@ impl MDBook {
}
}
info!("Running the {} backend", renderer.name());
self.render(&preprocessed_book, renderer)?;
Ok(())
}
fn render(&self, preprocessed_book: &Book, renderer: &dyn Renderer) -> Result<()> {
let name = renderer.name();
let build_dir = self.build_dir_for(name);
let mut render_context = RenderContext::new(
let render_context = RenderContext::new(
self.root.clone(),
preprocessed_book,
preprocessed_book.clone(),
self.config.clone(),
build_dir,
);
render_context
.chapter_titles
.extend(preprocess_ctx.chapter_titles.borrow_mut().drain());
info!("Running the {} backend", renderer.name());
renderer
.render(&render_context)
.with_context(|| "Rendering failed")
}
/// You can change the default renderer to another one by using this method.
/// The only requirement is that your renderer implement the [`Renderer`]
/// trait.
/// The only requirement is for your renderer to implement the [`Renderer`
/// trait](../renderer/trait.Renderer.html)
pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self {
self.renderers.push(Box::new(renderer));
self
}
/// Register a [`Preprocessor`] to be used when rendering the book.
/// Register a [`Preprocessor`](../preprocess/trait.Preprocessor.html) to be used when rendering the book.
pub fn with_preprocessor<P: Preprocessor + 'static>(&mut self, preprocessor: P) -> &mut Self {
self.preprocessors.push(Box::new(preprocessor));
self
@@ -289,9 +277,6 @@ impl MDBook {
RustEdition::E2018 => {
cmd.args(&["--edition", "2018"]);
}
RustEdition::E2021 => {
cmd.args(&["--edition", "2021"]);
}
}
}
@@ -318,7 +303,7 @@ impl MDBook {
/// artefacts.
///
/// If there is only 1 renderer, put it in the directory pointed to by the
/// `build.build_dir` key in [`Config`]. If there is more than one then the
/// `build.build_dir` key in `Config`. If there is more than one then the
/// renderer gets its own directory within the main build dir.
///
/// i.e. If there were only one renderer (in this case, the HTML renderer):
@@ -386,7 +371,12 @@ fn determine_renderers(config: &Config) -> Vec<Box<dyn Renderer>> {
renderers
}
const DEFAULT_PREPROCESSORS: &[&'static str] = &["links", "index"];
fn default_preprocessors() -> Vec<Box<dyn Preprocessor>> {
vec![
Box::new(LinkPreprocessor::new()),
Box::new(IndexPreprocessor::new()),
]
}
fn is_default_preprocessor(pre: &dyn Preprocessor) -> bool {
let name = pre.name();
@@ -395,127 +385,36 @@ fn is_default_preprocessor(pre: &dyn Preprocessor) -> bool {
/// Look at the `MDBook` and try to figure out what preprocessors to run.
fn determine_preprocessors(config: &Config) -> Result<Vec<Box<dyn Preprocessor>>> {
// Collect the names of all preprocessors intended to be run, and the order
// in which they should be run.
let mut preprocessor_names = TopologicalSort::<String>::new();
let mut preprocessors = Vec::new();
if config.build.use_default_preprocessors {
for name in DEFAULT_PREPROCESSORS {
preprocessor_names.insert(name.to_string());
}
preprocessors.extend(default_preprocessors());
}
if let Some(preprocessor_table) = config.get("preprocessor").and_then(Value::as_table) {
for (name, table) in preprocessor_table.iter() {
preprocessor_names.insert(name.to_string());
let exists = |name| {
(config.build.use_default_preprocessors && DEFAULT_PREPROCESSORS.contains(&name))
|| preprocessor_table.contains_key(name)
};
if let Some(before) = table.get("before") {
let before = before.as_array().ok_or_else(|| {
Error::msg(format!(
"Expected preprocessor.{}.before to be an array",
name
))
})?;
for after in before {
let after = after.as_str().ok_or_else(|| {
Error::msg(format!(
"Expected preprocessor.{}.before to contain strings",
name
))
})?;
if !exists(after) {
// Only warn so that preprocessors can be toggled on and off (e.g. for
// troubleshooting) without having to worry about order too much.
warn!(
"preprocessor.{}.after contains \"{}\", which was not found",
name, after
);
} else {
preprocessor_names.add_dependency(name, after);
}
}
}
if let Some(after) = table.get("after") {
let after = after.as_array().ok_or_else(|| {
Error::msg(format!(
"Expected preprocessor.{}.after to be an array",
name
))
})?;
for before in after {
let before = before.as_str().ok_or_else(|| {
Error::msg(format!(
"Expected preprocessor.{}.after to contain strings",
name
))
})?;
if !exists(before) {
// See equivalent warning above for rationale
warn!(
"preprocessor.{}.before contains \"{}\", which was not found",
name, before
);
} else {
preprocessor_names.add_dependency(before, name);
}
}
for key in preprocessor_table.keys() {
match key.as_ref() {
"links" => preprocessors.push(Box::new(LinkPreprocessor::new())),
"index" => preprocessors.push(Box::new(IndexPreprocessor::new())),
name => preprocessors.push(interpret_custom_preprocessor(
name,
&preprocessor_table[name],
)),
}
}
}
// Now that all links have been established, queue preprocessors in a suitable order
let mut preprocessors = Vec::with_capacity(preprocessor_names.len());
// `pop_all()` returns an empty vector when no more items are not being depended upon
for mut names in std::iter::repeat_with(|| preprocessor_names.pop_all())
.take_while(|names| !names.is_empty())
{
// The `topological_sort` crate does not guarantee a stable order for ties, even across
// runs of the same program. Thus, we break ties manually by sorting.
// Careful: `str`'s default sorting, which we are implicitly invoking here, uses code point
// values ([1]), which may not be an alphabetical sort.
// As mentioned in [1], doing so depends on locale, which is not desirable for deciding
// preprocessor execution order.
// [1]: https://doc.rust-lang.org/stable/std/cmp/trait.Ord.html#impl-Ord-14
names.sort();
for name in names {
let preprocessor: Box<dyn Preprocessor> = match name.as_str() {
"links" => Box::new(LinkPreprocessor::new()),
"index" => Box::new(IndexPreprocessor::new()),
_ => {
// The only way to request a custom preprocessor is through the `preprocessor`
// table, so it must exist, be a table, and contain the key.
let table = &config.get("preprocessor").unwrap().as_table().unwrap()[&name];
let command = get_custom_preprocessor_cmd(&name, table);
Box::new(CmdPreprocessor::new(name, command))
}
};
preprocessors.push(preprocessor);
}
}
// "If `pop_all` returns an empty vector and `len` is not 0, there are cyclic dependencies."
// Normally, `len() == 0` is equivalent to `is_empty()`, so we'll use that.
if preprocessor_names.is_empty() {
Ok(preprocessors)
} else {
Err(Error::msg("Cyclic dependency detected in preprocessors"))
}
Ok(preprocessors)
}
fn get_custom_preprocessor_cmd(key: &str, table: &Value) -> String {
table
fn interpret_custom_preprocessor(key: &str, table: &Value) -> Box<CmdPreprocessor> {
let command = table
.get("command")
.and_then(Value::as_str)
.map(ToString::to_string)
.unwrap_or_else(|| format!("mdbook-{}", key))
.unwrap_or_else(|| format!("mdbook-{}", key));
Box::new(CmdPreprocessor::new(key.to_string(), command))
}
fn interpret_custom_renderer(key: &str, table: &Value) -> Box<CmdRenderer> {
@@ -615,8 +514,8 @@ mod tests {
assert!(got.is_ok());
assert_eq!(got.as_ref().unwrap().len(), 2);
assert_eq!(got.as_ref().unwrap()[0].name(), "index");
assert_eq!(got.as_ref().unwrap()[1].name(), "links");
assert_eq!(got.as_ref().unwrap()[0].name(), "links");
assert_eq!(got.as_ref().unwrap()[1].name(), "index");
}
#[test]
@@ -663,123 +562,9 @@ mod tests {
// make sure the `preprocessor.random` table exists
let random = cfg.get_preprocessor("random").unwrap();
let random = get_custom_preprocessor_cmd("random", &Value::Table(random.clone()));
let random = interpret_custom_preprocessor("random", &Value::Table(random.clone()));
assert_eq!(random, "python random.py");
}
#[test]
fn preprocessor_before_must_be_array() {
let cfg_str = r#"
[preprocessor.random]
before = 0
"#;
let cfg = Config::from_str(cfg_str).unwrap();
assert!(determine_preprocessors(&cfg).is_err());
}
#[test]
fn preprocessor_after_must_be_array() {
let cfg_str = r#"
[preprocessor.random]
after = 0
"#;
let cfg = Config::from_str(cfg_str).unwrap();
assert!(determine_preprocessors(&cfg).is_err());
}
#[test]
fn preprocessor_order_is_honored() {
let cfg_str = r#"
[preprocessor.random]
before = [ "last" ]
after = [ "index" ]
[preprocessor.last]
after = [ "links", "index" ]
"#;
let cfg = Config::from_str(cfg_str).unwrap();
let preprocessors = determine_preprocessors(&cfg).unwrap();
let index = |name| {
preprocessors
.iter()
.enumerate()
.find(|(_, preprocessor)| preprocessor.name() == name)
.unwrap()
.0
};
let assert_before = |before, after| {
if index(before) >= index(after) {
eprintln!("Preprocessor order:");
for preprocessor in &preprocessors {
eprintln!(" {}", preprocessor.name());
}
panic!("{} should come before {}", before, after);
}
};
assert_before("index", "random");
assert_before("index", "last");
assert_before("random", "last");
assert_before("links", "last");
}
#[test]
fn cyclic_dependencies_are_detected() {
let cfg_str = r#"
[preprocessor.links]
before = [ "index" ]
[preprocessor.index]
before = [ "links" ]
"#;
let cfg = Config::from_str(cfg_str).unwrap();
assert!(determine_preprocessors(&cfg).is_err());
}
#[test]
fn dependencies_dont_register_undefined_preprocessors() {
let cfg_str = r#"
[preprocessor.links]
before = [ "random" ]
"#;
let cfg = Config::from_str(cfg_str).unwrap();
let preprocessors = determine_preprocessors(&cfg).unwrap();
assert!(preprocessors
.iter()
.find(|preprocessor| preprocessor.name() == "random")
.is_none());
}
#[test]
fn dependencies_dont_register_builtin_preprocessors_if_disabled() {
let cfg_str = r#"
[preprocessor.random]
before = [ "links" ]
[build]
use-default-preprocessors = false
"#;
let cfg = Config::from_str(cfg_str).unwrap();
let preprocessors = determine_preprocessors(&cfg).unwrap();
assert!(preprocessors
.iter()
.find(|preprocessor| preprocessor.name() == "links")
.is_none());
assert_eq!(random.cmd(), "python random.py");
}
#[test]

View File

@@ -1,6 +1,6 @@
use crate::errors::*;
use memchr::{self, Memchr};
use pulldown_cmark::{self, Event, HeadingLevel, Tag};
use pulldown_cmark::{self, Event, Tag};
use std::fmt::{self, Display, Formatter};
use std::iter::FromIterator;
use std::ops::{Deref, DerefMut};
@@ -161,7 +161,7 @@ impl From<Link> for SummaryItem {
/// > match the following regex: "[^<>\n[]]+".
struct SummaryParser<'a> {
src: &'a str,
stream: pulldown_cmark::OffsetIter<'a, 'a>,
stream: pulldown_cmark::OffsetIter<'a>,
offset: usize,
/// We can't actually put an event back into the `OffsetIter` stream, so instead we store it
@@ -173,7 +173,7 @@ struct SummaryParser<'a> {
/// `Event::End` is encountered which matches the `$delimiter` pattern.
///
/// This is the equivalent of doing
/// `$stream.take_while(|e| e != $delimiter).collect()` but it allows you to
/// `$stream.take_while(|e| e != $delimeter).collect()` but it allows you to
/// use pattern matching and you won't get errors because `take_while()`
/// moves `$stream` out of self.
macro_rules! collect_events {
@@ -263,7 +263,7 @@ impl<'a> SummaryParser<'a> {
loop {
match self.next_event() {
Some(ev @ Event::Start(Tag::List(..)))
| Some(ev @ Event::Start(Tag::Heading(HeadingLevel::H1, ..))) => {
| Some(ev @ Event::Start(Tag::Heading(1))) => {
if is_prefix {
// we've finished prefix chapters and are at the start
// of the numbered section.
@@ -302,10 +302,10 @@ impl<'a> SummaryParser<'a> {
break;
}
Some(Event::Start(Tag::Heading(HeadingLevel::H1, ..))) => {
Some(Event::Start(Tag::Heading(1))) => {
debug!("Found a h1 in the SUMMARY");
let tags = collect_events!(self.stream, end Tag::Heading(HeadingLevel::H1, ..));
let tags = collect_events!(self.stream, end Tag::Heading(1));
Some(stringify_events(tags))
}
@@ -375,14 +375,14 @@ impl<'a> SummaryParser<'a> {
}
// The expectation is that pulldown cmark will terminate a paragraph before a new
// heading, so we can always count on this to return without skipping headings.
Some(ev @ Event::Start(Tag::Heading(HeadingLevel::H1, ..))) => {
Some(ev @ Event::Start(Tag::Heading(1))) => {
// we're starting a new part
self.back(ev);
break;
}
Some(ev @ Event::Start(Tag::List(..))) => {
self.back(ev);
let mut bunch_of_items = self.parse_nested_numbered(root_number)?;
let mut bunch_of_items = self.parse_nested_numbered(&root_number)?;
// if we've resumed after something like a rule the root sections
// will be numbered from 1. We need to manually go back and update
@@ -525,23 +525,14 @@ impl<'a> SummaryParser<'a> {
/// Try to parse the title line.
fn parse_title(&mut self) -> Option<String> {
loop {
match self.next_event() {
Some(Event::Start(Tag::Heading(HeadingLevel::H1, ..))) => {
debug!("Found a h1 in the SUMMARY");
match self.next_event() {
Some(Event::Start(Tag::Heading(1))) => {
debug!("Found a h1 in the SUMMARY");
let tags = collect_events!(self.stream, end Tag::Heading(HeadingLevel::H1, ..));
return Some(stringify_events(tags));
}
// Skip a HTML element such as a comment line.
Some(Event::Html(_)) => {}
// Otherwise, no title.
Some(ev) => {
self.back(ev);
return None;
}
_ => return None,
let tags = collect_events!(self.stream, end Tag::Heading(1));
Some(stringify_events(tags))
}
_ => None,
}
}
}
@@ -651,18 +642,6 @@ mod tests {
assert_eq!(got, should_be);
}
#[test]
fn no_initial_title() {
let src = "[Link]()";
let mut parser = SummaryParser::new(src);
assert!(parser.parse_title().is_none());
assert!(matches!(
parser.next_event(),
Some(Event::Start(Tag::Paragraph))
));
}
#[test]
fn parse_title_with_styling() {
let src = "# My **Awesome** Summary";
@@ -994,103 +973,4 @@ mod tests {
assert_eq!(got, should_be);
}
#[test]
fn skip_html_comments() {
let src = r#"<!--
# Title - En
-->
# Title - Local
<!--
[Prefix 00-01 - En](ch00-01.md)
[Prefix 00-02 - En](ch00-02.md)
-->
[Prefix 00-01 - Local](ch00-01.md)
[Prefix 00-02 - Local](ch00-02.md)
<!--
## Section Title - En
-->
## Section Title - Localized
<!--
- [Ch 01-00 - En](ch01-00.md)
- [Ch 01-01 - En](ch01-01.md)
- [Ch 01-02 - En](ch01-02.md)
-->
- [Ch 01-00 - Local](ch01-00.md)
- [Ch 01-01 - Local](ch01-01.md)
- [Ch 01-02 - Local](ch01-02.md)
<!--
- [Ch 02-00 - En](ch02-00.md)
-->
- [Ch 02-00 - Local](ch02-00.md)
<!--
[Appendix A - En](appendix-01.md)
[Appendix B - En](appendix-02.md)
-->`
[Appendix A - Local](appendix-01.md)
[Appendix B - Local](appendix-02.md)
"#;
let mut parser = SummaryParser::new(src);
// ---- Title ----
let title = parser.parse_title();
assert_eq!(title, Some(String::from("Title - Local")));
// ---- Prefix Chapters ----
let new_affix_item = |name, location| {
SummaryItem::Link(Link {
name: String::from(name),
location: Some(PathBuf::from(location)),
..Default::default()
})
};
let should_be = vec![
new_affix_item("Prefix 00-01 - Local", "ch00-01.md"),
new_affix_item("Prefix 00-02 - Local", "ch00-02.md"),
];
let got = parser.parse_affix(true).unwrap();
assert_eq!(got, should_be);
// ---- Numbered Chapters ----
let new_numbered_item = |name, location, numbers: &[u32], nested_items| {
SummaryItem::Link(Link {
name: String::from(name),
location: Some(PathBuf::from(location)),
number: Some(SectionNumber(numbers.to_vec())),
nested_items,
})
};
let ch01_nested = vec![
new_numbered_item("Ch 01-01 - Local", "ch01-01.md", &[1, 1], vec![]),
new_numbered_item("Ch 01-02 - Local", "ch01-02.md", &[1, 2], vec![]),
];
let should_be = vec![
new_numbered_item("Ch 01-00 - Local", "ch01-00.md", &[1], ch01_nested),
new_numbered_item("Ch 02-00 - Local", "ch02-00.md", &[2], vec![]),
];
let got = parser.parse_parts().unwrap();
assert_eq!(got, should_be);
// ---- Suffix Chapters ----
let should_be = vec![
new_affix_item("Appendix A - Local", "appendix-01.md"),
new_affix_item("Appendix B - Local", "appendix-02.md"),
];
let got = parser.parse_affix(false).unwrap();
assert_eq!(got, should_be);
}
}

View File

@@ -1,28 +1,22 @@
use crate::{get_book_dir, open};
use clap::{arg, App, Arg, ArgMatches};
use clap::{App, ArgMatches, SubCommand};
use mdbook::errors::Result;
use mdbook::MDBook;
// Create clap subcommand arguments
pub fn make_subcommand<'help>() -> App<'help> {
App::new("build")
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("build")
.about("Builds a book from its markdown files")
.arg(
Arg::new("dest-dir")
.short('d')
.long("dest-dir")
.value_name("dest-dir")
.help(
"Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
),
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
)
.arg(arg!([dir]
"Root directory for the book{n}\
(Defaults to the Current Directory when omitted)"
))
.arg(arg!(-o --open "Opens the compiled book in a web browser"))
.arg_from_usage(
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",
)
.arg_from_usage("-o, --open 'Opens the compiled book in a web browser'")
}
// Build command implementation

View File

@@ -1,28 +1,23 @@
use crate::get_book_dir;
use anyhow::Context;
use clap::{arg, App, Arg, ArgMatches};
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use std::fs;
// Create clap subcommand arguments
pub fn make_subcommand<'help>() -> App<'help> {
App::new("clean")
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("clean")
.about("Deletes a built book")
.arg(
Arg::new("dest-dir")
.short('d')
.long("dest-dir")
.value_name("dest-dir")
.help(
"Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
),
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
Running this command deletes this directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
)
.arg_from_usage(
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",
)
.arg(arg!([dir]
"Root directory for the book{n}\
(Defaults to the Current Directory when omitted)"
))
}
// Clean command implementation

View File

@@ -1,5 +1,5 @@
use crate::get_book_dir;
use clap::{arg, App, Arg, ArgMatches};
use clap::{App, ArgMatches, SubCommand};
use mdbook::config;
use mdbook::errors::Result;
use mdbook::MDBook;
@@ -8,31 +8,16 @@ use std::io::Write;
use std::process::Command;
// Create clap subcommand arguments
pub fn make_subcommand<'help>() -> App<'help> {
App::new("init")
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("init")
.about("Creates the boilerplate structure and files for a new book")
// the {n} denotes a newline which will properly aligned in all help messages
.arg(arg!([dir]
"Directory to create the book in{n}\
(Defaults to the Current Directory when omitted)"
))
.arg(arg!(--theme "Copies the default theme into your source folder"))
.arg(arg!(--force "Skips confirmation prompts"))
.arg(
Arg::new("title")
.long("title")
.takes_value(true)
.help("Sets the book title")
.required(false),
)
.arg(
Arg::new("ignore")
.long("ignore")
.takes_value(true)
.possible_values(&["none", "git"])
.help("Creates a VCS ignore file (i.e. .gitignore)")
.required(false),
.arg_from_usage(
"[dir] 'Directory to create the book in{n}\
(Defaults to the Current Directory when omitted)'",
)
.arg_from_usage("--theme 'Copies the default theme into your source folder'")
.arg_from_usage("--force 'Skips confirmation prompts'")
}
// Init command implementation
@@ -40,13 +25,18 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args);
let mut builder = MDBook::init(&book_dir);
let mut config = config::Config::default();
// If flag `--theme` is present, copy theme to src
if args.is_present("theme") {
let theme_dir = book_dir.join("theme");
println!();
println!("Copying the default theme to {}", theme_dir.display());
config.set("output.html.theme", "src/theme")?;
// Skip this if `--force` is present
if !args.is_present("force") && theme_dir.exists() {
if !args.is_present("force") {
// Print warning
println!();
println!(
"Copying the default theme to {}",
builder.config().book.src.display()
);
println!("This could potentially overwrite files already present in that directory.");
print!("\nAre you sure you want to continue? (y/n) ");
@@ -59,23 +49,13 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
}
}
if let Some(ignore) = args.value_of("ignore") {
match ignore {
"git" => builder.create_gitignore(true),
_ => builder.create_gitignore(false),
};
} else {
println!("\nDo you want a .gitignore to be created? (y/n)");
if confirm() {
builder.create_gitignore(true);
}
println!("\nDo you want a .gitignore to be created? (y/n)");
if confirm() {
builder.create_gitignore(true);
}
config.book.title = if args.is_present("title") {
args.value_of("title").map(String::from)
} else {
request_book_title()
};
config.book.title = request_book_title();
if let Some(author) = get_author_name() {
debug!("Obtained user name from gitconfig: {:?}", author);

View File

@@ -1,7 +1,7 @@
#[cfg(feature = "watch")]
use super::watch;
use crate::{get_book_dir, open};
use clap::{arg, App, Arg, ArgMatches};
use clap::{App, Arg, ArgMatches, SubCommand};
use futures_util::sink::SinkExt;
use futures_util::StreamExt;
use mdbook::errors::*;
@@ -18,43 +18,37 @@ use warp::Filter;
const LIVE_RELOAD_ENDPOINT: &str = "__livereload";
// Create clap subcommand arguments
pub fn make_subcommand<'help>() -> App<'help> {
App::new("serve")
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("serve")
.about("Serves a book at http://localhost:3000, and rebuilds it on changes")
.arg(
Arg::new("dest-dir")
.short('d')
.long("dest-dir")
.value_name("dest-dir")
.help(
"Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
),
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
)
.arg_from_usage(
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",
)
.arg(arg!([dir]
"Root directory for the book{n}\
(Defaults to the Current Directory when omitted)"
))
.arg(
Arg::new("hostname")
.short('n')
Arg::with_name("hostname")
.short("n")
.long("hostname")
.takes_value(true)
.default_value("localhost")
.forbid_empty_values(true)
.empty_values(false)
.help("Hostname to listen on for HTTP connections"),
)
.arg(
Arg::new("port")
.short('p')
Arg::with_name("port")
.short("p")
.long("port")
.takes_value(true)
.default_value("3000")
.forbid_empty_values(true)
.empty_values(false)
.help("Port to use for HTTP connections"),
)
.arg(arg!(-o --open "Opens the compiled book in a web browser"))
.arg_from_usage("-o, --open 'Opens the book server in a web browser'")
}
// Serve command implementation
@@ -68,10 +62,11 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
let address = format!("{}:{}", hostname, port);
let livereload_url = format!("ws://{}/{}", address, LIVE_RELOAD_ENDPOINT);
let update_config = |book: &mut MDBook| {
book.config
.set("output.html.live-reload-endpoint", &LIVE_RELOAD_ENDPOINT)
.expect("live-reload-endpoint update failed");
.set("output.html.livereload-url", &livereload_url)
.expect("livereload-url update failed");
if let Some(dest_dir) = args.value_of("dest-dir") {
book.config.build.build_dir = dest_dir.into();
}
@@ -166,12 +161,5 @@ async fn serve(
let fallback_route = warp::fs::file(build_dir.join(file_404))
.map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NOT_FOUND));
let routes = livereload.or(book_route).or(fallback_route);
std::panic::set_hook(Box::new(move |panic_info| {
// exit if serve panics
error!("Unable to serve: {}", panic_info);
std::process::exit(1);
}));
warp::serve(routes).run(address).await;
}

View File

@@ -1,37 +1,29 @@
use crate::get_book_dir;
use clap::{arg, App, Arg, ArgMatches};
use clap::{App, Arg, ArgMatches, SubCommand};
use mdbook::errors::Result;
use mdbook::MDBook;
// Create clap subcommand arguments
pub fn make_subcommand<'help>() -> App<'help> {
App::new("test")
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("test")
.about("Tests that a book's Rust code samples compile")
.arg(
Arg::new("dest-dir")
.short('d')
.long("dest-dir")
.value_name("dest-dir")
.help(
"Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
),
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
)
.arg(arg!([dir]
"Root directory for the book{n}\
(Defaults to the Current Directory when omitted)"
))
.arg(Arg::new("library-path")
.short('L')
.arg_from_usage(
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",
)
.arg(Arg::with_name("library-path")
.short("L")
.long("library-path")
.value_name("dir")
.takes_value(true)
.use_delimiter(true)
.require_delimiter(true)
.multiple_values(true)
.multiple_occurrences(true)
.forbid_empty_values(true)
.multiple(true)
.empty_values(false)
.help("A comma-separated list of directories to add to {n}the crate search path when building tests"))
}

View File

@@ -1,5 +1,5 @@
use crate::{get_book_dir, open};
use clap::{arg, App, Arg, ArgMatches};
use clap::{App, ArgMatches, SubCommand};
use mdbook::errors::Result;
use mdbook::utils;
use mdbook::MDBook;
@@ -10,25 +10,19 @@ use std::thread::sleep;
use std::time::Duration;
// Create clap subcommand arguments
pub fn make_subcommand<'help>() -> App<'help> {
App::new("watch")
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("watch")
.about("Watches a book's files and rebuilds it on changes")
.arg(
Arg::new("dest-dir")
.short('d')
.long("dest-dir")
.value_name("dest-dir")
.help(
"Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
),
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
)
.arg(arg!([dir]
"Root directory for the book{n}\
(Defaults to the Current Directory when omitted)"
))
.arg(arg!(-o --open "Opens the compiled book in a web browser"))
.arg_from_usage(
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",
)
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
}
// Watch command implementation
@@ -64,7 +58,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
Ok(())
}
fn remove_ignored_files(book_root: &Path, paths: &[PathBuf]) -> Vec<PathBuf> {
fn remove_ignored_files(book_root: &PathBuf, paths: &[PathBuf]) -> Vec<PathBuf> {
if paths.is_empty() {
return vec![];
}
@@ -87,7 +81,7 @@ fn remove_ignored_files(book_root: &Path, paths: &[PathBuf]) -> Vec<PathBuf> {
}
}
fn find_gitignore(book_root: &Path) -> Option<PathBuf> {
fn find_gitignore(book_root: &PathBuf) -> Option<PathBuf> {
book_root
.ancestors()
.map(|p| p.join(".gitignore"))

View File

@@ -2,7 +2,7 @@
//!
//! The main entrypoint of the `config` module is the `Config` struct. This acts
//! essentially as a bag of configuration information, with a couple
//! pre-determined tables ([`BookConfig`] and [`BuildConfig`]) as well as support
//! pre-determined tables (`BookConfig` and `BuildConfig`) as well as support
//! for arbitrary data which is exposed to plugins and alternative backends.
//!
//!
@@ -294,7 +294,6 @@ impl Default for Config {
}
}
}
impl<'de> Deserialize<'de> for Config {
fn deserialize<D: Deserializer<'de>>(de: D) -> std::result::Result<Self, D::Error> {
let raw = Value::deserialize(de)?;
@@ -311,10 +310,10 @@ impl<'de> Deserialize<'de> for Config {
return Ok(Config::from_legacy(raw));
}
use serde::de::Error;
let mut table = match raw {
Value::Table(t) => t,
_ => {
use serde::de::Error;
return Err(D::Error::custom(
"A config file should always be a toml table",
));
@@ -323,20 +322,17 @@ impl<'de> Deserialize<'de> for Config {
let book: BookConfig = table
.remove("book")
.map(|book| book.try_into().map_err(D::Error::custom))
.transpose()?
.and_then(|value| value.try_into().ok())
.unwrap_or_default();
let build: BuildConfig = table
.remove("build")
.map(|build| build.try_into().map_err(D::Error::custom))
.transpose()?
.and_then(|value| value.try_into().ok())
.unwrap_or_default();
let rust: RustConfig = table
.remove("rust")
.map(|rust| rust.try_into().map_err(D::Error::custom))
.transpose()?
.and_then(|value| value.try_into().ok())
.unwrap_or_default();
Ok(Config {
@@ -356,11 +352,6 @@ impl Serialize for Config {
let book_config = Value::try_from(&self.book).expect("should always be serializable");
table.insert("book", book_config);
if self.build != BuildConfig::default() {
let build_config = Value::try_from(&self.build).expect("should always be serializable");
table.insert("build", build_config);
}
if self.rust != RustConfig::default() {
let rust_config = Value::try_from(&self.rust).expect("should always be serializable");
table.insert("rust", rust_config);
@@ -467,9 +458,6 @@ pub struct RustConfig {
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
/// Rust edition to use for the code.
pub enum RustEdition {
/// The 2021 edition of Rust
#[serde(rename = "2021")]
E2021,
/// The 2018 edition of Rust
#[serde(rename = "2018")]
E2018,
@@ -529,18 +517,14 @@ pub struct HtmlConfig {
///
/// [custom domain]: https://docs.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site
pub cname: Option<String>,
/// Edit url template, when set shows a "Suggest an edit" button for
/// directly jumping to editing the currently viewed page.
/// Contains {path} that is replaced with chapter source file path
pub edit_url_template: Option<String>,
/// Endpoint of websocket, for livereload usage. Value loaded from .toml file
/// is ignored, because our code overrides this field with the value [`LIVE_RELOAD_ENDPOINT`]
///
/// [`LIVE_RELOAD_ENDPOINT`]: cmd::serve::LIVE_RELOAD_ENDPOINT
/// This is used as a bit of a workaround for the `mdbook serve` command.
/// Basically, because you set the websocket port from the command line, the
/// `mdbook serve` command needs a way to let the HTML renderer know where
/// to point livereloading at, if it has been enabled.
///
/// This config item *should not be edited* by the end user.
#[doc(hidden)]
pub live_reload_endpoint: Option<String>,
pub livereload_url: Option<String>,
/// The mapping from old pages to new pages/URLs to use when generating
/// redirects.
pub redirect: HashMap<String, String>,
@@ -565,11 +549,10 @@ impl Default for HtmlConfig {
search: None,
git_repository_url: None,
git_repository_icon: None,
edit_url_template: None,
input_404: None,
site_url: None,
cname: None,
live_reload_endpoint: None,
livereload_url: None,
redirect: HashMap::new(),
}
}
@@ -578,7 +561,7 @@ impl Default for HtmlConfig {
impl HtmlConfig {
/// Returns the directory of theme from the provided root directory. If the
/// directory is not present it will append the default directory of "theme"
pub fn theme_dir(&self, root: &Path) -> PathBuf {
pub fn theme_dir(&self, root: &PathBuf) -> PathBuf {
match self.theme {
Some(ref d) => root.join(d),
None => root.join("theme"),
@@ -588,20 +571,15 @@ impl HtmlConfig {
/// Configuration for how to render the print icon, print.html, and print.css.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
#[serde(rename_all = "kebab-case")]
pub struct Print {
/// Whether print support is enabled.
pub enable: bool,
/// Insert page breaks between chapters. Default: `true`.
pub page_break: bool,
}
impl Default for Print {
fn default() -> Self {
Self {
enable: true,
page_break: true,
}
Self { enable: true }
}
}
@@ -630,8 +608,6 @@ pub struct Playground {
pub copy_js: bool,
/// Display line numbers on playground snippets. Default: `false`.
pub line_numbers: bool,
/// Display the run button. Default: `true`
pub runnable: bool,
}
impl Default for Playground {
@@ -641,7 +617,6 @@ impl Default for Playground {
copyable: true,
copy_js: true,
line_numbers: false,
runnable: true,
}
}
}
@@ -671,7 +646,7 @@ pub struct Search {
pub boost_paragraph: u8,
/// True if the searchword `micro` should match `microwave`. Default: `true`.
pub expand: bool,
/// Documents are split into smaller parts, separated by headings. This defines, until which
/// Documents are split into smaller parts, seperated by headings. This defines, until which
/// level of heading documents should be split. Default: `3`. (`### This is a level 3 heading`)
pub heading_split_level: u8,
/// Copy JavaScript files for the search functionality to the output directory?
@@ -784,7 +759,6 @@ mod tests {
copyable: true,
copy_js: true,
line_numbers: false,
runnable: true,
};
let html_should_be = HtmlConfig {
curly_quotes: true,
@@ -815,22 +789,6 @@ mod tests {
assert_eq!(got.html_config().unwrap(), html_should_be);
}
#[test]
fn disable_runnable() {
let src = r#"
[book]
title = "Some Book"
description = "book book book"
authors = ["Shogo Takata"]
[output.html.playground]
runnable = false
"#;
let got = Config::from_str(src).unwrap();
assert_eq!(got.html_config().unwrap().playground.runnable, false);
}
#[test]
fn edition_2015() {
let src = r#"
@@ -883,26 +841,6 @@ mod tests {
assert_eq!(got.rust, rust_should_be);
}
#[test]
fn edition_2021() {
let src = r#"
[book]
title = "mdBook Documentation"
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
authors = ["Mathieu David"]
src = "./source"
[rust]
edition = "2021"
"#;
let rust_should_be = RustConfig {
edition: Some(RustEdition::E2021),
};
let got = Config::from_str(src).unwrap();
assert_eq!(got.rust, rust_should_be);
}
#[test]
fn load_arbitrary_output_type() {
#[derive(Debug, Deserialize, PartialEq)]
@@ -1122,77 +1060,4 @@ mod tests {
assert_eq!(html_config.input_404, Some("missing.md".to_string()));
assert_eq!(&get_404_output_file(&html_config.input_404), "missing.html");
}
#[test]
#[should_panic(expected = "Invalid configuration file")]
fn invalid_language_type_error() {
let src = r#"
[book]
title = "mdBook Documentation"
language = ["en", "pt-br"]
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
authors = ["Mathieu David"]
src = "./source"
"#;
Config::from_str(src).unwrap();
}
#[test]
#[should_panic(expected = "Invalid configuration file")]
fn invalid_title_type() {
let src = r#"
[book]
title = 20
language = "en"
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
authors = ["Mathieu David"]
src = "./source"
"#;
Config::from_str(src).unwrap();
}
#[test]
#[should_panic(expected = "Invalid configuration file")]
fn invalid_build_dir_type() {
let src = r#"
[build]
build-dir = 99
create-missing = false
"#;
Config::from_str(src).unwrap();
}
#[test]
#[should_panic(expected = "Invalid configuration file")]
fn invalid_rust_edition() {
let src = r#"
[rust]
edition = "1999"
"#;
Config::from_str(src).unwrap();
}
#[test]
fn print_config() {
let src = r#"
[output.html.print]
enable = false
"#;
let got = Config::from_str(src).unwrap();
let html_config = got.html_config().unwrap();
assert_eq!(html_config.print.enable, false);
assert_eq!(html_config.print.page_break, true);
let src = r#"
[output.html.print]
page-break = false
"#;
let got = Config::from_str(src).unwrap();
let html_config = got.html_config().unwrap();
assert_eq!(html_config.print.enable, true);
assert_eq!(html_config.print.page_break, false);
}
}

View File

@@ -76,9 +76,9 @@
//! access to the various methods for working with the [`Config`].
//!
//! [user guide]: https://rust-lang.github.io/mdBook/
//! [`RenderContext`]: renderer::RenderContext
//! [`RenderContext`]: renderer/struct.RenderContext.html
//! [relevant chapter]: https://rust-lang.github.io/mdBook/for_developers/backends.html
//! [`Config`]: config::Config
//! [`Config`]: config/struct.Config.html
#![deny(missing_docs)]
#![deny(rust_2018_idioms)]

View File

@@ -3,10 +3,8 @@ extern crate clap;
#[macro_use]
extern crate log;
use anyhow::anyhow;
use chrono::Local;
use clap::{App, AppSettings, Arg, ArgMatches};
use clap_complete::Shell;
use clap::{App, AppSettings, ArgMatches};
use env_logger::Builder;
use log::LevelFilter;
use mdbook::utils;
@@ -22,52 +20,14 @@ const VERSION: &str = concat!("v", crate_version!());
fn main() {
init_logger();
let app = create_clap_app();
// Check which subcomamnd the user ran...
let res = match app.get_matches().subcommand() {
Some(("init", sub_matches)) => cmd::init::execute(sub_matches),
Some(("build", sub_matches)) => cmd::build::execute(sub_matches),
Some(("clean", sub_matches)) => cmd::clean::execute(sub_matches),
#[cfg(feature = "watch")]
Some(("watch", sub_matches)) => cmd::watch::execute(sub_matches),
#[cfg(feature = "serve")]
Some(("serve", sub_matches)) => cmd::serve::execute(sub_matches),
Some(("test", sub_matches)) => cmd::test::execute(sub_matches),
Some(("completions", sub_matches)) => (|| {
let shell: Shell = sub_matches
.value_of("shell")
.ok_or_else(|| anyhow!("Shell name missing."))?
.parse()
.map_err(|s| anyhow!("Invalid shell: {}", s))?;
let mut complete_app = create_clap_app();
clap_complete::generate(
shell,
&mut complete_app,
"mdbook",
&mut std::io::stdout().lock(),
);
Ok(())
})(),
_ => unreachable!(),
};
if let Err(e) = res {
utils::log_backtrace(&e);
std::process::exit(101);
}
}
/// Create a list of valid arguments and sub-commands
fn create_clap_app() -> App<'static> {
// Create a list of valid arguments and sub-commands
let app = App::new(crate_name!())
.about(crate_description!())
.author("Mathieu David <mathieudavid@mathieudavid.org>")
.version(VERSION)
.setting(AppSettings::PropagateVersion)
.setting(AppSettings::GlobalVersion)
.setting(AppSettings::ArgRequiredElseHelp)
.setting(AppSettings::ColoredHelp)
.after_help(
"For more information about a specific command, try `mdbook <command> --help`\n\
The source code for mdBook is available at: https://github.com/rust-lang/mdBook",
@@ -75,26 +35,31 @@ fn create_clap_app() -> App<'static> {
.subcommand(cmd::init::make_subcommand())
.subcommand(cmd::build::make_subcommand())
.subcommand(cmd::test::make_subcommand())
.subcommand(cmd::clean::make_subcommand())
.subcommand(
App::new("completions")
.about("Generate shell completions for your shell to stdout")
.arg(
Arg::new("shell")
.takes_value(true)
.possible_values(Shell::possible_values())
.help("the shell to generate completions for")
.value_name("SHELL")
.required(true),
),
);
.subcommand(cmd::clean::make_subcommand());
#[cfg(feature = "watch")]
let app = app.subcommand(cmd::watch::make_subcommand());
#[cfg(feature = "serve")]
let app = app.subcommand(cmd::serve::make_subcommand());
app
// Check which subcomamnd the user ran...
let res = match app.get_matches().subcommand() {
("init", Some(sub_matches)) => cmd::init::execute(sub_matches),
("build", Some(sub_matches)) => cmd::build::execute(sub_matches),
("clean", Some(sub_matches)) => cmd::clean::execute(sub_matches),
#[cfg(feature = "watch")]
("watch", Some(sub_matches)) => cmd::watch::execute(sub_matches),
#[cfg(feature = "serve")]
("serve", Some(sub_matches)) => cmd::serve::execute(sub_matches),
("test", Some(sub_matches)) => cmd::test::execute(sub_matches),
(_, _) => unreachable!(),
};
if let Err(e) = res {
utils::log_backtrace(&e);
std::process::exit(101);
}
}
fn init_logger() {
@@ -138,13 +103,7 @@ fn get_book_dir(args: &ArgMatches) -> PathBuf {
}
fn open<P: AsRef<OsStr>>(path: P) {
info!("Opening web browser");
if let Err(e) = opener::open(path) {
if let Err(e) = open::that(path) {
error!("Error opening web browser: {}", e);
}
}
#[test]
fn verify_app() {
create_clap_app().debug_assert();
}

View File

@@ -49,7 +49,7 @@ impl CmdPreprocessor {
fn write_input_to_child(&self, child: &mut Child, book: &Book, ctx: &PreprocessorContext) {
let stdin = child.stdin.take().expect("Child has stdin");
if let Err(e) = self.write_input(stdin, book, ctx) {
if let Err(e) = self.write_input(stdin, &book, &ctx) {
// Looks like the backend hung up before we could finish
// sending it the render context. Log the error and keep going
warn!("Error writing the RenderContext to the backend, {}", e);
@@ -109,28 +109,18 @@ impl Preprocessor for CmdPreprocessor {
self.write_input_to_child(&mut child, &book, ctx);
let output = child.wait_with_output().with_context(|| {
format!(
"Error waiting for the \"{}\" preprocessor to complete",
self.name
)
})?;
let output = child
.wait_with_output()
.with_context(|| "Error waiting for the preprocessor to complete")?;
trace!("{} exited with output: {:?}", self.cmd, output);
ensure!(
output.status.success(),
format!(
"The \"{}\" preprocessor exited unsuccessfully with {} status",
self.name, output.status
)
"The preprocessor exited unsuccessfully"
);
serde_json::from_slice(&output.stdout).with_context(|| {
format!(
"Unable to parse the preprocessed book from \"{}\" processor",
self.name
)
})
serde_json::from_slice(&output.stdout)
.with_context(|| "Unable to parse the preprocessed book")
}
fn supports_renderer(&self, renderer: &str) -> bool {

View File

@@ -23,7 +23,6 @@ const MAX_LINK_NESTED_DEPTH: usize = 10;
/// This hides the lines from initial display but shows them when the reader expands the code
/// block and provides them to Rustdoc for testing.
/// - `{{# playground}}` - Insert runnable Rust files
/// - `{{# title}}` - Override \<title\> of a webpage.
#[derive(Default)]
pub struct LinkPreprocessor;
@@ -52,15 +51,8 @@ impl Preprocessor for LinkPreprocessor {
.map(|dir| src_dir.join(dir))
.expect("All book items have a parent");
let mut chapter_title = ch.name.clone();
let content =
replace_all(&ch.content, base, chapter_path, 0, &mut chapter_title);
let content = replace_all(&ch.content, base, chapter_path, 0);
ch.content = content;
if chapter_title != ch.name {
ctx.chapter_titles
.borrow_mut()
.insert(chapter_path.clone(), chapter_title);
}
}
}
});
@@ -69,13 +61,7 @@ impl Preprocessor for LinkPreprocessor {
}
}
fn replace_all<P1, P2>(
s: &str,
path: P1,
source: P2,
depth: usize,
chapter_title: &mut String,
) -> String
fn replace_all<P1, P2>(s: &str, path: P1, source: P2, depth: usize) -> String
where
P1: AsRef<Path>,
P2: AsRef<Path>,
@@ -91,17 +77,11 @@ where
for link in find_links(s) {
replaced.push_str(&s[previous_end_index..link.start_index]);
match link.render_with_path(&path, chapter_title) {
match link.render_with_path(&path) {
Ok(new_content) => {
if depth < MAX_LINK_NESTED_DEPTH {
if let Some(rel_path) = link.link_type.relative_path(path) {
replaced.push_str(&replace_all(
&new_content,
rel_path,
source,
depth + 1,
chapter_title,
));
replaced.push_str(&replace_all(&new_content, rel_path, source, depth + 1));
} else {
replaced.push_str(&new_content);
}
@@ -136,7 +116,6 @@ enum LinkType<'a> {
Include(PathBuf, RangeOrAnchor),
Playground(PathBuf, Vec<&'a str>),
RustdocInclude(PathBuf, RangeOrAnchor),
Title(&'a str),
}
#[derive(PartialEq, Debug, Clone)]
@@ -206,7 +185,6 @@ impl<'a> LinkType<'a> {
LinkType::Include(p, _) => Some(return_relative_path(base, &p)),
LinkType::Playground(p, _) => Some(return_relative_path(base, &p)),
LinkType::RustdocInclude(p, _) => Some(return_relative_path(base, &p)),
LinkType::Title(_) => None,
}
}
}
@@ -277,9 +255,6 @@ struct Link<'a> {
impl<'a> Link<'a> {
fn from_capture(cap: Captures<'a>) -> Option<Link<'a>> {
let link_type = match (cap.get(0), cap.get(1), cap.get(2)) {
(_, Some(typ), Some(title)) if typ.as_str() == "title" => {
Some(LinkType::Title(title.as_str()))
}
(_, Some(typ), Some(rest)) => {
let mut path_props = rest.as_str().split_whitespace();
let file_arg = path_props.next();
@@ -316,11 +291,7 @@ impl<'a> Link<'a> {
})
}
fn render_with_path<P: AsRef<Path>>(
&self,
base: P,
chapter_title: &mut String,
) -> Result<String> {
fn render_with_path<P: AsRef<Path>>(&self, base: P) -> Result<String> {
let base = base.as_ref();
match self.link_type {
// omit the escape char
@@ -364,7 +335,7 @@ impl<'a> Link<'a> {
LinkType::Playground(ref pat, ref attrs) => {
let target = base.join(pat);
let mut contents = fs::read_to_string(&target).with_context(|| {
let contents = fs::read_to_string(&target).with_context(|| {
format!(
"Could not read file for link {} ({})",
self.link_text,
@@ -372,20 +343,13 @@ impl<'a> Link<'a> {
)
})?;
let ftype = if !attrs.is_empty() { "rust," } else { "rust" };
if !contents.ends_with('\n') {
contents.push('\n');
}
Ok(format!(
"```{}{}\n{}```\n",
"```{}{}\n{}\n```\n",
ftype,
attrs.join(","),
contents
))
}
LinkType::Title(title) => {
*chapter_title = title.to_owned();
Ok(String::new())
}
}
}
}
@@ -406,17 +370,17 @@ impl<'a> Iterator for LinkIter<'a> {
fn find_links(contents: &str) -> LinkIter<'_> {
// lazily compute following regex
// r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([^}]+)\}\}")?;
// r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([a-zA-Z0-9_.\-:/\\\s]+)\}\}")?;
lazy_static! {
static ref RE: Regex = Regex::new(
r"(?x) # insignificant whitespace mode
\\\{\{\#.*\}\} # match escaped link
| # or
\{\{\s* # link opening parens and whitespace
\#([a-zA-Z0-9_]+) # link type
\s+ # separating whitespace
([^}]+) # link target path and space separated properties
\}\} # link closing parens"
r"(?x) # insignificant whitespace mode
\\\{\{\#.*\}\} # match escaped link
| # or
\{\{\s* # link opening parens and whitespace
\#([a-zA-Z0-9_]+) # link type
\s+ # separating whitespace
([a-zA-Z0-9\s_.\-:/\\\+]+) # link target path and space separated properties
\s*\}\} # whitespace and link closing parens"
)
.unwrap();
}
@@ -439,21 +403,7 @@ mod tests {
```hbs
{{#include file.rs}} << an escaped link!
```";
let mut chapter_title = "test_replace_all_escaped".to_owned();
assert_eq!(replace_all(start, "", "", 0, &mut chapter_title), end);
}
#[test]
fn test_set_chapter_title() {
let start = r"{{#title My Title}}
# My Chapter
";
let end = r"
# My Chapter
";
let mut chapter_title = "test_set_chapter_title".to_owned();
assert_eq!(replace_all(start, "", "", 0, &mut chapter_title), end);
assert_eq!(chapter_title, "My Title");
assert_eq!(replace_all(start, "", "", 0), end);
}
#[test]

View File

@@ -12,8 +12,6 @@ use crate::book::Book;
use crate::config::Config;
use crate::errors::*;
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::PathBuf;
/// Extra information for a `Preprocessor` to give them more context when
@@ -29,8 +27,6 @@ pub struct PreprocessorContext {
/// The calling `mdbook` version.
pub mdbook_version: String,
#[serde(skip)]
pub(crate) chapter_titles: RefCell<HashMap<PathBuf, String>>,
#[serde(skip)]
__non_exhaustive: (),
}
@@ -42,7 +38,6 @@ impl PreprocessorContext {
config,
renderer,
mdbook_version: crate::MDBOOK_VERSION.to_string(),
chapter_titles: RefCell::new(HashMap::new()),
__non_exhaustive: (),
}
}

View File

@@ -1,5 +1,5 @@
use crate::book::{Book, BookItem};
use crate::config::{BookConfig, Config, HtmlConfig, Playground, RustEdition};
use crate::config::{Config, HtmlConfig, Playground, RustEdition};
use crate::errors::*;
use crate::renderer::html_handlebars::helpers;
use crate::renderer::{RenderContext, Renderer};
@@ -37,32 +37,14 @@ impl HtmlHandlebars {
_ => return Ok(()),
};
if let Some(ref edit_url_template) = ctx.html_config.edit_url_template {
let full_path = ctx.book_config.src.to_str().unwrap_or_default().to_owned()
+ "/"
+ ch.source_path
.clone()
.unwrap_or_default()
.to_str()
.unwrap_or_default();
let edit_url = edit_url_template.replace("{path}", &full_path);
ctx.data
.insert("git_repository_edit_url".to_owned(), json!(edit_url));
}
let content = ch.content.clone();
let content = utils::render_markdown(&content, ctx.html_config.curly_quotes);
let fixed_content =
utils::render_markdown_with_path(&ch.content, ctx.html_config.curly_quotes, Some(path));
if !ctx.is_index && ctx.html_config.print.page_break {
// Add page break between chapters
// See https://developer.mozilla.org/en-US/docs/Web/CSS/break-before and https://developer.mozilla.org/en-US/docs/Web/CSS/page-break-before
// Add both two CSS properties because of the compatibility issue
print_content
.push_str(r#"<div style="break-before: page; page-break-before: always;"></div>"#);
}
let fixed_content = utils::render_markdown_with_path(
&ch.content,
ctx.html_config.curly_quotes,
Some(&path),
);
print_content.push_str(&fixed_content);
// Update the context with data for this file
@@ -82,12 +64,9 @@ impl HtmlHandlebars {
.and_then(serde_json::Value::as_str)
.unwrap_or("");
let title = if let Some(title) = ctx.chapter_titles.get(path) {
title.clone()
} else if book_title.is_empty() {
ch.name.clone()
} else {
ch.name.clone() + " - " + book_title
let title = match book_title {
"" => ch.name.clone(),
_ => ch.name.clone() + " - " + book_title,
};
ctx.data.insert("path".to_owned(), json!(path));
@@ -131,7 +110,7 @@ impl HtmlHandlebars {
&self,
ctx: &RenderContext,
html_config: &HtmlConfig,
src_dir: &Path,
src_dir: &PathBuf,
handlebars: &mut Handlebars<'_>,
data: &mut serde_json::Map<String, serde_json::Value>,
) -> Result<()> {
@@ -170,19 +149,12 @@ impl HtmlHandlebars {
// Set a dummy path to ensure other paths (e.g. in the TOC) are generated correctly
data_404.insert("path".to_owned(), json!("404.md"));
data_404.insert("content".to_owned(), json!(html_content_404));
let mut title = String::from("Page not found");
if let Some(book_title) = &ctx.config.book.title {
title.push_str(" - ");
title.push_str(book_title);
}
data_404.insert("title".to_owned(), json!(title));
let rendered = handlebars.render("index", &data_404)?;
let rendered =
self.post_process(rendered, &html_config.playground, ctx.config.rust.edition);
let output_file = get_404_output_file(&html_config.input_404);
utils::fs::write_file(destination, output_file, rendered.as_bytes())?;
utils::fs::write_file(&destination, output_file, rendered.as_bytes())?;
debug!("Creating 404.html ✓");
Ok(())
}
@@ -227,10 +199,10 @@ impl HtmlHandlebars {
}
write_file(destination, "css/variables.css", &theme.variables_css)?;
if let Some(contents) = &theme.favicon_png {
write_file(destination, "favicon.png", contents)?;
write_file(destination, "favicon.png", &contents)?;
}
if let Some(contents) = &theme.favicon_svg {
write_file(destination, "favicon.svg", contents)?;
write_file(destination, "favicon.svg", &contents)?;
}
write_file(destination, "highlight.css", &theme.highlight_css)?;
write_file(destination, "tomorrow-night.css", &theme.tomorrow_night_css)?;
@@ -394,7 +366,7 @@ impl HtmlHandlebars {
// Note: all paths are relative to the build directory, so the
// leading slash in an absolute path means nothing (and would mess
// up `root.join(original)`).
let original = original.trim_start_matches('/');
let original = original.trim_start_matches("/");
let filename = root.join(original);
self.emit_redirect(handlebars, &filename, new)?;
}
@@ -465,7 +437,6 @@ impl Renderer for HtmlHandlebars {
}
fn render(&self, ctx: &RenderContext) -> Result<()> {
let book_config = &ctx.config.book;
let html_config = ctx.config.html_config().unwrap_or_default();
let src_dir = ctx.root.join(&ctx.config.book.src);
let destination = &ctx.destination;
@@ -481,7 +452,7 @@ impl Renderer for HtmlHandlebars {
let mut handlebars = Handlebars::new();
let theme_dir = match html_config.theme {
Some(ref theme) => ctx.root.join(theme),
Some(ref theme) => theme.to_path_buf(),
None => ctx.root.join("theme"),
};
@@ -513,7 +484,7 @@ impl Renderer for HtmlHandlebars {
debug!("Register handlebars helpers");
self.register_hbs_helpers(&mut handlebars, &html_config);
let mut data = make_data(&ctx.root, book, &ctx.config, &html_config, &theme)?;
let mut data = make_data(&ctx.root, &book, &ctx.config, &html_config, &theme)?;
// Print version
let mut print_content = String::new();
@@ -528,10 +499,8 @@ impl Renderer for HtmlHandlebars {
destination: destination.to_path_buf(),
data: data.clone(),
is_index,
book_config: book_config.clone(),
html_config: html_config.clone(),
edition: ctx.config.rust.edition,
chapter_titles: &ctx.chapter_titles,
};
self.render_item(item, ctx, &mut print_content)?;
is_index = false;
@@ -556,14 +525,14 @@ impl Renderer for HtmlHandlebars {
let rendered =
self.post_process(rendered, &html_config.playground, ctx.config.rust.edition);
utils::fs::write_file(destination, "print.html", rendered.as_bytes())?;
utils::fs::write_file(&destination, "print.html", rendered.as_bytes())?;
debug!("Creating print.html ✓");
}
debug!("Copy static files");
self.copy_static_files(destination, &theme, &html_config)
self.copy_static_files(&destination, &theme, &html_config)
.with_context(|| "Unable to copy across static files")?;
self.copy_additional_css_and_js(&html_config, &ctx.root, destination)
self.copy_additional_css_and_js(&html_config, &ctx.root, &destination)
.with_context(|| "Unable to copy across additional CSS and JS")?;
// Render search index
@@ -571,7 +540,7 @@ impl Renderer for HtmlHandlebars {
{
let search = html_config.search.unwrap_or_default();
if search.enable {
super::search::create_files(&search, destination, book)?;
super::search::create_files(&search, &destination, &book)?;
}
}
@@ -579,7 +548,7 @@ impl Renderer for HtmlHandlebars {
.context("Unable to emit redirects")?;
// Copy all remaining files, avoid a recursive copy from/to the book build dir
utils::fs::copy_files_except_ext(&src_dir, destination, true, Some(&build_dir), &["md"])?;
utils::fs::copy_files_except_ext(&src_dir, &destination, true, Some(&build_dir), &["md"])?;
Ok(())
}
@@ -613,11 +582,8 @@ fn make_data(
if theme.favicon_svg.is_some() {
data.insert("favicon_svg".to_owned(), json!("favicon.svg"));
}
if let Some(ref live_reload_endpoint) = html_config.live_reload_endpoint {
data.insert(
"live_reload_endpoint".to_owned(),
json!(live_reload_endpoint),
);
if let Some(ref livereload) = html_config.livereload_url {
data.insert("livereload".to_owned(), json!(livereload));
}
let default_theme = match html_config.default_theme {
@@ -778,10 +744,19 @@ fn insert_link_into_header(
content: &str,
id_counter: &mut HashMap<String, usize>,
) -> String {
let id = utils::unique_id_from_content(content, id_counter);
let raw_id = utils::id_from_content(content);
let id_count = id_counter.entry(raw_id.clone()).or_insert(0);
let id = match *id_count {
0 => raw_id,
other => format!("{}-{}", raw_id, other),
};
*id_count += 1;
format!(
r##"<h{level} id="{id}"><a class="header" href="#{id}">{text}</a></h{level}>"##,
r##"<h{level}><a class="header" href="#{id}" id="{id}">{text}</a></h{level}>"##,
level = level,
id = id,
text = content
@@ -829,21 +804,18 @@ fn add_playground_pre(
if classes.contains("language-rust") {
if (!classes.contains("ignore")
&& !classes.contains("noplayground")
&& !classes.contains("noplaypen")
&& playground_config.runnable)
&& !classes.contains("noplaypen"))
|| classes.contains("mdbook-runnable")
{
let contains_e2015 = classes.contains("edition2015");
let contains_e2018 = classes.contains("edition2018");
let contains_e2021 = classes.contains("edition2021");
let edition_class = if contains_e2015 || contains_e2018 || contains_e2021 {
let edition_class = if contains_e2015 || contains_e2018 {
// the user forced edition, we should not overwrite it
""
} else {
match edition {
Some(RustEdition::E2015) => " edition2015",
Some(RustEdition::E2018) => " edition2018",
Some(RustEdition::E2021) => " edition2021",
None => "",
}
};
@@ -927,10 +899,10 @@ fn partition_source(s: &str) -> (String, String) {
if !header || after_header {
after_header = true;
after.push_str(line);
after.push('\n');
after.push_str("\n");
} else {
before.push_str(line);
before.push('\n');
before.push_str("\n");
}
}
@@ -942,10 +914,8 @@ struct RenderItemContext<'a> {
destination: PathBuf,
data: serde_json::Map<String, serde_json::Value>,
is_index: bool,
book_config: BookConfig,
html_config: HtmlConfig,
edition: Option<RustEdition>,
chapter_titles: &'a HashMap<PathBuf, String>,
}
#[cfg(test)]
@@ -957,32 +927,32 @@ mod tests {
let inputs = vec![
(
"blah blah <h1>Foo</h1>",
r##"blah blah <h1 id="foo"><a class="header" href="#foo">Foo</a></h1>"##,
r##"blah blah <h1><a class="header" href="#foo" id="foo">Foo</a></h1>"##,
),
(
"<h1>Foo</h1>",
r##"<h1 id="foo"><a class="header" href="#foo">Foo</a></h1>"##,
r##"<h1><a class="header" href="#foo" id="foo">Foo</a></h1>"##,
),
(
"<h3>Foo^bar</h3>",
r##"<h3 id="foobar"><a class="header" href="#foobar">Foo^bar</a></h3>"##,
r##"<h3><a class="header" href="#foobar" id="foobar">Foo^bar</a></h3>"##,
),
(
"<h4></h4>",
r##"<h4 id=""><a class="header" href="#"></a></h4>"##,
r##"<h4><a class="header" href="#" id=""></a></h4>"##,
),
(
"<h4><em>Hï</em></h4>",
r##"<h4 id="hï"><a class="header" href="#hï"><em>Hï</em></a></h4>"##,
r##"<h4><a class="header" href="#hï" id="hï"><em>Hï</em></a></h4>"##,
),
(
"<h1>Foo</h1><h3>Foo</h3>",
r##"<h1 id="foo"><a class="header" href="#foo">Foo</a></h1><h3 id="foo-1"><a class="header" href="#foo-1">Foo</a></h3>"##,
r##"<h1><a class="header" href="#foo" id="foo">Foo</a></h1><h3><a class="header" href="#foo-1" id="foo-1">Foo</a></h3>"##,
),
];
for (src, should_be) in inputs {
let got = build_header_links(src);
let got = build_header_links(&src);
assert_eq!(got, should_be);
}
}
@@ -1065,28 +1035,4 @@ mod tests {
assert_eq!(&*got, *should_be);
}
}
#[test]
fn add_playground_edition2021() {
let inputs = [
("<code class=\"language-rust\">x()</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2021\">\n<span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
("<code class=\"language-rust\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2021\">fn main() {}\n</code></pre>"),
("<code class=\"language-rust edition2015\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
("<code class=\"language-rust edition2018\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
];
for (src, should_be) in &inputs {
let got = add_playground_pre(
src,
&Playground {
editable: true,
..Playground::default()
},
Some(RustEdition::E2021),
);
assert_eq!(&*got, *should_be);
}
}
}

View File

@@ -91,7 +91,7 @@ fn find_chapter(
match item.get("path") {
Some(path) if !path.is_empty() => {
if let Some(previous) = previous {
if let Some(item) = target.find(&base_path, path, &item, &previous)? {
if let Some(item) = target.find(&base_path, &path, &item, &previous)? {
return Ok(Some(item));
}
}

View File

@@ -1,5 +1,4 @@
use std::collections::BTreeMap;
use std::io;
use std::path::Path;
use crate::utils;
@@ -103,7 +102,7 @@ impl HelperDef for RenderToc {
// Part title
if let Some(title) = item.get("part") {
out.write("<li class=\"part-title\">")?;
write_escaped(out, title)?;
out.write(title)?;
out.write("</li>")?;
continue;
}
@@ -142,7 +141,7 @@ impl HelperDef for RenderToc {
// Section does not necessarily exist
if let Some(section) = item.get("section") {
out.write("<strong aria-hidden=\"true\">")?;
out.write(section)?;
out.write(&section)?;
out.write("</strong> ")?;
}
}
@@ -161,7 +160,7 @@ impl HelperDef for RenderToc {
html::push_html(&mut markdown_parsed_name, parser);
// write to the handlebars template
write_escaped(out, &markdown_parsed_name)?;
out.write(&markdown_parsed_name)?;
}
if path_exists {
@@ -205,18 +204,3 @@ fn write_li_open_tag(
li.push_str("\">");
out.write(&li)
}
fn write_escaped(out: &mut dyn Output, mut title: &str) -> io::Result<()> {
let needs_escape: &[char] = &['<', '>'];
while let Some(next) = title.find(needs_escape) {
out.write(&title[..next])?;
match title.as_bytes()[next] {
b'<' => out.write("&lt;")?,
b'>' => out.write("&gt;")?,
_ => unreachable!(),
}
title = &title[next + 1..];
}
out.write(title)?;
Ok(())
}

View File

@@ -17,10 +17,10 @@ pub fn create_files(search_config: &Search, destination: &Path, book: &Book) ->
let mut doc_urls = Vec::with_capacity(book.sections.len());
for item in book.iter() {
render_item(&mut index, search_config, &mut doc_urls, item)?;
render_item(&mut index, &search_config, &mut doc_urls, item)?;
}
let index = write_to_json(index, search_config, doc_urls)?;
let index = write_to_json(index, &search_config, doc_urls)?;
debug!("Writing search index ✓");
if index.len() > 10_000_000 {
warn!("searchindex.json is very large ({} bytes)", index.len());
@@ -85,7 +85,7 @@ fn render_item(
.with_context(|| "Could not convert HTML path to str")?;
let anchor_base = utils::fs::normalize_path(filepath);
let mut p = utils::new_cmark_parser(&chapter.content, false).peekable();
let mut p = utils::new_cmark_parser(&chapter.content).peekable();
let mut in_heading = false;
let max_section_depth = u32::from(search_config.heading_split_level);
@@ -95,12 +95,9 @@ fn render_item(
let mut breadcrumbs = chapter.parent_names.clone();
let mut footnote_numbers = HashMap::new();
breadcrumbs.push(chapter.name.clone());
let mut id_counter = HashMap::new();
while let Some(event) = p.next() {
match event {
Event::Start(Tag::Heading(i, ..)) if i as u32 <= max_section_depth => {
Event::Start(Tag::Heading(i)) if i <= max_section_depth => {
if !heading.is_empty() {
// Section finished, the next heading is following now
// Write the data to the index, and clear it for the next section
@@ -119,9 +116,9 @@ fn render_item(
in_heading = true;
}
Event::End(Tag::Heading(i, ..)) if i as u32 <= max_section_depth => {
Event::End(Tag::Heading(i)) if i <= max_section_depth => {
in_heading = false;
section_id = Some(utils::unique_id_from_content(&heading, &mut id_counter));
section_id = Some(utils::id_from_content(&heading));
breadcrumbs.push(heading.clone());
}
Event::Start(Tag::FootnoteDefinition(name)) => {
@@ -135,14 +132,14 @@ fn render_item(
// in an HtmlBlock tag. We must collect consecutive Html events
// into a block ourselves.
while let Some(Event::Html(html)) = p.peek() {
html_block.push_str(html);
html_block.push_str(&html);
p.next();
}
body.push_str(&clean_html(&html_block));
}
Event::Start(_) | Event::End(_) | Event::Rule | Event::SoftBreak | Event::HardBreak => {
// Insert spaces where HTML output would usually separate text
// Insert spaces where HTML output would usually seperate text
// to ensure words don't get merged together
if in_heading {
heading.push(' ');
@@ -166,12 +163,7 @@ fn render_item(
}
}
if !body.is_empty() || !heading.is_empty() {
if heading.is_empty() {
if let Some(chapter) = breadcrumbs.first() {
heading = chapter.clone();
}
}
if !heading.is_empty() {
// Make sure the last section is added to the index
add_doc(
index,

View File

@@ -18,10 +18,9 @@ mod html_handlebars;
mod markdown_renderer;
use shlex::Shlex;
use std::collections::HashMap;
use std::fs;
use std::io::{self, ErrorKind, Read};
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::process::{Command, Stdio};
use crate::book::Book;
@@ -35,9 +34,12 @@ use toml::Value;
/// provide your own renderer, there are two main renderer implementations that
/// 99% of users will ever use:
///
/// - [`HtmlHandlebars`] - the built-in HTML renderer
/// - [`CmdRenderer`] - a generic renderer which shells out to a program to do the
/// - [HtmlHandlebars] - the built-in HTML renderer
/// - [CmdRenderer] - a generic renderer which shells out to a program to do the
/// actual rendering
///
/// [HtmlHandlebars]: struct.HtmlHandlebars.html
/// [CmdRenderer]: struct.CmdRenderer.html
pub trait Renderer {
/// The `Renderer`'s name.
fn name(&self) -> &str;
@@ -65,8 +67,6 @@ pub struct RenderContext {
/// guaranteed to be empty or even exist.
pub destination: PathBuf,
#[serde(skip)]
pub(crate) chapter_titles: HashMap<PathBuf, String>,
#[serde(skip)]
__non_exhaustive: (),
}
@@ -83,7 +83,6 @@ impl RenderContext {
version: crate::MDBOOK_VERSION.to_string(),
root: root.into(),
destination: destination.into(),
chapter_titles: HashMap::new(),
__non_exhaustive: (),
}
}
@@ -134,44 +133,14 @@ impl CmdRenderer {
CmdRenderer { name, cmd }
}
fn compose_command(&self, root: &Path, destination: &Path) -> Result<Command> {
fn compose_command(&self) -> Result<Command> {
let mut words = Shlex::new(&self.cmd);
let exe = match words.next() {
Some(e) => PathBuf::from(e),
let executable = match words.next() {
Some(e) => e,
None => bail!("Command string was empty"),
};
let exe = if exe.components().count() == 1 {
// Search PATH for the executable.
exe
} else {
// Relative paths are preferred to be relative to the book root.
let abs_exe = root.join(&exe);
if abs_exe.exists() {
abs_exe
} else {
// Historically paths were relative to the destination, but
// this is not the preferred way.
let legacy_path = destination.join(&exe);
if legacy_path.exists() {
warn!(
"Renderer command `{}` uses a path relative to the \
renderer output directory `{}`. This was previously \
accepted, but has been deprecated. Relative executable \
paths should be relative to the book root.",
exe.display(),
destination.display()
);
legacy_path
} else {
// Let this bubble through to later be handled by
// handle_render_command_error.
abs_exe
}
}
};
let mut cmd = Command::new(exe);
let mut cmd = Command::new(executable);
for arg in words {
cmd.arg(arg);
@@ -226,7 +195,7 @@ impl Renderer for CmdRenderer {
let _ = fs::create_dir_all(&ctx.destination);
let mut child = match self
.compose_command(&ctx.root, &ctx.destination)?
.compose_command()?
.stdin(Stdio::piped())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())

View File

@@ -108,12 +108,9 @@ function playground_text(playground) {
let text = playground_text(code_block);
let classes = code_block.querySelector('code').classList;
let edition = "2015";
if(classes.contains("edition2018")) {
edition = "2018";
} else if(classes.contains("edition2021")) {
edition = "2021";
}
let has_2018 = classes.contains("edition2018");
let edition = has_2018 ? "2018" : "2015";
var params = {
version: "stable",
optimize: "0",
@@ -136,15 +133,7 @@ function playground_text(playground) {
body: JSON.stringify(params)
})
.then(response => response.json())
.then(response => {
if (response.result.trim() === '') {
result_block.innerText = "No output";
result_block.classList.add("result-no-output");
} else {
result_block.innerText = response.result;
result_block.classList.remove("result-no-output");
}
})
.then(response => result_block.innerText = response.result)
.catch(error => result_block.innerText = "Playground Communication: " + error.message);
}
@@ -162,13 +151,12 @@ function playground_text(playground) {
if (window.ace) {
// language-rust class needs to be removed for editable
// blocks or highlightjs will capture events
code_nodes
.filter(function (node) {return node.classList.contains("editable"); })
Array
.from(document.querySelectorAll('code.editable'))
.forEach(function (block) { block.classList.remove('language-rust'); });
Array
code_nodes
.filter(function (node) {return !node.classList.contains("editable"); })
.from(document.querySelectorAll('code:not(.editable)'))
.forEach(function (block) { hljs.highlightBlock(block); });
} else {
code_nodes.forEach(function (block) { hljs.highlightBlock(block); });
@@ -371,14 +359,7 @@ function playground_text(playground) {
});
themePopup.addEventListener('click', function (e) {
var theme;
if (e.target.className === "theme") {
theme = e.target.id;
} else if (e.target.parentElement.className === "theme") {
theme = e.target.parentElement.id;
} else {
return;
}
var theme = e.target.id || e.target.parentElement.id;
set_theme(theme);
});

View File

@@ -93,7 +93,7 @@ a > .hljs {
.menu-title {
display: inline-block;
font-weight: 200;
font-size: 2.4rem;
font-size: 2rem;
line-height: var(--menu-bar-height);
text-align: center;
margin: 0;

View File

@@ -12,7 +12,6 @@ html {
color: var(--fg);
background-color: var(--bg);
text-size-adjust: none;
-webkit-text-size-adjust: none;
}
body {
@@ -46,23 +45,20 @@ h4, h5 { margin-top: 2em; }
margin-top: 1em;
}
h1:target::before,
h2:target::before,
h3:target::before,
h4:target::before,
h5:target::before,
h6:target::before {
h1 a.header:target::before,
h2 a.header:target::before,
h3 a.header:target::before,
h4 a.header:target::before {
display: inline-block;
content: "»";
margin-left: -30px;
width: 30px;
}
/* This is broken on Safari as of version 14, but is fixed
in Safari Technology Preview 117 which I think will be Safari 14.2.
https://bugs.webkit.org/show_bug.cgi?id=218076
*/
:target {
h1 a.header:target,
h2 a.header:target,
h3 a.header:target,
h4 a.header:target {
scroll-margin-top: calc(var(--menu-bar-height) + 0.5em);
}
@@ -93,7 +89,7 @@ h6:target::before {
.content ul { line-height: 1.45em; }
.content a { text-decoration: none; }
.content a:hover { text-decoration: underline; }
.content img, .content video { max-width: 100%; }
.content img { max-width: 100%; }
.content .header:link,
.content .header:visited {
color: var(--fg);
@@ -176,7 +172,3 @@ blockquote {
margin: 5px 0px;
font-weight: bold;
}
.result-no-output {
font-style: italic;
}

View File

@@ -67,7 +67,7 @@
--links: #2b79a2;
--inline-code-color: #c5c8c6;
--inline-code-color: #c5c8c6;;
--theme-popup-bg: #141617;
--theme-popup-border: #43484d;
@@ -92,22 +92,22 @@
.light {
--bg: hsl(0, 0%, 100%);
--fg: hsl(0, 0%, 0%);
--fg: #333333;
--sidebar-bg: #fafafa;
--sidebar-fg: hsl(0, 0%, 0%);
--sidebar-fg: #364149;
--sidebar-non-existant: #aaaaaa;
--sidebar-active: #1f1fff;
--sidebar-active: #008cff;
--sidebar-spacer: #f4f4f4;
--scrollbar: #8F8F8F;
--scrollbar: #cccccc;
--icons: #747474;
--icons-hover: #000000;
--icons: #cccccc;
--icons-hover: #333333;
--links: #20609f;
--links: #4183c4;
--inline-code-color: #301900;
--inline-code-color: #6e6b5e;
--theme-popup-bg: #fafafa;
--theme-popup-border: #cccccc;
@@ -147,7 +147,7 @@
--links: #2b79a2;
--inline-code-color: #c5c8c6;
--inline-code-color: #c5c8c6;;
--theme-popup-bg: #161923;
--theme-popup-border: #737480;
@@ -228,7 +228,7 @@
--links: #2b79a2;
--inline-code-color: #c5c8c6;
--inline-code-color: #c5c8c6;;
--theme-popup-bg: #141617;
--theme-popup-border: #43484d;

View File

@@ -1,18 +1,14 @@
/*
* An increased contrast highlighting scheme loosely based on the
* "Base16 Atelier Dune Light" theme by Bram de Haan
* (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune)
* Original Base16 color scheme by Chris Kempson
* (https://github.com/chriskempson/base16)
*/
/* Base16 Atelier Dune Light - Theme */
/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */
/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */
/* Comment */
/* Atelier-Dune Comment */
.hljs-comment,
.hljs-quote {
color: #575757;
color: #AAA;
}
/* Red */
/* Atelier-Dune Red */
.hljs-variable,
.hljs-template-variable,
.hljs-attribute,
@@ -23,10 +19,10 @@
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: #d70025;
color: #d73737;
}
/* Orange */
/* Atelier-Dune Orange */
.hljs-number,
.hljs-meta,
.hljs-built_in,
@@ -34,33 +30,33 @@
.hljs-literal,
.hljs-type,
.hljs-params {
color: #b21e00;
color: #b65611;
}
/* Green */
/* Atelier-Dune Green */
.hljs-string,
.hljs-symbol,
.hljs-bullet {
color: #008200;
color: #60ac39;
}
/* Blue */
/* Atelier-Dune Blue */
.hljs-title,
.hljs-section {
color: #0030f2;
color: #6684e1;
}
/* Purple */
/* Atelier-Dune Purple */
.hljs-keyword,
.hljs-selector-tag {
color: #9d00ec;
color: #b854d4;
}
.hljs {
display: block;
overflow-x: auto;
background: #f6f7f6;
color: #000;
background: #f1f1f1;
color: #6e6b5e;
padding: 0.5em;
}

View File

@@ -148,19 +148,13 @@
<i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
</a>
{{/if}}
{{#if git_repository_edit_url}}
<a href="{{git_repository_edit_url}}" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></i>
</a>
{{/if}}
</div>
</div>
{{#if search_enabled}}
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
@@ -219,12 +213,10 @@
</div>
{{#if live_reload_endpoint}}
{{#if livereload}}
<!-- Livereload script (if served using the cli tool) -->
<script type="text/javascript">
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
const socket = new WebSocket(wsAddress);
var socket = new WebSocket("{{{livereload}}}");
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();

View File

@@ -296,7 +296,7 @@ window.search = window.search || {};
}
if (url.params.hasOwnProperty(URL_MARK_PARAM)) {
var words = decodeURIComponent(url.params[URL_MARK_PARAM]).split(' ');
var words = url.params[URL_MARK_PARAM].split(' ');
marker.mark(words, {
exclude: mark_exclude
});
@@ -427,7 +427,6 @@ window.search = window.search || {};
delete url.params[URL_MARK_PARAM];
url.hash = "";
} else {
delete url.params[URL_MARK_PARAM];
delete url.params[URL_SEARCH_PARAM];
}
// A new search will also add a new history item, so the user can go back

View File

@@ -4,7 +4,7 @@ use std::fs::{self, File};
use std::io::Write;
use std::path::{Component, Path, PathBuf};
/// Naively replaces any path separator with a forward-slash '/'
/// Naively replaces any path seperator with a forward-slash '/'
pub fn normalize_path(path: &str) -> String {
use std::path::is_separator;
path.chars()
@@ -110,10 +110,7 @@ pub fn copy_files_except_ext(
for entry in fs::read_dir(from)? {
let entry = entry?;
let metadata = entry
.path()
.metadata()
.with_context(|| format!("Failed to read {:?}", entry.path()))?;
let metadata = entry.path().metadata()?;
// If the entry is a dir and the recursive option is enabled, call itself
if metadata.is_dir() && recursive {
@@ -247,7 +244,7 @@ mod tests {
}
if let Err(e) =
copy_files_except_ext(tmp.path(), &tmp.path().join("output"), true, None, &["md"])
copy_files_except_ext(&tmp.path(), &tmp.path().join("output"), true, None, &["md"])
{
panic!("Error while executing the function:\n{:?}", e);
}

View File

@@ -9,7 +9,6 @@ use regex::Regex;
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag};
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::Write;
use std::path::Path;
@@ -45,47 +44,33 @@ pub fn normalize_id(content: &str) -> String {
/// Generate an ID for use with anchors which is derived from a "normalised"
/// string.
// This function should be made private when the deprecation expires.
#[deprecated(since = "0.4.16", note = "use unique_id_from_content instead")]
pub fn id_from_content(content: &str) -> String {
let mut content = content.to_string();
// Skip any tags or html-encoded stuff
lazy_static! {
static ref HTML: Regex = Regex::new(r"(<.*?>)").unwrap();
}
content = HTML.replace_all(&content, "").into();
const REPL_SUB: &[&str] = &["&lt;", "&gt;", "&amp;", "&#39;", "&quot;"];
const REPL_SUB: &[&str] = &[
"<em>",
"</em>",
"<code>",
"</code>",
"<strong>",
"</strong>",
"&lt;",
"&gt;",
"&amp;",
"&#39;",
"&quot;",
];
for sub in REPL_SUB {
content = content.replace(sub, "");
}
// Remove spaces and hashes indicating a header
let trimmed = content.trim().trim_start_matches('#').trim();
normalize_id(trimmed)
}
/// Generate an ID for use with anchors which is derived from a "normalised"
/// string.
///
/// Each ID returned will be unique, if the same `id_counter` is provided on
/// each call.
pub fn unique_id_from_content(content: &str, id_counter: &mut HashMap<String, usize>) -> String {
let id = {
#[allow(deprecated)]
id_from_content(content)
};
// If we have headers with the same normalized id, append an incrementing counter
let id_count = id_counter.entry(id.clone()).or_insert(0);
let unique_id = match *id_count {
0 => id,
id_count => format!("{}-{}", id, id_count),
};
*id_count += 1;
unique_id
}
/// Fix links to the correct location.
///
/// This adjusts links, such as turning `.md` extensions to `.html`.
@@ -183,40 +168,67 @@ pub fn render_markdown(text: &str, curly_quotes: bool) -> String {
render_markdown_with_path(text, curly_quotes, None)
}
pub fn new_cmark_parser(text: &str, curly_quotes: bool) -> Parser<'_, '_> {
pub fn new_cmark_parser(text: &str) -> Parser<'_> {
let mut opts = Options::empty();
opts.insert(Options::ENABLE_TABLES);
opts.insert(Options::ENABLE_FOOTNOTES);
opts.insert(Options::ENABLE_STRIKETHROUGH);
opts.insert(Options::ENABLE_TASKLISTS);
if curly_quotes {
opts.insert(Options::ENABLE_SMART_PUNCTUATION);
}
Parser::new_ext(text, opts)
}
pub fn render_markdown_with_path(text: &str, curly_quotes: bool, path: Option<&Path>) -> String {
let mut s = String::with_capacity(text.len() * 3 / 2);
let p = new_cmark_parser(text, curly_quotes);
let p = new_cmark_parser(text);
let mut converter = EventQuoteConverter::new(curly_quotes);
let events = p
.map(clean_codeblock_headers)
.map(|event| adjust_links(event, path));
.map(|event| adjust_links(event, path))
.map(|event| converter.convert(event));
html::push_html(&mut s, events);
s
}
struct EventQuoteConverter {
enabled: bool,
convert_text: bool,
}
impl EventQuoteConverter {
fn new(enabled: bool) -> Self {
EventQuoteConverter {
enabled,
convert_text: true,
}
}
fn convert<'a>(&mut self, event: Event<'a>) -> Event<'a> {
if !self.enabled {
return event;
}
match event {
Event::Start(Tag::CodeBlock(_)) => {
self.convert_text = false;
event
}
Event::End(Tag::CodeBlock(_)) => {
self.convert_text = true;
event
}
Event::Text(ref text) if self.convert_text => {
Event::Text(CowStr::from(convert_quotes_to_curly(text)))
}
_ => event,
}
}
}
fn clean_codeblock_headers(event: Event<'_>) -> Event<'_> {
match event {
Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(ref info))) => {
let info: String = info
.chars()
.map(|x| match x {
' ' | '\t' => ',',
_ => x,
})
.filter(|ch| !ch.is_whitespace())
.collect();
let info: String = info.chars().filter(|ch| !ch.is_whitespace()).collect();
Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::from(info))))
}
@@ -224,6 +236,38 @@ fn clean_codeblock_headers(event: Event<'_>) -> Event<'_> {
}
}
fn convert_quotes_to_curly(original_text: &str) -> String {
// We'll consider the start to be "whitespace".
let mut preceded_by_whitespace = true;
original_text
.chars()
.map(|original_char| {
let converted_char = match original_char {
'\'' => {
if preceded_by_whitespace {
''
} else {
''
}
}
'"' => {
if preceded_by_whitespace {
'“'
} else {
'”'
}
}
_ => original_char,
};
preceded_by_whitespace = original_char.is_whitespace();
converted_char
})
.collect()
}
/// Prints a "backtrace" of some `Error`.
pub fn log_backtrace(e: &Error) {
error!("Error: {}", e);
@@ -328,7 +372,7 @@ more text with spaces
```
"#;
let expected = r#"<pre><code class="language-rust,,,,,no_run,,,should_panic,,,,property_3"></code></pre>
let expected = r#"<pre><code class="language-rust,no_run,,,should_panic,,property_3"></code></pre>
"#;
assert_eq!(render_markdown(input, false), expected);
assert_eq!(render_markdown(input, true), expected);
@@ -355,9 +399,8 @@ more text with spaces
}
}
#[allow(deprecated)]
mod id_from_content {
use super::super::id_from_content;
mod html_munging {
use super::super::{id_from_content, normalize_id};
#[test]
fn it_generates_anchors() {
@@ -367,10 +410,6 @@ more text with spaces
);
assert_eq!(id_from_content("## **Bold** title"), "bold-title");
assert_eq!(id_from_content("## `Code` title"), "code-title");
assert_eq!(
id_from_content("## title <span dir=rtl>foo</span>"),
"title-foo"
);
}
#[test]
@@ -385,10 +424,6 @@ more text with spaces
);
assert_eq!(id_from_content("## Über"), "Über");
}
}
mod html_munging {
use super::super::{normalize_id, unique_id_from_content};
#[test]
fn it_normalizes_ids() {
@@ -407,28 +442,24 @@ more text with spaces
assert_eq!(normalize_id("한국어"), "한국어");
assert_eq!(normalize_id(""), "");
}
}
mod convert_quotes_to_curly {
use super::super::convert_quotes_to_curly;
#[test]
fn it_generates_unique_ids_from_content() {
// Same id if not given shared state
assert_eq!(
unique_id_from_content("## 中文標題 CJK title", &mut Default::default()),
"中文標題-cjk-title"
);
assert_eq!(
unique_id_from_content("## 中文標題 CJK title", &mut Default::default()),
"中文標題-cjk-title"
);
fn it_converts_single_quotes() {
assert_eq!(convert_quotes_to_curly("'one', 'two'"), "one, two");
}
// Different id if given shared state
let mut id_counter = Default::default();
assert_eq!(unique_id_from_content("## Über", &mut id_counter), "Über");
assert_eq!(
unique_id_from_content("## 中文標題 CJK title", &mut id_counter),
"中文標題-cjk-title"
);
assert_eq!(unique_id_from_content("## Über", &mut id_counter), "Über-1");
assert_eq!(unique_id_from_content("## Über", &mut id_counter), "Über-2");
#[test]
fn it_converts_double_quotes() {
assert_eq!(convert_quotes_to_curly(r#""one", "two""#), "“one”, “two”");
}
#[test]
fn it_treats_tab_as_whitespace() {
assert_eq!(convert_quotes_to_curly("\t'one'"), "\tone");
}
}
}

View File

@@ -70,7 +70,7 @@ pub fn take_rustdoc_include_lines<R: RangeBounds<usize>>(s: &str, range: R) -> S
output.push_str("# ");
}
output.push_str(line);
output.push('\n');
output.push_str("\n");
}
output.pop();
output
@@ -95,7 +95,7 @@ pub fn take_rustdoc_include_anchored_lines(s: &str, anchor: &str) -> String {
None => {
if !ANCHOR_START.is_match(l) {
output.push_str(l);
output.push('\n');
output.push_str("\n");
}
}
}
@@ -106,7 +106,7 @@ pub fn take_rustdoc_include_anchored_lines(s: &str, anchor: &str) -> String {
} else if !ANCHOR_END.is_match(l) {
output.push_str("# ");
output.push_str(l);
output.push('\n');
output.push_str("\n");
}
}

View File

@@ -53,7 +53,7 @@ impl TomlExt for Value {
}
fn split(key: &str) -> Option<(&str, &str)> {
let ix = key.find('.')?;
let ix = key.find(".")?;
let (head, tail) = key.split_at(ix);
// splitting will leave the "."

View File

@@ -1,27 +0,0 @@
[book]
title = "mdBook test book"
description = "A demo book to test and validate changes"
authors = ["YJDoc2"]
language = "en"
[rust]
edition = "2018"
[output.html]
mathjax-support = true
[output.html.playground]
editable = true
line-numbers = true
[output.html.search]
limit-results = 20
use-boolean-and = true
boost-title = 2
boost-hierarchy = 2
boost-paragraph = 1
expand = true
heading-split-level = 2
[output.html.redirect]
"/format/config.html" = "configuration/index.html"

View File

@@ -1,12 +0,0 @@
# Demo Book
This is a simple demo book, which is intended to be used for verifying and validating style changes in mdBook.
This contains dummy examples of various markdown elements and code languages, so that one can check changes made in mdBook styles.
This rough outline is :
- individual : contains basic markdown elements such as headings, paragraphs, links etc.
- languages : contains a `hello world` in each of supported language to see changes in syntax highlighting
- rust : contains language examples specific to rust, such as play pen, runnable examples etc.
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.

View File

@@ -1,33 +0,0 @@
# Summary
[Prefix Chapter](prefix.md)
---
- [Introduction](README.md)
- [Draft Chapter]()
# Actual Markdown Tag Examples
- [Markdown Individual tags](individual/README.md)
- [Heading](individual/heading.md)
- [Paragraphs](individual/paragraph.md)
- [Line Break](individual/linebreak.md)
- [Emphasis](individual/emphasis.md)
- [Blockquote](individual/blockquote.md)
- [List](individual/list.md)
- [Code](individual/code.md)
- [Image](individual/image.md)
- [Links and Horizontal Rule](individual/link_hr.md)
- [Tables](individual/table.md)
- [Tasks](individual/task.md)
- [Strikethrough](individual/strikethrough.md)
- [Mixed](individual/mixed.md)
- [Languages](languages/README.md)
- [Syntax Highlight](languages/highlight.md)
- [Rust Specific](rust/README.md)
- [Rust Codeblocks](rust/rust_codeblock.md)
---
[Suffix Chapter](suffix.md)

View File

@@ -1,17 +0,0 @@
# Individual Common mark tags
This contains following tags:
- Headings
- Paragraphs
- Line breaks
- Emphasis
- Blockquotes
- Lists
- Code blocks
- Images
- Links and Horizontal rules
- Github tables
- Github Task Lists
- Strikethrough
- Mixed

View File

@@ -1,30 +0,0 @@
# Blockquote
> This is a quoted sentence.
> This is a quoted paragraph
>
> separated lines
> here
> Nested
>
> > Quoted
> > Paragraph
> ### And now,
>
> **Let us _introduce_**
> All kinds of
>
> - tags
> - etc
> - stuff
>
> 1. In
> 2. The
> 3. blockquote
>
> > cause we can
> >
> > > Cause we can

View File

@@ -1,31 +0,0 @@
# Code
This section only does simple code blocks and inline code, detailed syntax highlight and stuff is in the languages section
---
```
This is a codeblock
```
---
This line contains `inline code`
---
````
escaping ``` in ```, fun, isn't is?
````
---
```bash,editable
This is an editable codeblock
```
---
```rust
// This links to a playpen
```

View File

@@ -1,13 +0,0 @@
# Emphasis
This has **bold text** in between normal.
This has _italic text_ in between normal.
A **line** having _both_, bold and italic text.
**A bold line _having_ italic text**
_An Italic line having **bold** text_
Now this is going **_out of hands_**.

View File

@@ -1,15 +0,0 @@
# Chapter Heading
---
# Really Big Heading
## Big Heading
### Normal-ish Heading
#### Small Heading...?
##### Really Small Heading
###### Is it even a heading anymore - heading

View File

@@ -1,27 +0,0 @@
# Images
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)
## A 16x16 image
![16x16 rust-lang logo](http://rust-lang.org/logos/rust-logo-16x16.png)
## A 32x32 image
![32x32 rust-lang logo](http://rust-lang.org/logos/rust-logo-32x32-blk.png)
## A 256x256 image
![256x256 rust-lang logo](http://rust-lang.org/logos/rust-logo-256x256.png)
## A 512x512 image
![512x512 rust-lang logo](http://rust-lang.org/logos/rust-logo-512x512-blk.png)
## A large image
![2018 rust-conf art](https://raw.githubusercontent.com/rust-lang/rust-artwork/master/2018-RustConf/lucy-mountain-climber.png)
## A SVG image
![2018 rust-conf art svg](https://raw.githubusercontent.com/rust-lang/rust-artwork/461afe27d8e02451cf9f46e507f2c2a71d2b276b/2018-RustConf/lucy-mountain-climber.svg)

View File

@@ -1,8 +0,0 @@
# Line breaks
This is a long
line with a couple of
line breaks in <br/>
between : both with two
spaces and return, <br/>
and with HTML tags.

View File

@@ -1,15 +0,0 @@
# Links and Horizontal Rule
This is followed by a Horizontal rule
---
And this is preceded by a horizontal rule.
[This](www.rust-lang.org) should link to rust-lang website
[So should this][rl].
**[This][rl]** is a strong link.
_[This][rl]_ is italic.
**_[This][rl]_** is both.
[rl]: www.rust-lang.org

View File

@@ -1,35 +0,0 @@
# Lists
1. A
2. Normal
3. Ordered
4. List
---
1. A
1. Nested
2. List
2. But
3. Still
4. Normal
---
- An
- Unordered
- Normal
- List
---
- Nested
- Unordered
- List
---
- This
1. Is
2. Normal
- ?!

View File

@@ -1,61 +0,0 @@
# Mixed
This contains all tags randomly mixed together, to make sure style changes in one does not affect others.
### A heading
**Quite a Strong statement , to make**
~~No, cross that~~
> Whose **quote** is this
>
> > And ~~this~~
> >
> > > - and
> > > - this
> > > - also
```
You encountered a wild codepen
```
```rust,editable
// The codepen is editable and runnable
fn main(){
println!("Hello world!");
}
```
A random image sprinkled in between
![16x16 rust-lang logo](http://rust-lang.org/logos/rust-logo-16x16.png)
---
- ~~An unordered list~~
- **Hello**
- _World_
- What
1. Should
2. be
3. `put`
4. here?
| col1 | col2 | col 3 | col 4 | col 5 | col 6 |
| ---- | ---- | ----- | ----- | ----- | ----- |
| val1 | val2 | val3 | val5 | val4 | val6 |
| col1 | col2 | col 3 | An Questionable table header | col 5 | col 6 |
| ---- | ---- | ----- | ---------------------------- | ----- | ---------------------------------------- |
| val1 | val2 | val3 | val5 | val4 | An equally Questionable long table value |
### Things to do
- [x] Add individual tags
- [ ] Add language examples
- [ ] Add rust specific examples
And another image
![2018 rust-conf art svg](https://raw.githubusercontent.com/rust-lang/rust-artwork/461afe27d8e02451cf9f46e507f2c2a71d2b276b/2018-RustConf/lucy-mountain-climber.svg)

View File

@@ -1,25 +0,0 @@
Just a simple paragraph.
Let's stress test this.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Hopefully everything above was rendered nicely, on both desktop and mobile.

View File

@@ -1,5 +0,0 @@
# Strikethrough
~~This is Striked~~
~~This is **strong**, _italic_ , **_both_** and striked~~

View File

@@ -1,28 +0,0 @@
# Tables
| col1 | col2 |
| ---- | ---- |
---
| col1 | col2 |
| ---- | ---- |
| val1 | val2 |
---
| col1 | col2 | col 3 | col 4 | col 5 | col 6 |
| ---- | ---- | ----- | ----- | ----- | ----- |
| val1 | val2 | val3 | val5 | val4 | val6 |
| val1 | val2 | val3 | val5 | val4 | val6 |
| val1 | val2 | val3 | val5 | val4 | val6 |
| val1 | val2 | val3 | val5 | val4 | val6 |
---
| col1 | col2 | col 3 | col 4 | col 5 | col 6 |
| -------------------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ----- | -------------------------------------------------------------------------------------------------------------- |
| 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 |
| val1 | val2 | val3 | val5 | val4 | val6 |
| 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. |
| 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 |

View File

@@ -1,11 +0,0 @@
# Tasks
- [ ] Task 1
- [ ] Task 2
- [x] Completed Task 1
- [x] Completed Task 2
---
- [ ] **Important Task**
- [x] _Completed Important task_

View File

@@ -1,47 +0,0 @@
# Syntax Highlighting
This Currently contains following languages
- apache
- armasm
- bash
- c
- coffeescript
- cpp
- csharp
- css
- d
- diff
- go
- handlebars
- haskell
- http
- ini
- java
- javascript
- json
- julia
- kotlin
- less
- lua
- makefile
- markdown
- nginx
- objectivec
- perl
- php
- plaintext
- properties
- python
- r
- ruby
- rust
- scala
- scss
- shell
- sql
- swift
- typescript
- x86asm
- xml
- yaml

View File

@@ -1,927 +0,0 @@
# Syntax Highlights
## apache
```apache
# rewrite`s rules for wordpress pretty url
LoadModule rewrite_module modules/mod_rewrite.so
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php [NC,L]
ExpiresActive On
ExpiresByType application/x-javascript "access plus 1 days"
Order Deny,Allow
Allow from All
<Location /maps/>
RewriteMap map txt:map.txt
RewriteMap lower int:tolower
RewriteCond %{REQUEST_URI} ^/([^/.]+)\.html$ [NC]
RewriteCond ${map:${lower:%1}|NOT_FOUND} !NOT_FOUND
RewriteRule .? /index.php?q=${map:${lower:%1}} [NC,L]
</Location>
20.164.151.111 - - [20/Aug/2015:22:20:18 -0400] "GET /mywebpage/index.php HTTP/1.1" 403 772 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1"
```
## armasm
```armasm
.data
/* Data segment: define our message string and calculate its length. */
msg:
.ascii "Hello, ARM!\n"
len = . - msg
.text
/* Our application's entry point. */
.globl _start
_start:
/* syscall write(int fd, const void *buf, size_t count) */
mov %r0, $1 /* fd := STDOUT_FILENO */
ldr %r1, =msg /* buf := msg */
ldr %r2, =len /* count := len */
mov %r7, $4 /* write is syscall #4 */
swi $0 /* invoke syscall */
/* syscall exit(int status) */
mov %r0, $0 /* status := 0 */
mov %r7, $1 /* exit is syscall #1 */
swi $0 /* invoke syscall */
```
## bash
```
#!/bin/bash
###### CONFIG
ACCEPTED_HOSTS="/root/.hag_accepted.conf"
BE_VERBOSE=false
if [ "$UID" -ne 0 ]
then
echo "Superuser rights required"
exit 2
fi
genApacheConf(){
echo -e "# Host ${HOME_DIR}$1/$2 :"
}
echo '"quoted"' | tr -d \" > text.txt
```
## c
```c
#include <stdio.h>
void main(int argc,char ** argv){
printf("Hello World!");
}
```
## coffeescript
```coffeescript
grade = (student, period=(if b? then 7 else 6)) ->
if student.excellentWork
"A+"
else if student.okayStuff
if student.triedHard then "B" else "B-"
else
"C"
class Animal extends Being
constructor: (@name) ->
move: (meters) ->
alert @name + " moved #{meters}m."
```
## cpp
```cpp
#include <iostream>
using namespace std;
int main() {
cout << "Hello, World!" << endl; // This prints Hello, World!
return 0;
}
```
## csharp
```csharp
using System;
class App
{
static void Main()
{
Console.WriteLine("Hello World!");
}
}
```
## css
```css
@font-face {
font-family: Chunkfive;
src: url('Chunkfive.otf');
}
body,
.usertext {
color: #f0f0f0;
background: #600;
font-family: Chunkfive, sans;
--heading-1: 30px/32px Helvetica, sans-serif;
}
@import url(print.css);
@media print {
a[href^='http']::after {
content: attr(href);
}
}
```
## d
```d
/* This program prints a
hello world message
to the console. */
import std.stdio;
void main()
{
writeln("Hello, World!");
}
```
## diff
```diff
Index: languages/ini.js
===================================================================
--- languages/ini.js (revision 199)
+++ languages/ini.js (revision 200)
@@ -1,8 +1,7 @@
hljs.LANGUAGES.ini =
{
case_insensitive: true,
- defaultMode:
- {
+ defaultMode: {
contains: ['comment', 'title', 'setting'],
illegal: '[^\\s]'
},
*** /path/to/original timestamp
--- /path/to/new timestamp
***************
*** 1,3 ****
--- 1,9 ----
+ This is an important
+ notice! It should
+ therefore be located at
+ the beginning of this
+ document!
! compress the size of the
! changes.
It is important to spell
```
## go
```go
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
```
## handlebars
```handlebars
<div class='entry'>
{{! only show if author exists }}
{{#if author}}
<h1>{{firstName}} {{lastName}}</h1>
{{/if}}
</div>
```
## haskell
```haskell
main :: IO ()
main = putStrLn "Hello World!"
```
## http
```http
POST /task?id=1 HTTP/1.1
Host: example.org
Content-Type: application/json; charset=utf-8
Content-Length: 137
```
## ini
```ini
; boilerplate
[package]
name = "some_name"
authors = ["Author"]
description = "This is \
a description"
[[lib]]
name = ${NAME}
default = True
auto = no
counter = 1_000
```
## java
```java
class Main {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
```
## javascript
```javascript
function $initHighlight(block, cls) {
try {
if (cls.search(/\bno\-highlight\b/) != -1)
return process(block, true, 0x0F) +
` class="${cls}"`;
} catch (e) {
/* handle exception */
}
for (var i = 0 / 2; i < classes.length; i++) {
if (checkCondition(classes[i]) === undefined)
console.log('undefined');
}
return (
<div>
<web-component>{block}</web-component>
</div>
)
}
export $initHighlight;
```
## json
```json
[
{
"title": "apples",
"count": [12000, 20000],
"description": { "text": "...", "sensitive": false }
},
{
"title": "oranges",
"count": [17500, null],
"description": { "text": "...", "sensitive": false }
}
]
```
## julia
```julia
# function to calculate the volume of a sphere
function sphere_vol(r)
# julia allows Unicode names (in UTF-8 encoding)
# so either "pi" or the symbol π can be used
return 4/3*pi*r^3
end
# functions can also be defined more succinctly
quadratic(a, sqr_term, b) = (-b + sqr_term) / 2a
# calculates x for 0 = a*x^2+b*x+c, arguments types can be defined in function definitions
function quadratic2(a::Float64, b::Float64, c::Float64)
# unlike other languages 2a is equivalent to 2*a
# a^2 is used instead of a**2 or pow(a,2)
sqr_term = sqrt(b^2-4a*c)
r1 = quadratic(a, sqr_term, b)
r2 = quadratic(a, -sqr_term, b)
# multiple values can be returned from a function using tuples
# if the return keyword is omitted, the last term is returned
r1, r2
end
vol = sphere_vol(3)
```
## kotlin
```kotlin
package org.kotlinlang.play
fun main() {
println("Hello, World!")
}
```
## less
```less
@import 'fruits';
@rhythm: 1.5em;
@media screen and (min-resolution: 2dppx) {
body {
font-size: 125%;
}
}
section > .foo + #bar:hover [href*='less'] {
margin: @rhythm 0 0 @rhythm;
padding: calc(5% + 20px);
background: #f00ba7 url(http://placehold.alpha-centauri/42.png) no-repeat;
background-image: linear-gradient(-135deg, wheat, fuchsia) !important ;
background-blend-mode: multiply;
}
@font-face {
font-family: /* ? */ 'Omega';
src: url('../fonts/omega-webfont.woff?v=2.0.2');
}
.icon-baz::before {
display: inline-block;
font-family: 'Omega', Alpha, sans-serif;
content: '\f085';
color: rgba(98, 76 /* or 54 */, 231, 0.75);
}
```
## lua
```lua
--[[
Simple signal/slot implementation
]]
local signal_mt = {
__index = {
register = table.insert
}
}
function signal_mt.__index:emit(... --[[ Comment in params ]])
for _, slot in ipairs(self) do
slot(self, ...)
end
end
local function create_signal()
return setmetatable({}, signal_mt)
end
-- Signal test
local signal = create_signal()
signal:register(function(signal, ...)
print(...)
end)
signal:emit('Answer to Life, the Universe, and Everything:', 42)
--[==[ [=[ [[
Nested ]]
multi-line ]=]
comment ]==]
[==[ Nested
[=[ multi-line
[[ string
]] ]=] ]==]
```
## makefile
```makefile
# Makefile
BUILDDIR = _build
EXTRAS ?= $(BUILDDIR)/extras
.PHONY: main clean
main:
@echo "Building main facility..."
build_main $(BUILDDIR)
clean:
rm -rf $(BUILDDIR)/*
```
## markdown
```markdown
# hello world
you can write text [with links](http://example.com) inline or [link references][1].
- one _thing_ has *em*phasis
- two **things** are **bold**
[1]: http://example.com
---
# hello world
<this_is inline="xml"></this_is>
> markdown is so cool
so are code segments
1. one thing (yeah!)
2. two thing `i can write code`, and `more` wipee!
```
## nginx
```nginx
user www www;
worker_processes 2;
pid /var/run/nginx.pid;
error_log /var/log/nginx.error_log debug | info | notice | warn | error | crit;
events {
connections 2000;
use kqueue | rtsig | epoll | /dev/poll | select | poll;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $bytes_sent '
'"$http_referer" "$http_user_agent" '
'"$gzip_ratio"';
send_timeout 3m;
client_header_buffer_size 1k;
gzip on;
gzip_min_length 1100;
#lingering_time 30;
server {
server_name one.example.com www.one.example.com;
access_log /var/log/nginx.access_log main;
rewrite (.*) /index.php?page=$1 break;
location / {
proxy_pass http://127.0.0.1/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
charset koi8-r;
}
location /api/ {
fastcgi_pass 127.0.0.1:9000;
}
location ~* \.(jpg|jpeg|gif)$ {
root /spool/www;
}
}
}
```
## objectivec
```objectivec
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@mylak {
NSLog(@"Hello World!");
}
return 0;
}
```
## perl
```perl
print "Hello World!\n";
```
## php
```php
<?php
echo "Hello World!";
?>
```
## plaintext
```plaintext
I think this is simply plain text?
Hello World!
```
## properties
```properties
# .properties
! Exclamation mark = comments, too
key1 = value1
key2 : value2
key3 value3
key\ spaces multiline\
value4
empty_key
! Key can contain escaped chars
\:\= = value5
```
## python
```python
@requires_authorization(roles=["ADMIN"])
def somefunc(param1='', param2=0):
r'''A docstring'''
if param1 > param2: # interesting
print 'Gre\'ater'
return (param2 - param1 + 1 + 0b10l) or None
class SomeClass:
pass
>>> message = '''interpreter
... prompt'''
```
## r
```r
require(stats)
#' Compute different averages
#'
#' @param x \code{numeric} vector of sample data
#' @param type \code{character} vector of length 1 specifying the average type
#' @return \code{centre} returns the sample average according to the chosen method.
#' @examples
#' centre(rcauchy(10), "mean")
#' @export
centre <- function(x, type) {
switch(type,
mean = mean(x),
median = median(x),
trimmed = mean(x, trim = .1))
}
x <- rcauchy(10)
centre(x, "mean")
library(ggplot2)
models <- tibble::tribble(
~model_name, ~ formula,
"length-width", Sepal.Length ~ Petal.Width + Petal.Length,
"interaction", Sepal.Length ~ Petal.Width * Petal.Length
)
iris %>%
nest_by(Species) %>%
left_join(models, by = character()) %>%
rowwise(Species, model_name) %>%
mutate(model = list(lm(formula, data = data))) %>%
summarise(broom::glance(model))
```
## ruby
```ruby
# The Greeter class
class Greeter
def initialize(name)
@name = name.capitalize
end
def salute
puts "Hello #{@name}!"
end
end
g = Greeter.new("world")
g.salute
```
## rust
```rust
fn main()->(){
println!("Hello World!");
}
```
## scala
```scala
/**
* A person has a name and an age.
*/
case class Person(name: String, age: Int)
abstract class Vertical extends CaseJeu
case class Haut(a: Int) extends Vertical
case class Bas(name: String, b: Double) extends Vertical
sealed trait Ior[+A, +B]
case class Left[A](a: A) extends Ior[A, Nothing]
case class Right[B](b: B) extends Ior[Nothing, B]
case class Both[A, B](a: A, b: B) extends Ior[A, B]
trait Functor[F[_]] {
def map[A, B](fa: F[A], f: A => B): F[B]
}
// beware Int.MinValue
def absoluteValue(n: Int): Int =
if (n < 0) -n else n
def interp(n: Int): String =
s"there are $n ${color} balloons.\n"
type ξ[A] = (A, A)
trait Hist { lhs =>
def ⊕(rhs: Hist): Hist
}
def gsum[A: Ring](as: Seq[A]): A =
as.foldLeft(Ring[A].zero)(_ + _)
val actions: List[Symbol] =
'init :: 'read :: 'write :: 'close :: Nil
```
## scss
```scss
import "compass/reset";
// variables
$colorGreen: #008000;
$colorGreenDark: darken($colorGreen, 10);
@mixin container {
max-width: 980px;
}
// mixins with parameters
@mixin button($color:green) {
@if ($color == green) {
background-color: #008000;
}
@else if ($color == red) {
background-color: #B22222;
}
}
button {
@include button(red);
}
div,
.navbar,
#header,
input[type="input"] {
font-family: "Helvetica Neue", Arial, sans-serif;
width: auto;
margin: 0 auto;
display: block;
}
.row-12 > [class*="spans"] {
border-left: 1px solid #B5C583;
}
```
## shell
```shell
$ echo $EDITOR
vim
$ git checkout main
Switched to branch 'main'
Your branch is up-to-date with 'origin/main'.
$ git push
Everything up-to-date
$ echo 'All
> done!'
All
done!
```
## sql
```sql
CREATE TABLE "topic" (
"id" integer NOT NULL PRIMARY KEY,
"forum_id" integer NOT NULL,
"subject" varchar(255) NOT NULL
);
ALTER TABLE "topic"
ADD CONSTRAINT forum_id FOREIGN KEY ("forum_id")
REFERENCES "forum" ("id");
-- Initials
insert into "topic" ("forum_id", "subject")
values (2, 'D''artagnian');
```
## swift
```swift
import Foundation
@objc class Person: Entity {
var name: String!
var age: Int!
init(name: String, age: Int) {
/* /* ... */ */
}
// Return a descriptive string for this person
func description(offset: Int = 0) -> String {
return "\(name) is \(age + offset) years old"
}
}
```
## typescript
```typescript
class MyClass {
public static myValue: string;
constructor(init: string) {
this.myValue = init;
}
}
import fs = require("fs");
module MyModule {
export interface MyInterface extends Other {
myProperty: any;
}
}
declare magicNumber number;
myArray.forEach(() => { }); // fat arrow syntax
```
## x86asm
```x86asm
section .text
extern _MessageBoxA@16
%if __NASM_VERSION_ID__ >= 0x02030000
safeseh handler ; register handler as "safe handler"
%endif
handler:
push dword 1 ; MB_OKCANCEL
push dword caption
push dword text
push dword 0
call _MessageBoxA@16
sub eax,1 ; incidentally suits as return value
; for exception handler
ret
global _main
_main: push dword handler
push dword [fs:0]
mov dword [fs:0], esp
xor eax,eax
mov eax, dword[eax] ; cause exception
pop dword [fs:0] ; disengage exception handler
add esp, 4
ret
avx2: vzeroupper
push rbx
mov rbx, rsp
sub rsp, 0h20
vmovdqa ymm0, [rcx]
vpaddb ymm0, [rdx]
leave
ret
text: db 'OK to rethrow, CANCEL to generate core dump',0
caption:db 'SEGV',0
section .drectve info
db '/defaultlib:user32.lib /defaultlib:msvcrt.lib '
```
## xml
```xml
<!DOCTYPE html>
<title>Title</title>
<style>body {width: 500px;}</style>
<script type="application/javascript">
function $init() {return true;}
</script>
<body>
<p checked class="title" id='title'>Title</p>
<!-- here goes the rest of the page -->
</body>
```
## yaml
```yaml
---
# comment
string_1: "Bar"
string_2: 'bar'
string_3: bar
inline_keys_ignored: sompath/name/file.jpg
keywords_in_yaml:
- true
- false
- TRUE
- FALSE
- 21
- 21.0
- !!str 123
"quoted_key": &foobar
bar: foo
foo:
"foo": bar
reference: *foobar
multiline_1: |
Multiline
String
multiline_2: >
Multiline
String
multiline_3: "
Multiline string
"
ansible_variables: "foo {{variable}}"
array_nested:
- a
- b: 1
c: 2
- b
- comment
```
ansible_variables: "foo {{variable}}"
array_nested:
- a
- b: 1
c: 2
- b
- comment
```

View File

@@ -1,3 +0,0 @@
# Prefix Chapter
This is to verify the placement and style of prefix chapter in book index.

View File

@@ -1 +0,0 @@
# Rust specific code examples

View File

@@ -1,27 +0,0 @@
## Rust codeblocks
This contains various examples of codeblocks, specific to rust
## Simple
```rust
fn main(){
println!("Hello world!");
}
```
## With Hidden lines
```rust
# fn main(){
println!("Hello world!");
# }
```
## Editable
```rust,editable
fn main(){
println!("Hello world!");
}
```

View File

@@ -1,3 +0,0 @@
# Suffix Chapter
This is to verify the placement and style of suffix chapter in book index.

View File

@@ -2,7 +2,7 @@
use mdbook::config::Config;
use mdbook::MDBook;
use std::fs;
#[cfg(not(windows))]
use std::path::Path;
use tempfile::{Builder as TempFileBuilder, TempDir};
@@ -71,45 +71,6 @@ fn backends_receive_render_context_via_stdin() {
assert!(got.is_ok());
}
#[test]
fn relative_command_path() {
// Checks behavior of relative paths for the `command` setting.
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
let renderers = temp.path().join("renderers");
fs::create_dir(&renderers).unwrap();
rust_exe(
&renderers,
"myrenderer",
r#"fn main() {
std::fs::write("output", "test").unwrap();
}"#,
);
let do_test = |cmd_path| {
let mut config = Config::default();
config
.set("output.html", toml::value::Table::new())
.unwrap();
config.set("output.myrenderer.command", cmd_path).unwrap();
let md = MDBook::init(&temp.path())
.with_config(config)
.build()
.unwrap();
let output = temp.path().join("book/myrenderer/output");
assert!(!output.exists());
md.build().unwrap();
assert!(output.exists());
fs::remove_file(output).unwrap();
};
// Legacy paths work, relative to the output directory.
if cfg!(windows) {
do_test("../../renderers/myrenderer.exe");
} else {
do_test("../../renderers/myrenderer");
}
// Modern path, relative to the book directory.
do_test("renderers/myrenderer");
}
fn dummy_book_with_backend(
name: &str,
command: &str,
@@ -151,14 +112,3 @@ fn success_cmd() -> &'static str {
"true"
}
}
fn rust_exe(temp: &Path, name: &str, src: &str) {
let rs = temp.join(name).with_extension("rs");
fs::write(&rs, src).unwrap();
let status = std::process::Command::new("rustc")
.arg(rs)
.current_dir(temp)
.status()
.expect("rustc should run");
assert!(status.success());
}

Some files were not shown because too many files have changed in this diff Show More