Compare commits

..

273 Commits

Author SHA1 Message Date
Eric Huss
ffa8284743 Merge pull request #1661 from ehuss/bump-version
Update to 0.4.13
2021-10-03 15:39:23 -07:00
Eric Huss
3e91f9cd5d Update to 0.4.13 2021-10-03 15:15:21 -07:00
Eric Huss
f55028b61a Merge pull request #1657 from lclc/gitlabPages
Documentation: CI: GitLab Pages Example
2021-10-03 14:51:08 -07:00
Eric Huss
0d887505af Merge pull request #1607 from ISSOtm/preproc-order
Allow specifying preprocessor order
2021-10-03 14:22:15 -07:00
Eric Huss
6c20736a55 Merge pull request #1658 from pickfire/patch-1
Remove extra semicolon in variables.css
2021-10-03 12:24:11 -07:00
Ivan Tham
e4a46c9477 Remove extra semicolon in variables.css 2021-10-03 22:23:09 +08:00
Ivan Tham
6ae5c686d9 Remove extra semicolon in variables.css 2021-10-03 22:19:51 +08:00
Lucas Betschart
b862080006 Documentation: CI: GitLab Pages Example
Replace 'only' with 'rules'.
'only' is not actively developed anymore with GitLab (see https://docs.gitlab.com/ee/ci/yaml/#only--except)
2021-09-29 12:42:15 +02:00
ISSOtm
6b790b83ec Warn when preproc order references unknown preprocs 2021-09-28 09:25:17 +02:00
ISSOtm
d8ad68c947 Produce an error if before or after field is not a table 2021-09-27 21:28:21 +02:00
ISSOtm
6b784be616 Stabilize ties in preproc order through name sort 2021-09-27 21:28:01 +02:00
Eric Huss
f5598b2eee Merge pull request #1656 from johannst/switch-to-opener-crate
switch from open to opener
2021-09-26 12:33:23 -07:00
Johannes Stoelp
ff4b8e7a8d switch from open to opener
By default, `opener` launches the subprocess without waiting for its
completion, compared to `open` which waits for its completion.

This is helpful in case the `watch` feature is enabled and one of the
following commands `watch | serve --open` is used. If this command would
open the browser, listening for changes would be blocked by the browser.
2021-09-26 12:33:09 -07:00
ISSOtm
9c34e602bd Allow specifying preprocessor order
Fixes #1172
2021-09-26 20:55:02 +02:00
Eric Huss
a306da3ad7 Merge pull request #1604 from notriddle/notriddle/test-cli
Add CLI tests
2021-09-26 11:37:09 -07:00
Michael Howell
9bede85efa Move cli tests to their own subdir 2021-09-26 11:29:36 -07:00
Michael Howell
11b1e86187 Fix path dumps under windows 2021-09-26 11:29:36 -07:00
Michael Howell
10d30a2dc0 Add CLI tests
Part of #1568
2021-09-26 11:29:35 -07:00
Eric Huss
601ebc5499 Merge pull request #1651 from notriddle/notriddle/theme-switcher-js
Only switch to themes on buttons that have the `theme` class
2021-09-16 17:32:01 -07:00
Michael Howell
4251d7a838 Only switch to themes on buttons that have the theme class
Fixes #1649
2021-09-14 10:05:35 -07:00
Eric Huss
93008cf20b Merge pull request #1643 from apogeeoak/update-open
Update open crate to latest version.
2021-09-07 10:20:24 -07:00
apogeeoak
3f9f681b9e Update open crate to latest version. 2021-09-06 15:52:43 -04:00
Eric Huss
63680d0786 Merge pull request #1480 from pauliyobo/preprocessor_docs
Improving documentation on implementation of preprocessors.
2021-09-05 18:46:09 -07:00
Eric Huss
656a1825cc Minor fixes for preprocessor docs. 2021-09-05 18:45:37 -07:00
pauliyobo
1a2fa29209 introduced proposed suggestions related to the documentation 2021-09-04 23:44:27 +02:00
Eric Huss
d22299d998 Merge pull request #1631 from benarmstead/master
Format better, remove unnecessary borrows, and update depends
2021-09-03 10:15:16 -07:00
Eric Huss
0af417085f Merge pull request #1634 from vv9k/master
Add information about a new backend `mdbook-man`
2021-09-03 10:11:32 -07:00
Eric Huss
9634798eb7 Merge pull request #1637 from notriddle/no-headers
Include chapters with no headers in the search index
2021-09-03 10:10:07 -07:00
Michael Howell
2a8af1c21d Include chapters with no headers in the search index 2021-08-31 12:48:21 -07:00
Wojciech Kępka
981f8695ff Add information about a new backend mdbook-man 2021-08-26 17:45:22 +02:00
Eric Huss
48b5e52f62 Merge pull request #1632 from tsoutsman/patch-1
Fix preprocessor example
2021-08-24 10:23:06 -07:00
Klim Tsoutsman
c4fec94c4c Appease Clippy 2021-08-24 23:04:32 +10:00
Ben Armstead
ab0c338c08 Update depends 2021-08-24 08:57:36 +01:00
Ben Armstead
8a82f6336a Format with cargo correctly 2021-08-24 08:48:24 +01:00
Ben Armstead
1700783594 Format better and remove unnecessary borrows 2021-08-24 08:45:06 +01:00
Eric Huss
e6629cd75b Merge pull request #1623 from ehuss/release-next
Update to 0.4.12
2021-08-02 08:40:26 -07:00
Eric Huss
5a077b9ff4 Update to 0.4.12 2021-08-02 08:19:34 -07:00
Eric Huss
8b4e488de1 Merge pull request #1621 from ehuss/revert-highlightjs11
Revert #1597 - Update to highlight.js 11.0.
2021-08-02 08:15:45 -07:00
Eric Huss
68d8ceec47 Revert #1597 - Update to highlight.js 11.0. 2021-08-02 08:02:13 -07:00
Eric Huss
db337d4a6f Merge pull request #1616 from joshrotenberg/add_cleanup_contributors
Add a couple contributors, clean up a little
2021-07-27 08:59:46 -07:00
josh rotenberg
5e277140be add a couple, clean up a little 2021-07-26 21:23:42 -07:00
Eric Huss
14add9c290 Merge pull request #1615 from ehuss/release-next
Update to 0.4.11
2021-07-26 13:47:37 -07:00
Eric Huss
87877a9dae Update to 0.4.11 2021-07-26 13:47:08 -07:00
Eric Huss
2cf00d0880 Merge pull request #1597 from ehuss/update-hightlight.js
Update to highlight.js 11.0.
2021-07-26 13:22:42 -07:00
Eric Huss
8c7af3c767 Merge pull request #1614 from joshrotenberg/code_of_conduct
Add explicit code of conduct, copying other rust-lang repos
2021-07-26 12:42:15 -07:00
Eric Huss
6dd785ea6c Update to highlight.js 11.0. 2021-07-26 12:40:28 -07:00
Eric Huss
8d131b4310 Merge pull request #1598 from ehuss/update-contributing
Some minor updates to contributing docs.
2021-07-26 12:38:06 -07:00
Eric Huss
97b38063b1 Merge pull request #1613 from FWYongxing/master
Don't highlight `inline code` blocks in headers with output.html. playpen(playgroud).editable=true
2021-07-26 12:37:39 -07:00
josh rotenberg
d23734f82e add explicit code of conduct, copying other rust-lang repos 2021-07-26 11:52:24 -07:00
Eric Huss
2ccfaadd1d Merge pull request #1609 from joshrotenberg/intro_updates
Updates to the mdbook introduction
2021-07-26 11:41:51 -07:00
Eric Huss
3d04e5c7ff Merge pull request #1612 from joshrotenberg/update_tokio_warp
Update tokio and warp to latest versions
2021-07-26 11:37:54 -07:00
FWYongxing
49ef7b6f02 Don't highlight inline code blocks in headers with output.html.playpen(playgroud).editable=true 2021-07-27 01:02:17 +08:00
josh rotenberg
da7026190c add Cargo.lock change too 2021-07-26 09:49:38 -07:00
josh rotenberg
92377013cc 1.46.0 has if, match, and loop expressions can now be used in const functions. 2021-07-25 19:33:21 -07:00
josh rotenberg
34b586ab32 tokio msrv is 1.45.2 2021-07-25 19:27:47 -07:00
josh rotenberg
a79065b0d3 update warp and tokio versions 2021-07-25 19:14:40 -07:00
josh rotenberg
b3ab93a4b3 update warp and tokio versions 2021-07-25 19:14:34 -07:00
josh rotenberg
49b75810fa fix a few typos 2021-07-24 21:17:41 -07:00
josh rotenberg
b85d5eb455 updates to the mdbook introduction 2021-07-24 18:41:53 -07:00
Eric Huss
27faa54ae8 Merge pull request #1469 from tuyen-at-work/patch-1
Support space as seperator between language and additional class in c…
2021-07-10 09:34:33 -07:00
Eric Huss
fae0759626 Split lang on tab to be consistent with rustdoc and GitHub. 2021-07-10 09:33:34 -07:00
Tuyen Pham
cc74ca2e6e Update mod.rs 2021-07-10 09:26:41 -07:00
Tuyen Pham
7cae3a058d Support space as seperator between language and additional class in code block
This PR try to resolve this issue: https://github.com/rust-lang/mdBook/issues/1384
2021-07-10 09:26:41 -07:00
Eric Huss
8fb6ac7987 Merge pull request #1599 from notriddle/notriddle/play-button-no-output
feat(playground): show "No output" on playgrounds that print nothing
2021-07-08 13:17:15 -07:00
Michael Howell
82d32ee761 feat(playground): show "No output" on playgrounds that print nothing
Fixes #1594
2021-07-07 10:44:51 -07:00
Eric Huss
fe9b534ad7 Some minor updates to contributing docs. 2021-07-06 11:42:42 -07:00
Eric Huss
56652e8fa6 Merge pull request #1559 from joshrotenberg/title_ignore_flags_init
Add --title option and --gitignore flag to mdbook init
2021-07-06 09:22:38 -07:00
Eric Huss
c3a1e41ed7 Update CLI docs for flag name change. 2021-07-06 09:22:05 -07:00
Eric Huss
3976c9d8f0 Merge pull request #1576 from kana4/master
MyPaths are not required and fail
2021-07-06 08:59:54 -07:00
Eric Huss
96b6f02834 Merge pull request #1425 from alexmaco/completions
Add ability to generate shell completions via clap
2021-07-06 08:59:06 -07:00
Eric Huss
b571511737 Merge pull request #1596 from joshrotenberg/rust_edition_2021_support
Rust Edition 2021 Support
2021-07-06 08:34:18 -07:00
josh rotenberg
ebdab38a32 remove debugging 2021-07-04 20:25:04 -07:00
josh rotenberg
c06f450e7d add edition2021 as an option 2021-07-04 16:28:52 -07:00
josh rotenberg
b87c231fc3 first pass at 2021 support 2021-07-04 14:44:23 -07:00
josh rotenberg
8024b08f93 format 2021-07-04 12:00:13 -07:00
josh rotenberg
8ec0bf6e30 ignore now takes a value 2021-07-04 11:57:46 -07:00
kana
a8926d5392 Update continuous-integration.md 2021-07-04 18:28:56 +01:00
kana
00473d8420 Add comments to edit in case of custom book path 2021-07-04 18:27:53 +01:00
josh rotenberg
86d390032b drop short flags 2021-07-04 08:15:51 -07:00
Eric Huss
b3c0b01350 Merge pull request #1586 from joshrotenberg/fix_library_path_doc
Clarify the library path documentation
2021-07-04 07:41:42 -07:00
Eric Huss
e33192753d Merge pull request #1591 from fritsstegmann/patch-1
Update renderers.md
2021-07-04 07:30:44 -07:00
Frits Stegmann
7932e13512 Update renderers.md
I was doing some work on the epub plugin repo and when trying to build an epub I came across what looked like some broken references.
2021-07-04 08:12:18 +02:00
josh rotenberg
9fd2509c0d fix link 2021-06-25 08:24:57 -07:00
josh rotenberg
5dec8508c7 clarify the library path documentation 2021-06-24 19:08:23 -07:00
Eric Huss
4d2dc6f482 Merge pull request #1581 from tuyen-at-work/patch-2
Fix inconsistent between bash version and rust version of the sample
2021-06-19 07:21:44 -07:00
Tuyen Pham
efb13d7bc1 Fix inconsistent between bash version and rust version of the sample
In the bash version, `y` have value 6, while rust version it has value 7
2021-06-19 20:08:27 +07:00
kana
27b1e05c87 MyPaths are not required and fail
Inputting paths are not necessary and will break ci, we can just leave paths out. We also changed 'master' to main since anyone reading this is probably going to be making mains
2021-06-11 20:21:28 +01:00
Eric Huss
e440094b37 Merge pull request #1573 from ehuss/release-next
Update to 0.4.10
2021-06-08 15:24:58 -07:00
Eric Huss
15cae10ca8 Update to 0.4.10 2021-06-08 15:02:48 -07:00
Eric Huss
dc2062ab36 Merge pull request #1541 from ehuss/id-chapter_begin
Remove chapter_begin id from print output.
2021-06-08 14:55:06 -07:00
Eric Huss
d9ce98d710 Merge pull request #1572 from joshrotenberg/revert-c1b2bec7d7a56909f695f103d316453dab68798e
Revert "book: use non_exhaustive attribute for struct Book"
2021-06-08 14:54:51 -07:00
josh rotenberg
b59aab56f2 Revert "book: use non_exhaustive attribute for struct Book"
This reverts commit c1b2bec7d7.
2021-06-08 12:46:27 -07:00
Eric Huss
b899c48019 Merge pull request #1566 from joshrotenberg/markdown_list_clarification
Documentation clarification on markdown list markers
2021-06-07 15:30:21 -07:00
josh rotenberg
515a253e97 doc clarification on markdown list markers
doc clarification on markdown list markers

shorten
2021-06-06 18:59:53 -07:00
Eric Huss
7ddc3df945 Merge pull request #1550 from sunng87/feature/hbs4
update handlebars to 4.0
2021-06-06 13:04:54 -07:00
Eric Huss
2f7293af5c Merge pull request #1567 from tfidfwastaken/patch-1
Fix typo in documentation
2021-06-06 12:03:36 -07:00
Atharva Raykar
fa3ae53d46 Fix typo in documentation
a very pedantic ommitted -> omitted
2021-06-05 22:50:45 +05:30
Eric Huss
6425c29893 Merge pull request #1562 from ehuss/bump-version
Update to 0.4.9
2021-06-02 09:54:43 -07:00
Eric Huss
d0bb830491 Update to 0.4.9 2021-06-01 18:50:29 -07:00
Eric Huss
d325c601bb Merge pull request #1554 from joshrotenberg/edit_url_custom_src
Use the configured book src directory for the edit url template path
2021-06-01 18:36:32 -07:00
Eric Huss
e9e889f523 Merge pull request #1560 from joshrotenberg/clippy_path_fixes
clippy: PathBuf to Path fixes.
2021-06-01 10:12:08 -07:00
josh rotenberg
e5e10c681a add init flags
add init flags

update init command in guide
2021-05-31 20:47:51 -07:00
josh rotenberg
05edc4421b clippy: PathBuf to Path 2021-05-31 20:27:52 -07:00
Eric Huss
22ea5fe335 Merge pull request #1557 from xrmx/cargo-clippy-fixes
Some clippy fixes
2021-05-31 07:10:30 -07:00
Riccardo Magliocchetti
714c5fb81e renderer: remove redundant clone in CmdRenderer
As suggested by clippy:
https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone
2021-05-30 19:33:46 +02:00
Riccardo Magliocchetti
56ceb627b8 book: simplify is_draft_chapter
As suggested by clippy:
https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching
2021-05-30 19:33:46 +02:00
Riccardo Magliocchetti
c1b2bec7d7 book: use non_exhaustive attribute for struct Book
As suggested by clippy:
https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
2021-05-30 19:33:46 +02:00
Eric Huss
8201b411ab Merge pull request #1555 from joshrotenberg/exit_serve_if_panic
Use std::panic::set_hook to exit serve thread if serving fails
2021-05-29 15:12:04 -07:00
Eric Huss
836546cf0d Merge pull request #1544 from joshrotenberg/guide_configuration_restructure
Restructure guide configuration section
2021-05-29 14:46:58 -07:00
Ning Sun
9813802b3e bump msrv to 1.45 as handlebars 4.0 requires 2021-05-28 22:59:56 +08:00
josh rotenberg
fcf8f938d2 use panic::set_hook to exit 2021-05-28 07:40:56 -07:00
josh rotenberg
60aaa7ae31 use book_config.src for edit path
use book_config.src for edit path

use book_config.src for edit path

fmt

concat the path, this is a url
2021-05-27 21:54:58 -07:00
Ning Sun
1b584d1746 update handlebars to 4.0 2021-05-26 23:11:01 +08:00
josh rotenberg
aa4cb9465f restructure guide configuration section
restructure guide configuration section

restructure guide configuration section

redirect to new config top level

fix broken links

use a relative path for redirect

Co-authored-by: Eric Huss <eric@huss.org>

remove extra heading
2021-05-25 07:43:36 -07:00
Eldred Habert
89a2e39b80 Mention missing file creation in build/watch/serve's docs (#1548)
Fixes #1246
2021-05-25 13:45:30 +02:00
Eric Huss
3c2b8cd10f Merge pull request #1539 from joshrotenberg/report_config_errors
Report book.toml parse error when invalid fields are found
2021-05-24 13:25:07 -07:00
josh rotenberg
6b0b42ebcc update build and rust config change 2021-05-24 12:01:56 -07:00
josh rotenberg
7a3513200f Update src/config.rs
Co-authored-by: Eric Huss <eric@huss.org>
2021-05-24 11:59:32 -07:00
Eric Huss
3db0c0b9a1 Merge pull request #1511 from joshrotenberg/example-semver
Use semver crate to validate version compatibility in nop-preprocessor example
2021-05-24 11:10:21 -07:00
Eric Huss
2c7aac6d7a Merge pull request #1542 from ISSOtm/patch-1
Apply max width to video as well as images
2021-05-24 10:33:23 -07:00
Eldred Habert
3ee22fb430 Apply max width to video as well as images
Since videos are essentially animated images, this same max width makes sense for both.
2021-05-23 16:10:26 +02:00
Eric Huss
16c5ec4d74 Remove chapter_begin id from print output. 2021-05-22 15:12:54 -07:00
Eric Huss
7e7e779ef7 Merge pull request #1526 from apatniv/add_log_messages
Add useful messages when command line preprocessor fails
2021-05-21 10:06:36 -07:00
Andrea Gelmini
b364e8ea2c Fix typos (#1540)
Signed-off-by: Andrea Gelmini <andrea.gelmini@gelma.net>
2021-05-21 12:56:32 +02:00
josh rotenberg
78325aaccb report book.toml parse errors
check config for book parse errors

add invalid_title_type

handle build and rust config errors
2021-05-19 09:32:24 -07:00
Eric Huss
1411ea967a Merge pull request #1534 from joshrotenberg/docs/guide-summary-updates
Guide updates: summary.md
2021-05-17 14:38:18 -07:00
josh rotenberg
d147a85006 summary.md updates 2021-05-14 22:05:24 -07:00
Eric Huss
0f0dce8d6c Merge pull request #1530 from anoadragon453/patch-1
Fix a small typo
2021-05-13 16:14:10 -07:00
Andrew Morgan
379574dc61 Fix a small typo
misssing -> missing
2021-05-13 23:55:51 +01:00
Eric Huss
6a7de13c6f Update Cargo.lock, Bump msrv to 1.42 (#1528)
* Update Cargo.lock

* Bump MSRV to 1.42.

There are a few dependencies that require this version.
2021-05-10 20:08:18 +02:00
Eric Huss
331aad1597 Merge pull request #1525 from joshrotenberg/guide/server-command-updates
Update serve documentation in the guide
2021-05-10 09:40:00 -07:00
Eric Huss
7e01cf9e18 Update to 0.4.8 (#1527) 2021-05-10 18:38:43 +02:00
josh rotenberg
c922b8aae6 backquote port 2021-05-08 09:51:30 -07:00
apatniv
b21446898a Add useful messages when command line preprocessor fails 2021-05-08 12:48:57 -04:00
josh rotenberg
f4b4a331d7 consisten note format, update gitignore language 2021-05-07 21:02:55 -07:00
josh rotenberg
aa349e0b7c update serve documentation 2021-05-07 20:29:01 -07:00
Eric Huss
b592b10633 Merge pull request #1519 from tshepang/patch-1
Update init.md
2021-05-05 08:32:52 -07:00
Eric Huss
d62cf8e883 Merge pull request #1520 from apatniv/patch-1
Add entry to contributor section
2021-05-05 08:32:23 -07:00
Vivek Bharath Akupatni
c6844dd771 Add entry to contributor section 2021-05-05 08:42:43 -04:00
Tshepang Lekhonkhobe
009247be01 Update init.md
make sentence more simple
2021-05-05 12:06:56 +02:00
Eric Huss
84b3b7218e Merge pull request #1437 from rust-lang-ja/summary-with-html-comments
Skip HTML nodes in SUMMARY.md
2021-05-04 07:26:03 -07:00
Eric Huss
71ba6c9eb8 Merge pull request #1514 from c-cube/patch-1
more detailed example in summary.md
2021-05-03 17:46:56 -07:00
Simon Cruanes
9d4ee689db Update guide/src/format/summary.md
Co-authored-by: Eric Huss <eric@huss.org>
2021-05-03 20:16:11 -04:00
Simon Cruanes
ffe88d7e29 Update guide/src/format/summary.md
Co-authored-by: Eric Huss <eric@huss.org>
2021-05-03 20:16:05 -04:00
Simon Cruanes
9f930706bb Update guide/src/format/summary.md
Co-authored-by: josh rotenberg <joshrotenberg@users.noreply.github.com>
2021-05-02 21:21:41 -04:00
Simon Cruanes
24fa615149 more detailed example in summary.md
this shows a richer structure of numbered chapters to better illustrate that it uses nested markdown lists.
2021-05-02 11:27:28 -04:00
Eric Huss
a72d6002b7 Merge pull request #1506 from flavio/feature/suggest-an-edit-link
Feature/suggest an edit link
2021-04-26 11:03:50 -07:00
josh rotenberg
5b7abf4714 add semver v0.11.0 for example 2021-04-26 08:08:53 -07:00
josh rotenberg
d0ef70e574 use semver to validate version 2021-04-26 08:08:24 -07:00
Flavio Castelli
7525b35383 Rename git-repository-edit-url-template
Change the name of the git-repository-edit-url-template to be more
generic: `edit-url-template`

Signed-off-by: Flavio Castelli <fcastelli@suse.com>
2021-04-26 09:59:08 +02:00
Eric Huss
b54e73e3b6 Merge pull request #1509 from GuillaumeGomez/duplicate-name
Remove duplicate "name" attribute on input
2021-04-25 20:08:55 -07:00
josh rotenberg
59c76fa665 Reword incomplete sentence in Preprocessors section in the guide (#1510)
* fix and reword incomplete sentence

* remove unused reference
2021-04-26 01:18:57 +02:00
Guillaume Gomez
c1d982d92b Remove duplicate "name" attribute on input 2021-04-24 17:27:49 +02:00
syntezoid
3db275d68a Change in gitlab CI (#1507) 2021-04-23 11:51:42 +02:00
Flavio Castelli
94e797fba0 mdbook book: show edit link
Add "edit" links to the pages of mdbook own book
2021-04-19 19:08:15 +02:00
Flavio Castelli
c3beecc96a Update docs to include example of edit links feature
Add a working example of the edit links feature to the examples
2021-04-19 19:07:37 +02:00
Flavio Castelli
7aff98a859 Fix generation of edit links
The `IndexPreprocessor` rewrites the path for files
named `README.md` to be `index.md`. This breaks the edit link
in some circumstances.

To address this issues, the `Chapter` struct has now a new attribute
called `source_path`. This is initialized with the same value as
`path`, but is never ever changed.

Finally, the edit link is built by using the `source_path` rather
than the `path`.
2021-04-19 18:58:15 +02:00
Jonas Berlin
bbf54d7459 [ReviewFix] Replace edit baseurl with template and make visibility independent of git_repository_url. 2021-04-19 16:16:08 +02:00
Jonas Berlin
dcc642e66d [ReviewFix] cargo fmt 2021-04-19 14:51:16 +02:00
Jonas Berlin
2b738d4425 [ReviewFix] Fix variable naming 2021-04-19 14:51:16 +02:00
Jonas Berlin
b3670ece0e Add "Suggest an edit" link next to "Git repository"
Includes new configuration option `git-repository-edit-baseurl` for
supporting non-GitHub repository layouts.
2021-04-19 14:51:14 +02:00
Tatsuya Kawano
30ce7e79ac Skip HTML comments in the summary
- Revert changes in parse_numbered(). They were unnecessary.
2021-04-05 18:31:11 +08:00
David Tolnay
94f7578576 Add page title override: {{#title My Title}} (#1381)
* Add page title override: {{#title My Title}}

* Document {{#title}} in guide
2021-03-24 02:36:45 +01:00
Eric Huss
e6568a70eb Merge pull request #1485 from Evian-Zhang/add-pagebreak
Add page-break
2021-03-17 09:45:30 -07:00
Evian-Zhang
0eb23efd44 Make page-break not configurable 2021-03-16 09:33:19 +08:00
Evian-Zhang
e78a8471c7 Add page-break option 2021-03-12 14:00:29 +08:00
Paul
dcccd3289d Apply suggestions from code review
improved readability and cleared sections which could have caused more confusion

Co-authored-by: Eric Huss <eric@huss.org>
2021-03-08 20:39:39 +01:00
pauliyobo
5637a66459 hopefully made the documentation more clearer on what concerns preprocessor implementation with non rust languages 2021-03-05 21:11:29 +01:00
Eric Huss
536873ca26 Merge pull request #1478 from camelid/patch-1
docs: Use inline code for regex
2021-03-01 08:15:20 -08:00
Camelid
d6ea4e3f7a docs: Use inline code for regex
And fix a typo.
2021-02-27 20:17:36 -08:00
mbartlett21
fcceee4761 Update examples with hidden lines (#1476)
* Update example.rs to have correct indent

The three hidden lines in example.rs now have four spaces indent for the hidden lines.

* Update mdbook.md
2021-02-27 02:40:14 +01:00
Eric Huss
3f39ba82f9 Merge pull request #1474 from ehuss/bump-version
Update to 0.4.7
2021-02-22 15:44:42 -08:00
Eric Huss
7da38715c1 Update to 0.4.7 2021-02-22 14:56:44 -08:00
Eric Huss
c83bbd6319 Merge pull request #1463 from ehuss/fix-header-scroll
Fix some issues with fragment scrolling and linking.
2021-02-22 14:50:17 -08:00
Eric Huss
fad3c663f4 Merge pull request #1461 from ehuss/guide-repo-link
Add git repository url link to user guide.
2021-02-22 14:50:05 -08:00
Eric Huss
f8b9054265 Merge pull request #1470 from tim-seoss/light_theme_contrast_enhancement
Enhance text contrast of `light` theme to improve accessibility.
2021-02-22 14:49:07 -08:00
Fenhl
f26116a491 Upgrade to shlex 1 (#1471) 2021-02-22 00:15:16 +01:00
Tim Small
7f59fdd9bd Enhance text contrast of light theme to improve accessibility.
The existing light theme has relatively low contrast between the text
(and other UI elements) and background (especially within code blocks).
This presents difficulties for people with reduced visual contrast
perception (common in older adults).

This patch makes changes to the default `light` theme to meet the
minimum contrast requirement of the v2.1 W3C WCAG (Web Content
Accessibility Guidelines)
https://www.w3.org/WAI/WCAG21/quickref/#contrast-minimum

The small size, and slender font used for the title text makes it hard
to read, even with the increased contrast colour scheme, so this patch
also increases the size of the title text font by 20%.

Closes #1442
2021-02-21 14:35:10 +00:00
Eric Huss
45d41eac5f Fix some issues with fragment scrolling and linking. 2021-02-12 16:37:07 -08:00
Eric Huss
2b5890e2ed Add git repository url link to user guide. 2021-02-12 07:34:20 -08:00
Eric Huss
0b9570b160 Merge pull request #1456 from danieleades/typo
fix small typos on 'syntax-highlighting' page
2021-01-30 09:09:37 -08:00
Daniel Eades
90396c5b76 fix small typos on 'syntax-highlighting' page 2021-01-30 08:11:45 +00:00
Eric Huss
24b76dd879 Fix sentence on installation page. 2021-01-15 07:50:06 -08:00
Eric Huss
9a9eb0124a Merge pull request #1447 from ehuss/bump-version
Update to 0.4.6.
2021-01-14 17:36:08 -08:00
Eric Huss
257374d76b Update to 0.4.6. 2021-01-14 17:27:54 -08:00
Eric Huss
1a0c296532 Merge pull request #1426 from ehuss/search-mark-words
Fix search highlighting with multiple words.
2021-01-14 17:01:52 -08:00
Eric Huss
9b4ab72a80 Merge pull request #1418 from ehuss/relative-renderer-command
Fix relative paths for renderer commands.
2021-01-14 17:01:41 -08:00
Eric Huss
b1c2e466e7 Merge pull request #1438 from pierwill/edit-docs
Add intra-docs links to docs
2021-01-13 14:26:44 -08:00
Eric Huss
cdea0f6b61 Add missing parenthesis in doc comment. 2021-01-13 14:26:14 -08:00
pierwill
e9b0be7090 Add intra-docs links to docs
Also fixes some punctuation and changes some wording.
2021-01-10 14:51:30 -08:00
Tatsuya Kawano
d402a12e88 Skip HTML comments in the summary 2021-01-08 18:16:03 +08:00
Tatsuya Kawano
218e200117 Fix a wrong version 0.4.4 in Cargo.toml (Should be 0.4.5) (#1435) 2021-01-07 14:56:21 +01:00
Eric Huss
3d55375f61 Update theme CSS docs. (#1431) 2021-01-07 01:56:20 +01:00
Eric Huss
77e7cfd22b Change init --theme to place theme in root. (#1432) 2021-01-07 01:29:38 +01:00
Eric Huss
76cd39e5e2 Merge pull request #1390 from sburris0/gitlabci
Guide: Add instructions for publishing via GitLab Pages
2021-01-06 13:11:58 -08:00
Eric Huss
09e7bb76dc Remove mark from URL on escape. (#1427) 2021-01-05 19:31:16 +01:00
Eric Huss
28387130c0 Fix search highlighting with multiple words. 2021-01-04 14:20:36 -08:00
Eric Huss
33d3d9c3ec Merge pull request #1389 from avitex/search-chapter-name
Add chapter name to search result breadcrumbs
2021-01-04 14:01:30 -08:00
Alexandru Macovei
beec17e55d Add ability to generate shell completions via clap 2021-01-04 23:51:02 +02:00
Eric Huss
e651f4d734 Merge pull request #1420 from apatniv/clippy_remove_clone
Clippy lint: Remove unnecessary clone
2021-01-04 11:33:37 -08:00
Eric Huss
87d2cd9845 Merge pull request #1421 from apatniv/clippy_use_single_char
clippy: use char instead of str
2021-01-04 11:31:06 -08:00
Pietro Albini
32abeef088 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:14:57 -08:00
Eric Huss
5de9b6841e Update changelog for 0.4.5 2021-01-04 07:13:30 -08:00
apatniv
95e0743bc0 clippy: use char instead of str 2020-12-31 15:37:34 -05:00
apatniv
3c97525743 Clippy lint: Remove unnecessary clone 2020-12-31 15:18:37 -05:00
Eric Huss
9a65c8ab92 Fix relative paths for renderer commands. 2020-12-30 17:46:29 -08:00
Eric Huss
a64a7b7470 Merge pull request #1405 from francis-du/master
fix: readerer get theme dir path bug
2020-12-28 09:22:11 -08:00
francis-du
fd4137a9ea fix: readerer get theme dir path bug 2020-12-28 09:20:23 -08:00
Vivek Bharath Akupatni
a3d4febe3e Provide useful feedback if user executes command in different folder (#1407)
Provides better feedback if user executes in a different folder than what is expected by mdbook

After the changes `mdbook build`
```
2020-12-19 14:27:35 [ERROR] (mdbook::utils): Error: Couldn't open SUMMARY.md in "/Users/vicky/rust/source_codes/mdbook_testing/src/src" directory
2020-12-19 14:27:35 [ERROR] (mdbook::utils):    Caused By: No such file or directory (os error 2)
```

Previously: `mdbook build`
```
2020-12-19 14:28:46 [ERROR] (mdbook::utils): Error: Couldn't open SUMMARY.md
2020-12-19 14:28:46 [ERROR] (mdbook::utils):    Caused By: No such file or directory (os error 2)
```
2020-12-27 12:45:11 -08:00
Maxime BORGES
7af4b1dfe8 [README] Add optional directory parameter for the init command (#1409)
With the current description of the command, I was expecting to get a directory named with the project name, but the files were created in the current directory.
I Think a more precise description would help first-time users.
2020-12-23 10:03:31 -08:00
Eric Huss
ba6bffac5a Merge pull request #1410 from maximeborges/patch-2
[guide/format/theme/index-hbs] `chapter_title` and `book_title` are inverted
2020-12-23 08:55:46 -08:00
Maxime BORGES
6201e577fe [guide/format/theme/index-hbs] chapter_title and book_title are inverted
Looking into the code, we can confirm that the implementation is `{{ chapter_title }} - {{ book_title }}` and not `{{ book_title }} - {{ chapter_title }}` as written in the guide:
4c951d530d/src/renderer/html_handlebars/hbs_renderer.rs (L69)
2020-12-22 22:36:58 +01:00
Eric Huss
cf2459f730 Merge pull request #1393 from apatniv/fixing_error
Missing chapters Error Reporting: Add file name
2020-12-14 08:33:36 -08:00
Eric Huss
45a481049e Merge pull request #1399 from u7693/issue-1396
Show path in the error message (#1396)
2020-12-14 08:08:05 -08:00
Kousuke Takaki
6bcabcbb6b Improve error message 2020-12-14 16:11:01 +09:00
apatniv
ef993e8cc2 Run rust formmater 2020-12-05 20:27:03 -05:00
apatniv
a3a5386da0 Add more context regarding which missing file creation failed 2020-12-05 20:17:45 -05:00
Eric Huss
3ab911afa1 Merge pull request #1392 from lzanini/master
Add Katex preprocessor to 3rd Party Plugins
2020-12-04 10:53:21 -08:00
Spencer Burris
4615ce2f8c Remove --debug from GitLab CI 2020-12-04 09:12:00 -08:00
Lucas Zanini
7cb8087469 katex preprocessor
Added the katex preprocessor to the list of 3rd Party Plugins
2020-12-03 23:54:12 +01:00
Spencer Burris
d1721667b6 Add instructions for publishing via GitLab Pages 2020-12-01 13:25:28 -08:00
avitex
1038f0b7f5 Fix search index tests 2020-11-26 09:02:11 +11:00
avitex
942cc12a74 Add chapter name to search result breadcrumbs 2020-11-25 14:46:49 +11:00
Eric Huss
59f2a9bf4e Merge pull request #1373 from avisionh/docs/ci-guide-update
Docs: CI guide update
2020-11-13 08:19:52 -08:00
A Ho
75d0f1efd4 Suggest dplyv2 changes to .travis.yml as a note
This is following @ehuss' comment to not recommend something currently in beta release.
2020-11-13 09:40:33 +00:00
Eric Huss
552e3378cf Merge pull request #1378 from daynin/serialize-build-section
allow to serialize the "build" section
2020-11-12 10:41:12 -08:00
Sergey Golovin
7c0ddff96a allow to serialize the "build" section 2020-11-12 19:32:32 +03:00
Eric Huss
07e72757d3 Merge pull request #1376 from dtolnay/html
Escape `<` and `>` in rendered toc to match handlebars' escaping in <title>
2020-11-10 14:46:24 -08:00
Eric Huss
58f66a146d Merge pull request #1375 from dtolnay/playground
Fix stray spacing after #playground code
2020-11-10 14:06:09 -08:00
Eric Huss
643d5ecc5c Merge pull request #1285 from FrankHB/patch-1
Handled UTF-8 BOM
2020-11-10 14:02:01 -08:00
David Tolnay
c712ba7aab Escape <> in rendered toc 2020-11-08 22:26:30 -08:00
David Tolnay
1450070f73 Fix stray spacing after #playground code 2020-11-05 21:43:04 -08:00
A Ho
e310dfc605 Add avisionh to contributors
This is for contributing to the travis deployments chapter.
2020-11-05 22:28:54 +00:00
A Ho
cbfd75a821 Update CI section in guide for travis dpl-v2
This is so that the instructions work when deploying the book to GitHub pages via the gh-pages branch.
2020-11-05 21:43:52 +00:00
Eric Huss
eaa6914205 Merge pull request #1360 from ehuss/release-next
Release 0.4.4
2020-10-20 08:06:27 -07:00
Eric Huss
a76557a678 Release 0.4.4 2020-10-20 07:58:24 -07:00
Eric Huss
01836ba5d4 Merge pull request #1348 from ehuss/github-deprecated-action-commands
Update deprecated GitHub Actions commands.
2020-10-20 07:48:26 -07:00
Eric Huss
46ce077de6 Update deprecated GitHub Actions commands. 2020-10-08 10:00:06 -07:00
Eric Huss
f7c9180d80 Fix typo in changelog 2020-10-08 09:46:39 -07:00
FrankHB
9e9cf49c50 Added a test.
Signed-off-by: FrankHB <frankhb1989@gmail.com>
2020-10-07 22:50:25 +08:00
Camelid
4c951d530d List supported Highlight.js languages in guide (#1345)
* List supported Highlight.js languages in guide

Generated using the technique described in
https://github.com/rust-lang/mdBook/issues/1275#issuecomment-655903967.

* Improve wording in guide
2020-09-30 01:56:31 +02:00
FrankHB
780fb979a0 Avoided the redundant allocation.
Signed-off-by: FrankHB <frankhb1989@gmail.com>
2020-09-29 18:01:06 +08:00
Camelid
b77942d3c8 Rename book-example to guide (#1336)
`book-example` is a bit of a strange name given that it's not just an
example.
2020-09-23 03:16:09 +02:00
Eric Huss
d0deee90b0 Merge pull request #1335 from ehuss/fix-print
Fix print icon.
2020-09-22 13:34:15 -07:00
Eric Huss
e6ac8ecdd9 Fix print icon. 2020-09-22 13:24:13 -07:00
Eric Huss
d1f5ecc103 Merge pull request #1169 from rossmacarthur/ft/no-print
Add config option to disable print html, css, and icon
2020-09-22 11:50:22 -07:00
Ross MacArthur
e0b247e9d6 Add config option to disable print html, css, and icon 2020-09-22 11:40:02 -07:00
Eric Huss
db8a2821ea Merge pull request #1323 from iffyio/copy-symlinks
Resolve full file paths when copying files
2020-09-22 10:51:50 -07:00
Eric Huss
39d7130019 Merge pull request #1325 from camelid/patch-2
Improve README wording
2020-09-12 06:19:41 -07:00
Camelid
2eccb457d2 gitignore: Ignore Vim temporary and swap files (#1328) 2020-09-11 15:10:38 +02:00
Camelid
d1682d27fb Improve README wording 2020-09-10 13:28:24 -07:00
Eric Huss
a94a940ff7 Fix macOS CI deploy take 2. 2020-09-09 12:05:14 -07:00
Eric Huss
daf402e1dc Merge pull request #1324 from ehuss/purge
Fix macOS CI deploy.
2020-09-09 11:47:00 -07:00
Eric Huss
5ebd2c0527 Fix macOS CI deploy. 2020-09-09 10:00:48 -07:00
ifeanyi
b349e8abc9 Fix windows typo 2020-09-09 17:57:45 +02:00
ifeanyi
e225586953 Resolve full file paths when copying files
Currently, the `copy_files` function doesn't support symlink files due to
its use of `DirEntry::metadata` - To avoid this issue, this patch
resolves the file path first before checking for metadata.

Fixes #1157
2020-09-09 17:42:14 +02:00
Eric Huss
cf7663f800 Merge pull request #1317 from ehuss/release-next
Release 0.4.3
2020-09-08 08:21:17 -07:00
Eric Huss
3155c63e88 Release 0.4.3 2020-09-08 08:06:05 -07:00
Eric Huss
4df9ec90af Merge pull request #1307 from camelid/prefer-bundled-scp
Prefer bundled version of Source Code Pro
2020-09-06 10:58:23 -07:00
Camelid
73cabeb904 Remove local version from @font-face
It's unlikely that the bundled version wouldn't load, and it's
especially unlikely that the page would load but the bundled version
would not.

Also, if it doesn't load, it should fall back to another monospace font,
which is fine.
2020-09-06 10:34:11 -07:00
Eric Huss
4b773024ae Merge pull request #1309 from nerosnm/add-missing-space
Fix missing space before draft chapter titles
2020-09-06 09:40:55 -07:00
Eric Huss
33ea661350 Merge pull request #1310 from dtolnay/nojekyll
End .nojekyll file with newline
2020-09-06 09:21:59 -07:00
Eric Huss
1b18740b56 Merge pull request #1311 from dtolnay/cname
Support emitting CNAME file for publishing at a custom domain
2020-09-06 09:21:29 -07:00
Eric Huss
6fed9e52f9 Merge pull request #1313 from guswynn/master
collect all test failures before failing
2020-09-06 09:12:42 -07:00
Eric Huss
fd59dc73e5 Adjust wording 2020-09-06 09:11:53 -07:00
Eric Huss
146bea48c6 Merge pull request #1314 from camelid/patch-1
Fix grammar
2020-09-06 08:59:11 -07:00
Camelid
efb5bc285d Fix grammar
who -> which
2020-09-04 13:21:37 -07:00
Gus Wynn
5ea8e55aea collect all test failures before failing 2020-09-03 18:36:51 -07:00
David Tolnay
1acf23ff73 Support emitting CNAME file for publishing at a custom domain 2020-09-02 11:24:48 -07:00
David Tolnay
69cc1fa005 End .nojekyll file with newline
Before:

    /path/to$  cat book/.nojekyll
    This file makes sure that Github Pages doesn't process mdBook's output./path/to$  ▎

After:

    /path/to$  cat book/.nojekyll
    This file makes sure that Github Pages doesn't process mdBook's output.
    /path/to$  ▎
2020-09-02 11:06:19 -07:00
Søren Mortensen
2fb489137b Wrap draft chapter titles in a <div>
Fixes the problem of a missing space before the title of draft chapters,
due to the title not being wrapped in an `<a></a>`.
2020-08-31 10:00:03 +01:00
Camelid
4d9eb9b4b4 Prefer bundled version of Source Code Pro
This prevents an issue with Firefox 80 on macOS that prevents syntax
highlighting from working.
2020-08-29 13:22:51 -07:00
FrankHB
65d9eb6f7e Handled UTF-8 BOM
Fixed #1155 .
2020-07-25 13:02:44 +08:00
89 changed files with 3507 additions and 1346 deletions

View File

@@ -31,12 +31,12 @@ jobs:
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable
- name: Build book
run: cargo run -- build book-example
run: cargo run -- build guide
- name: Deploy to GitHub
env:
GITHUB_DEPLOY_KEY: ${{ secrets.GITHUB_DEPLOY_KEY }}
run: |
touch book-example/book/.nojekyll
touch guide/book/.nojekyll
curl -LsSf https://raw.githubusercontent.com/rust-lang/simpleinfra/master/setup-deploy-keys/src/deploy.rs | rustc - -o /tmp/deploy
cd book-example/book
cd guide/book
/tmp/deploy

View File

@@ -31,7 +31,7 @@ jobs:
rust: stable
- build: msrv
os: ubuntu-latest
rust: 1.39.0
rust: 1.46.0
steps:
- uses: actions/checkout@master
- name: Install Rust

6
.gitignore vendored
View File

@@ -4,10 +4,14 @@ target
.DS_Store
book-test
book-example/book
guide/book
.vscode
tests/dummy_book/book/
# Ignore Jetbrains specific files.
.idea/
# Ignore Vim temporary and swap files.
*.sw?
*~

View File

@@ -1,5 +1,205 @@
# Changelog
## 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)
### Added
- Added the `output.html.print.enable` configuration value to disable the
"print" page.
[#1169](https://github.com/rust-lang/mdBook/pull/1169)
- Added a list of supported languages for syntax-highlighting to the
documentation.
[#1345](https://github.com/rust-lang/mdBook/pull/1345)
### Fixed
- Now supports symbolic links for files in the `src` directory.
[#1323](https://github.com/rust-lang/mdBook/pull/1323)
## mdBook 0.4.3
[9278b83...4df9ec9](https://github.com/rust-lang/mdBook/compare/9278b83...4df9ec9)
### Added
- Added `output.html.cname` option to emit a `CNAME` file which is used by
GitHub Pages to know which domain is being used.
[#1311](https://github.com/rust-lang/mdBook/pull/1311)
### Changed
- `mdbook test` no longer stops on the first test failure, but instead will
run all the tests.
[#1313](https://github.com/rust-lang/mdBook/pull/1313)
- Removed the `local` font source for Source Code Pro, as the locally
installed font may not render properly on FireFox on macOS.
[#1307](https://github.com/rust-lang/mdBook/pull/1307)
### Fixed
- Added newline to end of `.nojekyll` file.
[#1310](https://github.com/rust-lang/mdBook/pull/1310)
- Fixed missing space before draft chapter titles.
[#1309](https://github.com/rust-lang/mdBook/pull/1309)
## mdBook 0.4.2
[649f355...9278b83](https://github.com/rust-lang/mdBook/compare/649f355...9278b83)

3
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,3 @@
# 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,7 +6,6 @@ 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
@@ -46,7 +45,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
@@ -79,7 +78,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 therfore helps to improve your code.
Clippy is a code analyser/linter detecting mistakes, and therefore 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.
@@ -106,3 +105,26 @@ 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. (TODO: It would be nice to have a demo file in the repo to help with this.)

1026
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.2"
version = "0.4.13"
authors = [
"Mathieu David <mathieudavid@mathieudavid.org>",
"Michael-F-Bryan <michaelfbryan@gmail.com>",
@@ -8,7 +8,7 @@ authors = [
]
documentation = "http://rust-lang.github.io/mdBook/index.html"
edition = "2018"
exclude = ["/book-example/*"]
exclude = ["/guide/*"]
keywords = ["book", "gitbook", "rustbook", "markdown"]
license = "MPL-2.0"
readme = "README.md"
@@ -20,19 +20,20 @@ anyhow = "1.0.28"
chrono = "0.4"
clap = "2.24"
env_logger = "0.7.1"
handlebars = "3.0"
handlebars = "4.0"
lazy_static = "1.0"
log = "0.4"
memchr = "2.0"
open = "1.1"
opener = "0.5"
pulldown-cmark = "0.7.0"
regex = "1.0.0"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
shlex = "0.1"
shlex = "1"
tempfile = "3.0"
toml = "0.5.1"
topological-sort = "0.1.0"
# Watch feature
notify = { version = "4.0", optional = true }
@@ -40,15 +41,18 @@ gitignore = { version = "1.0", optional = true }
# Serve feature
futures-util = { version = "0.3.4", optional = true }
tokio = { version = "0.2.18", features = ["macros"], optional = true }
warp = { version = "0.2.2", default-features = false, features = ["websocket"], optional = true }
tokio = { version = "1", features = ["macros", "rt-multi-thread"], optional = true }
warp = { version = "0.3.1", 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 = "0.11.0"
pretty_assertions = "0.6"
walkdir = "2.0"

View File

@@ -85,16 +85,17 @@ There are multiple ways to install mdBook.
## Usage
mdBook will primarily be used as a command line tool, even though it exposes
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`
- `mdbook init <directory>`
The init command will create a directory with the minimal boilerplate to
start with.
start with. If the `<directory>` parameter is omitted, the current
directory will be used.
```
book-test/
@@ -149,6 +150,7 @@ preprocessors are:
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.
- [`katex`](https://github.com/lzanini/mdbook-katex) - a preprocessor rendering LaTex equations to HTML.
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
@@ -161,6 +163,7 @@ of a book in order to validate links or run tests. Some existing renderers are:
preprocessors.
- [`linkcheck`] - a backend which will check that all links are valid
- [`epub`] - an experimental EPUB generator
- [`man`] - a backend that generates manual pages from the book
> **Note for Developers:** Feel free to send us a PR if you've developed your
> own plugin and want it mentioned here.
@@ -209,7 +212,7 @@ 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] who helps
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
@@ -232,3 +235,4 @@ All the code in this repository is released under the ***Mozilla Public License
[master-docs]: http://rust-lang.github.io/mdBook/
[`linkcheck`]: https://crates.io/crates/mdbook-linkcheck
[`epub`]: https://crates.io/crates/mdbook-epub
[`man`]: https://crates.io/crates/mdbook-man

View File

@@ -1,25 +0,0 @@
# mdBook
**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).
What you are reading serves as an example of the output of mdBook and at the
same time as a high-level documentation.
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).
## API docs
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
mdBook, all the source code, is released under the [Mozilla Public License
v2.0](https://www.mozilla.org/MPL/2.0/).

View File

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

View File

@@ -1,66 +0,0 @@
# SUMMARY.md
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.
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
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.
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
[Title of prefix element](relative/path/to/markdown.md)
```
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 book. The title is rendered as unclickable text.
Titles are optional, and the numbered chapters can be broken into as many
parts as desired.
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
- [Title of the Chapter](relative/path/to/markdown.md)
# Title of Another Part
- [More Chapters](relative/path/to/markdown2.md)
```
You can either use `-` or `*` to indicate a numbered chapter.
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.
All other elements are unsupported and will be ignored at best or result in an
error.
#### Other elements
- ***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

@@ -21,4 +21,4 @@ case $1 in
;;
esac
echo "##[add-path]$PWD/hub/bin"
echo "$PWD/hub/bin" >> $GITHUB_PATH

View File

@@ -15,10 +15,20 @@ export CARGO_PROFILE_RELEASE_LTO=true
cargo build --bin mdbook --release
cd target/release
case $1 in
ubuntu* | macos*)
ubuntu*)
asset="mdbook-$TAG-$host.tar.gz"
tar czf ../../$asset mdbook
;;
macos*)
asset="mdbook-$TAG-$host.tar.gz"
# There is a bug with BSD tar on macOS where the first 8MB of the file are
# sometimes all NUL bytes. See https://github.com/actions/cache/issues/403
# and https://github.com/rust-lang/cargo/issues/8603 for some more
# information. An alternative solution here is to install GNU tar, but
# flushing the disk cache seems to work, too.
sudo /usr/sbin/purge
tar czf ../../$asset mdbook
;;
windows*)
asset="mdbook-$TAG-$host.zip"
7z a ../../$asset mdbook.exe

View File

@@ -3,6 +3,7 @@ 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;
@@ -33,9 +34,10 @@ fn main() {
fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
if ctx.mdbook_version != mdbook::MDBOOK_VERSION {
// We should probably use the `semver` crate to check compatibility
// here...
let book_version = Version::parse(&ctx.mdbook_version)?;
let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?;
if !version_req.matches(&book_version) {
eprintln!(
"Warning: The {} plugin was built against version {} of mdbook, \
but we're being called from version {}",
@@ -53,7 +55,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,6 +10,8 @@ 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
@@ -23,3 +25,6 @@ boost-hierarchy = 2
boost-paragraph = 1
expand = true
heading-split-level = 2
[output.html.redirect]
"/format/config.html" = "configuration/index.html"

40
guide/src/README.md Normal file
View File

@@ -0,0 +1,40 @@
# Introduction
**mdBook** is a command line tool and Rust crate to create books with Markdown. The output resembles tools like Gitbook,
and is ideal for creating product or API documentation, tutorials, course materials or anything that requires a clean,
easily navigable and customizable presentation. mdBook is written in [Rust](https://www.rust-lang.org); its performance
and simplicity made it ideal for use as a tool to publish directly to hosted websites such
as [GitHub Pages](https://pages.github.com) via automation. This guide, in fact, serves as both the mdBook documentation
and a fine example of what mdBook produces.
mdBook includes built in support for both preprocessing your Markdown and alternative renderers for producing formats
other than HTML. These facilities also enable other functionality such as
validation. [Searching](https://crates.io/search?q=mdbook&sort=relevance) Rust's [crates.io](https://crates.io) is a
great way to discover more extensions.
## API Documentation
In addition to the above features, mdBook also has a Rust [API](https://docs.rs/mdbook/*/mdbook/). This allows you to
write your own preprocessor or renderer, as well as incorporate mdBook features into other applications.
The [For Developers](for_developers) section of this guide contains more information and some examples.
## Markdown
mdBook's [parser](https://github.com/raphlinus/pulldown-cmark) adheres to the [CommonMark](https://commonmark.org/)
specification. You can take a quick [tutorial](https://commonmark.org/help/tutorial/),
or [try out](https://spec.commonmark.org/dingus/) CommonMark in real time. For a more in-depth experience, check out the
[Markdown Guide](https://www.markdownguide.org).
## 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).
## License
The mdBook source and documentation are released under
the [Mozilla Public License v2.0](https://www.mozilla.org/MPL/2.0/).

View File

@@ -1,6 +1,6 @@
# Summary
- [mdBook](README.md)
- [Introduction](README.md)
- [Command Line Tool](cli/README.md)
- [init](cli/init.md)
- [build](cli/build.md)
@@ -11,13 +11,17 @@
- [Format](format/README.md)
- [SUMMARY.md](format/summary.md)
- [Draft chapter]()
- [Configuration](format/config.md)
- [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)
- [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)
- [mdBook-specific features](format/mdbook.md)
- [Continuous Integration](continuous-integration.md)
- [For Developers](for_developers/README.md)
- [Preprocessors](for_developers/preprocessors.md)

View File

@@ -12,7 +12,7 @@ to download the appropriate version for your platform.
## Install From Source
mdBook can also be installed from source
mdBook can also be installed by compiling the source code on your local machine.
### Pre-requisite

View File

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

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` 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)
- The `SUMMARY.md` is the skeleton of your
book, and is discussed in more detail [in another
chapter](../format/summary.md).
#### Tip: Generate chapters from SUMMARY.md
@@ -52,3 +52,19 @@ 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,8 +1,15 @@
# The serve command
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
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
connection is used to trigger the client-side refresh.
***Note:*** *The `serve` command is for testing a book's HTML output, and is not
@@ -17,24 +24,14 @@ root instead of the current working directory.
mdbook serve path/to/book
```
#### Server options
### Server options
`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:
The `serve` hostname defaults to `localhost`, and the port defaults to `3000`. Either option can be specified on the command line:
```bash
mdbook serve path/to/book -p 8000 -n 127.0.0.1 --websocket-hostname 192.168.1.100
mdbook serve path/to/book -p 8000 -n 127.0.0.1
```
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
@@ -55,5 +52,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 `.gitignore` from book root directory is used. Global
`$HOME/.gitignore` or `.gitignore` files in parent directories are not used._
***Note:*** *Only the `.gitignore` from the book root directory is used. Global
`$HOME/.gitignore` or `.gitignore` files in parent directories are not used.*

View File

@@ -43,7 +43,17 @@ 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`).
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.
#### --dest-dir

View File

@@ -3,7 +3,8 @@
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.
build automatically whenever you modify a file; this includes re-creating
deleted files still mentioned in `SUMMARY.md`!
#### Specify a directory

View File

@@ -26,7 +26,7 @@ before_script:
- cargo install-update -a
script:
- mdbook build path/to/mybook && mdbook test path/to/mybook
- mdbook build && mdbook test # In case of custom book path: mdbook build path/to/mybook && mdbook test path/to/mybook
```
## Deploying Your Book to GitHub Pages
@@ -39,6 +39,9 @@ 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.
Whilst still in your repository's settings page, navigate to Options and change the
Source on GitHub pages to `gh-pages`.
Then, append this snippet to your `.travis.yml` and update the path to the
`book` directory:
@@ -47,14 +50,48 @@ deploy:
provider: pages
skip-cleanup: true
github-token: $GITHUB_TOKEN
local-dir: path/to/mybook/book
local-dir: book # In case of custom book path: path/to/mybook/book
keep-history: false
on:
branch: master
branch: main
```
That's it!
Note: Travis has a new [dplv2](https://blog.travis-ci.com/2019-08-27-deployment-tooling-dpl-v2-preview-release) configuration that is currently in beta. To use this new format, update your `.travis.yml` file to:
```yaml
language: rust
os: linux
dist: xenial
cache:
- cargo
rust:
- stable
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
script:
- mdbook build && mdbook test # In case of custom book path: mdbook build path/to/mybook && mdbook test path/to/mybook
deploy:
provider: pages
strategy: git
edge: true
cleanup: false
github-token: $GITHUB_TOKEN
local-dir: book # In case of custom book path: path/to/mybook/book
keep-history: false
on:
branch: main
target_branch: gh-pages
```
### Deploying to GitHub Pages manually
If your CI doesn't support GitHub pages, or you're deploying somewhere else
@@ -87,3 +124,31 @@ deploy: book
git commit -m "deployed on $(shell date) by ${USER}" && \
git push origin gh-pages
```
## Deploying Your Book to GitLab Pages
Inside your repository's project root, create a file named `.gitlab-ci.yml` with the following contents:
```yml
stages:
- deploy
pages:
stage: deploy
image: rust
variables:
CARGO_HOME: $CI_PROJECT_DIR/cargo
before_script:
- export PATH="$PATH:$CARGO_HOME/bin"
- mdbook --version || cargo install mdbook
script:
- mdbook build -d public
rules:
- if: '$CI_COMMIT_REF_NAME == "master"'
artifacts:
paths:
- public
cache:
paths:
- $CARGO_HOME/bin
```
After you commit and push this new file, GitLab CI will run and your book will be available!

View File

@@ -13,6 +13,7 @@ rough example of how this is accomplished in practice.
- [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`)
- [mdbook-man] - generate manual pages from the book
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
@@ -377,6 +378,7 @@ the source code or ask questions.
[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
[mdbook-man]: https://github.com/vv9k/mdbook-man
[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

View File

@@ -18,9 +18,9 @@ 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.
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.
A preprocessor can be hard-coded to specify which backend(s) it should be run
for with the `preprocessor.foo.renderer` key. For example, it doesn't make sense for
[MathJax](../format/mathjax.md) to be used for non-HTML renderers.
```toml
[book]
@@ -34,8 +34,15 @@ command = "python3 /path/to/foo.py"
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.
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.
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.
The preprocessor should return the JSON format of the [`Book`] object to stdout, with any modifications it wishes to perform.
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
@@ -106,6 +113,33 @@ 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
@@ -113,3 +147,5 @@ For everything else, have a look [at the complete example][example].
[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

View File

@@ -0,0 +1,12 @@
# 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

@@ -0,0 +1,38 @@
# 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

@@ -0,0 +1,97 @@
# 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.
- **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.
- **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.

View File

@@ -0,0 +1,80 @@
# 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"
```
### 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,161 +1,4 @@
# 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
# Configuring Renderers
### HTML renderer options
@@ -175,7 +18,7 @@ The following configuration options are available:
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
- **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
@@ -187,6 +30,9 @@ The following configuration options are available:
- **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`.
@@ -198,18 +44,37 @@ The following configuration options are available:
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`.
- **edit-url-template:** Edit url template, when provided shows a
"Suggest an edit" button 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.
- **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.
- **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
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:
@@ -272,9 +137,14 @@ 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"
[output.html.print]
enable = true
[output.html.fold]
enable = false
level = 0
@@ -318,13 +188,13 @@ Enable it by adding an empty table to your `book.toml` as follows:
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
See [the preprocessors documentation](preprocessors.md) 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
`book.toml`. Similar to [preprocessors](preprocessors.md) this will
instruct `mdbook` to pass a representation of the book to `mdbook-foo` for
rendering. See the [alternative backends] chapter for more detail.
@@ -336,43 +206,4 @@ anything under `[output.foo]`). mdBook checks for two common fields:
- **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.
[alternative backends]: ../../for_developers/backends.md

View File

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

View File

@@ -1,9 +1,9 @@
# mdBook-specific markdown
# mdBook-specific features
## Hiding code lines
There is a feature in mdBook that lets you hide code lines by prepending them
with a `#` [in the same way that Rustdoc does][rustdoc-hide].
with a `#` [like you would with Rustdoc][rustdoc-hide].
[rustdoc-hide]: https://doc.rust-lang.org/stable/rustdoc/documentation-tests.html#hiding-portions-of-the-example
@@ -21,7 +21,7 @@ Will render as
```rust
# fn main() {
let x = 5;
let y = 7;
let y = 6;
println!("{}", x + y);
# }
@@ -37,10 +37,10 @@ With the following syntax, you can include files into your book:
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
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
interpretting them.
interpreting them.
````hbs
```
@@ -49,7 +49,7 @@ interpretting 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
@@ -68,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:
@@ -156,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
#}
# }
```
````
@@ -170,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
@@ -192,3 +192,12 @@ Here is what a rendered code snippet looks like:
{{#playground example.rs}}
[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

@@ -0,0 +1,99 @@
# SUMMARY.md
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.
### 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. ***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 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)
```
1. ***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.
Titles are optional, and the numbered chapters can be broken into as many
parts as desired.
```markdown
# My Part Tile
- [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.).
```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 Another Part
- [Another Chapter](relative/path/to/markdown4.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)
[Title of Suffix Chapter](relative/path/to/markdown2.md)
```
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]()
```
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)
---
- [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}}
```

View File

@@ -14,9 +14,11 @@ 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.
- **_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.
- **_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.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,
@@ -40,5 +42,5 @@ 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`]: ../config.md#html-renderer-options
[`output.html.preferred-dark-theme`]: ../configuration/renderers.md#html-renderer-options
[newer browsers]: https://caniuse.com/#feat=link-icon-svg

View File

@@ -33,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 overriden by providing a different folder:
the functionality may be overridden by providing a different folder:
```toml
[output.html.playground]
@@ -42,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 overriden as it has some couplings with the
the `theme` folder will need to be overridden 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 `{{ book_title }} - {{ chapter_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 `{{ chapter_title }} - {{ book_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

@@ -1,16 +1,67 @@
# Syntax Highlighting
For syntax highlighting I use [Highlight.js](https://highlightjs.org) with a
custom theme.
mdBook uses [Highlight.js](https://highlightjs.org) with a custom theme
for syntax highlighting.
Automatic language detection has been turned off, so you will probably want to
specify the programming language you use like this
specify the programming language you use like this:
<pre><code class="language-markdown">```rust
~~~markdown
```rust
fn main() {
// Some code
}
```</code></pre>
```
~~~
## Supported languages
These languages are supported by default, but you can add more by supplying
your own `highlight.js` file:
- 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
## Custom theme
Like the rest of the theme, the files used for syntax highlighting can be
@@ -61,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 to many flashy colors.
Overall the theme should be light and sober, without too many flashy colors.

View File

@@ -15,6 +15,10 @@ 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))
- 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))
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

@@ -14,14 +14,15 @@ 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(|| "Couldn't open SUMMARY.md")?
File::open(&summary_md)
.with_context(|| format!("Couldn't open SUMMARY.md in {:?} directory", src_dir))?
.read_to_string(&mut summary_content)?;
let summary = parse_summary(&summary_content).with_context(|| "Summary parsing failed")?;
let summary = parse_summary(&summary_content)
.with_context(|| format!("Summary parsing failed for file={:?}", summary_md))?;
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)
@@ -49,7 +50,9 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
}
debug!("Creating missing file {}", filename.display());
let mut f = File::create(&filename)?;
let mut f = File::create(&filename).with_context(|| {
format!("Unable to create missing file: {}", filename.display())
})?;
writeln!(f, "# {}", link.name)?;
}
}
@@ -63,7 +66,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()`].
@@ -157,7 +160,9 @@ pub struct Chapter {
pub sub_items: Vec<BookItem>,
/// The chapter's location, relative to the `SUMMARY.md` file.
pub path: Option<PathBuf>,
/// An ordered list of the names of each chapter above this one, in the hierarchy.
/// 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.
pub parent_names: Vec<String>,
}
@@ -166,36 +171,36 @@ impl Chapter {
pub fn new<P: Into<PathBuf>>(
name: &str,
content: String,
path: P,
p: P,
parent_names: Vec<String>,
) -> Chapter {
let path: PathBuf = p.into();
Chapter {
name: name.to_string(),
content,
path: Some(path.into()),
path: Some(path.clone()),
source_path: Some(path),
parent_names,
..Default::default()
}
}
/// Create a new draft chapter that is not attached to a source markdown file and has
/// thus no content.
/// Create a new draft chapter that is not attached to a source markdown file (and thus
/// has 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 {
match self.path {
Some(_) => false,
None => true,
}
self.path.is_none()
}
}
@@ -264,6 +269,10 @@ 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");
@@ -273,7 +282,7 @@ fn load_chapter<P: AsRef<Path>>(
Chapter::new_draft(&link.name, parent_names.clone())
};
let mut sub_item_parents = parent_names.clone();
let mut sub_item_parents = parent_names;
ch.number = link.number.clone();
@@ -295,8 +304,6 @@ 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>,
}
@@ -374,7 +381,7 @@ And here is some \
root.nested_items.push(second.clone().into());
root.nested_items.push(SummaryItem::Separator);
root.nested_items.push(second.clone().into());
root.nested_items.push(second.into());
(root, temp_dir)
}
@@ -393,6 +400,29 @@ 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");
@@ -410,6 +440,7 @@ 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(),
};
@@ -418,11 +449,12 @@ 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.clone()),
BookItem::Chapter(nested),
],
});
@@ -442,6 +474,7 @@ 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()
@@ -482,6 +515,7 @@ 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(
@@ -534,6 +568,7 @@ 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
@@ -109,12 +109,8 @@ impl BookBuilder {
fn copy_across_theme(&self) -> Result<()> {
debug!("Copying theme");
let themedir = self
.config
.html_config()
.and_then(|html| html.theme)
.unwrap_or_else(|| self.config.book.src.join("theme"));
let themedir = self.root.join(themedir);
let html_config = self.config.html_config().unwrap_or_default();
let themedir = html_config.theme_dir(&self.root);
if !themedir.exists() {
debug!(
@@ -128,7 +124,9 @@ impl BookBuilder {
index.write_all(theme::INDEX)?;
let cssdir = themedir.join("css");
fs::create_dir(&cssdir)?;
if !cssdir.exists() {
fs::create_dir(&cssdir)?;
}
let mut general_css = File::create(cssdir.join("general.css"))?;
general_css.write_all(theme::GENERAL_CSS)?;
@@ -136,8 +134,10 @@ impl BookBuilder {
let mut chrome_css = File::create(cssdir.join("chrome.css"))?;
chrome_css.write_all(theme::CHROME_CSS)?;
let mut print_css = File::create(cssdir.join("print.css"))?;
print_css.write_all(theme::PRINT_CSS)?;
if html_config.print.enable {
let mut print_css = File::create(cssdir.join("print.css"))?;
print_css.write_all(theme::PRINT_CSS)?;
}
let mut variables_css = File::create(cssdir.join("variables.css"))?;
variables_css.write_all(theme::VARIABLES_CSS)?;

View File

@@ -20,6 +20,7 @@ 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::{
@@ -40,7 +41,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>>,
}
@@ -78,7 +79,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();
@@ -97,7 +98,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,
@@ -121,7 +122,7 @@ impl MDBook {
}
/// Returns a flat depth-first iterator over the elements of the book,
/// it returns an [BookItem enum](bookitem.html):
/// it returns a [`BookItem`] enum:
/// `(section: String, bookitem: &BookItem)`
///
/// ```no_run
@@ -180,7 +181,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(
@@ -196,37 +197,34 @@ 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 render_context = RenderContext::new(
let mut render_context = RenderContext::new(
self.root.clone(),
preprocessed_book.clone(),
preprocessed_book,
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 for your renderer to implement the [`Renderer`
/// trait](../renderer/trait.Renderer.html)
/// The only requirement is that your renderer implement the [`Renderer`]
/// trait.
pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self {
self.renderers.push(Box::new(renderer));
self
}
/// Register a [`Preprocessor`](../preprocess/trait.Preprocessor.html) to be used when rendering the book.
/// Register a [`Preprocessor`] 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
@@ -250,6 +248,7 @@ impl MDBook {
// Index Preprocessor is disabled so that chapter paths continue to point to the
// actual markdown files.
let mut failed = false;
for item in book.iter() {
if let BookItem::Chapter(ref ch) = *item {
let chapter_path = match ch.path {
@@ -276,13 +275,18 @@ impl MDBook {
RustEdition::E2018 => {
cmd.args(&["--edition", "2018"]);
}
RustEdition::E2021 => {
cmd.args(&["--edition", "2021"])
.args(&["-Z", "unstable-options"]);
}
}
}
let output = cmd.output()?;
if !output.status.success() {
bail!(
failed = true;
error!(
"rustdoc returned an error:\n\
\n--- stdout\n{}\n--- stderr\n{}",
String::from_utf8_lossy(&output.stdout),
@@ -291,6 +295,9 @@ impl MDBook {
}
}
}
if failed {
bail!("One or more tests failed");
}
Ok(())
}
@@ -298,7 +305,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):
@@ -366,12 +373,7 @@ fn determine_renderers(config: &Config) -> Vec<Box<dyn Renderer>> {
renderers
}
fn default_preprocessors() -> Vec<Box<dyn Preprocessor>> {
vec![
Box::new(LinkPreprocessor::new()),
Box::new(IndexPreprocessor::new()),
]
}
const DEFAULT_PREPROCESSORS: &[&'static str] = &["links", "index"];
fn is_default_preprocessor(pre: &dyn Preprocessor) -> bool {
let name = pre.name();
@@ -380,36 +382,127 @@ 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>>> {
let mut preprocessors = Vec::new();
// 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();
if config.build.use_default_preprocessors {
preprocessors.extend(default_preprocessors());
for name in DEFAULT_PREPROCESSORS {
preprocessor_names.insert(name.to_string());
}
}
if let Some(preprocessor_table) = config.get("preprocessor").and_then(Value::as_table) {
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],
)),
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);
}
}
}
}
}
Ok(preprocessors)
// 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"))
}
}
fn interpret_custom_preprocessor(key: &str, table: &Value) -> Box<CmdPreprocessor> {
let command = table
fn get_custom_preprocessor_cmd(key: &str, table: &Value) -> String {
table
.get("command")
.and_then(Value::as_str)
.map(ToString::to_string)
.unwrap_or_else(|| format!("mdbook-{}", key));
Box::new(CmdPreprocessor::new(key.to_string(), command))
.unwrap_or_else(|| format!("mdbook-{}", key))
}
fn interpret_custom_renderer(key: &str, table: &Value) -> Box<CmdRenderer> {
@@ -509,8 +602,8 @@ mod tests {
assert!(got.is_ok());
assert_eq!(got.as_ref().unwrap().len(), 2);
assert_eq!(got.as_ref().unwrap()[0].name(), "links");
assert_eq!(got.as_ref().unwrap()[1].name(), "index");
assert_eq!(got.as_ref().unwrap()[0].name(), "index");
assert_eq!(got.as_ref().unwrap()[1].name(), "links");
}
#[test]
@@ -557,9 +650,123 @@ mod tests {
// make sure the `preprocessor.random` table exists
let random = cfg.get_preprocessor("random").unwrap();
let random = interpret_custom_preprocessor("random", &Value::Table(random.clone()));
let random = get_custom_preprocessor_cmd("random", &Value::Table(random.clone()));
assert_eq!(random.cmd(), "python random.py");
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());
}
#[test]

View File

@@ -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 != $delimeter).collect()` but it allows you to
/// `$stream.take_while(|e| e != $delimiter).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 {
@@ -382,7 +382,7 @@ impl<'a> SummaryParser<'a> {
}
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,14 +525,19 @@ impl<'a> SummaryParser<'a> {
/// Try to parse the title line.
fn parse_title(&mut self) -> Option<String> {
match self.next_event() {
Some(Event::Start(Tag::Heading(1))) => {
debug!("Found a h1 in the SUMMARY");
loop {
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(1));
Some(stringify_events(tags))
let tags = collect_events!(self.stream, end Tag::Heading(1));
return Some(stringify_events(tags));
}
// Skip a HTML element such as a comment line.
Some(Event::Html(_)) => {}
// Otherwise, no title.
_ => return None,
}
_ => None,
}
}
}
@@ -973,4 +978,103 @@ 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,5 +1,5 @@
use crate::get_book_dir;
use clap::{App, ArgMatches, SubCommand};
use clap::{App, Arg, ArgMatches, SubCommand};
use mdbook::config;
use mdbook::errors::Result;
use mdbook::MDBook;
@@ -18,6 +18,21 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
)
.arg_from_usage("--theme 'Copies the default theme into your source folder'")
.arg_from_usage("--force 'Skips confirmation prompts'")
.arg(
Arg::with_name("title")
.long("title")
.takes_value(true)
.help("Sets the book title")
.required(false),
)
.arg(
Arg::with_name("ignore")
.long("ignore")
.takes_value(true)
.possible_values(&["none", "git"])
.help("Creates a VCS ignore file (i.e. .gitignore)")
.required(false),
)
}
// Init command implementation
@@ -25,18 +40,13 @@ 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") {
config.set("output.html.theme", "src/theme")?;
let theme_dir = book_dir.join("theme");
println!();
println!("Copying the default theme to {}", theme_dir.display());
// Skip this if `--force` is present
if !args.is_present("force") {
// Print warning
println!();
println!(
"Copying the default theme to {}",
builder.config().book.src.display()
);
if !args.is_present("force") && theme_dir.exists() {
println!("This could potentially overwrite files already present in that directory.");
print!("\nAre you sure you want to continue? (y/n) ");
@@ -49,13 +59,23 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
}
}
println!("\nDo you want a .gitignore to be created? (y/n)");
if confirm() {
builder.create_gitignore(true);
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);
}
}
config.book.title = request_book_title();
config.book.title = if args.is_present("title") {
args.value_of("title").map(String::from)
} else {
request_book_title()
};
if let Some(author) = get_author_name() {
debug!("Obtained user name from gitconfig: {:?}", author);

View File

@@ -161,5 +161,12 @@ 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

@@ -58,7 +58,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
Ok(())
}
fn remove_ignored_files(book_root: &PathBuf, paths: &[PathBuf]) -> Vec<PathBuf> {
fn remove_ignored_files(book_root: &Path, paths: &[PathBuf]) -> Vec<PathBuf> {
if paths.is_empty() {
return vec![];
}
@@ -81,7 +81,7 @@ fn remove_ignored_files(book_root: &PathBuf, paths: &[PathBuf]) -> Vec<PathBuf>
}
}
fn find_gitignore(book_root: &PathBuf) -> Option<PathBuf> {
fn find_gitignore(book_root: &Path) -> 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,6 +294,7 @@ 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)?;
@@ -310,10 +311,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",
));
@@ -322,17 +323,20 @@ impl<'de> Deserialize<'de> for Config {
let book: BookConfig = table
.remove("book")
.and_then(|value| value.try_into().ok())
.map(|book| book.try_into().map_err(D::Error::custom))
.transpose()?
.unwrap_or_default();
let build: BuildConfig = table
.remove("build")
.and_then(|value| value.try_into().ok())
.map(|build| build.try_into().map_err(D::Error::custom))
.transpose()?
.unwrap_or_default();
let rust: RustConfig = table
.remove("rust")
.and_then(|value| value.try_into().ok())
.map(|rust| rust.try_into().map_err(D::Error::custom))
.transpose()?
.unwrap_or_default();
Ok(Config {
@@ -352,6 +356,11 @@ 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);
@@ -458,6 +467,9 @@ 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,
@@ -495,6 +507,8 @@ pub struct HtmlConfig {
/// Playground settings.
#[serde(alias = "playpen")]
pub playground: Playground,
/// Print settings.
pub print: Print,
/// Don't render section labels.
pub no_section_label: bool,
/// Search settings. If `None`, the default will be used.
@@ -508,6 +522,17 @@ pub struct HtmlConfig {
pub input_404: Option<String>,
/// Absolute url to site, used to emit correct paths for the 404 page, which might be accessed in a deeply nested directory
pub site_url: Option<String>,
/// 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
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>,
/// 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
@@ -535,12 +560,15 @@ impl Default for HtmlConfig {
additional_js: Vec::new(),
fold: Fold::default(),
playground: Playground::default(),
print: Print::default(),
no_section_label: false,
search: None,
git_repository_url: None,
git_repository_icon: None,
edit_url_template: None,
input_404: None,
site_url: None,
cname: None,
livereload_url: None,
redirect: HashMap::new(),
}
@@ -550,7 +578,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: &PathBuf) -> PathBuf {
pub fn theme_dir(&self, root: &Path) -> PathBuf {
match self.theme {
Some(ref d) => root.join(d),
None => root.join("theme"),
@@ -558,6 +586,20 @@ impl HtmlConfig {
}
}
/// Configuration for how to render the print icon, print.html, and print.css.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Print {
/// Whether print support is enabled.
pub enable: bool,
}
impl Default for Print {
fn default() -> Self {
Self { enable: true }
}
}
/// Configuration for how to fold chapters of sidebar.
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
@@ -621,7 +663,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, seperated by headings. This defines, until which
/// Documents are split into smaller parts, separated 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?
@@ -816,6 +858,26 @@ 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)]
@@ -1035,4 +1097,57 @@ 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();
}
}

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/struct.RenderContext.html
//! [`RenderContext`]: renderer::RenderContext
//! [relevant chapter]: https://rust-lang.github.io/mdBook/for_developers/backends.html
//! [`Config`]: config/struct.Config.html
//! [`Config`]: config::Config
#![deny(missing_docs)]
#![deny(rust_2018_idioms)]

View File

@@ -3,8 +3,9 @@ extern crate clap;
#[macro_use]
extern crate log;
use anyhow::anyhow;
use chrono::Local;
use clap::{App, AppSettings, ArgMatches};
use clap::{App, AppSettings, Arg, ArgMatches, Shell, SubCommand};
use env_logger::Builder;
use log::LevelFilter;
use mdbook::utils;
@@ -20,7 +21,40 @@ const VERSION: &str = concat!("v", crate_version!());
fn main() {
init_logger();
// Create a list of valid arguments and sub-commands
let app = create_clap_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),
("completions", Some(sub_matches)) => (|| {
let shell: Shell = sub_matches
.value_of("shell")
.ok_or_else(|| anyhow!("Shell name missing."))?
.parse()
.map_err(|s| anyhow!("Invalid shell: {}", s))?;
create_clap_app().gen_completions_to("mdbook", shell, &mut std::io::stdout().lock());
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<'a, 'b>() -> App<'a, 'b> {
let app = App::new(crate_name!())
.about(crate_description!())
.author("Mathieu David <mathieudavid@mathieudavid.org>")
@@ -35,31 +69,26 @@ fn main() {
.subcommand(cmd::init::make_subcommand())
.subcommand(cmd::build::make_subcommand())
.subcommand(cmd::test::make_subcommand())
.subcommand(cmd::clean::make_subcommand());
.subcommand(cmd::clean::make_subcommand())
.subcommand(
SubCommand::with_name("completions")
.about("Generate shell completions for your shell to stdout")
.arg(
Arg::with_name("shell")
.takes_value(true)
.possible_values(&Shell::variants())
.help("the shell to generate completions for")
.value_name("SHELL")
.required(true),
),
);
#[cfg(feature = "watch")]
let app = app.subcommand(cmd::watch::make_subcommand());
#[cfg(feature = "serve")]
let app = app.subcommand(cmd::serve::make_subcommand());
// 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);
}
app
}
fn init_logger() {
@@ -103,7 +132,8 @@ fn get_book_dir(args: &ArgMatches) -> PathBuf {
}
fn open<P: AsRef<OsStr>>(path: P) {
if let Err(e) = open::that(path) {
info!("Opening web browser");
if let Err(e) = opener::open(path) {
error!("Error opening web browser: {}", e);
}
}

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,18 +109,28 @@ impl Preprocessor for CmdPreprocessor {
self.write_input_to_child(&mut child, &book, ctx);
let output = child
.wait_with_output()
.with_context(|| "Error waiting for the preprocessor to complete")?;
let output = child.wait_with_output().with_context(|| {
format!(
"Error waiting for the \"{}\" preprocessor to complete",
self.name
)
})?;
trace!("{} exited with output: {:?}", self.cmd, output);
ensure!(
output.status.success(),
"The preprocessor exited unsuccessfully"
format!(
"The \"{}\" preprocessor exited unsuccessfully with {} status",
self.name, output.status
)
);
serde_json::from_slice(&output.stdout)
.with_context(|| "Unable to parse the preprocessed book")
serde_json::from_slice(&output.stdout).with_context(|| {
format!(
"Unable to parse the preprocessed book from \"{}\" processor",
self.name
)
})
}
fn supports_renderer(&self, renderer: &str) -> bool {
@@ -171,15 +181,15 @@ mod tests {
use crate::MDBook;
use std::path::Path;
fn book_example() -> MDBook {
let example = Path::new(env!("CARGO_MANIFEST_DIR")).join("book-example");
fn guide() -> MDBook {
let example = Path::new(env!("CARGO_MANIFEST_DIR")).join("guide");
MDBook::load(example).unwrap()
}
#[test]
fn round_trip_write_and_parse_input() {
let cmd = CmdPreprocessor::new("test".to_string(), "test".to_string());
let md = book_example();
let md = guide();
let ctx = PreprocessorContext::new(
md.root.clone(),
md.config.clone(),

View File

@@ -23,6 +23,7 @@ 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;
@@ -51,8 +52,15 @@ impl Preprocessor for LinkPreprocessor {
.map(|dir| src_dir.join(dir))
.expect("All book items have a parent");
let content = replace_all(&ch.content, base, chapter_path, 0);
let mut chapter_title = ch.name.clone();
let content =
replace_all(&ch.content, base, chapter_path, 0, &mut chapter_title);
ch.content = content;
if chapter_title != ch.name {
ctx.chapter_titles
.borrow_mut()
.insert(chapter_path.clone(), chapter_title);
}
}
}
});
@@ -61,7 +69,13 @@ impl Preprocessor for LinkPreprocessor {
}
}
fn replace_all<P1, P2>(s: &str, path: P1, source: P2, depth: usize) -> String
fn replace_all<P1, P2>(
s: &str,
path: P1,
source: P2,
depth: usize,
chapter_title: &mut String,
) -> String
where
P1: AsRef<Path>,
P2: AsRef<Path>,
@@ -77,11 +91,17 @@ where
for link in find_links(s) {
replaced.push_str(&s[previous_end_index..link.start_index]);
match link.render_with_path(&path) {
match link.render_with_path(&path, chapter_title) {
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));
replaced.push_str(&replace_all(
&new_content,
rel_path,
source,
depth + 1,
chapter_title,
));
} else {
replaced.push_str(&new_content);
}
@@ -116,6 +136,7 @@ enum LinkType<'a> {
Include(PathBuf, RangeOrAnchor),
Playground(PathBuf, Vec<&'a str>),
RustdocInclude(PathBuf, RangeOrAnchor),
Title(&'a str),
}
#[derive(PartialEq, Debug, Clone)]
@@ -185,6 +206,7 @@ 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,
}
}
}
@@ -255,6 +277,9 @@ 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();
@@ -291,7 +316,11 @@ impl<'a> Link<'a> {
})
}
fn render_with_path<P: AsRef<Path>>(&self, base: P) -> Result<String> {
fn render_with_path<P: AsRef<Path>>(
&self,
base: P,
chapter_title: &mut String,
) -> Result<String> {
let base = base.as_ref();
match self.link_type {
// omit the escape char
@@ -335,7 +364,7 @@ impl<'a> Link<'a> {
LinkType::Playground(ref pat, ref attrs) => {
let target = base.join(pat);
let contents = fs::read_to_string(&target).with_context(|| {
let mut contents = fs::read_to_string(&target).with_context(|| {
format!(
"Could not read file for link {} ({})",
self.link_text,
@@ -343,13 +372,20 @@ 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())
}
}
}
}
@@ -370,17 +406,17 @@ impl<'a> Iterator for LinkIter<'a> {
fn find_links(contents: &str) -> LinkIter<'_> {
// lazily compute following regex
// r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([a-zA-Z0-9_.\-:/\\\s]+)\}\}")?;
// r"\\\{\{#.*\}\}|\{\{#([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
([a-zA-Z0-9\s_.\-:/\\\+]+) # link target path and space separated properties
\s*\}\} # whitespace and 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
([^}]+) # link target path and space separated properties
\}\} # link closing parens"
)
.unwrap();
}
@@ -403,7 +439,21 @@ mod tests {
```hbs
{{#include file.rs}} << an escaped link!
```";
assert_eq!(replace_all(start, "", "", 0), end);
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");
}
#[test]

View File

@@ -12,6 +12,8 @@ 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
@@ -27,6 +29,8 @@ 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: (),
}
@@ -38,6 +42,7 @@ 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::{Config, HtmlConfig, Playground, RustEdition};
use crate::config::{BookConfig, Config, HtmlConfig, Playground, RustEdition};
use crate::errors::*;
use crate::renderer::html_handlebars::helpers;
use crate::renderer::{RenderContext, Renderer};
@@ -37,14 +37,32 @@ 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),
);
let fixed_content =
utils::render_markdown_with_path(&ch.content, ctx.html_config.curly_quotes, Some(path));
if !ctx.is_index {
// 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>"#);
}
print_content.push_str(&fixed_content);
// Update the context with data for this file
@@ -64,9 +82,12 @@ impl HtmlHandlebars {
.and_then(serde_json::Value::as_str)
.unwrap_or("");
let title = match book_title {
"" => ch.name.clone(),
_ => ch.name.clone() + " - " + book_title,
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
};
ctx.data.insert("path".to_owned(), json!(path));
@@ -110,7 +131,7 @@ impl HtmlHandlebars {
&self,
ctx: &RenderContext,
html_config: &HtmlConfig,
src_dir: &PathBuf,
src_dir: &Path,
handlebars: &mut Handlebars<'_>,
data: &mut serde_json::Map<String, serde_json::Value>,
) -> Result<()> {
@@ -154,7 +175,7 @@ impl HtmlHandlebars {
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(())
}
@@ -184,19 +205,25 @@ impl HtmlHandlebars {
write_file(
destination,
".nojekyll",
b"This file makes sure that Github Pages doesn't process mdBook's output.",
b"This file makes sure that Github Pages doesn't process mdBook's output.\n",
)?;
if let Some(cname) = &html_config.cname {
write_file(destination, "CNAME", format!("{}\n", cname).as_bytes())?;
}
write_file(destination, "book.js", &theme.js)?;
write_file(destination, "css/general.css", &theme.general_css)?;
write_file(destination, "css/chrome.css", &theme.chrome_css)?;
write_file(destination, "css/print.css", &theme.print_css)?;
if html_config.print.enable {
write_file(destination, "css/print.css", &theme.print_css)?;
}
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)?;
@@ -360,7 +387,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)?;
}
@@ -431,6 +458,7 @@ 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;
@@ -446,7 +474,7 @@ impl Renderer for HtmlHandlebars {
let mut handlebars = Handlebars::new();
let theme_dir = match html_config.theme {
Some(ref theme) => theme.to_path_buf(),
Some(ref theme) => ctx.root.join(theme),
None => ctx.root.join("theme"),
};
@@ -478,7 +506,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();
@@ -493,8 +521,10 @@ 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;
@@ -512,19 +542,21 @@ impl Renderer for HtmlHandlebars {
}
// Render the handlebars template with the data
debug!("Render template");
let rendered = handlebars.render("index", &data)?;
if html_config.print.enable {
debug!("Render template");
let rendered = handlebars.render("index", &data)?;
let rendered =
self.post_process(rendered, &html_config.playground, ctx.config.rust.edition);
let rendered =
self.post_process(rendered, &html_config.playground, ctx.config.rust.edition);
utils::fs::write_file(&destination, "print.html", rendered.as_bytes())?;
debug!("Creating print.html ✓");
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
@@ -532,7 +564,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)?;
}
}
@@ -540,7 +572,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(())
}
@@ -640,8 +672,9 @@ fn make_data(
data.insert("playground_copyable".to_owned(), json!(true));
}
data.insert("fold_enable".to_owned(), json!((html_config.fold.enable)));
data.insert("fold_level".to_owned(), json!((html_config.fold.level)));
data.insert("print_enable".to_owned(), json!(html_config.print.enable));
data.insert("fold_enable".to_owned(), json!(html_config.fold.enable));
data.insert("fold_level".to_owned(), json!(html_config.fold.level));
let search = html_config.search.clone();
if cfg!(feature = "search") {
@@ -747,7 +780,7 @@ fn insert_link_into_header(
*id_count += 1;
format!(
r##"<h{level}><a class="header" href="#{id}" id="{id}">{text}</a></h{level}>"##,
r##"<h{level} id="{id}"><a class="header" href="#{id}">{text}</a></h{level}>"##,
level = level,
id = id,
text = content
@@ -800,13 +833,15 @@ fn add_playground_pre(
{
let contains_e2015 = classes.contains("edition2015");
let contains_e2018 = classes.contains("edition2018");
let edition_class = if contains_e2015 || contains_e2018 {
let contains_e2021 = classes.contains("edition2021");
let edition_class = if contains_e2015 || contains_e2018 || contains_e2021 {
// 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 => "",
}
};
@@ -890,10 +925,10 @@ fn partition_source(s: &str) -> (String, String) {
if !header || after_header {
after_header = true;
after.push_str(line);
after.push_str("\n");
after.push('\n');
} else {
before.push_str(line);
before.push_str("\n");
before.push('\n');
}
}
@@ -905,8 +940,10 @@ 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)]
@@ -918,32 +955,32 @@ mod tests {
let inputs = vec![
(
"blah blah <h1>Foo</h1>",
r##"blah blah <h1><a class="header" href="#foo" id="foo">Foo</a></h1>"##,
r##"blah blah <h1 id="foo"><a class="header" href="#foo">Foo</a></h1>"##,
),
(
"<h1>Foo</h1>",
r##"<h1><a class="header" href="#foo" id="foo">Foo</a></h1>"##,
r##"<h1 id="foo"><a class="header" href="#foo">Foo</a></h1>"##,
),
(
"<h3>Foo^bar</h3>",
r##"<h3><a class="header" href="#foobar" id="foobar">Foo^bar</a></h3>"##,
r##"<h3 id="foobar"><a class="header" href="#foobar">Foo^bar</a></h3>"##,
),
(
"<h4></h4>",
r##"<h4><a class="header" href="#" id=""></a></h4>"##,
r##"<h4 id=""><a class="header" href="#"></a></h4>"##,
),
(
"<h4><em>Hï</em></h4>",
r##"<h4><a class="header" href="#hï" id="hï"><em>Hï</em></a></h4>"##,
r##"<h4 id="hï"><a class="header" href="#hï"><em>Hï</em></a></h4>"##,
),
(
"<h1>Foo</h1><h3>Foo</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>"##,
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>"##,
),
];
for (src, should_be) in inputs {
let got = build_header_links(&src);
let got = build_header_links(src);
assert_eq!(got, should_be);
}
}
@@ -1026,4 +1063,28 @@ 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,4 +1,5 @@
use std::collections::BTreeMap;
use std::io;
use std::path::Path;
use crate::utils;
@@ -102,38 +103,38 @@ impl HelperDef for RenderToc {
// Part title
if let Some(title) = item.get("part") {
out.write("<li class=\"part-title\">")?;
out.write(title)?;
write_escaped(out, title)?;
out.write("</li>")?;
continue;
}
// Link
let path_exists = if let Some(path) = item.get("path") {
if !path.is_empty() {
out.write("<a href=\"")?;
let path_exists = if let Some(path) =
item.get("path")
.and_then(|p| if p.is_empty() { None } else { Some(p) })
{
out.write("<a href=\"")?;
let tmp = Path::new(item.get("path").expect("Error: path should be Some(_)"))
.with_extension("html")
.to_str()
.unwrap()
// Hack for windows who tends to use `\` as separator instead of `/`
.replace("\\", "/");
let tmp = Path::new(item.get("path").expect("Error: path should be Some(_)"))
.with_extension("html")
.to_str()
.unwrap()
// Hack for windows who tends to use `\` as separator instead of `/`
.replace("\\", "/");
// Add link
out.write(&utils::fs::path_to_root(&current_path))?;
out.write(&tmp)?;
out.write("\"")?;
// Add link
out.write(&utils::fs::path_to_root(&current_path))?;
out.write(&tmp)?;
out.write("\"")?;
if path == &current_path {
out.write(" class=\"active\"")?;
}
out.write(">")?;
true
} else {
false
if path == &current_path {
out.write(" class=\"active\"")?;
}
out.write(">")?;
true
} else {
out.write("<div>")?;
false
};
@@ -141,7 +142,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> ")?;
}
}
@@ -160,11 +161,13 @@ impl HelperDef for RenderToc {
html::push_html(&mut markdown_parsed_name, parser);
// write to the handlebars template
out.write(&markdown_parsed_name)?;
write_escaped(out, &markdown_parsed_name)?;
}
if path_exists {
out.write("</a>")?;
} else {
out.write("</div>")?;
}
// Render expand/collapse toggle
@@ -202,3 +205,18 @@ 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());
@@ -95,6 +95,8 @@ fn render_item(
let mut breadcrumbs = chapter.parent_names.clone();
let mut footnote_numbers = HashMap::new();
breadcrumbs.push(chapter.name.clone());
while let Some(event) = p.next() {
match event {
Event::Start(Tag::Heading(i)) if i <= max_section_depth => {
@@ -132,14 +134,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 seperate text
// Insert spaces where HTML output would usually separate text
// to ensure words don't get merged together
if in_heading {
heading.push(' ');
@@ -163,7 +165,12 @@ fn render_item(
}
}
if !heading.is_empty() {
if !body.is_empty() || !heading.is_empty() {
if heading.is_empty() {
if let Some(chapter) = breadcrumbs.first() {
heading = chapter.clone();
}
}
// Make sure the last section is added to the index
add_doc(
index,

View File

@@ -18,9 +18,10 @@ 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::PathBuf;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use crate::book::Book;
@@ -34,12 +35,9 @@ 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;
@@ -67,6 +65,8 @@ 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,6 +83,7 @@ impl RenderContext {
version: crate::MDBOOK_VERSION.to_string(),
root: root.into(),
destination: destination.into(),
chapter_titles: HashMap::new(),
__non_exhaustive: (),
}
}
@@ -133,14 +134,44 @@ impl CmdRenderer {
CmdRenderer { name, cmd }
}
fn compose_command(&self) -> Result<Command> {
fn compose_command(&self, root: &Path, destination: &Path) -> Result<Command> {
let mut words = Shlex::new(&self.cmd);
let executable = match words.next() {
Some(e) => e,
let exe = match words.next() {
Some(e) => PathBuf::from(e),
None => bail!("Command string was empty"),
};
let mut cmd = Command::new(executable);
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);
for arg in words {
cmd.arg(arg);
@@ -195,7 +226,7 @@ impl Renderer for CmdRenderer {
let _ = fs::create_dir_all(&ctx.destination);
let mut child = match self
.compose_command()?
.compose_command(&ctx.root, &ctx.destination)?
.stdin(Stdio::piped())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())

View File

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

View File

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

View File

@@ -45,20 +45,23 @@ h4, h5 { margin-top: 2em; }
margin-top: 1em;
}
h1 a.header:target::before,
h2 a.header:target::before,
h3 a.header:target::before,
h4 a.header:target::before {
h1:target::before,
h2:target::before,
h3:target::before,
h4:target::before,
h5:target::before,
h6:target::before {
display: inline-block;
content: "»";
margin-left: -30px;
width: 30px;
}
h1 a.header:target,
h2 a.header:target,
h3 a.header:target,
h4 a.header:target {
/* 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 {
scroll-margin-top: calc(var(--menu-bar-height) + 0.5em);
}
@@ -89,7 +92,7 @@ h4 a.header:target {
.content ul { line-height: 1.45em; }
.content a { text-decoration: none; }
.content a:hover { text-decoration: underline; }
.content img { max-width: 100%; }
.content img, .content video { max-width: 100%; }
.content .header:link,
.content .header:visited {
color: var(--fg);
@@ -172,3 +175,7 @@ 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: #333333;
--fg: hsl(0, 0%, 0%);
--sidebar-bg: #fafafa;
--sidebar-fg: #364149;
--sidebar-fg: hsl(0, 0%, 0%);
--sidebar-non-existant: #aaaaaa;
--sidebar-active: #008cff;
--sidebar-active: #1f1fff;
--sidebar-spacer: #f4f4f4;
--scrollbar: #cccccc;
--scrollbar: #8F8F8F;
--icons: #cccccc;
--icons-hover: #333333;
--icons: #747474;
--icons-hover: #000000;
--links: #4183c4;
--links: #20609f;
--inline-code-color: #6e6b5e;
--inline-code-color: #301900;
--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

@@ -96,6 +96,5 @@
font-family: 'Source Code Pro';
font-style: normal;
font-weight: 500;
src: local('Source Code Pro Medium'), local('SourceCodePro-Medium'),
url('source-code-pro-v11-all-charsets-500.woff2') format('woff2');
src: url('source-code-pro-v11-all-charsets-500.woff2') format('woff2');
}

View File

@@ -1,14 +1,18 @@
/* 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) */
/*
* 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)
*/
/* Atelier-Dune Comment */
/* Comment */
.hljs-comment,
.hljs-quote {
color: #AAA;
color: #575757;
}
/* Atelier-Dune Red */
/* Red */
.hljs-variable,
.hljs-template-variable,
.hljs-attribute,
@@ -19,10 +23,10 @@
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: #d73737;
color: #d70025;
}
/* Atelier-Dune Orange */
/* Orange */
.hljs-number,
.hljs-meta,
.hljs-built_in,
@@ -30,33 +34,33 @@
.hljs-literal,
.hljs-type,
.hljs-params {
color: #b65611;
color: #b21e00;
}
/* Atelier-Dune Green */
/* Green */
.hljs-string,
.hljs-symbol,
.hljs-bullet {
color: #60ac39;
color: #008200;
}
/* Atelier-Dune Blue */
/* Blue */
.hljs-title,
.hljs-section {
color: #6684e1;
color: #0030f2;
}
/* Atelier-Dune Purple */
/* Purple */
.hljs-keyword,
.hljs-selector-tag {
color: #b854d4;
color: #9d00ec;
}
.hljs {
display: block;
overflow-x: auto;
background: #f1f1f1;
color: #6e6b5e;
background: #f6f7f6;
color: #000;
padding: 0.5em;
}

View File

@@ -29,7 +29,9 @@
<link rel="stylesheet" href="{{ path_to_root }}css/variables.css">
<link rel="stylesheet" href="{{ path_to_root }}css/general.css">
<link rel="stylesheet" href="{{ path_to_root }}css/chrome.css">
{{#if print_enable}}
<link rel="stylesheet" href="{{ path_to_root }}css/print.css" media="print">
{{/if}}
<!-- Fonts -->
<link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
@@ -136,21 +138,29 @@
<h1 class="menu-title">{{ book_title }}</h1>
<div class="right-buttons">
{{#if print_enable}}
<a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
{{/if}}
{{#if git_repository_url}}
<a href="{{git_repository_url}}" title="Git repository" aria-label="Git repository">
<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" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
<input type="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>

View File

@@ -145,6 +145,11 @@ window.search = window.search || {};
url.push("");
}
// encodeURIComponent escapes all chars that could allow an XSS except
// for '. Due to that we also manually replace ' with its url-encoded
// representation (%27).
var searchterms = encodeURIComponent(searchterms.join(" ")).replace(/\'/g, "%27");
return '<a href="' + path_to_root + url[0] + '?' + URL_MARK_PARAM + '=' + searchterms + '#' + url[1]
+ '" aria-details="teaser_' + teaser_count + '">' + result.doc.breadcrumbs + '</a>'
+ '<span class="teaser" id="teaser_' + teaser_count + '" aria-label="Search Result Teaser">'
@@ -291,7 +296,7 @@ window.search = window.search || {};
}
if (url.params.hasOwnProperty(URL_MARK_PARAM)) {
var words = url.params[URL_MARK_PARAM].split(' ');
var words = decodeURIComponent(url.params[URL_MARK_PARAM]).split(' ');
marker.mark(words, {
exclude: mark_exclude
});
@@ -422,6 +427,7 @@ 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 seperator with a forward-slash '/'
/// Naively replaces any path separator with a forward-slash '/'
pub fn normalize_path(path: &str) -> String {
use std::path::is_separator;
path.chars()
@@ -110,7 +110,10 @@ pub fn copy_files_except_ext(
for entry in fs::read_dir(from)? {
let entry = entry?;
let metadata = entry.metadata()?;
let metadata = entry
.path()
.metadata()
.with_context(|| format!("Failed to read {:?}", entry.path()))?;
// If the entry is a dir and the recursive option is enabled, call itself
if metadata.is_dir() && recursive {
@@ -187,7 +190,17 @@ pub fn get_404_output_file(input_404: &Option<String>) -> String {
#[cfg(test)]
mod tests {
use super::copy_files_except_ext;
use std::fs;
use std::{fs, io::Result, path::Path};
#[cfg(target_os = "windows")]
fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> {
std::os::windows::fs::symlink_file(src, dst)
}
#[cfg(not(target_os = "windows"))]
fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> {
std::os::unix::fs::symlink(src, dst)
}
#[test]
fn copy_files_except_ext_test() {
@@ -218,6 +231,12 @@ mod tests {
if let Err(err) = fs::File::create(&tmp.path().join("sub_dir_exists/file.txt")) {
panic!("Could not create sub_dir_exists/file.txt: {}", err);
}
if let Err(err) = symlink(
&tmp.path().join("file.png"),
&tmp.path().join("symlink.png"),
) {
panic!("Could not symlink file.png: {}", err);
}
// Create output dir
if let Err(err) = fs::create_dir(&tmp.path().join("output")) {
@@ -228,7 +247,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);
}
@@ -249,5 +268,8 @@ mod tests {
if !(&tmp.path().join("output/sub_dir_exists/file.txt")).exists() {
panic!("output/sub_dir/file.png should exist")
}
if !(&tmp.path().join("output/symlink.png")).exists() {
panic!("output/symlink.png should exist")
}
}
}

View File

@@ -228,7 +228,14 @@ impl EventQuoteConverter {
fn clean_codeblock_headers(event: Event<'_>) -> Event<'_> {
match event {
Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(ref info))) => {
let info: String = info.chars().filter(|ch| !ch.is_whitespace()).collect();
let info: String = info
.chars()
.map(|x| match x {
' ' | '\t' => ',',
_ => x,
})
.filter(|ch| !ch.is_whitespace())
.collect();
Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::from(info))))
}
@@ -372,7 +379,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);

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_str("\n");
output.push('\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_str("\n");
output.push('\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_str("\n");
output.push('\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

@@ -2,7 +2,7 @@
use mdbook::config::Config;
use mdbook::MDBook;
#[cfg(not(windows))]
use std::fs;
use std::path::Path;
use tempfile::{Builder as TempFileBuilder, TempDir};
@@ -71,6 +71,45 @@ 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,
@@ -112,3 +151,14 @@ 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());
}

29
tests/cli/build.rs Normal file
View File

@@ -0,0 +1,29 @@
use crate::dummy_book::DummyBook;
use assert_cmd::Command;
#[test]
fn mdbook_cli_dummy_book_generates_index_html() {
let temp = DummyBook::new().build().unwrap();
// doesn't exist before
assert!(!temp.path().join("book").exists());
let mut cmd = Command::cargo_bin("mdbook").unwrap();
cmd.arg("build").current_dir(temp.path());
cmd.assert()
.success()
.stderr(
predicates::str::is_match(r##"Stack depth exceeded in first[\\/]recursive.md."##)
.unwrap(),
)
.stderr(predicates::str::contains(
r##"[INFO] (mdbook::book): Running the html backend"##,
));
// exists afterward
assert!(temp.path().join("book").exists());
let index_file = temp.path().join("book/index.html");
assert!(index_file.exists());
}

2
tests/cli/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
mod build;
mod test;

34
tests/cli/test.rs Normal file
View File

@@ -0,0 +1,34 @@
use crate::dummy_book::DummyBook;
use assert_cmd::Command;
use predicates::boolean::PredicateBooleanExt;
#[test]
fn mdbook_cli_can_correctly_test_a_passing_book() {
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
let mut cmd = Command::cargo_bin("mdbook").unwrap();
cmd.arg("test").current_dir(temp.path());
cmd.assert().success()
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]README.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]intro.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]index.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]nested.md""##).unwrap())
.stderr(predicates::str::is_match(r##"rustdoc returned an error:\n\n"##).unwrap().not())
.stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap().not());
}
#[test]
fn mdbook_cli_detects_book_with_failing_tests() {
let temp = DummyBook::new().with_passing_test(false).build().unwrap();
let mut cmd = Command::cargo_bin("mdbook").unwrap();
cmd.arg("test").current_dir(temp.path());
cmd.assert().failure()
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]README.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]intro.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]index.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]nested.md""##).unwrap())
.stderr(predicates::str::is_match(r##"rustdoc returned an error:\n\n"##).unwrap())
.stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap());
}

2
tests/cli_tests.rs Normal file
View File

@@ -0,0 +1,2 @@
mod cli;
mod dummy_book;

View File

@@ -135,11 +135,11 @@ fn recursive_copy<A: AsRef<Path>, B: AsRef<Path>>(from: A, to: B) -> Result<()>
}
pub fn new_copy_of_example_book() -> Result<TempDir> {
let temp = TempFileBuilder::new().prefix("book-example").tempdir()?;
let temp = TempFileBuilder::new().prefix("guide").tempdir()?;
let book_example = Path::new(env!("CARGO_MANIFEST_DIR")).join("book-example");
let guide = Path::new(env!("CARGO_MANIFEST_DIR")).join("guide");
recursive_copy(book_example, temp.path())?;
recursive_copy(guide, temp.path())?;
Ok(temp)
}

View File

@@ -12,6 +12,7 @@
- [Recursive](first/recursive.md)
- [Markdown](first/markdown.md)
- [Unicode](first/unicode.md)
- [No Headers](first/no-headers.md)
- [Second Chapter](second.md)
- [Nested Chapter](second/nested.md)

View File

@@ -0,0 +1,3 @@
Capybara capybara capybara.
Capybara capybara capybara.

View File

@@ -91,6 +91,12 @@ fn run_mdbook_init_with_custom_book_and_src_locations() {
file
);
}
let contents = fs::read_to_string(temp.path().join("book.toml")).unwrap();
assert_eq!(
contents,
"[book]\nauthors = []\nlanguage = \"en\"\nmultilingual = false\nsrc = \"in\"\n\n[build]\nbuild-dir = \"out\"\ncreate-missing = true\nuse-default-preprocessors = true\n"
);
}
#[test]
@@ -102,3 +108,37 @@ fn book_toml_isnt_required() {
md.build().unwrap();
}
#[test]
fn copy_theme() {
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
MDBook::init(temp.path()).copy_theme(true).build().unwrap();
let expected = vec![
"book.js",
"css/chrome.css",
"css/general.css",
"css/print.css",
"css/variables.css",
"favicon.png",
"favicon.svg",
"highlight.css",
"highlight.js",
"index.hbs",
];
let theme_dir = temp.path().join("theme");
let mut actual: Vec<_> = walkdir::WalkDir::new(&theme_dir)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| !e.file_type().is_dir())
.map(|e| {
e.path()
.strip_prefix(&theme_dir)
.unwrap()
.to_str()
.unwrap()
.replace('\\', "/")
})
.collect();
actual.sort();
assert_eq!(actual, expected);
}

View File

@@ -34,6 +34,7 @@ const TOC_SECOND_LEVEL: &[&str] = &[
"1.3. Recursive",
"1.4. Markdown",
"1.5. Unicode",
"1.6. No Headers",
"2.1. Nested Chapter",
];
@@ -104,12 +105,12 @@ fn check_correct_cross_links_in_nested_dir() {
assert_contains_strings(
first.join("index.html"),
&[r##"href="#some-section" id="some-section""##],
&[r##"<h2 id="some-section"><a class="header" href="#some-section">"##],
);
assert_contains_strings(
first.join("nested.html"),
&[r##"href="#some-section" id="some-section""##],
&[r##"<h2 id="some-section"><a class="header" href="#some-section">"##],
);
}
@@ -264,7 +265,7 @@ fn root_index_html() -> Result<Document> {
fn check_second_toc_level() {
let doc = root_index_html().unwrap();
let mut should_be = Vec::from(TOC_SECOND_LEVEL);
should_be.sort();
should_be.sort_unstable();
let pred = descendants!(
Class("chapter"),
@@ -288,7 +289,7 @@ fn check_first_toc_level() {
let mut should_be = Vec::from(TOC_TOP_LEVEL);
should_be.extend(TOC_SECOND_LEVEL);
should_be.sort();
should_be.sort_unstable();
let pred = descendants!(
Class("chapter"),
@@ -345,7 +346,7 @@ fn create_missing_file_with_config() {
}
/// This makes sure you can include a Rust file with `{{#playground example.rs}}`.
/// Specification is in `book-example/src/format/rust.md`
/// Specification is in `guide/src/format/rust.md`
#[test]
fn able_to_include_playground_files_in_chapters() {
let temp = DummyBook::new().build().unwrap();
@@ -373,7 +374,7 @@ fn able_to_include_files_in_chapters() {
let includes = temp.path().join("book/first/includes.html");
let summary_strings = &[
r##"<h1><a class="header" href="#summary" id="summary">Summary</a></h1>"##,
r##"<h1 id="summary"><a class="header" href="#summary">Summary</a></h1>"##,
">First Chapter</a>",
];
assert_contains_strings(&includes, summary_strings);
@@ -535,12 +536,63 @@ fn redirects_are_emitted_correctly() {
let mut redirect_file = md.build_dir_for("html");
// append everything except the bits that make it absolute
// (e.g. "/" or "C:\")
redirect_file.extend(remove_absolute_components(&original));
redirect_file.extend(remove_absolute_components(original));
let contents = fs::read_to_string(&redirect_file).unwrap();
assert!(contents.contains(redirect));
}
}
#[test]
fn edit_url_has_default_src_dir_edit_url() {
let temp = DummyBook::new().build().unwrap();
let book_toml = r#"
[book]
title = "implicit"
[output.html]
edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
"#;
write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
let md = MDBook::load(temp.path()).unwrap();
md.build().unwrap();
let index_html = temp.path().join("book").join("index.html");
assert_contains_strings(
index_html,
&[
r#"href="https://github.com/rust-lang/mdBook/edit/master/guide/src/README.md" title="Suggest an edit""#,
],
);
}
#[test]
fn edit_url_has_configured_src_dir_edit_url() {
let temp = DummyBook::new().build().unwrap();
let book_toml = r#"
[book]
title = "implicit"
src = "src2"
[output.html]
edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
"#;
write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
let md = MDBook::load(temp.path()).unwrap();
md.build().unwrap();
let index_html = temp.path().join("book").join("index.html");
assert_contains_strings(
index_html,
&[
r#"href="https://github.com/rust-lang/mdBook/edit/master/guide/src2/README.md" title="Suggest an edit""#,
],
);
}
fn remove_absolute_components(path: &Path) -> impl Iterator<Item = Component> + '_ {
path.components().skip_while(|c| match c {
Component::Prefix(_) | Component::RootDir => true,
@@ -560,7 +612,7 @@ mod search {
let index = fs::read_to_string(index).unwrap();
let index = index.trim_start_matches("Object.assign(window.search, ");
let index = index.trim_end_matches(");");
serde_json::from_str(&index).unwrap()
serde_json::from_str(index).unwrap()
}
#[test]
@@ -580,6 +632,7 @@ mod search {
let introduction = get_doc_ref("intro.html#introduction");
let some_section = get_doc_ref("first/index.html#some-section");
let summary = get_doc_ref("first/includes.html#summary");
let no_headers = get_doc_ref("first/no-headers.html");
let conclusion = get_doc_ref("conclusion.html#conclusion");
let bodyidx = &index["index"]["index"]["body"]["root"];
@@ -593,10 +646,21 @@ mod search {
assert_eq!(docs[&some_section]["body"], "");
assert_eq!(
docs[&summary]["body"],
"Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode Second Chapter Nested Chapter Conclusion"
"Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode No Headers Second Chapter Nested Chapter Conclusion"
);
assert_eq!(
docs[&summary]["breadcrumbs"],
"First Chapter » Includes » Summary"
);
assert_eq!(docs[&summary]["breadcrumbs"], "First Chapter » Summary");
assert_eq!(docs[&conclusion]["body"], "I put &lt;HTML&gt; in here!");
assert_eq!(
docs[&no_headers]["breadcrumbs"],
"First Chapter » No Headers"
);
assert_eq!(
docs[&no_headers]["body"],
"Capybara capybara capybara. Capybara capybara capybara."
);
}
// Setting this to `true` may cause issues with `cargo watch`,

File diff suppressed because it is too large Load Diff