mirror of
https://github.com/rust-lang/mdBook.git
synced 2025-12-28 17:21:52 -05:00
Compare commits
259 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5eb7d46a99 | ||
|
|
dffcedf031 | ||
|
|
c9b6be8660 | ||
|
|
23af80c506 | ||
|
|
857acb9759 | ||
|
|
2ddcb43899 | ||
|
|
1c0983b811 | ||
|
|
1be69af553 | ||
|
|
c63000f365 | ||
|
|
bbaa0ea1fa | ||
|
|
58bc92d380 | ||
|
|
17d1ed3716 | ||
|
|
8df8ce063d | ||
|
|
c3ff4a5129 | ||
|
|
4d20fa578b | ||
|
|
9e47498458 | ||
|
|
903469a45f | ||
|
|
b8ef89db62 | ||
|
|
c283211a37 | ||
|
|
d5af051d0e | ||
|
|
68f9afe64b | ||
|
|
ffa8284743 | ||
|
|
3e91f9cd5d | ||
|
|
f55028b61a | ||
|
|
0d887505af | ||
|
|
6c20736a55 | ||
|
|
e4a46c9477 | ||
|
|
6ae5c686d9 | ||
|
|
b862080006 | ||
|
|
6b790b83ec | ||
|
|
d8ad68c947 | ||
|
|
6b784be616 | ||
|
|
f5598b2eee | ||
|
|
ff4b8e7a8d | ||
|
|
9c34e602bd | ||
|
|
a306da3ad7 | ||
|
|
9bede85efa | ||
|
|
11b1e86187 | ||
|
|
10d30a2dc0 | ||
|
|
601ebc5499 | ||
|
|
4251d7a838 | ||
|
|
93008cf20b | ||
|
|
3f9f681b9e | ||
|
|
63680d0786 | ||
|
|
656a1825cc | ||
|
|
1a2fa29209 | ||
|
|
6be81214b1 | ||
|
|
d22299d998 | ||
|
|
0af417085f | ||
|
|
9634798eb7 | ||
|
|
2a8af1c21d | ||
|
|
981f8695ff | ||
|
|
48b5e52f62 | ||
|
|
c4fec94c4c | ||
|
|
ab0c338c08 | ||
|
|
8a82f6336a | ||
|
|
1700783594 | ||
|
|
e6629cd75b | ||
|
|
5a077b9ff4 | ||
|
|
8b4e488de1 | ||
|
|
68d8ceec47 | ||
|
|
db337d4a6f | ||
|
|
5e277140be | ||
|
|
14add9c290 | ||
|
|
87877a9dae | ||
|
|
2cf00d0880 | ||
|
|
8c7af3c767 | ||
|
|
6dd785ea6c | ||
|
|
8d131b4310 | ||
|
|
97b38063b1 | ||
|
|
d23734f82e | ||
|
|
2ccfaadd1d | ||
|
|
3d04e5c7ff | ||
|
|
49ef7b6f02 | ||
|
|
da7026190c | ||
|
|
92377013cc | ||
|
|
34b586ab32 | ||
|
|
a79065b0d3 | ||
|
|
b3ab93a4b3 | ||
|
|
49b75810fa | ||
|
|
b85d5eb455 | ||
|
|
27faa54ae8 | ||
|
|
fae0759626 | ||
|
|
cc74ca2e6e | ||
|
|
7cae3a058d | ||
|
|
8fb6ac7987 | ||
|
|
82d32ee761 | ||
|
|
fe9b534ad7 | ||
|
|
56652e8fa6 | ||
|
|
c3a1e41ed7 | ||
|
|
3976c9d8f0 | ||
|
|
96b6f02834 | ||
|
|
b571511737 | ||
|
|
ebdab38a32 | ||
|
|
c06f450e7d | ||
|
|
b87c231fc3 | ||
|
|
8024b08f93 | ||
|
|
8ec0bf6e30 | ||
|
|
a8926d5392 | ||
|
|
00473d8420 | ||
|
|
86d390032b | ||
|
|
b3c0b01350 | ||
|
|
e33192753d | ||
|
|
7932e13512 | ||
|
|
9fd2509c0d | ||
|
|
5dec8508c7 | ||
|
|
4d2dc6f482 | ||
|
|
efb13d7bc1 | ||
|
|
27b1e05c87 | ||
|
|
e440094b37 | ||
|
|
15cae10ca8 | ||
|
|
dc2062ab36 | ||
|
|
d9ce98d710 | ||
|
|
b59aab56f2 | ||
|
|
b899c48019 | ||
|
|
515a253e97 | ||
|
|
7ddc3df945 | ||
|
|
2f7293af5c | ||
|
|
fa3ae53d46 | ||
|
|
6425c29893 | ||
|
|
d0bb830491 | ||
|
|
d325c601bb | ||
|
|
e9e889f523 | ||
|
|
e5e10c681a | ||
|
|
05edc4421b | ||
|
|
22ea5fe335 | ||
|
|
714c5fb81e | ||
|
|
56ceb627b8 | ||
|
|
c1b2bec7d7 | ||
|
|
8201b411ab | ||
|
|
836546cf0d | ||
|
|
9813802b3e | ||
|
|
fcf8f938d2 | ||
|
|
60aaa7ae31 | ||
|
|
1b584d1746 | ||
|
|
aa4cb9465f | ||
|
|
89a2e39b80 | ||
|
|
3c2b8cd10f | ||
|
|
6b0b42ebcc | ||
|
|
7a3513200f | ||
|
|
3db0c0b9a1 | ||
|
|
2c7aac6d7a | ||
|
|
3ee22fb430 | ||
|
|
16c5ec4d74 | ||
|
|
7e7e779ef7 | ||
|
|
b364e8ea2c | ||
|
|
78325aaccb | ||
|
|
1411ea967a | ||
|
|
d147a85006 | ||
|
|
0f0dce8d6c | ||
|
|
379574dc61 | ||
|
|
6a7de13c6f | ||
|
|
331aad1597 | ||
|
|
7e01cf9e18 | ||
|
|
c922b8aae6 | ||
|
|
b21446898a | ||
|
|
f4b4a331d7 | ||
|
|
aa349e0b7c | ||
|
|
b592b10633 | ||
|
|
d62cf8e883 | ||
|
|
c6844dd771 | ||
|
|
009247be01 | ||
|
|
84b3b7218e | ||
|
|
71ba6c9eb8 | ||
|
|
9d4ee689db | ||
|
|
ffe88d7e29 | ||
|
|
9f930706bb | ||
|
|
24fa615149 | ||
|
|
a72d6002b7 | ||
|
|
5b7abf4714 | ||
|
|
d0ef70e574 | ||
|
|
7525b35383 | ||
|
|
b54e73e3b6 | ||
|
|
59c76fa665 | ||
|
|
c1d982d92b | ||
|
|
3db275d68a | ||
|
|
94e797fba0 | ||
|
|
c3beecc96a | ||
|
|
7aff98a859 | ||
|
|
bbf54d7459 | ||
|
|
dcc642e66d | ||
|
|
2b738d4425 | ||
|
|
b3670ece0e | ||
|
|
30ce7e79ac | ||
|
|
94f7578576 | ||
|
|
e6568a70eb | ||
|
|
0eb23efd44 | ||
|
|
e78a8471c7 | ||
|
|
dcccd3289d | ||
|
|
5637a66459 | ||
|
|
536873ca26 | ||
|
|
d6ea4e3f7a | ||
|
|
fcceee4761 | ||
|
|
3f39ba82f9 | ||
|
|
7da38715c1 | ||
|
|
c83bbd6319 | ||
|
|
fad3c663f4 | ||
|
|
f8b9054265 | ||
|
|
f26116a491 | ||
|
|
7f59fdd9bd | ||
|
|
45d41eac5f | ||
|
|
2b5890e2ed | ||
|
|
0b9570b160 | ||
|
|
90396c5b76 | ||
|
|
24b76dd879 | ||
|
|
9a9eb0124a | ||
|
|
257374d76b | ||
|
|
1a0c296532 | ||
|
|
9b4ab72a80 | ||
|
|
b1c2e466e7 | ||
|
|
cdea0f6b61 | ||
|
|
e9b0be7090 | ||
|
|
d402a12e88 | ||
|
|
218e200117 | ||
|
|
3d55375f61 | ||
|
|
77e7cfd22b | ||
|
|
76cd39e5e2 | ||
|
|
09e7bb76dc | ||
|
|
28387130c0 | ||
|
|
33d3d9c3ec | ||
|
|
beec17e55d | ||
|
|
e651f4d734 | ||
|
|
87d2cd9845 | ||
|
|
32abeef088 | ||
|
|
5de9b6841e | ||
|
|
95e0743bc0 | ||
|
|
3c97525743 | ||
|
|
9a65c8ab92 | ||
|
|
a64a7b7470 | ||
|
|
fd4137a9ea | ||
|
|
a3d4febe3e | ||
|
|
7af4b1dfe8 | ||
|
|
ba6bffac5a | ||
|
|
6201e577fe | ||
|
|
cf2459f730 | ||
|
|
45a481049e | ||
|
|
6bcabcbb6b | ||
|
|
ef993e8cc2 | ||
|
|
a3a5386da0 | ||
|
|
3ab911afa1 | ||
|
|
4615ce2f8c | ||
|
|
7cb8087469 | ||
|
|
d1721667b6 | ||
|
|
1038f0b7f5 | ||
|
|
942cc12a74 | ||
|
|
59f2a9bf4e | ||
|
|
75d0f1efd4 | ||
|
|
552e3378cf | ||
|
|
7c0ddff96a | ||
|
|
07e72757d3 | ||
|
|
58f66a146d | ||
|
|
643d5ecc5c | ||
|
|
c712ba7aab | ||
|
|
1450070f73 | ||
|
|
e310dfc605 | ||
|
|
cbfd75a821 | ||
|
|
9e9cf49c50 | ||
|
|
780fb979a0 | ||
|
|
65d9eb6f7e |
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -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
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ guide/book
|
||||
|
||||
.vscode
|
||||
tests/dummy_book/book/
|
||||
test_book/book/
|
||||
|
||||
# Ignore Jetbrains specific files.
|
||||
.idea/
|
||||
|
||||
185
CHANGELOG.md
185
CHANGELOG.md
@@ -1,5 +1,190 @@
|
||||
# Changelog
|
||||
|
||||
## mdBook 0.4.14
|
||||
[ffa8284...c9b6be8](https://github.com/rust-lang/mdBook/compare/ffa8284...c9b6be8)
|
||||
|
||||
### Added
|
||||
- The 2021 Rust edition option has been stabilized.
|
||||
[#1642](https://github.com/rust-lang/mdBook/pull/1642)
|
||||
|
||||
### Changed
|
||||
- Header anchors no longer include any HTML tags. Previously only a small
|
||||
subset were excluded.
|
||||
[#1683](https://github.com/rust-lang/mdBook/pull/1683)
|
||||
- Deprecated the google-analytics option. Books using this option should place
|
||||
the appropriate code in the `theme/head.hbs` file instead.
|
||||
[#1675](https://github.com/rust-lang/mdBook/pull/1675)
|
||||
|
||||
### Fixed
|
||||
- Updated the markdown parser which brings in a few small fixes and removes
|
||||
the custom smart quote handling.
|
||||
[#1668](https://github.com/rust-lang/mdBook/pull/1668)
|
||||
- Fixed iOS Safari enlarging text when going into landscape mode.
|
||||
[#1685](https://github.com/rust-lang/mdBook/pull/1685)
|
||||
|
||||
## mdBook 0.4.13
|
||||
[e6629cd...f55028b](https://github.com/rust-lang/mdBook/compare/e6629cd...f55028b)
|
||||
|
||||
### Added
|
||||
|
||||
- Added the ability to specify the preprocessor order.
|
||||
[#1607](https://github.com/rust-lang/mdBook/pull/1607)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Include chapters with no headers in the search index
|
||||
[#1637](https://github.com/rust-lang/mdBook/pull/1637)
|
||||
- Switched to the `opener` crate for opening a web browser, which should fix
|
||||
some issues with blocking.
|
||||
[#1656](https://github.com/rust-lang/mdBook/pull/1656)
|
||||
- Fixed clicking the border of the theme switcher breaking the theme selection.
|
||||
[#1651](https://github.com/rust-lang/mdBook/pull/1651)
|
||||
|
||||
## mdBook 0.4.12
|
||||
[14add9c...8b4e488](https://github.com/rust-lang/mdBook/compare/14add9c...8b4e488)
|
||||
|
||||
### Changed
|
||||
- Reverted the change to update to highlight.js 11, as it broke hidden code lines.
|
||||
[#1597](https://github.com/rust-lang/mdBook/pull/1621)
|
||||
|
||||
## mdBook 0.4.11
|
||||
[e440094...2cf00d0](https://github.com/rust-lang/mdBook/compare/e440094...2cf00d0)
|
||||
|
||||
### Added
|
||||
- Added support for Rust 2021 edition.
|
||||
[#1596](https://github.com/rust-lang/mdBook/pull/1596)
|
||||
- Added `mdbook completions` subcommand which provides shell completions.
|
||||
[#1425](https://github.com/rust-lang/mdBook/pull/1425)
|
||||
- Added `--title` and `--ignore` flags to `mdbook init` to avoid the
|
||||
interactive input.
|
||||
[#1559](https://github.com/rust-lang/mdBook/pull/1559)
|
||||
|
||||
### Changed
|
||||
- If running a Rust example does not have any output, it now displays the text
|
||||
"No output" instead of not showing anything.
|
||||
[#1599](https://github.com/rust-lang/mdBook/pull/1599)
|
||||
- Code block language tags can now be separated by space or tab (along with
|
||||
commas) to match the behavior of other sites like GitHub and rustdoc.
|
||||
[#1469](https://github.com/rust-lang/mdBook/pull/1469)
|
||||
- Updated `warp` (the web server) to the latest version.
|
||||
This also updates the minimum supported Rust version to 1.46.
|
||||
[#1612](https://github.com/rust-lang/mdBook/pull/1612)
|
||||
- Updated to highlight.js 11. This has various highlighting improvements.
|
||||
[#1597](https://github.com/rust-lang/mdBook/pull/1597)
|
||||
|
||||
### Fixed
|
||||
- Inline code blocks inside a header are no longer highlighted when
|
||||
`output.html.playground.editable` is `true`.
|
||||
[#1613](https://github.com/rust-lang/mdBook/pull/1613)
|
||||
|
||||
## mdBook 0.4.10
|
||||
[2f7293a...dc2062a](https://github.com/rust-lang/mdBook/compare/2f7293a...dc2062a)
|
||||
|
||||
### Changed
|
||||
- Reverted breaking change in 0.4.9 that removed the `__non_exhaustive` marker
|
||||
on the `Book` struct.
|
||||
[#1572](https://github.com/rust-lang/mdBook/pull/1572)
|
||||
- Updated handlebars to 4.0.
|
||||
[#1550](https://github.com/rust-lang/mdBook/pull/1550)
|
||||
- Removed the `chapter_begin` id on the print page's chapter separators.
|
||||
[#1541](https://github.com/rust-lang/mdBook/pull/1541)
|
||||
|
||||
## mdBook 0.4.9
|
||||
[7e01cf9...d325c60](https://github.com/rust-lang/mdBook/compare/7e01cf9...d325c60)
|
||||
|
||||
### Changed
|
||||
- Updated all dependencies and raised the minimum Rust version to 1.42.
|
||||
[#1528](https://github.com/rust-lang/mdBook/pull/1528)
|
||||
- Added more detail to error message when a preprocessor fails.
|
||||
[#1526](https://github.com/rust-lang/mdBook/pull/1526)
|
||||
- Set max-width of HTML video tags to 100% to match img tags.
|
||||
[#1542](https://github.com/rust-lang/mdBook/pull/1542)
|
||||
|
||||
### Fixed
|
||||
- Type errors when parsing `book.toml` are no longer ignored.
|
||||
[#1539](https://github.com/rust-lang/mdBook/pull/1539)
|
||||
- Better handling if `mdbook serve` fails to start the http server.
|
||||
[#1555](https://github.com/rust-lang/mdBook/pull/1555)
|
||||
- Fixed the path for `edit-url-template` if the book used a source directory
|
||||
other than `src`.
|
||||
[#1554](https://github.com/rust-lang/mdBook/pull/1554)
|
||||
|
||||
## mdBook 0.4.8
|
||||
[fcceee4...b592b10](https://github.com/rust-lang/mdBook/compare/fcceee4...b592b10)
|
||||
|
||||
### Added
|
||||
- Added the option `output.html.edit-url-template` which can be a URL which is
|
||||
linked on each page to direct the user to a site (such as GitHub) where the
|
||||
user can directly suggest an edit for the page they are currently reading.
|
||||
[#1506](https://github.com/rust-lang/mdBook/pull/1506)
|
||||
|
||||
### Changed
|
||||
- Printed output now includes a page break between chapters.
|
||||
[#1485](https://github.com/rust-lang/mdBook/pull/1485)
|
||||
|
||||
### Fixed
|
||||
- HTML, such as HTML comments, is now ignored if it appears above the title line
|
||||
in `SUMMARY.md`.
|
||||
[#1437](https://github.com/rust-lang/mdBook/pull/1437)
|
||||
|
||||
## mdBook 0.4.7
|
||||
[9a9eb01...c83bbd6](https://github.com/rust-lang/mdBook/compare/9a9eb01...c83bbd6)
|
||||
|
||||
### Changed
|
||||
- Updated shlex parser to fix a minor parsing issue (used by the
|
||||
preprocessor/backend custom command config).
|
||||
[#1471](https://github.com/rust-lang/mdBook/pull/1471)
|
||||
- Enhanced text contrast of `light` theme to improve accessibility.
|
||||
[#1470](https://github.com/rust-lang/mdBook/pull/1470)
|
||||
|
||||
### Fixed
|
||||
- Fixed some issues with fragment scrolling and linking.
|
||||
[#1463](https://github.com/rust-lang/mdBook/pull/1463)
|
||||
|
||||
## mdBook 0.4.6
|
||||
[eaa6914...1a0c296](https://github.com/rust-lang/mdBook/compare/eaa6914...1a0c296)
|
||||
|
||||
### Changed
|
||||
- The chapter name is now included in the search breadcrumbs.
|
||||
[#1389](https://github.com/rust-lang/mdBook/pull/1389)
|
||||
- Pressing Escape will remove the `?highlight` argument from the URL.
|
||||
[#1427](https://github.com/rust-lang/mdBook/pull/1427)
|
||||
- `mdbook init --theme` will now place the theme in the root of the book
|
||||
directory instead of in the `src` directory.
|
||||
[#1432](https://github.com/rust-lang/mdBook/pull/1432)
|
||||
- A custom renderer that sets the `command` to a relative path now interprets
|
||||
the relative path relative to the book root. Previously it was inconsistent
|
||||
based on the platform (either relative to the current directory, or relative
|
||||
to the renderer output directory). Paths relative to the output directory
|
||||
are still supported with a deprecation warning.
|
||||
[#1418](https://github.com/rust-lang/mdBook/pull/1418)
|
||||
- The `theme` directory in the config is now interpreted as relative to the
|
||||
book root, instead of the current directory.
|
||||
[#1405](https://github.com/rust-lang/mdBook/pull/1405)
|
||||
- Handle UTF-8 BOM for chapter sources.
|
||||
[#1285](https://github.com/rust-lang/mdBook/pull/1285)
|
||||
- Removed extra whitespace added to `{{#playground}}` snippets.
|
||||
[#1375](https://github.com/rust-lang/mdBook/pull/1375)
|
||||
|
||||
### Fixed
|
||||
- Clicking on a search result with multiple search words will now correctly
|
||||
highlight all of the words.
|
||||
[#1426](https://github.com/rust-lang/mdBook/pull/1426)
|
||||
- Properly handle `<` and `>` characters in the table of contents.
|
||||
[#1376](https://github.com/rust-lang/mdBook/pull/1376)
|
||||
- Fixed to properly serialize the `build` table in the config, which prevented
|
||||
setting it in the API.
|
||||
[#1378](https://github.com/rust-lang/mdBook/pull/1378)
|
||||
|
||||
## mdBook 0.4.5
|
||||
[eaa6914...f66df09](https://github.com/rust-lang/mdBook/compare/eaa6914...f66df09)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed XSS in the search page.
|
||||
[CVE-2020-26297](https://groups.google.com/g/rustlang-security-announcements/c/3-sO6of29O0)
|
||||
[648c9ae](https://github.com/rust-lang/mdBook/commit/648c9ae772bec83f0a5954d17b4287d5bb1d6606)
|
||||
|
||||
## mdBook 0.4.4
|
||||
[4df9ec9...01836ba](https://github.com/rust-lang/mdBook/compare/4df9ec9...01836ba)
|
||||
|
||||
|
||||
3
CODE_OF_CONDUCT.md
Normal file
3
CODE_OF_CONDUCT.md
Normal 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).
|
||||
@@ -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
1026
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
18
Cargo.toml
18
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mdbook"
|
||||
version = "0.4.4"
|
||||
version = "0.4.14"
|
||||
authors = [
|
||||
"Mathieu David <mathieudavid@mathieudavid.org>",
|
||||
"Michael-F-Bryan <michaelfbryan@gmail.com>",
|
||||
@@ -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"
|
||||
pulldown-cmark = "0.7.0"
|
||||
opener = "0.5"
|
||||
pulldown-cmark = "0.8.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"
|
||||
|
||||
|
||||
@@ -91,10 +91,11 @@ 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.
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,25 +1,40 @@
|
||||
# mdBook
|
||||
# Introduction
|
||||
|
||||
**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).
|
||||
**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.
|
||||
|
||||
What you are reading serves as an example of the output of mdBook and at the
|
||||
same time as a high-level documentation.
|
||||
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.
|
||||
|
||||
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 Documentation
|
||||
|
||||
## API docs
|
||||
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.
|
||||
|
||||
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.
|
||||
## 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
|
||||
|
||||
mdBook, all the source code, is released under the [Mozilla Public License
|
||||
v2.0](https://www.mozilla.org/MPL/2.0/).
|
||||
The mdBook source and documentation are released under
|
||||
the [Mozilla Public License v2.0](https://www.mozilla.org/MPL/2.0/).
|
||||
|
||||
@@ -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,7 +11,11 @@
|
||||
- [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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.*
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
12
guide/src/format/configuration/README.md
Normal file
12
guide/src/format/configuration/README.md
Normal 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
|
||||
38
guide/src/format/configuration/environment-variables.md
Normal file
38
guide/src/format/configuration/environment-variables.md
Normal 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.
|
||||
97
guide/src/format/configuration/general.md
Normal file
97
guide/src/format/configuration/general.md
Normal 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.
|
||||
80
guide/src/format/configuration/preprocessors.md
Normal file
80
guide/src/format/configuration/preprocessors.md
Normal 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.
|
||||
@@ -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,11 +18,11 @@ 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
|
||||
it by simply specifying your ID in the configuration file.
|
||||
- **google-analytics:** This field has been deprecated and will be removed in a future release.
|
||||
Use the `theme/head.hbs` file to add the appropriate Google Analytics code instead.
|
||||
- **additional-css:** If you need to slightly change the appearance of your book
|
||||
without overwriting the whole style, you can specify a set of stylesheets that
|
||||
will be loaded after the default ones where you can surgically change the
|
||||
@@ -201,13 +44,21 @@ 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
|
||||
@@ -280,12 +131,12 @@ preferred-dark-theme = "navy"
|
||||
curly-quotes = true
|
||||
mathjax-support = false
|
||||
copy-fonts = true
|
||||
google-analytics = "UA-123456-7"
|
||||
additional-css = ["custom.css", "custom2.css"]
|
||||
additional-js = ["custom.js"]
|
||||
no-section-label = false
|
||||
git-repository-url = "https://github.com/rust-lang/mdBook"
|
||||
git-repository-icon = "fa-github"
|
||||
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"
|
||||
@@ -336,13 +187,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.
|
||||
|
||||
@@ -354,43 +205,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
|
||||
@@ -1,6 +1,6 @@
|
||||
fn main() {
|
||||
println!("Hello World!");
|
||||
#
|
||||
# // You can even hide lines! :D
|
||||
# println!("I am hidden! Expand the code snippet to see me");
|
||||
# // You can even hide lines! :D
|
||||
# println!("I am hidden! Expand the code snippet to see me");
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ Will render as
|
||||
```rust
|
||||
# fn main() {
|
||||
let x = 5;
|
||||
let y = 7;
|
||||
let y = 6;
|
||||
|
||||
println!("{}", x + y);
|
||||
# }
|
||||
@@ -40,7 +40,7 @@ The path to the file has to be relative from the current source file.
|
||||
mdBook will interpret included files as Markdown. Since the include command
|
||||
is usually used for inserting code snippets and examples, you will often
|
||||
wrap the command with ```` ``` ```` to display the file contents without
|
||||
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}}
|
||||
```
|
||||
|
||||
@@ -4,63 +4,96 @@ 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.
|
||||
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
|
||||
### 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.
|
||||
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
|
||||
[Title of prefix element](relative/path/to/markdown.md)
|
||||
# Summary
|
||||
```
|
||||
|
||||
3. ***Part Title:*** Headers can be used as a title for the following numbered
|
||||
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 book. The title is rendered as unclickable text.
|
||||
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
|
||||
|
||||
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.)
|
||||
- [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
|
||||
|
||||
- [Title of the Chapter](relative/path/to/markdown.md)
|
||||
- [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
|
||||
|
||||
- [More Chapters](relative/path/to/markdown2.md)
|
||||
- [Another Chapter](relative/path/to/markdown4.md)
|
||||
```
|
||||
You can either use `-` or `*` to indicate a numbered chapter.
|
||||
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)
|
||||
|
||||
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.
|
||||
[Title of Suffix Chapter](relative/path/to/markdown2.md)
|
||||
```
|
||||
|
||||
All other elements are unsupported and will be ignored at best or result in an
|
||||
error.
|
||||
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]()
|
||||
```
|
||||
|
||||
#### Other elements
|
||||
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)
|
||||
|
||||
- ***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]()
|
||||
```
|
||||
---
|
||||
|
||||
- [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}}
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -112,10 +112,10 @@ everyone can benefit from it.**
|
||||
## Improve default theme
|
||||
|
||||
If you think the default theme doesn't look quite right for a specific language,
|
||||
or could be improved. Feel free to [submit a new
|
||||
or could be improved, feel free to [submit a new
|
||||
issue](https://github.com/rust-lang/mdBook/issues) explaining what you
|
||||
have in mind and I will take a look at it.
|
||||
|
||||
You could also create a pull-request with the proposed improvements.
|
||||
|
||||
Overall the theme should be light and sober, without to many flashy colors.
|
||||
Overall the theme should be light and sober, without too many flashy colors.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -28,7 +28,7 @@ impl BookBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the `Config` to be used.
|
||||
/// Set the [`Config`] to be used.
|
||||
pub fn with_config(&mut self, cfg: Config) -> &mut BookBuilder {
|
||||
self.config = cfg;
|
||||
self
|
||||
@@ -110,10 +110,7 @@ impl BookBuilder {
|
||||
debug!("Copying theme");
|
||||
|
||||
let html_config = self.config.html_config().unwrap_or_default();
|
||||
let themedir = html_config
|
||||
.theme
|
||||
.unwrap_or_else(|| self.config.book.src.join("theme"));
|
||||
let themedir = self.root.join(themedir);
|
||||
let themedir = html_config.theme_dir(&self.root);
|
||||
|
||||
if !themedir.exists() {
|
||||
debug!(
|
||||
@@ -127,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)?;
|
||||
|
||||
303
src/book/mod.rs
303
src/book/mod.rs
@@ -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>>,
|
||||
}
|
||||
|
||||
@@ -69,6 +70,20 @@ impl MDBook {
|
||||
|
||||
config.update_from_env();
|
||||
|
||||
if config
|
||||
.html_config()
|
||||
.map_or(false, |html| html.google_analytics.is_some())
|
||||
{
|
||||
warn!(
|
||||
"The output.html.google-analytics field has been deprecated; \
|
||||
it will be removed in a future release.\n\
|
||||
Consider placing the appropriate site tag code into the \
|
||||
theme/head.hbs file instead.\n\
|
||||
The tracking code may be found in the Google Analytics Admin page.\n\
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
if log_enabled!(log::Level::Trace) {
|
||||
for line in format!("Config: {:#?}", config).lines() {
|
||||
trace!("{}", line);
|
||||
@@ -78,7 +93,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 +112,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 +136,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 +195,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 +211,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
|
||||
@@ -277,6 +289,9 @@ impl MDBook {
|
||||
RustEdition::E2018 => {
|
||||
cmd.args(&["--edition", "2018"]);
|
||||
}
|
||||
RustEdition::E2021 => {
|
||||
cmd.args(&["--edition", "2021"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +318,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):
|
||||
@@ -371,12 +386,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();
|
||||
@@ -385,36 +395,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> {
|
||||
@@ -514,8 +615,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]
|
||||
@@ -562,9 +663,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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
|
||||
104
src/config.rs
104
src/config.rs
@@ -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,
|
||||
@@ -517,6 +529,10 @@ pub struct HtmlConfig {
|
||||
///
|
||||
/// [custom domain]: https://docs.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site
|
||||
pub cname: Option<String>,
|
||||
/// Edit url template, when set shows a "Suggest an edit" button for
|
||||
/// directly jumping to editing the currently viewed page.
|
||||
/// Contains {path} that is replaced with chapter source file path
|
||||
pub edit_url_template: Option<String>,
|
||||
/// 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
|
||||
@@ -549,6 +565,7 @@ impl Default for HtmlConfig {
|
||||
search: None,
|
||||
git_repository_url: None,
|
||||
git_repository_icon: None,
|
||||
edit_url_template: None,
|
||||
input_404: None,
|
||||
site_url: None,
|
||||
cname: None,
|
||||
@@ -561,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"),
|
||||
@@ -646,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?
|
||||
@@ -841,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)]
|
||||
@@ -1060,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
74
src/main.rs
74
src/main.rs
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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: (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -199,10 +220,10 @@ impl HtmlHandlebars {
|
||||
}
|
||||
write_file(destination, "css/variables.css", &theme.variables_css)?;
|
||||
if let Some(contents) = &theme.favicon_png {
|
||||
write_file(destination, "favicon.png", &contents)?;
|
||||
write_file(destination, "favicon.png", contents)?;
|
||||
}
|
||||
if let Some(contents) = &theme.favicon_svg {
|
||||
write_file(destination, "favicon.svg", &contents)?;
|
||||
write_file(destination, "favicon.svg", contents)?;
|
||||
}
|
||||
write_file(destination, "highlight.css", &theme.highlight_css)?;
|
||||
write_file(destination, "tomorrow-night.css", &theme.tomorrow_night_css)?;
|
||||
@@ -366,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)?;
|
||||
}
|
||||
@@ -437,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;
|
||||
@@ -452,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"),
|
||||
};
|
||||
|
||||
@@ -484,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();
|
||||
@@ -499,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;
|
||||
@@ -525,14 +549,14 @@ impl Renderer for HtmlHandlebars {
|
||||
let rendered =
|
||||
self.post_process(rendered, &html_config.playground, ctx.config.rust.edition);
|
||||
|
||||
utils::fs::write_file(&destination, "print.html", rendered.as_bytes())?;
|
||||
utils::fs::write_file(destination, "print.html", rendered.as_bytes())?;
|
||||
debug!("Creating print.html ✓");
|
||||
}
|
||||
|
||||
debug!("Copy static files");
|
||||
self.copy_static_files(&destination, &theme, &html_config)
|
||||
self.copy_static_files(destination, &theme, &html_config)
|
||||
.with_context(|| "Unable to copy across static files")?;
|
||||
self.copy_additional_css_and_js(&html_config, &ctx.root, &destination)
|
||||
self.copy_additional_css_and_js(&html_config, &ctx.root, destination)
|
||||
.with_context(|| "Unable to copy across additional CSS and JS")?;
|
||||
|
||||
// Render search index
|
||||
@@ -540,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)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,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(())
|
||||
}
|
||||
@@ -756,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
|
||||
@@ -809,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 => "",
|
||||
}
|
||||
};
|
||||
@@ -899,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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -914,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)]
|
||||
@@ -927,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);
|
||||
}
|
||||
}
|
||||
@@ -1035,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::utils;
|
||||
@@ -102,7 +103,7 @@ 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;
|
||||
}
|
||||
@@ -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(§ion)?;
|
||||
out.write(section)?;
|
||||
out.write("</strong> ")?;
|
||||
}
|
||||
}
|
||||
@@ -160,7 +161,7 @@ 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 {
|
||||
@@ -204,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("<")?,
|
||||
b'>' => out.write(">")?,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
title = &title[next + 1..];
|
||||
}
|
||||
out.write(title)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ pub fn create_files(search_config: &Search, destination: &Path, book: &Book) ->
|
||||
let mut doc_urls = Vec::with_capacity(book.sections.len());
|
||||
|
||||
for item in book.iter() {
|
||||
render_item(&mut index, &search_config, &mut doc_urls, item)?;
|
||||
render_item(&mut index, search_config, &mut doc_urls, item)?;
|
||||
}
|
||||
|
||||
let index = write_to_json(index, &search_config, doc_urls)?;
|
||||
let index = write_to_json(index, search_config, doc_urls)?;
|
||||
debug!("Writing search index ✓");
|
||||
if index.len() > 10_000_000 {
|
||||
warn!("searchindex.json is very large ({} bytes)", index.len());
|
||||
@@ -85,7 +85,7 @@ fn render_item(
|
||||
.with_context(|| "Could not convert HTML path to str")?;
|
||||
let anchor_base = utils::fs::normalize_path(filepath);
|
||||
|
||||
let mut p = utils::new_cmark_parser(&chapter.content).peekable();
|
||||
let mut p = utils::new_cmark_parser(&chapter.content, false).peekable();
|
||||
|
||||
let mut in_heading = false;
|
||||
let max_section_depth = u32::from(search_config.heading_split_level);
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -12,6 +12,7 @@ html {
|
||||
color: var(--fg);
|
||||
background-color: var(--bg);
|
||||
text-size-adjust: none;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -45,20 +46,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 +93,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 +176,7 @@ blockquote {
|
||||
margin: 5px 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.result-no-output {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -148,13 +148,19 @@
|
||||
<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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.path().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 {
|
||||
@@ -244,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);
|
||||
}
|
||||
|
||||
130
src/utils/mod.rs
130
src/utils/mod.rs
@@ -48,19 +48,11 @@ pub fn id_from_content(content: &str) -> String {
|
||||
let mut content = content.to_string();
|
||||
|
||||
// Skip any tags or html-encoded stuff
|
||||
const REPL_SUB: &[&str] = &[
|
||||
"<em>",
|
||||
"</em>",
|
||||
"<code>",
|
||||
"</code>",
|
||||
"<strong>",
|
||||
"</strong>",
|
||||
"<",
|
||||
">",
|
||||
"&",
|
||||
"'",
|
||||
""",
|
||||
];
|
||||
lazy_static! {
|
||||
static ref HTML: Regex = Regex::new(r"(<.*?>)").unwrap();
|
||||
}
|
||||
content = HTML.replace_all(&content, "").into();
|
||||
const REPL_SUB: &[&str] = &["<", ">", "&", "'", """];
|
||||
for sub in REPL_SUB {
|
||||
content = content.replace(sub, "");
|
||||
}
|
||||
@@ -168,67 +160,40 @@ pub fn render_markdown(text: &str, curly_quotes: bool) -> String {
|
||||
render_markdown_with_path(text, curly_quotes, None)
|
||||
}
|
||||
|
||||
pub fn new_cmark_parser(text: &str) -> Parser<'_> {
|
||||
pub fn new_cmark_parser(text: &str, curly_quotes: bool) -> Parser<'_> {
|
||||
let mut opts = Options::empty();
|
||||
opts.insert(Options::ENABLE_TABLES);
|
||||
opts.insert(Options::ENABLE_FOOTNOTES);
|
||||
opts.insert(Options::ENABLE_STRIKETHROUGH);
|
||||
opts.insert(Options::ENABLE_TASKLISTS);
|
||||
if curly_quotes {
|
||||
opts.insert(Options::ENABLE_SMART_PUNCTUATION);
|
||||
}
|
||||
Parser::new_ext(text, opts)
|
||||
}
|
||||
|
||||
pub fn render_markdown_with_path(text: &str, curly_quotes: bool, path: Option<&Path>) -> String {
|
||||
let mut s = String::with_capacity(text.len() * 3 / 2);
|
||||
let p = new_cmark_parser(text);
|
||||
let mut converter = EventQuoteConverter::new(curly_quotes);
|
||||
let p = new_cmark_parser(text, curly_quotes);
|
||||
let events = p
|
||||
.map(clean_codeblock_headers)
|
||||
.map(|event| adjust_links(event, path))
|
||||
.map(|event| converter.convert(event));
|
||||
.map(|event| adjust_links(event, path));
|
||||
|
||||
html::push_html(&mut s, events);
|
||||
s
|
||||
}
|
||||
|
||||
struct EventQuoteConverter {
|
||||
enabled: bool,
|
||||
convert_text: bool,
|
||||
}
|
||||
|
||||
impl EventQuoteConverter {
|
||||
fn new(enabled: bool) -> Self {
|
||||
EventQuoteConverter {
|
||||
enabled,
|
||||
convert_text: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert<'a>(&mut self, event: Event<'a>) -> Event<'a> {
|
||||
if !self.enabled {
|
||||
return event;
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::Start(Tag::CodeBlock(_)) => {
|
||||
self.convert_text = false;
|
||||
event
|
||||
}
|
||||
Event::End(Tag::CodeBlock(_)) => {
|
||||
self.convert_text = true;
|
||||
event
|
||||
}
|
||||
Event::Text(ref text) if self.convert_text => {
|
||||
Event::Text(CowStr::from(convert_quotes_to_curly(text)))
|
||||
}
|
||||
_ => event,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clean_codeblock_headers(event: Event<'_>) -> Event<'_> {
|
||||
match event {
|
||||
Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(ref info))) => {
|
||||
let info: String = info.chars().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))))
|
||||
}
|
||||
@@ -236,38 +201,6 @@ fn clean_codeblock_headers(event: Event<'_>) -> Event<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_quotes_to_curly(original_text: &str) -> String {
|
||||
// We'll consider the start to be "whitespace".
|
||||
let mut preceded_by_whitespace = true;
|
||||
|
||||
original_text
|
||||
.chars()
|
||||
.map(|original_char| {
|
||||
let converted_char = match original_char {
|
||||
'\'' => {
|
||||
if preceded_by_whitespace {
|
||||
'‘'
|
||||
} else {
|
||||
'’'
|
||||
}
|
||||
}
|
||||
'"' => {
|
||||
if preceded_by_whitespace {
|
||||
'“'
|
||||
} else {
|
||||
'”'
|
||||
}
|
||||
}
|
||||
_ => original_char,
|
||||
};
|
||||
|
||||
preceded_by_whitespace = original_char.is_whitespace();
|
||||
|
||||
converted_char
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Prints a "backtrace" of some `Error`.
|
||||
pub fn log_backtrace(e: &Error) {
|
||||
error!("Error: {}", e);
|
||||
@@ -372,7 +305,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);
|
||||
@@ -410,6 +343,10 @@ more text with spaces
|
||||
);
|
||||
assert_eq!(id_from_content("## **Bold** title"), "bold-title");
|
||||
assert_eq!(id_from_content("## `Code` title"), "code-title");
|
||||
assert_eq!(
|
||||
id_from_content("## title <span dir=rtl>foo</span>"),
|
||||
"title-foo"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -443,23 +380,4 @@ more text with spaces
|
||||
assert_eq!(normalize_id(""), "");
|
||||
}
|
||||
}
|
||||
|
||||
mod convert_quotes_to_curly {
|
||||
use super::super::convert_quotes_to_curly;
|
||||
|
||||
#[test]
|
||||
fn it_converts_single_quotes() {
|
||||
assert_eq!(convert_quotes_to_curly("'one', 'two'"), "‘one’, ‘two’");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_converts_double_quotes() {
|
||||
assert_eq!(convert_quotes_to_curly(r#""one", "two""#), "“one”, “two”");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_treats_tab_as_whitespace() {
|
||||
assert_eq!(convert_quotes_to_curly("\t'one'"), "\t‘one’");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 "."
|
||||
|
||||
27
test_book/book.toml
Normal file
27
test_book/book.toml
Normal file
@@ -0,0 +1,27 @@
|
||||
[book]
|
||||
title = "mdBook test book"
|
||||
description = "A demo book to test and validate changes"
|
||||
authors = ["YJDoc2"]
|
||||
language = "en"
|
||||
|
||||
[rust]
|
||||
edition = "2018"
|
||||
|
||||
[output.html]
|
||||
mathjax-support = true
|
||||
|
||||
[output.html.playground]
|
||||
editable = true
|
||||
line-numbers = true
|
||||
|
||||
[output.html.search]
|
||||
limit-results = 20
|
||||
use-boolean-and = true
|
||||
boost-title = 2
|
||||
boost-hierarchy = 2
|
||||
boost-paragraph = 1
|
||||
expand = true
|
||||
heading-split-level = 2
|
||||
|
||||
[output.html.redirect]
|
||||
"/format/config.html" = "configuration/index.html"
|
||||
12
test_book/src/README.md
Normal file
12
test_book/src/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Demo Book
|
||||
|
||||
This is a simple demo book, which is intended to be used for verifying and validating style changes in mdBook.
|
||||
This contains dummy examples of various markdown elements and code languages, so that one can check changes made in mdBook styles.
|
||||
|
||||
This rough outline is :
|
||||
|
||||
- individual : contains basic markdown elements such as headings, paragraphs, links etc.
|
||||
- languages : contains a `hello world` in each of supported language to see changes in syntax highlighting
|
||||
- rust : contains language examples specific to rust, such as play pen, runnable examples etc.
|
||||
|
||||
This is more for checking and fixing style, rather than verifying that correct code is generated for given markdown, that is better handled in tests.
|
||||
33
test_book/src/SUMMARY.md
Normal file
33
test_book/src/SUMMARY.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Summary
|
||||
|
||||
[Prefix Chapter](prefix.md)
|
||||
|
||||
---
|
||||
|
||||
- [Introduction](README.md)
|
||||
- [Draft Chapter]()
|
||||
|
||||
# Actual Markdown Tag Examples
|
||||
|
||||
- [Markdown Individual tags](individual/README.md)
|
||||
- [Heading](individual/heading.md)
|
||||
- [Paragraphs](individual/paragraph.md)
|
||||
- [Line Break](individual/linebreak.md)
|
||||
- [Emphasis](individual/emphasis.md)
|
||||
- [Blockquote](individual/blockquote.md)
|
||||
- [List](individual/list.md)
|
||||
- [Code](individual/code.md)
|
||||
- [Image](individual/image.md)
|
||||
- [Links and Horizontal Rule](individual/link_hr.md)
|
||||
- [Tables](individual/table.md)
|
||||
- [Tasks](individual/task.md)
|
||||
- [Strikethrough](individual/strikethrough.md)
|
||||
- [Mixed](individual/mixed.md)
|
||||
- [Languages](languages/README.md)
|
||||
- [Syntax Highlight](languages/highlight.md)
|
||||
- [Rust Specific](rust/README.md)
|
||||
- [Rust Codeblocks](rust/rust_codeblock.md)
|
||||
|
||||
---
|
||||
|
||||
[Suffix Chapter](suffix.md)
|
||||
17
test_book/src/individual/README.md
Normal file
17
test_book/src/individual/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Individual Common mark tags
|
||||
|
||||
This contains following tags:
|
||||
|
||||
- Headings
|
||||
- Paragraphs
|
||||
- Line breaks
|
||||
- Emphasis
|
||||
- Blockquotes
|
||||
- Lists
|
||||
- Code blocks
|
||||
- Images
|
||||
- Links and Horizontal rules
|
||||
- Github tables
|
||||
- Github Task Lists
|
||||
- Strikethrough
|
||||
- Mixed
|
||||
30
test_book/src/individual/blockquote.md
Normal file
30
test_book/src/individual/blockquote.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Blockquote
|
||||
|
||||
> This is a quoted sentence.
|
||||
|
||||
> This is a quoted paragraph
|
||||
>
|
||||
> separated lines
|
||||
> here
|
||||
|
||||
> Nested
|
||||
>
|
||||
> > Quoted
|
||||
> > Paragraph
|
||||
|
||||
> ### And now,
|
||||
>
|
||||
> **Let us _introduce_**
|
||||
> All kinds of
|
||||
>
|
||||
> - tags
|
||||
> - etc
|
||||
> - stuff
|
||||
>
|
||||
> 1. In
|
||||
> 2. The
|
||||
> 3. blockquote
|
||||
>
|
||||
> > cause we can
|
||||
> >
|
||||
> > > Cause we can
|
||||
31
test_book/src/individual/code.md
Normal file
31
test_book/src/individual/code.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Code
|
||||
|
||||
This section only does simple code blocks and inline code, detailed syntax highlight and stuff is in the languages section
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
This is a codeblock
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This line contains `inline code`
|
||||
|
||||
---
|
||||
|
||||
````
|
||||
escaping ``` in ```, fun, isn't is?
|
||||
````
|
||||
|
||||
---
|
||||
|
||||
```bash,editable
|
||||
This is an editable codeblock
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```rust
|
||||
// This links to a playpen
|
||||
```
|
||||
13
test_book/src/individual/emphasis.md
Normal file
13
test_book/src/individual/emphasis.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Emphasis
|
||||
|
||||
This has **bold text** in between normal.
|
||||
|
||||
This has _italic text_ in between normal.
|
||||
|
||||
A **line** having _both_, bold and italic text.
|
||||
|
||||
**A bold line _having_ italic text**
|
||||
|
||||
_An Italic line having **bold** text_
|
||||
|
||||
Now this is going **_out of hands_**.
|
||||
15
test_book/src/individual/heading.md
Normal file
15
test_book/src/individual/heading.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Chapter Heading
|
||||
|
||||
---
|
||||
|
||||
# Really Big Heading
|
||||
|
||||
## Big Heading
|
||||
|
||||
### Normal-ish Heading
|
||||
|
||||
#### Small Heading...?
|
||||
|
||||
##### Really Small Heading
|
||||
|
||||
###### Is it even a heading anymore - heading
|
||||
27
test_book/src/individual/image.md
Normal file
27
test_book/src/individual/image.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Images
|
||||
|
||||
For copyright and trademark information on these images, please check [rust-artwork repository](https://github.com/rust-lang/rust-artworkhttps://github.com/rust-lang/rust-artwork)
|
||||
|
||||
## A 16x16 image
|
||||
|
||||

|
||||
|
||||
## A 32x32 image
|
||||
|
||||

|
||||
|
||||
## A 256x256 image
|
||||
|
||||

|
||||
|
||||
## A 512x512 image
|
||||
|
||||

|
||||
|
||||
## A large image
|
||||
|
||||

|
||||
|
||||
## A SVG image
|
||||
|
||||

|
||||
8
test_book/src/individual/linebreak.md
Normal file
8
test_book/src/individual/linebreak.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Line breaks
|
||||
|
||||
This is a long
|
||||
line with a couple of
|
||||
line breaks in <br/>
|
||||
between : both with two
|
||||
spaces and return, <br/>
|
||||
and with HTML tags.
|
||||
15
test_book/src/individual/link_hr.md
Normal file
15
test_book/src/individual/link_hr.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Links and Horizontal Rule
|
||||
|
||||
This is followed by a Horizontal rule
|
||||
|
||||
---
|
||||
|
||||
And this is preceded by a horizontal rule.
|
||||
|
||||
[This](www.rust-lang.org) should link to rust-lang website
|
||||
[So should this][rl].
|
||||
**[This][rl]** is a strong link.
|
||||
_[This][rl]_ is italic.
|
||||
**_[This][rl]_** is both.
|
||||
|
||||
[rl]: www.rust-lang.org
|
||||
35
test_book/src/individual/list.md
Normal file
35
test_book/src/individual/list.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Lists
|
||||
|
||||
1. A
|
||||
2. Normal
|
||||
3. Ordered
|
||||
4. List
|
||||
|
||||
---
|
||||
|
||||
1. A
|
||||
1. Nested
|
||||
2. List
|
||||
2. But
|
||||
3. Still
|
||||
4. Normal
|
||||
|
||||
---
|
||||
|
||||
- An
|
||||
- Unordered
|
||||
- Normal
|
||||
- List
|
||||
|
||||
---
|
||||
|
||||
- Nested
|
||||
- Unordered
|
||||
- List
|
||||
|
||||
---
|
||||
|
||||
- This
|
||||
1. Is
|
||||
2. Normal
|
||||
- ?!
|
||||
61
test_book/src/individual/mixed.md
Normal file
61
test_book/src/individual/mixed.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Mixed
|
||||
|
||||
This contains all tags randomly mixed together, to make sure style changes in one does not affect others.
|
||||
|
||||
### A heading
|
||||
|
||||
**Quite a Strong statement , to make**
|
||||
|
||||
~~No, cross that~~
|
||||
|
||||
> Whose **quote** is this
|
||||
>
|
||||
> > And ~~this~~
|
||||
> >
|
||||
> > > - and
|
||||
> > > - this
|
||||
> > > - also
|
||||
|
||||
```
|
||||
You encountered a wild codepen
|
||||
```
|
||||
|
||||
```rust,editable
|
||||
// The codepen is editable and runnable
|
||||
fn main(){
|
||||
println!("Hello world!");
|
||||
}
|
||||
```
|
||||
|
||||
A random image sprinkled in between
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
- ~~An unordered list~~
|
||||
- **Hello**
|
||||
- _World_
|
||||
- What
|
||||
1. Should
|
||||
2. be
|
||||
3. `put`
|
||||
4. here?
|
||||
|
||||
| col1 | col2 | col 3 | col 4 | col 5 | col 6 |
|
||||
| ---- | ---- | ----- | ----- | ----- | ----- |
|
||||
| val1 | val2 | val3 | val5 | val4 | val6 |
|
||||
|
||||
| col1 | col2 | col 3 | An Questionable table header | col 5 | col 6 |
|
||||
| ---- | ---- | ----- | ---------------------------- | ----- | ---------------------------------------- |
|
||||
| val1 | val2 | val3 | val5 | val4 | An equally Questionable long table value |
|
||||
|
||||
### Things to do
|
||||
|
||||
- [x] Add individual tags
|
||||
- [ ] Add language examples
|
||||
- [ ] Add rust specific examples
|
||||
|
||||
And another image
|
||||
|
||||

|
||||
25
test_book/src/individual/paragraph.md
Normal file
25
test_book/src/individual/paragraph.md
Normal file
@@ -0,0 +1,25 @@
|
||||
Just a simple paragraph.
|
||||
|
||||
Let's stress test this.
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer elit lorem, eleifend eu leo sit amet, suscipit feugiat libero. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Proin congue lectus sit amet lacus venenatis, ac sollicitudin purus condimentum. Suspendisse pretium volutpat sapien at gravida. In tincidunt, sem non accumsan consectetur, leo libero porttitor dolor, at imperdiet erat nibh quis leo. Cras dictum erat augue, quis pharetra justo porttitor posuere. Aenean sed lacinia justo, vel suscipit nisl. Etiam eleifend id mauris at gravida. Aliquam molestie cursus lorem pulvinar sollicitudin. Nam et ex dignissim, posuere sem non, pellentesque lacus. Morbi vulputate sed lorem et convallis. Duis non turpis eget elit posuere volutpat. Donec accumsan euismod enim, id consequat ex rhoncus ac. Pellentesque ac felis nisl. Duis imperdiet vel tellus ac iaculis.
|
||||
|
||||
Vivamus nec tempus enim. Integer in ligula eget elit ornare vulputate id et est. Proin mi elit, sagittis nec urna et, iaculis imperdiet neque. Vestibulum placerat cursus dolor. Donec eu sodales nulla. Praesent ac tellus eros. Donec venenatis ligula id ex porttitor malesuada. Aliquam maximus, nisi in fringilla finibus, ante elit rhoncus dui, placerat semper nisl tellus quis odio. Cras luctus magna ultrices dolor pharetra volutpat. Maecenas non enim vitae ligula efficitur aliquet id quis quam. In sagittis mollis magna eu porta. Morbi at nulla et ante elementum pharetra in sed est. Nam commodo purus enim.
|
||||
|
||||
Ut non elit sit amet urna luctus facilisis vel et sapien. Morbi nec metus at libero imperdiet sollicitudin eget quis lacus. Donec in ipsum at enim accumsan tempor vel sed magna. Aliquam non imperdiet neque. Etiam pharetra neque sed pretium interdum. Suspendisse potenti. Phasellus varius, lectus quis dapibus faucibus, purus mauris accumsan nibh, vel tempor quam metus nec sem. Nunc sagittis suscipit lorem eu finibus. Nullam augue leo, imperdiet vel diam et, vulputate scelerisque turpis. Nullam ut volutpat diam. Praesent cursus accumsan dui a commodo. Vivamus sed libero sed turpis facilisis rutrum id sed ligula. Ut id sollicitudin dui. Nulla pulvinar commodo lectus. Cras ut quam congue, consectetur dolor ac, consequat ante.
|
||||
|
||||
Curabitur scelerisque sed leo eu facilisis. Nam faucibus neque eget dictum hendrerit. Duis efficitur ex sed vulputate volutpat. Praesent condimentum nisl ac sapien efficitur laoreet. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut ut nibh elit. Nunc a neque lobortis, tempus diam vitae, interdum magna. Aenean eget nisl sed justo volutpat interdum. Mauris malesuada ex nisl, a dignissim dui elementum eget. Suspendisse potenti.
|
||||
|
||||
Praesent congue fringilla sem sed faucibus. Vivamus malesuada eget mauris at molestie. In sed faucibus nulla. Vivamus elementum accumsan metus quis suscipit. Maecenas interdum est nulla. Cras volutpat cursus nibh quis sollicitudin. Morbi vitae massa laoreet, aliquet tellus quis, consectetur ipsum. Mauris euismod congue purus non condimentum. Etiam laoreet mi vel sem consectetur gravida. Vestibulum volutpat magna nunc, vitae ultrices risus commodo eu.
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer elit lorem, eleifend eu leo sit amet, suscipit feugiat libero. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Proin congue lectus sit amet lacus venenatis, ac sollicitudin purus condimentum. Suspendisse pretium volutpat sapien at gravida. In tincidunt, sem non accumsan consectetur, leo libero porttitor dolor, at imperdiet erat nibh quis leo. Cras dictum erat augue, quis pharetra justo porttitor posuere. Aenean sed lacinia justo, vel suscipit nisl. Etiam eleifend id mauris at gravida. Aliquam molestie cursus lorem pulvinar sollicitudin. Nam et ex dignissim, posuere sem non, pellentesque lacus. Morbi vulputate sed lorem et convallis. Duis non turpis eget elit posuere volutpat. Donec accumsan euismod enim, id consequat ex rhoncus ac. Pellentesque ac felis nisl. Duis imperdiet vel tellus ac iaculis.
|
||||
|
||||
Vivamus nec tempus enim. Integer in ligula eget elit ornare vulputate id et est. Proin mi elit, sagittis nec urna et, iaculis imperdiet neque. Vestibulum placerat cursus dolor. Donec eu sodales nulla. Praesent ac tellus eros. Donec venenatis ligula id ex porttitor malesuada. Aliquam maximus, nisi in fringilla finibus, ante elit rhoncus dui, placerat semper nisl tellus quis odio. Cras luctus magna ultrices dolor pharetra volutpat. Maecenas non enim vitae ligula efficitur aliquet id quis quam. In sagittis mollis magna eu porta. Morbi at nulla et ante elementum pharetra in sed est. Nam commodo purus enim.
|
||||
|
||||
Ut non elit sit amet urna luctus facilisis vel et sapien. Morbi nec metus at libero imperdiet sollicitudin eget quis lacus. Donec in ipsum at enim accumsan tempor vel sed magna. Aliquam non imperdiet neque. Etiam pharetra neque sed pretium interdum. Suspendisse potenti. Phasellus varius, lectus quis dapibus faucibus, purus mauris accumsan nibh, vel tempor quam metus nec sem. Nunc sagittis suscipit lorem eu finibus. Nullam augue leo, imperdiet vel diam et, vulputate scelerisque turpis. Nullam ut volutpat diam. Praesent cursus accumsan dui a commodo. Vivamus sed libero sed turpis facilisis rutrum id sed ligula. Ut id sollicitudin dui. Nulla pulvinar commodo lectus. Cras ut quam congue, consectetur dolor ac, consequat ante.
|
||||
|
||||
Curabitur scelerisque sed leo eu facilisis. Nam faucibus neque eget dictum hendrerit. Duis efficitur ex sed vulputate volutpat. Praesent condimentum nisl ac sapien efficitur laoreet. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut ut nibh elit. Nunc a neque lobortis, tempus diam vitae, interdum magna. Aenean eget nisl sed justo volutpat interdum. Mauris malesuada ex nisl, a dignissim dui elementum eget. Suspendisse potenti.
|
||||
|
||||
Praesent congue fringilla sem sed faucibus. Vivamus malesuada eget mauris at molestie. In sed faucibus nulla. Vivamus elementum accumsan metus quis suscipit. Maecenas interdum est nulla. Cras volutpat cursus nibh quis sollicitudin. Morbi vitae massa laoreet, aliquet tellus quis, consectetur ipsum. Mauris euismod congue purus non condimentum. Etiam laoreet mi vel sem consectetur gravida. Vestibulum volutpat magna nunc, vitae ultrices risus commodo eu.
|
||||
|
||||
Hopefully everything above was rendered nicely, on both desktop and mobile.
|
||||
5
test_book/src/individual/strikethrough.md
Normal file
5
test_book/src/individual/strikethrough.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Strikethrough
|
||||
|
||||
~~This is Striked~~
|
||||
|
||||
~~This is **strong**, _italic_ , **_both_** and striked~~
|
||||
28
test_book/src/individual/table.md
Normal file
28
test_book/src/individual/table.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Tables
|
||||
|
||||
| col1 | col2 |
|
||||
| ---- | ---- |
|
||||
|
||||
---
|
||||
|
||||
| col1 | col2 |
|
||||
| ---- | ---- |
|
||||
| val1 | val2 |
|
||||
|
||||
---
|
||||
|
||||
| col1 | col2 | col 3 | col 4 | col 5 | col 6 |
|
||||
| ---- | ---- | ----- | ----- | ----- | ----- |
|
||||
| val1 | val2 | val3 | val5 | val4 | val6 |
|
||||
| val1 | val2 | val3 | val5 | val4 | val6 |
|
||||
| val1 | val2 | val3 | val5 | val4 | val6 |
|
||||
| val1 | val2 | val3 | val5 | val4 | val6 |
|
||||
|
||||
---
|
||||
|
||||
| col1 | col2 | col 3 | col 4 | col 5 | col 6 |
|
||||
| -------------------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ----- | -------------------------------------------------------------------------------------------------------------- |
|
||||
| This is a simple demo book, which is intended to be used for verifying and validating style changes in mdBook. | val2 | val3 | val5 | val4 | val6 |
|
||||
| val1 | val2 | val3 | val5 | val4 | val6 |
|
||||
| val1 | val2 | val3 | val5 | val4 | This is a simple demo book, which is intended to be used for verifying and validating style changes in mdBook. |
|
||||
| val1 | val2 | This is a simple demo book, which is intended to be used for verifying and validating style changes in mdBook. | This is a simple demo book, which is intended to be used for verifying and validating style changes in mdBook. | val4 | val6 |
|
||||
11
test_book/src/individual/task.md
Normal file
11
test_book/src/individual/task.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Tasks
|
||||
|
||||
- [ ] Task 1
|
||||
- [ ] Task 2
|
||||
- [x] Completed Task 1
|
||||
- [x] Completed Task 2
|
||||
|
||||
---
|
||||
|
||||
- [ ] **Important Task**
|
||||
- [x] _Completed Important task_
|
||||
47
test_book/src/languages/README.md
Normal file
47
test_book/src/languages/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Syntax Highlighting
|
||||
|
||||
This Currently contains following languages
|
||||
|
||||
- apache
|
||||
- armasm
|
||||
- bash
|
||||
- c
|
||||
- coffeescript
|
||||
- cpp
|
||||
- csharp
|
||||
- css
|
||||
- d
|
||||
- diff
|
||||
- go
|
||||
- handlebars
|
||||
- haskell
|
||||
- http
|
||||
- ini
|
||||
- java
|
||||
- javascript
|
||||
- json
|
||||
- julia
|
||||
- kotlin
|
||||
- less
|
||||
- lua
|
||||
- makefile
|
||||
- markdown
|
||||
- nginx
|
||||
- objectivec
|
||||
- perl
|
||||
- php
|
||||
- plaintext
|
||||
- properties
|
||||
- python
|
||||
- r
|
||||
- ruby
|
||||
- rust
|
||||
- scala
|
||||
- scss
|
||||
- shell
|
||||
- sql
|
||||
- swift
|
||||
- typescript
|
||||
- x86asm
|
||||
- xml
|
||||
- yaml
|
||||
927
test_book/src/languages/highlight.md
Normal file
927
test_book/src/languages/highlight.md
Normal file
@@ -0,0 +1,927 @@
|
||||
# Syntax Highlights
|
||||
|
||||
## apache
|
||||
|
||||
```apache
|
||||
# rewrite`s rules for wordpress pretty url
|
||||
LoadModule rewrite_module modules/mod_rewrite.so
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule . index.php [NC,L]
|
||||
|
||||
ExpiresActive On
|
||||
ExpiresByType application/x-javascript "access plus 1 days"
|
||||
|
||||
Order Deny,Allow
|
||||
Allow from All
|
||||
|
||||
<Location /maps/>
|
||||
RewriteMap map txt:map.txt
|
||||
RewriteMap lower int:tolower
|
||||
RewriteCond %{REQUEST_URI} ^/([^/.]+)\.html$ [NC]
|
||||
RewriteCond ${map:${lower:%1}|NOT_FOUND} !NOT_FOUND
|
||||
RewriteRule .? /index.php?q=${map:${lower:%1}} [NC,L]
|
||||
</Location>
|
||||
|
||||
20.164.151.111 - - [20/Aug/2015:22:20:18 -0400] "GET /mywebpage/index.php HTTP/1.1" 403 772 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1"
|
||||
```
|
||||
|
||||
## armasm
|
||||
|
||||
```armasm
|
||||
.data
|
||||
|
||||
/* Data segment: define our message string and calculate its length. */
|
||||
msg:
|
||||
.ascii "Hello, ARM!\n"
|
||||
len = . - msg
|
||||
|
||||
.text
|
||||
|
||||
/* Our application's entry point. */
|
||||
.globl _start
|
||||
_start:
|
||||
/* syscall write(int fd, const void *buf, size_t count) */
|
||||
mov %r0, $1 /* fd := STDOUT_FILENO */
|
||||
ldr %r1, =msg /* buf := msg */
|
||||
ldr %r2, =len /* count := len */
|
||||
mov %r7, $4 /* write is syscall #4 */
|
||||
swi $0 /* invoke syscall */
|
||||
|
||||
/* syscall exit(int status) */
|
||||
mov %r0, $0 /* status := 0 */
|
||||
mov %r7, $1 /* exit is syscall #1 */
|
||||
swi $0 /* invoke syscall */
|
||||
|
||||
```
|
||||
|
||||
## bash
|
||||
|
||||
```
|
||||
#!/bin/bash
|
||||
|
||||
###### CONFIG
|
||||
ACCEPTED_HOSTS="/root/.hag_accepted.conf"
|
||||
BE_VERBOSE=false
|
||||
|
||||
if [ "$UID" -ne 0 ]
|
||||
then
|
||||
echo "Superuser rights required"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
genApacheConf(){
|
||||
echo -e "# Host ${HOME_DIR}$1/$2 :"
|
||||
}
|
||||
|
||||
echo '"quoted"' | tr -d \" > text.txt
|
||||
|
||||
```
|
||||
|
||||
## c
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
void main(int argc,char ** argv){
|
||||
printf("Hello World!");
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## coffeescript
|
||||
|
||||
```coffeescript
|
||||
grade = (student, period=(if b? then 7 else 6)) ->
|
||||
if student.excellentWork
|
||||
"A+"
|
||||
else if student.okayStuff
|
||||
if student.triedHard then "B" else "B-"
|
||||
else
|
||||
"C"
|
||||
|
||||
class Animal extends Being
|
||||
constructor: (@name) ->
|
||||
|
||||
move: (meters) ->
|
||||
alert @name + " moved #{meters}m."
|
||||
```
|
||||
|
||||
## cpp
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
int main() {
|
||||
cout << "Hello, World!" << endl; // This prints Hello, World!
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## csharp
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
class App
|
||||
{
|
||||
static void Main()
|
||||
{
|
||||
Console.WriteLine("Hello World!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## css
|
||||
|
||||
```css
|
||||
@font-face {
|
||||
font-family: Chunkfive;
|
||||
src: url('Chunkfive.otf');
|
||||
}
|
||||
|
||||
body,
|
||||
.usertext {
|
||||
color: #f0f0f0;
|
||||
background: #600;
|
||||
font-family: Chunkfive, sans;
|
||||
--heading-1: 30px/32px Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
@import url(print.css);
|
||||
@media print {
|
||||
a[href^='http']::after {
|
||||
content: attr(href);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## d
|
||||
|
||||
```d
|
||||
/* This program prints a
|
||||
hello world message
|
||||
to the console. */
|
||||
|
||||
import std.stdio;
|
||||
|
||||
void main()
|
||||
{
|
||||
writeln("Hello, World!");
|
||||
}
|
||||
```
|
||||
|
||||
## diff
|
||||
|
||||
```diff
|
||||
Index: languages/ini.js
|
||||
===================================================================
|
||||
--- languages/ini.js (revision 199)
|
||||
+++ languages/ini.js (revision 200)
|
||||
@@ -1,8 +1,7 @@
|
||||
hljs.LANGUAGES.ini =
|
||||
{
|
||||
case_insensitive: true,
|
||||
- defaultMode:
|
||||
- {
|
||||
+ defaultMode: {
|
||||
contains: ['comment', 'title', 'setting'],
|
||||
illegal: '[^\\s]'
|
||||
},
|
||||
|
||||
*** /path/to/original timestamp
|
||||
--- /path/to/new timestamp
|
||||
***************
|
||||
*** 1,3 ****
|
||||
--- 1,9 ----
|
||||
+ This is an important
|
||||
+ notice! It should
|
||||
+ therefore be located at
|
||||
+ the beginning of this
|
||||
+ document!
|
||||
|
||||
! compress the size of the
|
||||
! changes.
|
||||
|
||||
It is important to spell
|
||||
```
|
||||
|
||||
## go
|
||||
|
||||
```go
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello World!")
|
||||
}
|
||||
```
|
||||
|
||||
## handlebars
|
||||
|
||||
```handlebars
|
||||
<div class='entry'>
|
||||
{{! only show if author exists }}
|
||||
{{#if author}}
|
||||
<h1>{{firstName}} {{lastName}}</h1>
|
||||
{{/if}}
|
||||
</div>
|
||||
```
|
||||
|
||||
## haskell
|
||||
|
||||
```haskell
|
||||
main :: IO ()
|
||||
main = putStrLn "Hello World!"
|
||||
|
||||
```
|
||||
|
||||
## http
|
||||
|
||||
```http
|
||||
POST /task?id=1 HTTP/1.1
|
||||
Host: example.org
|
||||
Content-Type: application/json; charset=utf-8
|
||||
Content-Length: 137
|
||||
|
||||
```
|
||||
|
||||
## ini
|
||||
|
||||
```ini
|
||||
; boilerplate
|
||||
[package]
|
||||
name = "some_name"
|
||||
authors = ["Author"]
|
||||
description = "This is \
|
||||
a description"
|
||||
|
||||
[[lib]]
|
||||
name = ${NAME}
|
||||
default = True
|
||||
auto = no
|
||||
counter = 1_000
|
||||
```
|
||||
|
||||
## java
|
||||
|
||||
```java
|
||||
class Main {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello World!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## javascript
|
||||
|
||||
```javascript
|
||||
function $initHighlight(block, cls) {
|
||||
try {
|
||||
if (cls.search(/\bno\-highlight\b/) != -1)
|
||||
return process(block, true, 0x0F) +
|
||||
` class="${cls}"`;
|
||||
} catch (e) {
|
||||
/* handle exception */
|
||||
}
|
||||
for (var i = 0 / 2; i < classes.length; i++) {
|
||||
if (checkCondition(classes[i]) === undefined)
|
||||
console.log('undefined');
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<web-component>{block}</web-component>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export $initHighlight;
|
||||
```
|
||||
|
||||
## json
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"title": "apples",
|
||||
"count": [12000, 20000],
|
||||
"description": { "text": "...", "sensitive": false }
|
||||
},
|
||||
{
|
||||
"title": "oranges",
|
||||
"count": [17500, null],
|
||||
"description": { "text": "...", "sensitive": false }
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## julia
|
||||
|
||||
```julia
|
||||
# function to calculate the volume of a sphere
|
||||
function sphere_vol(r)
|
||||
# julia allows Unicode names (in UTF-8 encoding)
|
||||
# so either "pi" or the symbol π can be used
|
||||
return 4/3*pi*r^3
|
||||
end
|
||||
|
||||
# functions can also be defined more succinctly
|
||||
quadratic(a, sqr_term, b) = (-b + sqr_term) / 2a
|
||||
|
||||
# calculates x for 0 = a*x^2+b*x+c, arguments types can be defined in function definitions
|
||||
function quadratic2(a::Float64, b::Float64, c::Float64)
|
||||
# unlike other languages 2a is equivalent to 2*a
|
||||
# a^2 is used instead of a**2 or pow(a,2)
|
||||
sqr_term = sqrt(b^2-4a*c)
|
||||
r1 = quadratic(a, sqr_term, b)
|
||||
r2 = quadratic(a, -sqr_term, b)
|
||||
# multiple values can be returned from a function using tuples
|
||||
# if the return keyword is omitted, the last term is returned
|
||||
r1, r2
|
||||
end
|
||||
|
||||
vol = sphere_vol(3)
|
||||
```
|
||||
|
||||
## kotlin
|
||||
|
||||
```kotlin
|
||||
package org.kotlinlang.play
|
||||
|
||||
fun main() {
|
||||
println("Hello, World!")
|
||||
}
|
||||
```
|
||||
|
||||
## less
|
||||
|
||||
```less
|
||||
@import 'fruits';
|
||||
|
||||
@rhythm: 1.5em;
|
||||
|
||||
@media screen and (min-resolution: 2dppx) {
|
||||
body {
|
||||
font-size: 125%;
|
||||
}
|
||||
}
|
||||
|
||||
section > .foo + #bar:hover [href*='less'] {
|
||||
margin: @rhythm 0 0 @rhythm;
|
||||
padding: calc(5% + 20px);
|
||||
background: #f00ba7 url(http://placehold.alpha-centauri/42.png) no-repeat;
|
||||
background-image: linear-gradient(-135deg, wheat, fuchsia) !important ;
|
||||
background-blend-mode: multiply;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: /* ? */ 'Omega';
|
||||
src: url('../fonts/omega-webfont.woff?v=2.0.2');
|
||||
}
|
||||
|
||||
.icon-baz::before {
|
||||
display: inline-block;
|
||||
font-family: 'Omega', Alpha, sans-serif;
|
||||
content: '\f085';
|
||||
color: rgba(98, 76 /* or 54 */, 231, 0.75);
|
||||
}
|
||||
```
|
||||
|
||||
## lua
|
||||
|
||||
```lua
|
||||
--[[
|
||||
Simple signal/slot implementation
|
||||
]]
|
||||
local signal_mt = {
|
||||
__index = {
|
||||
register = table.insert
|
||||
}
|
||||
}
|
||||
function signal_mt.__index:emit(... --[[ Comment in params ]])
|
||||
for _, slot in ipairs(self) do
|
||||
slot(self, ...)
|
||||
end
|
||||
end
|
||||
local function create_signal()
|
||||
return setmetatable({}, signal_mt)
|
||||
end
|
||||
|
||||
-- Signal test
|
||||
local signal = create_signal()
|
||||
signal:register(function(signal, ...)
|
||||
print(...)
|
||||
end)
|
||||
signal:emit('Answer to Life, the Universe, and Everything:', 42)
|
||||
|
||||
--[==[ [=[ [[
|
||||
Nested ]]
|
||||
multi-line ]=]
|
||||
comment ]==]
|
||||
[==[ Nested
|
||||
[=[ multi-line
|
||||
[[ string
|
||||
]] ]=] ]==]
|
||||
```
|
||||
|
||||
## makefile
|
||||
|
||||
```makefile
|
||||
# Makefile
|
||||
|
||||
BUILDDIR = _build
|
||||
EXTRAS ?= $(BUILDDIR)/extras
|
||||
|
||||
.PHONY: main clean
|
||||
|
||||
main:
|
||||
@echo "Building main facility..."
|
||||
build_main $(BUILDDIR)
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
```
|
||||
|
||||
## markdown
|
||||
|
||||
```markdown
|
||||
# hello world
|
||||
|
||||
you can write text [with links](http://example.com) inline or [link references][1].
|
||||
|
||||
- one _thing_ has *em*phasis
|
||||
- two **things** are **bold**
|
||||
|
||||
[1]: http://example.com
|
||||
|
||||
---
|
||||
|
||||
# hello world
|
||||
|
||||
<this_is inline="xml"></this_is>
|
||||
|
||||
> markdown is so cool
|
||||
|
||||
so are code segments
|
||||
|
||||
1. one thing (yeah!)
|
||||
2. two thing `i can write code`, and `more` wipee!
|
||||
```
|
||||
|
||||
## nginx
|
||||
|
||||
```nginx
|
||||
user www www;
|
||||
worker_processes 2;
|
||||
pid /var/run/nginx.pid;
|
||||
error_log /var/log/nginx.error_log debug | info | notice | warn | error | crit;
|
||||
|
||||
events {
|
||||
connections 2000;
|
||||
use kqueue | rtsig | epoll | /dev/poll | select | poll;
|
||||
}
|
||||
|
||||
http {
|
||||
log_format main '$remote_addr - $remote_user [$time_local] '
|
||||
'"$request" $status $bytes_sent '
|
||||
'"$http_referer" "$http_user_agent" '
|
||||
'"$gzip_ratio"';
|
||||
|
||||
send_timeout 3m;
|
||||
client_header_buffer_size 1k;
|
||||
|
||||
gzip on;
|
||||
gzip_min_length 1100;
|
||||
|
||||
#lingering_time 30;
|
||||
|
||||
server {
|
||||
server_name one.example.com www.one.example.com;
|
||||
access_log /var/log/nginx.access_log main;
|
||||
|
||||
rewrite (.*) /index.php?page=$1 break;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1/;
|
||||
proxy_redirect off;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
charset koi8-r;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
}
|
||||
|
||||
location ~* \.(jpg|jpeg|gif)$ {
|
||||
root /spool/www;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## objectivec
|
||||
|
||||
```objectivec
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
int main(int argc, const char * argv[]) {
|
||||
@mylak {
|
||||
NSLog(@"Hello World!");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## perl
|
||||
|
||||
```perl
|
||||
print "Hello World!\n";
|
||||
```
|
||||
|
||||
## php
|
||||
|
||||
```php
|
||||
<?php
|
||||
echo "Hello World!";
|
||||
?>
|
||||
```
|
||||
|
||||
## plaintext
|
||||
|
||||
```plaintext
|
||||
I think this is simply plain text?
|
||||
Hello World!
|
||||
```
|
||||
|
||||
## properties
|
||||
|
||||
```properties
|
||||
# .properties
|
||||
! Exclamation mark = comments, too
|
||||
|
||||
key1 = value1
|
||||
key2 : value2
|
||||
key3 value3
|
||||
key\ spaces multiline\
|
||||
value4
|
||||
empty_key
|
||||
! Key can contain escaped chars
|
||||
\:\= = value5
|
||||
```
|
||||
|
||||
## python
|
||||
|
||||
```python
|
||||
@requires_authorization(roles=["ADMIN"])
|
||||
def somefunc(param1='', param2=0):
|
||||
r'''A docstring'''
|
||||
if param1 > param2: # interesting
|
||||
print 'Gre\'ater'
|
||||
return (param2 - param1 + 1 + 0b10l) or None
|
||||
|
||||
class SomeClass:
|
||||
pass
|
||||
|
||||
>>> message = '''interpreter
|
||||
... prompt'''
|
||||
```
|
||||
|
||||
## r
|
||||
|
||||
```r
|
||||
require(stats)
|
||||
|
||||
#' Compute different averages
|
||||
#'
|
||||
#' @param x \code{numeric} vector of sample data
|
||||
#' @param type \code{character} vector of length 1 specifying the average type
|
||||
#' @return \code{centre} returns the sample average according to the chosen method.
|
||||
#' @examples
|
||||
#' centre(rcauchy(10), "mean")
|
||||
#' @export
|
||||
centre <- function(x, type) {
|
||||
switch(type,
|
||||
mean = mean(x),
|
||||
median = median(x),
|
||||
trimmed = mean(x, trim = .1))
|
||||
}
|
||||
x <- rcauchy(10)
|
||||
centre(x, "mean")
|
||||
|
||||
library(ggplot2)
|
||||
|
||||
models <- tibble::tribble(
|
||||
~model_name, ~ formula,
|
||||
"length-width", Sepal.Length ~ Petal.Width + Petal.Length,
|
||||
"interaction", Sepal.Length ~ Petal.Width * Petal.Length
|
||||
)
|
||||
|
||||
iris %>%
|
||||
nest_by(Species) %>%
|
||||
left_join(models, by = character()) %>%
|
||||
rowwise(Species, model_name) %>%
|
||||
mutate(model = list(lm(formula, data = data))) %>%
|
||||
summarise(broom::glance(model))
|
||||
```
|
||||
|
||||
## ruby
|
||||
|
||||
```ruby
|
||||
# The Greeter class
|
||||
class Greeter
|
||||
def initialize(name)
|
||||
@name = name.capitalize
|
||||
end
|
||||
|
||||
def salute
|
||||
puts "Hello #{@name}!"
|
||||
end
|
||||
end
|
||||
|
||||
g = Greeter.new("world")
|
||||
g.salute
|
||||
```
|
||||
|
||||
## rust
|
||||
|
||||
```rust
|
||||
fn main()->(){
|
||||
println!("Hello World!");
|
||||
}
|
||||
```
|
||||
|
||||
## scala
|
||||
|
||||
```scala
|
||||
/**
|
||||
* A person has a name and an age.
|
||||
*/
|
||||
case class Person(name: String, age: Int)
|
||||
|
||||
abstract class Vertical extends CaseJeu
|
||||
case class Haut(a: Int) extends Vertical
|
||||
case class Bas(name: String, b: Double) extends Vertical
|
||||
|
||||
sealed trait Ior[+A, +B]
|
||||
case class Left[A](a: A) extends Ior[A, Nothing]
|
||||
case class Right[B](b: B) extends Ior[Nothing, B]
|
||||
case class Both[A, B](a: A, b: B) extends Ior[A, B]
|
||||
|
||||
trait Functor[F[_]] {
|
||||
def map[A, B](fa: F[A], f: A => B): F[B]
|
||||
}
|
||||
|
||||
// beware Int.MinValue
|
||||
def absoluteValue(n: Int): Int =
|
||||
if (n < 0) -n else n
|
||||
|
||||
def interp(n: Int): String =
|
||||
s"there are $n ${color} balloons.\n"
|
||||
|
||||
type ξ[A] = (A, A)
|
||||
|
||||
trait Hist { lhs =>
|
||||
def ⊕(rhs: Hist): Hist
|
||||
}
|
||||
|
||||
def gsum[A: Ring](as: Seq[A]): A =
|
||||
as.foldLeft(Ring[A].zero)(_ + _)
|
||||
|
||||
val actions: List[Symbol] =
|
||||
'init :: 'read :: 'write :: 'close :: Nil
|
||||
```
|
||||
|
||||
## scss
|
||||
|
||||
```scss
|
||||
import "compass/reset";
|
||||
|
||||
// variables
|
||||
$colorGreen: #008000;
|
||||
$colorGreenDark: darken($colorGreen, 10);
|
||||
|
||||
@mixin container {
|
||||
max-width: 980px;
|
||||
}
|
||||
|
||||
// mixins with parameters
|
||||
@mixin button($color:green) {
|
||||
@if ($color == green) {
|
||||
background-color: #008000;
|
||||
}
|
||||
@else if ($color == red) {
|
||||
background-color: #B22222;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
@include button(red);
|
||||
}
|
||||
|
||||
div,
|
||||
.navbar,
|
||||
#header,
|
||||
input[type="input"] {
|
||||
font-family: "Helvetica Neue", Arial, sans-serif;
|
||||
width: auto;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.row-12 > [class*="spans"] {
|
||||
border-left: 1px solid #B5C583;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## shell
|
||||
|
||||
```shell
|
||||
$ echo $EDITOR
|
||||
vim
|
||||
$ git checkout main
|
||||
Switched to branch 'main'
|
||||
Your branch is up-to-date with 'origin/main'.
|
||||
$ git push
|
||||
Everything up-to-date
|
||||
$ echo 'All
|
||||
> done!'
|
||||
All
|
||||
done!
|
||||
|
||||
```
|
||||
|
||||
## sql
|
||||
|
||||
```sql
|
||||
CREATE TABLE "topic" (
|
||||
"id" integer NOT NULL PRIMARY KEY,
|
||||
"forum_id" integer NOT NULL,
|
||||
"subject" varchar(255) NOT NULL
|
||||
);
|
||||
ALTER TABLE "topic"
|
||||
ADD CONSTRAINT forum_id FOREIGN KEY ("forum_id")
|
||||
REFERENCES "forum" ("id");
|
||||
|
||||
-- Initials
|
||||
insert into "topic" ("forum_id", "subject")
|
||||
values (2, 'D''artagnian');
|
||||
```
|
||||
|
||||
## swift
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
|
||||
@objc class Person: Entity {
|
||||
var name: String!
|
||||
var age: Int!
|
||||
|
||||
init(name: String, age: Int) {
|
||||
/* /* ... */ */
|
||||
}
|
||||
|
||||
// Return a descriptive string for this person
|
||||
func description(offset: Int = 0) -> String {
|
||||
return "\(name) is \(age + offset) years old"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## typescript
|
||||
|
||||
```typescript
|
||||
class MyClass {
|
||||
public static myValue: string;
|
||||
constructor(init: string) {
|
||||
this.myValue = init;
|
||||
}
|
||||
}
|
||||
import fs = require("fs");
|
||||
module MyModule {
|
||||
export interface MyInterface extends Other {
|
||||
myProperty: any;
|
||||
}
|
||||
}
|
||||
declare magicNumber number;
|
||||
myArray.forEach(() => { }); // fat arrow syntax
|
||||
|
||||
```
|
||||
|
||||
## x86asm
|
||||
|
||||
```x86asm
|
||||
section .text
|
||||
extern _MessageBoxA@16
|
||||
%if __NASM_VERSION_ID__ >= 0x02030000
|
||||
safeseh handler ; register handler as "safe handler"
|
||||
%endif
|
||||
|
||||
handler:
|
||||
push dword 1 ; MB_OKCANCEL
|
||||
push dword caption
|
||||
push dword text
|
||||
push dword 0
|
||||
call _MessageBoxA@16
|
||||
sub eax,1 ; incidentally suits as return value
|
||||
; for exception handler
|
||||
ret
|
||||
|
||||
global _main
|
||||
_main: push dword handler
|
||||
push dword [fs:0]
|
||||
mov dword [fs:0], esp
|
||||
xor eax,eax
|
||||
mov eax, dword[eax] ; cause exception
|
||||
pop dword [fs:0] ; disengage exception handler
|
||||
add esp, 4
|
||||
ret
|
||||
|
||||
avx2: vzeroupper
|
||||
push rbx
|
||||
mov rbx, rsp
|
||||
sub rsp, 0h20
|
||||
vmovdqa ymm0, [rcx]
|
||||
vpaddb ymm0, [rdx]
|
||||
leave
|
||||
ret
|
||||
|
||||
text: db 'OK to rethrow, CANCEL to generate core dump',0
|
||||
caption:db 'SEGV',0
|
||||
|
||||
section .drectve info
|
||||
db '/defaultlib:user32.lib /defaultlib:msvcrt.lib '
|
||||
```
|
||||
|
||||
## xml
|
||||
|
||||
```xml
|
||||
<!DOCTYPE html>
|
||||
<title>Title</title>
|
||||
|
||||
<style>body {width: 500px;}</style>
|
||||
|
||||
<script type="application/javascript">
|
||||
function $init() {return true;}
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<p checked class="title" id='title'>Title</p>
|
||||
<!-- here goes the rest of the page -->
|
||||
</body>
|
||||
```
|
||||
|
||||
## yaml
|
||||
|
||||
```yaml
|
||||
---
|
||||
# comment
|
||||
string_1: "Bar"
|
||||
string_2: 'bar'
|
||||
string_3: bar
|
||||
inline_keys_ignored: sompath/name/file.jpg
|
||||
keywords_in_yaml:
|
||||
- true
|
||||
- false
|
||||
- TRUE
|
||||
- FALSE
|
||||
- 21
|
||||
- 21.0
|
||||
- !!str 123
|
||||
"quoted_key": &foobar
|
||||
bar: foo
|
||||
foo:
|
||||
"foo": bar
|
||||
|
||||
reference: *foobar
|
||||
|
||||
multiline_1: |
|
||||
Multiline
|
||||
String
|
||||
multiline_2: >
|
||||
Multiline
|
||||
String
|
||||
multiline_3: "
|
||||
Multiline string
|
||||
"
|
||||
|
||||
ansible_variables: "foo {{variable}}"
|
||||
|
||||
array_nested:
|
||||
- a
|
||||
- b: 1
|
||||
c: 2
|
||||
- b
|
||||
- comment
|
||||
```
|
||||
ansible_variables: "foo {{variable}}"
|
||||
|
||||
array_nested:
|
||||
- a
|
||||
- b: 1
|
||||
c: 2
|
||||
- b
|
||||
- comment
|
||||
```
|
||||
3
test_book/src/prefix.md
Normal file
3
test_book/src/prefix.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Prefix Chapter
|
||||
|
||||
This is to verify the placement and style of prefix chapter in book index.
|
||||
1
test_book/src/rust/README.md
Normal file
1
test_book/src/rust/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# Rust specific code examples
|
||||
27
test_book/src/rust/rust_codeblock.md
Normal file
27
test_book/src/rust/rust_codeblock.md
Normal file
@@ -0,0 +1,27 @@
|
||||
## Rust codeblocks
|
||||
|
||||
This contains various examples of codeblocks, specific to rust
|
||||
|
||||
## Simple
|
||||
|
||||
```rust
|
||||
fn main(){
|
||||
println!("Hello world!");
|
||||
}
|
||||
```
|
||||
|
||||
## With Hidden lines
|
||||
|
||||
```rust
|
||||
# fn main(){
|
||||
println!("Hello world!");
|
||||
# }
|
||||
```
|
||||
|
||||
## Editable
|
||||
|
||||
```rust,editable
|
||||
fn main(){
|
||||
println!("Hello world!");
|
||||
}
|
||||
```
|
||||
3
test_book/src/suffix.md
Normal file
3
test_book/src/suffix.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Suffix Chapter
|
||||
|
||||
This is to verify the placement and style of suffix chapter in book index.
|
||||
@@ -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
29
tests/cli/build.rs
Normal 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
2
tests/cli/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod build;
|
||||
mod test;
|
||||
34
tests/cli/test.rs
Normal file
34
tests/cli/test.rs
Normal 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
2
tests/cli_tests.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod cli;
|
||||
mod dummy_book;
|
||||
@@ -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)
|
||||
|
||||
|
||||
3
tests/dummy_book/src/first/no-headers.md
Normal file
3
tests/dummy_book/src/first/no-headers.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Capybara capybara capybara.
|
||||
|
||||
Capybara capybara capybara.
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
@@ -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 <HTML> 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
12
triagebot.toml
Normal file
12
triagebot.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
# This will allow users to self assign, and/or drop assignment
|
||||
[assign]
|
||||
|
||||
|
||||
[relabel]
|
||||
allow-unauthenticated = [
|
||||
# For Issue areas
|
||||
"A-*",
|
||||
"E-Help-Wanted",
|
||||
"Bug",
|
||||
"Feature-Request"
|
||||
]
|
||||
Reference in New Issue
Block a user