Compare commits

..

649 Commits

Author SHA1 Message Date
Eric Huss
f66df09324 Bump 0.4.5 2021-01-04 07:03:20 -08:00
Pietro Albini
648c9ae772 fix xss in the search page
Thanks to Kamil Vavra for responsibly disclosing the vulnerability
according to Rust's Security Policy.
2021-01-04 07:01:49 -08:00
Eric Huss
eaa6914205 Merge pull request #1360 from ehuss/release-next
Release 0.4.4
2020-10-20 08:06:27 -07:00
Eric Huss
a76557a678 Release 0.4.4 2020-10-20 07:58:24 -07:00
Eric Huss
01836ba5d4 Merge pull request #1348 from ehuss/github-deprecated-action-commands
Update deprecated GitHub Actions commands.
2020-10-20 07:48:26 -07:00
Eric Huss
46ce077de6 Update deprecated GitHub Actions commands. 2020-10-08 10:00:06 -07:00
Eric Huss
f7c9180d80 Fix typo in changelog 2020-10-08 09:46:39 -07:00
Camelid
4c951d530d List supported Highlight.js languages in guide (#1345)
* List supported Highlight.js languages in guide

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

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

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

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

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

After:

    /path/to$  cat book/.nojekyll
    This file makes sure that Github Pages doesn't process mdBook's output.
    /path/to$  ▎
2020-09-02 11:06:19 -07:00
Søren Mortensen
2fb489137b Wrap draft chapter titles in a <div>
Fixes the problem of a missing space before the title of draft chapters,
due to the title not being wrapped in an `<a></a>`.
2020-08-31 10:00:03 +01:00
Camelid
4d9eb9b4b4 Prefer bundled version of Source Code Pro
This prevents an issue with Firefox 80 on macOS that prevents syntax
highlighting from working.
2020-08-29 13:22:51 -07:00
Eric Huss
f6768b816c Fix release script LTO. 2020-08-11 15:39:03 -07:00
Eric Huss
8f7e030ac3 Merge pull request #1299 from ehuss/release-next
Release 0.4.2
2020-08-11 15:28:01 -07:00
Eric Huss
9180dd1659 Release 0.4.2 2020-08-11 15:19:39 -07:00
Eric Huss
9278b838a8 Merge pull request #1298 from FabienTregan/adding_info_about_markdown_flavor
Adds information about markdown flavor in the documentation.
2020-08-10 10:46:53 -07:00
Fabien Tregan
2674347768 Adds information about markdown flavor in the documentation. 2020-08-10 10:52:22 +02:00
Eric Huss
3d44553671 Merge pull request #1293 from Evian-Zhang/master
allow space in SUMMARY.md's link destination
2020-08-08 08:19:06 -07:00
Evian-Zhang
9d5c454e47 delete '+' replacement and use cargo fmt to format 2020-08-08 15:13:22 +08:00
Eric Huss
a00e7d1769 Merge pull request #1272 from ehuss/fix-favicon-link
Fix favicon when only one is overridden.
2020-08-07 08:34:46 -07:00
Evian-Zhang
60be20a783 allow space in SUMMARY.md's link destination 2020-08-03 09:49:01 +08:00
Eric Huss
8746206060 Merge pull request #1284 from ericonr/opt
Mention that uninstalled backend isn't marked optional.
2020-07-30 20:22:55 -07:00
Eric Huss
f5ae7c4f13 Merge pull request #1291 from ericonr/fix-break
summary: turn SoftBreak events into spaces.
2020-07-30 20:05:07 -07:00
Érico Rolim
dcf9462d1e summary: turn SoftBreak events into spaces.
Summary items that had their name split into two lines ended up with the
last word of one line and the first word of the next line glued
together, since a space wasn't added.

Added test case for it.

Fixes #1218
2020-07-29 15:53:27 -03:00
Érico Rolim
78aa2a16f8 Mention that uninstalled backend isn't marked optional.
If an uninstalled backend (command not found) isn't marked optional,
warn about it, since it causes mdbook commands to error out.

Fixes #1283
2020-07-28 18:08:32 -03:00
Eric Huss
303db0ddec Merge pull request #1277 from maniyar1/patch-1
Update highlight.js
2020-07-21 14:17:03 -07:00
Eric Huss
a884c2574e Merge pull request #1281 from onelson/more-obvious-show-hidden
Change "show hidden lines" icon to "eye" instead of "expand."
2020-07-21 12:23:14 -07:00
maniyar1
60029e4e15 Add R-lang support 2020-07-20 19:03:29 -04:00
Yuki Okushi
4e16d96ed5 Mention removed features (#1282) 2020-07-19 21:32:21 +02:00
Owen Nelson
0eefd63a13 Change "show hidden lines" icon to "eye" instead of "expand."
In a recent discussion in the amethyst docs discord channel,
it was suggested that using an "eye" icon might make the show hidden
lines feature of mdbook's code sample rendering more discoverable.

I myself overlooked the arrows that are in use now.

Fixes #663 at least in part.
2020-07-16 19:27:18 -07:00
Eric Huss
89c2743cc6 Merge pull request #1280 from bikeshedder/fix-title-doc
Fix documentation for title and book_title
2020-07-16 10:04:44 -07:00
Michael P. Jung
a825427722 Fix documentation for title and book_title 2020-07-16 16:43:46 +02:00
maniyar1
c99047bbda Update highlight.js
Updates to 10.1.1, with support for new languages added to the common set.
Built with:
common languages + D, handlebars, haskell, julia, scala, x86asm, armasm, yaml
Size difference:
New highlight.js: 132KiB
Old highlight.js: 84KiB
--
New highlight.js compressed: 48KiB
Old highlight.js compressed: 36KiB
2020-07-09 14:01:51 -04:00
Eric Huss
20a0b99c3d Fix favicon when only one is overridden.
A mistake in #1266, I neglected to also update the <link> generation
to elide the link if one of the favicon images is not included.
2020-07-08 12:44:07 -07:00
Eric Huss
ec495a7823 Merge pull request #1273 from ehuss/fix-ci-rustup
Fix CI due to new rustup.
2020-07-08 10:59:14 -07:00
Eric Huss
e38fb1ecc6 Fix CI due to new rustup. 2020-07-07 14:08:51 -07:00
Eric Huss
f37ea9a4e7 Merge pull request #1269 from ehuss/update-changelog
Update changelog.
2020-07-03 09:07:21 -07:00
Eric Huss
8f74804c70 Update changelog. 2020-07-03 08:52:46 -07:00
Eric Huss
649f3555e5 Merge pull request #1266 from ehuss/favicon-no-default
Don't copy default favicon if at least one is overridden.
2020-07-03 08:47:34 -07:00
Eric Huss
8432df1e80 Merge pull request #1265 from ehuss/part-title-active
Fix sidebar scrolling with part titles.
2020-07-03 08:34:52 -07:00
Igor Matuszewski
9eba9ed93a Update select dependency to 0.5 (#1267)
This allows to prune some of the old transitive dependencies from the
dep tree. In particular, this gets rid of <1.0 syn and related crates.
2020-06-30 19:11:09 +02:00
Eric Huss
b0c6f2d7a3 Don't copy default favicon if at least one is overridden. 2020-06-27 16:30:46 -07:00
Eric Huss
6e0688afef Update changelog, add note about SVG icon. 2020-06-27 15:37:13 -07:00
Eric Huss
e9951af73e Fix sidebar scrolling with part titles. 2020-06-24 21:13:02 -07:00
Eric Huss
138dc696b7 Merge pull request #1264 from ehuss/update-changelog
Update changelog.
2020-06-23 11:45:04 -07:00
Eric Huss
91b2fb86bf Update changelog. 2020-06-23 11:44:05 -07:00
Eric Huss
d4df7e7cdd Merge pull request #1230 from ehuss/favicon-svg
Add SVG favicon.
2020-06-23 11:34:03 -07:00
Eric Huss
4699269e49 Add SVG favicon. 2020-06-23 10:58:10 -07:00
Eric Huss
c1ed6ee108 Merge pull request #1228 from ehuss/fix-dest-dir
Fix dest-dir command-line flag.
2020-06-23 10:55:38 -07:00
Eric Huss
f59cfe7e2f Fix dest-dir command-line flag. 2020-06-23 10:55:01 -07:00
Eric Huss
9268884b17 Merge pull request #1221 from manuel-woelker/fb-539-not-found-page
Generate 404.html page (#539)
2020-06-23 10:48:08 -07:00
Eric Huss
4f435c62e6 Merge pull request #1249 from ThePuzzlemaker/thepuzzlemaker-stylepatch
Fix issue where Font Awesome overrides `.hidden`'s `display: none`
2020-06-22 12:28:09 -07:00
Eric Huss
9a97f0a096 Fix init creating empty [rust] table. (#1233) 2020-06-22 16:36:37 +02:00
Eric Huss
bc23d08fa5 Rename playpen to playground. (#1241)
looks good
2020-06-22 16:34:25 +02:00
ThePuzzlemaker
84d848f292 Fix issue where Font Awesome overrides .hidden's display: none 2020-06-10 12:55:22 -05:00
Manuel Woelker
d7df832cce fix test and formatting 2020-06-10 15:33:09 +02:00
Manuel Woelker
406b325c54 fix usage of newly stablized inner_deref/as_deref 2020-06-10 13:09:18 +02:00
Manuel Woelker
6d6e5407a3 document: config 404 config parameters output.html.site-url and output.html.site-url 2020-06-10 12:46:23 +02:00
Manuel Woelker
06efa7a675 additional changes to the 404 mechanism based on feedback:
- removed config output_404
 - ensure serve overrides the site url, and hosts the correct 404 file
 - refactor 404 rendering into separate fn
 - formatting
2020-06-10 12:46:23 +02:00
Manuel Woelker
bff36e7229 Add the config parameter output.html.site-url to set base url of the 404 page, making links and relative script/css loads behave correctly even in subdirectory paths 2020-06-10 12:46:23 +02:00
Manuel Woelker
cda28bb618 Generate 404.html page (#539) 2020-06-10 12:46:23 +02:00
Eric Huss
fe1ba71d45 Merge pull request #1199 from ehuss/preferred-dark-theme
Change default preferred-dark-theme to `navy`.
2020-06-08 12:38:06 -07:00
Eric Huss
23f5ffd6d6 Change default preferred-dark-theme to navy. 2020-06-08 11:40:38 -07:00
Eric Huss
484e5c0b8f Merge pull request #1244 from brycefisher/dont-open-search-bar-when-in-text-input
fix: don't jump away from form input to search bar
2020-06-07 13:19:03 -07:00
Eric Huss
a80febd318 Merge pull request #1245 from nihaals/improve-ga-doc
Improved Google Analytics example
2020-06-07 13:14:12 -07:00
Nihaal Sangha
16010ee28b Improved Google Analytics example 2020-06-06 18:05:32 +01:00
Bryce Fisher-Fleig
fb1476d1e3 fix: don't jump away from form input to search bar
Addresses #1020 with the suggestion from prior PR #1101
2020-06-04 14:35:49 -07:00
Eric Huss
b375f4e3d5 Merge pull request #1237 from Michael-F-Bryan/redirects
Implement URL redirecting
2020-05-30 10:30:09 -07:00
Eric Huss
25ec7ace1a Fix typo for redirect TOML. 2020-05-30 10:27:38 -07:00
Michael-F-Bryan
ebc01dbb71 Updated the test to follow our docs 2020-05-30 04:15:24 +08:00
Michael-F-Bryan
7b3e945a27 Updated the test to reflect how redirect keys should be absolute 2020-05-30 04:11:11 +08:00
Michael-F-Bryan
964a10ff29 Redid the wording in the docs because it was around the wrong way 2020-05-30 04:09:43 +08:00
Michael-F-Bryan
5907caa732 I should have used a more realistic example 2020-05-27 03:19:24 +08:00
Michael-F-Bryan
da55cf273f Changed redirect mapping to HashMap<String, String> and improved error handling 2020-05-27 03:12:57 +08:00
Michael-F-Bryan
a6ab4d8402 Explained how you can configure redirects 2020-05-27 02:52:59 +08:00
Michael-F-Bryan
4c2318922f Added integration tests to make sure files are redirected as intended 2020-05-27 02:43:01 +08:00
Michael-F-Bryan
b2d50392ea Emit redirects towards the end of the rendering process 2020-05-27 02:42:56 +08:00
Michael-F-Bryan
a5086a1e58 Added a redirect map to the HTML config 2020-05-27 02:38:04 +08:00
Eric Huss
6c4c3448e3 Update dependencies. (#1211)
* Removed the itertools dependency

* Removed an unused feature flag

* Stubbed out a toml_query replacement

* Update dependencies.

* Bump env_logger.

* Use warp instead of iron for http server.

Iron does not appear to be maintained anymore. warp/hyper seems to be
reasonably maintained. Unfortunately this takes a few seconds more
to compile, but shouldn't be too bad.

One benefit is that there is no longer a need for a separate websocket
port, which makes it easier to run multiple servers at once.

* Update pulldown-cmark to 0.7

* Switch from error-chain to anyhow.

* Bump MSRV to 1.39.

* Update elasticlunr-rs.

Co-authored-by: Michael Bryan <michaelfbryan@gmail.com>
2020-05-20 23:32:00 +02:00
Eric Huss
5d5c55e619 Merge pull request #1171 from mark-i-m/master
implement support for book parts
2020-05-20 12:17:45 -07:00
Eric Huss
e2023fd72d Tweak wording of documentation for part titles. 2020-05-20 12:17:17 -07:00
Eric Huss
e677b72eb8 Merge pull request #1188 from pheki/remove-google-surveillance
Remove google fonts by serving them locally
2020-05-20 11:01:07 -07:00
Aphek
7e090ca42f Also copy font licenses when copy-fonts is enabled 2020-05-19 03:17:01 -03:00
Aphek
122c988477 Rename config from no-copy-fonts to copy-fonts 2020-05-19 03:09:25 -03:00
mark
d0fe9bd41c make part titles another SummaryItem 2020-05-18 11:18:14 -05:00
mark
b1ccb30220 update docs 2020-05-17 17:00:03 -05:00
mark
91e3aa4b55 try to satisfy msrv? 2020-05-17 16:53:06 -05:00
mark
2d63286c63 make part titles bold 2020-05-17 16:53:06 -05:00
mark
5dd2a5bff4 implement support for book parts 2020-05-17 16:53:06 -05:00
Eric Huss
1b3b10d2ae Do not allow the sidebar to be dragged outside the window. (#1229) 2020-05-17 22:05:12 +02:00
Aphek
2c26c65f4d Remove google fonts by serving them locally
Co-authored-by: Aral Balkan <aral@ind.ie>
Co-authored-by: Collyn O'Kane <47607823+okaneco@users.noreply.github.com>
2020-05-15 02:48:28 -03:00
Eric Huss
e8d4bc52e1 Merge pull request #1215 from ehuss/block-cleanup
Remove some unnecessary blocks.
2020-05-10 09:35:46 -07:00
Eric Huss
6038af292f Remove some unnecessary blocks. 2020-05-10 09:23:40 -07:00
Eric Huss
578e4da5b6 Merge pull request #1153 from azerupi/draft-chapters
Bring back draft chapters
2020-05-10 09:18:02 -07:00
Mathieu David
43008ef2ef fix issues from code review 2020-05-10 09:04:02 -07:00
Mathieu David
d605938886 Bring back draft chapters 2020-05-10 09:04:02 -07:00
Eric Huss
7e11d37e49 Merge pull request #1206 from GillesRasigade/html-head-custom
HTML head customisation with `head.hbs`
2020-05-10 08:57:23 -07:00
Eric Huss
50bcf67f2b Consistent punctuation. 2020-05-10 08:42:53 -07:00
Gilles Rasigade
c2d58158da Revert HTML auto-formatting 2020-05-10 08:42:53 -07:00
Gilles Rasigade
1731779a8d Add header.hbs template documentation 2020-05-10 08:42:53 -07:00
Gilles Rasigade
f7e349d37f Add head.hbs template documentation 2020-05-10 08:42:53 -07:00
Gilles Rasigade
61c8413138 Allow to define own HTML <head> attributes
Create a `index.hbs` template file to render additional HTML <head> attributes
2020-05-10 08:42:53 -07:00
Eric Huss
8ee950e3de Merge pull request #1214 from ehuss/fix-clippy
Fix some clippy warnings.
2020-05-10 08:39:19 -07:00
Eric Huss
c44ef1b2f0 Fix some clippy warnings. 2020-05-10 08:29:50 -07:00
Eric Huss
07dfc4b89a Merge pull request #1207 from toyboot4e/fix-mdbook_book
Enable `MDBOOK_BOOK`  to overwrite `book.toml`
2020-05-08 06:51:21 -07:00
toyboot4e
282e55122e Update src/config.rs
Co-authored-by: Eric Huss <eric@huss.org>
2020-05-08 19:56:41 +09:00
Eric Huss
17210b058f Merge pull request #1208 from uint/links-preprocessor-support-pluses
#1098 Links preprocessor: support pluses
2020-05-07 15:36:42 -07:00
Tomasz Kurcz
b1cf3f117d Links preprocessor: support pluses in file paths 2020-05-03 14:50:03 +02:00
Tomasz Kurcz
d665732056 Links preprocessor: test links with special characters 2020-05-03 14:42:22 +02:00
toyboot4e
2f59dbf1ef Fix example of MDBOOK_BOOK (again) 2020-05-03 18:58:41 +09:00
toyboot4e
3a63276727 Not to use matches! 2020-05-03 18:16:44 +09:00
toyboot4e
4c64f23089 Fix example of MDBOOK_BOOK 2020-05-03 17:54:35 +09:00
toyboot4e
683d2b2240 Fix use of MDBOOK_BOOK 2020-05-03 17:54:17 +09:00
Eric Huss
11f95f76e6 Merge pull request #1122 from segfaultsourcery/zero-exit-code-when-plugin-not-found-893
Fixed zero exit code when plugin not found
2020-04-21 15:42:52 -07:00
Eric Huss
2732c5e8f7 Update docs and tweak error messages. 2020-04-21 15:34:59 -07:00
Kim Hermansson
6b550cb4bb Make missing backends optional. 2020-04-21 15:13:14 -07:00
Eric Huss
712362f9e7 Merge pull request #1163 from kngwyu/kpp-edition2018
Make new [rust] section to config and  place edition under it
2020-04-21 13:00:15 -07:00
Eric Huss
28ce8f5ac0 Some edition cleanup and fixes. 2020-04-21 12:26:48 -07:00
kngwyu
255756cfee Make new [rust] config and move edition config under it 2020-04-21 10:24:47 -07:00
Gabriel Majeri
53d821bf6d Add edition to hbs renderer 2020-04-21 10:24:47 -07:00
Gabriel Majeri
d39d4517aa Add support for Rust edition 2020-04-21 10:24:47 -07:00
Eric Huss
bd0f434225 Merge pull request #1164 from zdenek-crha/doc_gitignore_use
Mention that .gitignore is used for watch excludes (#1160)
2020-04-21 10:21:34 -07:00
Eric Huss
3806d7b6ea Merge pull request #1197 from ehuss/release-0.4.0
Start 0.4.0 release.
2020-04-20 09:58:13 -07:00
Eric Huss
d1b484ff35 Start 0.4.0 release. 2020-04-20 09:44:18 -07:00
Eric Huss
04c04dfc88 Merge pull request #1195 from englishm/me/suppress-more-warnings-for-small-listings
Broaden scope of suppressed warnings for listings without a main fn
2020-04-18 08:33:30 -07:00
Mike English
1d265fd143 Broaden scope of suppressed warnings for listings without a main fn
At present, code listings without a main function will be wrapped in one and
annotated with an allow lint check attribute provided by the following [code][]:

```
format!(
    "\n# #![allow(unused_variables)]\n{}#fn main() {{\n{}#}}",
    attrs, code
)
```

A broader lint check attribute such as `#![allow(unused)]` seems like it might
better fit the apparent intent of this code.

Addresses: https://github.com/rust-lang/mdBook/issues/1192

[code]: 769cc0a7c1/src/renderer/html_handlebars/hbs_renderer.rs (L635-L638)
2020-04-18 01:36:11 -04:00
Eric Huss
8e673c96c2 Release 0.3.7 2020-04-03 12:55:41 -07:00
Eric Huss
99ecd4f87c Merge pull request #1170 from ehuss/fix-theme-focus
Fix theme selector focus.
2020-04-03 12:45:33 -07:00
Eric Huss
e839ef0866 Fix sidebar line-height. (#1182) 2020-04-03 21:09:23 +02:00
Eric Huss
769cc0a7c1 Merge pull request #1026 from andrewdavidmackenzie/master
Avoid a possible recursive copy when destination (build_dir) is underneath source directory
2020-04-03 11:18:58 -07:00
Andrew Mackenzie
c2686a817a Address PR review comments 2020-04-03 10:39:15 -07:00
Andrew Mackenzie
bd14d0910a Avoid a possible recursive copy when destination (build_dir) is underneath source directory. Update docs to match. 2020-04-03 10:39:15 -07:00
Eric Huss
6eb597a556 Revert "Assure copy_files_except_ext(..) won't copy directories into itself (#1135)" (#1181)
This reverts commit d7a2b29f06.
2020-04-03 19:12:43 +02:00
Zdenek Crha
5c91041dad Mention that .gitignore is used for watch excludes (#1160)
The serve and watch commands use .gitignore file in book root directory
to read exclude patterns used when watching for changed files. A
.gitignore from parent directory or user home is not used though.

Add documentation of this behaviour to both commands.
2020-03-28 19:33:20 +01:00
Dylan DPC
59568208ff Revert "Support anchors in SUMMARY (#1173)" (#1175)
This reverts commit 2baed040c2.
2020-03-24 23:52:24 +01:00
Eric Huss
21a16c9b75 Scroll sidebar to middle instead of top. (#1161)
* Fix: Scroll sidebar to current active section (#1067)

* Clean: Some code related to PR #1052

* Change `scrollIntoViewIfNeeded` with `scrollIntoView`

* Don't use onload event for sidebar scroll to reduce flickering.

Co-authored-by: 李鸿章 <poodll@163.com>
2020-03-24 21:08:53 +01:00
Eric Huss
4e8e1e1408 Don't highlight code spans in headers. (#1162) 2020-03-24 20:43:20 +01:00
Martin Carton
2baed040c2 Support anchors in SUMMARY (#1173)
* Parse anchors in `SUMMARY.md`

* Support sub-links in SUMMARY with html renderer

* Add some tests for anchors
2020-03-24 19:15:28 +01:00
Mathieu David
101063093b Change CI badge to only push events (#1174)
I noticed that the CI badge was failing and it seems to be caused by the fact that it reflects the latest build including PRs. This change filters the events to only "push events on the master branch" so the badge stays green even if a PR fails.
2020-03-23 13:22:12 +01:00
Eric Huss
f7ffffbd1e Fix theme selector focus. 2020-03-20 11:12:42 -07:00
Eric Huss
760c9b0767 Bump quote and failure.
quote 1.0.2 was yanked, update failure to stay compatible.
2020-03-19 12:48:54 -07:00
Eric Huss
6016e12b90 Release 0.3.6. 2020-03-19 12:43:22 -07:00
Eric Huss
88684d843d Merge pull request #1167 from dalance/fix_summary_comment
Fix SUMMARY's parse_numbered with comment
2020-03-17 21:39:25 -07:00
dalance
b82562fe8a Fix SUMMARY's parse_numbered with comment 2020-03-18 12:28:27 +09:00
Eric Huss
44c3213f5d Merge pull request #1130 from sunng87/feature/handlebars-3.0
Update handlebars to 3.0
2020-03-08 18:54:39 -07:00
Dylan DPC
fd56a53e76 ui: improve menu folding (#989)
* ui: improve menu folding

Fold/unfold the menu bar just by the amount of scroll, not by its
full width

* refactor: use a variable for the menu bar height

* Fix menu scroll jittering, remove hover folding smoothness

Rewrite it to use `position:` `sticky` and `relative` instead
of continuous programmatic position changes

On-hover folding-unfolding transition removal is a side-effect
2020-03-06 01:11:37 +01:00
Dylan DPC
ca4b85b815 Increases line height for p, ul & ol (#1136) 2020-03-06 01:06:19 +01:00
Sebastian Thiel
d7a2b29f06 Assure copy_files_except_ext(..) won't copy directories into itself (#1135)
This prevents recursive copy-loops when the destination directory
is contained in the source directory.

Now it bails out with a descriptive error message.
2020-02-29 19:20:55 +01:00
Eric Huss
4039c72fd3 Merge pull request #1150 from EvanCarroll/master
Support rel=next and rel=previous
2020-02-18 07:55:40 -08:00
Evan Carroll
2bd8bdf798 Support rel=next and rel=previous
This resolves GH #1149
2020-02-18 09:20:17 -06:00
Sergey Pedan
0da7ba4abe Corrects inner/outer space in sidebar <li>s (#1137) 2020-02-17 22:04:59 +01:00
Eric Huss
d6cfa21fff Merge pull request #1145 from Rosto75/master
Add a case for an empty `book_title` parameter
2020-02-15 16:11:08 -08:00
Tomasz Różański
95fba3f357 Add a case for an empty book_title parameter
Html page title for every chapter in the book is created by the following code:
```rust
let title: String;
{
	let book_title = ctx
	.data
	.get("book_title")
	.and_then(serde_json::Value::as_str)
	.unwrap_or("");

	title = ch.name.clone() + " - " + book_title;
}
```

If the `book.toml` file is missing, and `book_title` parameter is empty, there's an awkward ` - ` string hanging at the end of each chapter's title.

This PR adds a `match` case to handle that kind of situation.
2020-02-15 23:13:37 +01:00
Eric Huss
d5999849d9 Merge pull request #1140 from willkg/patch-1
Fix typo in README.md
2020-02-06 08:47:57 -08:00
Will Kahn-Greene
8b2659e0f4 Fix typo in README.md 2020-02-06 11:39:37 -05:00
Eric Huss
c4a64ab599 Merge pull request #1133 from EverlastingBugstopper/avery/fix-derive-highlighting
fix: ayu theme meta highlighting
2020-01-31 18:59:22 -08:00
Eric Huss
6b4e3584b4 Merge pull request #1131 from ehuss/fix-rustup-ci
Fix CI: Don't update rustup.
2020-01-31 08:46:01 -08:00
Avery Harnish
b8fc7a1b2d fix: ayu theme meta highlighting 2020-01-30 09:57:45 -06:00
Eric Huss
2ee083dfbe Fix CI: Don't update rustup. 2020-01-27 13:46:42 -08:00
Ning Sun
1947f8ca65 Update handlebars to 3.0 2020-01-24 11:01:44 +08:00
Eric Huss
2f59943c04 rustfmt with 1.40
Some slight changes in formatting in 1.40.
2019-12-31 16:23:25 -08:00
Eric Huss
980f943179 Merge pull request #1123 from j127/patch-1
Fix typo in init.md
2019-12-31 15:49:29 -08:00
Josh
5e998788e9 Fix typo in init.md
"where" was spelled "were"
2019-12-31 15:36:12 -08:00
Arashmidos
6a94492238 fix scroll issue (#1108)
* fix sidebar scrolling

* fix query selector
2019-12-02 11:07:24 +01:00
Aleksey Kladov
e3717ad47b Add --features to CI recipe (#1103) 2019-11-30 01:10:11 +01:00
nickelc
49b7f08164 Fix doc comment of BuildConfig::create_missing (#1104) 2019-11-29 06:22:21 +01:00
Eric Huss
7def6d70e8 Merge pull request #1099 from Michael-F-Bryan/expose-execute-build-process
Exposed the MDBook::execute_build_process() method to 3rd parties
2019-11-23 10:44:58 -08:00
Eric Huss
554f29703f Merge pull request #1097 from dylanowen/cache
Prevent scrolling to the top of the page on websocket reload
2019-11-19 11:57:43 -08:00
Michael Bryan
730d7f8410 Exposed the execute_build_process() method to 3rd parties 2019-11-17 20:59:55 +08:00
Dylan Owen
b6603468d6 Stop scrolling on socket reload 2019-11-12 18:06:11 -08:00
Eric Huss
441a10bdd7 Release 0.3.5. 2019-11-11 13:36:13 -08:00
Eric Huss
efdb83266a Merge pull request #1021 from marcusklaas/pulldown0.6
Upgrade pulldown_cmark to 0.6
2019-11-11 13:27:19 -08:00
Eric Huss
ac9c12334a Update pulldown-cmark in Cargo.toml to 0.6.1. 2019-11-11 13:26:16 -08:00
Marcus Klaas de Vries
2a3088422a Upgrade pulldown_cmark to 0.6.1 2019-11-11 20:25:38 +01:00
Dylan DPC
1f505c2b2e Revert "Add support for Rust edition 2018 in playpens (#1086)" (#1093)
This reverts commit a7b3aa0444.
2019-11-11 13:24:13 +01:00
Gabriel Majeri
a7b3aa0444 Add support for Rust edition 2018 in playpens (#1086)
* Add support for Rust edition 2018 in playpens

* Add Rust edition support to rustdoc

* Run rustfmt

* Fix enum variant reference
2019-11-11 12:42:24 +01:00
Eric Huss
a9160acd64 Merge pull request #1088 from benediktwerner/master
Fix hiding of empty boring lines
2019-11-07 07:44:47 -08:00
Benedikt Werner
4c1bca1684 Don't hide macro lines 2019-11-07 02:42:02 +01:00
Benedikt Werner
8fffb2a704 Hide lines in ignored code blocks 2019-11-07 02:20:10 +01:00
Eric Huss
ba37cc8462 Merge pull request #1089 from Kampfkarren/patch-1
Change erroneous syntax highlighting definition
2019-11-06 12:45:01 -08:00
Ricky
3ea0f9b745 Lowercase matching the theme name (#1079)
* Using .to_lowercase() on the theme matching to avoid needed exact capitolization in the book.toml

* Changed the default-dark-theme from .to_string() to .to_lowercase() to match theme
2019-11-05 13:55:08 +01:00
boyned//Kampfkarren
29d8747e01 Change erroneous syntax highlighting definition
Doing what it says results in:

```
2019-11-04 16:13:15 [WARN] (mdbook::renderer::html_handlebars::hbs_renderer): Previous versions of mdBook erroneously accepted `./src/theme` as an automatic theme directory
2019-11-04 16:13:15 [WARN] (mdbook::renderer::html_handlebars::hbs_renderer): Please move your theme files to `./theme` for them to continue being used
```
2019-11-04 16:14:38 -08:00
Benedikt Werner
f5549f2267 Hide empty lines starting with '#' in playpens 2019-11-04 14:03:25 +01:00
Benedikt Werner
e2a8600712 Remove outdated unused var in theme js code 2019-11-04 14:03:24 +01:00
Eric Huss
f2cb601c11 Release 0.3.4. 2019-10-29 09:58:11 -07:00
Steve Klabnik
6e0d0facff Merge pull request #1083 from rust-lang/move-from-nursery
rust-lang-nursery -> rust-lang
2019-10-29 08:16:54 -05:00
Steve Klabnik
f79d5d4582 rust-lang-nursery -> rust-lang
Fixes #1080
2019-10-29 08:04:16 -05:00
Rostislav
820714a560 Use only relative font sizes (#894)
This replaces the only use of px for font-sizes by setting up a base
rem size on the root element in a way that is easy to calculate (1 rem =
10px) and scaling up according to browser settings.
2019-10-27 15:51:32 +01:00
Eric Huss
d5535d1226 Release 0.3.3. 2019-10-26 12:16:23 -07:00
Eric Huss
e5f77aaaf2 Merge pull request #1077 from mattheww/2019-10_scroll-margin
Add CSS `scroll-margin-top` to headings which contain link targets.
2019-10-26 11:34:57 -07:00
Matthew Woodcraft
86a368b726 Introduce a --menu-bar-height CSS variable 2019-10-26 13:21:26 +01:00
Matthew Woodcraft
1dc482b00d Add scroll-margin-top to headings which contain link targets.
This means when the link is followed, the page scrolls in such a way as to
leave space for the fixed menu bar.

Fixes #1040
2019-10-26 12:55:12 +01:00
Eric Huss
21d8f394ae Fix "next chapter" spacer handling. (#1075) 2019-10-25 17:33:21 +02:00
Benedikt Werner
c9dae170f3 Better automatic dark mode (#1069)
* Don't save default theme to localStorage

* Auto enable dark mode on no-js

* Fix light theme with no-js
2019-10-23 12:15:59 +02:00
Eric Huss
fcf2d7a03b Merge pull request #1073 from ehuss/fix-gh-pages
Fix gh-pages deploy.
2019-10-21 13:43:35 -07:00
Eric Huss
2498887dfc Fix gh-pages deploy. 2019-10-21 13:41:06 -07:00
Eric Huss
f04d7b802d Merge pull request #1072 from ehuss/release-0.3.2
Release 0.3.2.
2019-10-21 12:37:49 -07:00
Eric Huss
bfcddf2680 Release 0.3.2. 2019-10-21 11:41:39 -07:00
Eric Huss
2b649fe94f Merge pull request #1071 from ehuss/github-actions
Switch to GitHub Actions.
2019-10-21 10:49:18 -07:00
Eric Huss
fc4236eaa7 Switch to GitHub Actions. 2019-10-21 10:43:27 -07:00
rnitta
a592da33bb fix the behavior of sticky header (#1070) 2019-10-19 10:07:41 +02:00
Weihang Lo
6af6219e5b [Feature] expandable sidebar sections (ToC collapse) (#1027)
* render(toc): render expandable toc toggle

* ui(toc): js/css logic to toggle toc

* test: update rendered output css selector

* config: add `html.fold.[enable|level]`

* renderer: fold according to configs

* doc: add `output.html.fold`

* refactor: tidy fold config

- Derive default for `Fold`.
- Use `is_empty` instead of checking the length of chapters.
2019-10-19 09:56:08 +02:00
Andrew Pritchard
e5f74b6c86 Option to display copy buttons. (#1050)
* Option to display copy buttons.

- Added field to playpen data structure
- Communicate through window.playpen_copyable
- Javascript updated to check before displaying copy buttons.

* html -> html_config

Also:
- update description of copyable in source code.
- update description of line_numbers (my last PR to this repository)
2019-10-17 12:44:54 +02:00
Benedikt Werner
84a2ab0dba Reapply: Move hiding of boring lines into static content (#846) (#1065)
* Move hiding of boring lines into static content (#846)

* Fix test for hidden code
2019-10-16 11:27:14 +02:00
David Omar Flores Chávez
d63ef8330d Add !important to code {font-family} property (#1062)
If accepted, this will fix #1061
2019-10-11 14:21:13 +02:00
rnitta
01e50303a2 add a command to playpen (#1066) 2019-10-11 14:16:06 +02:00
Dylan DPC
2b3304cb8b Revert "Move hiding of boring lines into static content (#846)" (#1064)
This reverts commit 4448f3fc4b.
2019-10-10 14:31:55 +02:00
Adrian Heine né Lang
4448f3fc4b Move hiding of boring lines into static content (#846) 2019-10-10 13:55:29 +02:00
Chris Ladd
859659f197 Fix inline code display css (#1058) 2019-10-07 09:24:35 +02:00
Eric Huss
4a93eddae2 Fix "next" navigation on index.html (take 2). (#1005) 2019-10-06 17:55:36 +02:00
Eric Huss
0173451b67 Fix error message for missing output.html. (#1056) 2019-10-06 00:33:50 +02:00
Carol (Nichols || Goulding)
ac1749ff2f Implement a rustdoc_include preprocessor (#1003)
* Allow underscores in the link type name

* Add some tests for include anchors

* Include parts of Rust files and hide the rest

Fixes #618.

* Increase min supported Rust version to 1.35

* Add a test for a behavior of rustdoc_include I want to depend on

At first I thought this was a bug, but then I looked at some use cases
we have in TRPL and decided this was a feature that I'd like to use.
2019-10-06 00:27:03 +02:00
Eric Huss
8cdeb121c5 Merge pull request #1055 from amanjeev/amanjeev/clean-command
Fix (command:clean): removes error message 'dir not found' if 'clean' is run multiple times
2019-10-05 14:03:31 -07:00
Amanjeev Sethi
74313bb701 Fix (command:clean): removes error message 'dir not found' if 'clean' is run multiple times (uses existing path variable) 2019-10-05 15:59:34 -04:00
Amanjeev Sethi
3c25dba9b4 Revert "Fix (command:clean): removes error message 'dir not found' if 'clean' is run multiple times"
This reverts commit 2387942588.
2019-10-05 15:57:10 -04:00
Amanjeev Sethi
2387942588 Fix (command:clean): removes error message 'dir not found' if 'clean' is run multiple times 2019-10-05 15:01:01 -04:00
Eric Huss
93c9ae5700 Merge pull request #1037 from Flying-Toast/prefers-color-scheme
Automatically use a dark theme according to 'prefers-color-scheme'
2019-10-05 11:33:52 -07:00
Eric Huss
9efa9fd1c4 Merge pull request #1052 from morphologue/fix-sidebar-autoscroll
Fix #1029 sidebar not auto-scrolling
2019-10-05 10:19:29 -07:00
Eric Huss
8a33407cc5 Merge pull request #1051 from segfaultsourcery/fix-small-gitignore-bug
I fixed a small gitignore bug
2019-10-05 10:11:16 -07:00
morphologue
699844a5c3 Fix #1029 sidebar not auto-scrolling 2019-10-05 16:54:09 +10:00
Flying-Toast
9bdec5e7cc preferred-dark-theme defaults to default-theme 2019-10-04 19:32:03 -04:00
Kim Hermansson
930f730361 The .gitignore file is now searched for recursively.
Removed a warning if .gitignore is missing.
2019-10-04 22:56:56 +02:00
Eric Huss
09c738468f Merge pull request #1047 from rnitta/patch-1
Fix Search::use_boolean_and documents
2019-10-04 12:39:05 -07:00
Kim Hermansson
a3d1afdd1f This fixes a small bug where the gitignore location can be misinterpreted to be in the folder "above" where it actually is. 2019-10-04 19:44:36 +02:00
Kim Hå
8e8e53ae15 Added support for gitignore files. (#1044)
* Added support for gitignore files.
The watch command will now ignore files based on gitignore. This can be useful for when your editor creates cache or swap files.

* Ran cargo fmt.

* Made the code a bit tidier based on input from other Rust programmers.
Changed the type of the closure back to use PathBuf, not &PathBuf.
Reduced nesting.
2019-10-04 14:59:17 +02:00
rnitta
5fe801a7d1 fix Search::use_boolean_and documents 2019-10-03 11:35:42 +09:00
Eric Huss
a6f317e352 Update highlight.js (#1041) 2019-09-30 00:07:54 +02:00
Eric Huss
ed95252f05 Merge pull request #1039 from ehuss/fix-html-config
Fix merge conflict.
2019-09-26 11:49:57 -07:00
Eric Huss
a058da8b74 Fix merge conflict. 2019-09-26 11:03:51 -07:00
Eric Huss
73be1292ab Merge pull request #1035 from andymac-2/line-numbers
Added line numbers to editable sections of code.
2019-09-26 10:53:32 -07:00
Eric Huss
98ecd1178b Merge pull request #1033 from TjeuKayim/log-deserialization-error-html-config
Log deserialization errors for [html.config]
2019-09-26 10:28:18 -07:00
Eric Huss
996ac382c1 Merge pull request #1038 from ehuss/rustfmt-1.38
Rustfmt for 1.38.
2019-09-26 10:13:00 -07:00
Eric Huss
b88839cc25 Rustfmt for 1.38.
A minor change in the recent stable release.
2019-09-26 09:54:12 -07:00
Flying-Toast
1ef94c2a7e add preferred-dark-theme to book.toml example 2019-09-26 12:13:25 -04:00
Flying-Toast
f0ac13e3e2 Document preferred-dark-theme config option 2019-09-26 12:09:30 -04:00
Flying-Toast
b0ae14a2c7 Automatically use a dark theme according to 'prefers-color-scheme' 2019-09-25 19:11:28 -04:00
Andrew Pritchard
81ab2eb7db Added line numbers to editable sections of code.
- Added line numbers to config struct
- Added playpen_line_numbers field to hbs renderer.
- Added section to set `window.playpen_line_numbers = true` in page template
- Use line number global variable to show line numbers when required.
2019-09-24 21:27:02 +08:00
Tjeu Kayim
213171591a De-duplicate calling Config::html_config() 2019-09-22 21:48:49 +02:00
Tjeu Kayim
db13d8e561 Log HtmlConfig deserialization errors 2019-09-22 21:48:03 +02:00
Eric Huss
b4bb44292d Merge pull request #1030 from lzutao/rustup
Rustup
2019-09-21 09:03:34 -07:00
Lzu Tao
bb7a863d3e Bump compatible deps 2019-09-21 09:23:30 +00:00
Lzu Tao
e62a9dba87 Bump ws 2019-09-21 09:23:30 +00:00
Lzu Tao
4a94b656cd Bump ammonia 2019-09-21 09:23:30 +00:00
Carol (Nichols || Goulding)
a873d46871 Implement a markdown renderer (#1018)
Use case: when trying to `mdbook test` a file that has many `include`
directives, and a test fails, the line numbers in the `rustdoc` output
don't match the line numbers in the original markdown file.

Turning on the markdown renderer implemented here lets you see what is
being passed to `rustdoc` by saving the markdown after the preprocessors
have run.

This renderer could be helpful for debugging many preprocessors, but
it's probably not useful in the general case, so it's turned off by
default.
2019-08-30 12:20:53 +02:00
Carol (Nichols || Goulding)
ce0c5f1d07 Another refactoring in links.rs (#1001)
* Extract the concept of a link having a range or anchor specified

So that other kinds of links can use this concept too.

* Extract a function for parsing range or anchor
2019-08-13 11:19:42 +02:00
Eric Huss
33d7e86fb6 Merge pull request #999 from integer32llc/small-test-improvement
When this test fails, print out why to assist in debugging
2019-08-12 09:02:04 -07:00
Carol (Nichols || Goulding)
f9f9785839 When this test fails, print out why to assist in debugging 2019-08-12 09:50:54 -04:00
Eric Huss
0c37b912ba Merge pull request #824 from sdruskat/patch-1
Fix #823: Apply default padding to table headers
2019-08-09 09:49:56 -07:00
Stephan Druskat
e880fb6339 Fix #823: Apply default padding to table headers
This PR fixes #823 by applying the default padding for table cells (`padding: 3px 20px;`) to header cells.
2019-08-09 09:48:56 -07:00
Eric Huss
a8d6337ac6 Merge pull request #994 from WofWca/nav-chapters-style
ui: Improve next/prev chapter links' style
2019-08-07 11:27:20 -07:00
Eric Huss
f37a89cd4c Merge pull request #998 from integer32llc/links-improvements
Refactoring of some functionality in links.rs
2019-08-07 10:33:11 -07:00
Eric Huss
aaeb3e2852 Merge pull request #985 from Michael-F-Bryan/enable-caching
Allow backends to cache previous results
2019-08-07 10:26:06 -07:00
Carol (Nichols || Goulding)
8c4b292d58 Rework a match to possibly be more understandable 2019-08-06 22:14:17 -04:00
Carol (Nichols || Goulding)
40159362c0 Unnest another conditional 2019-08-06 22:14:16 -04:00
Carol (Nichols || Goulding)
aa67245743 Unnest a conditional 2019-08-06 22:14:16 -04:00
Carol (Nichols || Goulding)
d968443074 Don't bother splitting the path after the 3rd colon 2019-08-06 22:14:16 -04:00
Carol (Nichols || Goulding)
3716123e10 Increase test coverage of parse_include_path 2019-08-06 22:14:16 -04:00
Carol (Nichols || Goulding)
50a2ec3cf1 Ensure the iterator will always return None after the first None
I'm not sure in what cases this iterator might possibly return Some
again, but let's make absolutely sure.
2019-08-06 22:14:16 -04:00
Carol (Nichols || Goulding)
07459aef60 Factor out the use of different ranges from linktype
This eliminates some duplication and will enable different kinds of
LinkTypes to have line number ranges.

Implement `From` for the std `Range` types to enable easier
construction.

The new code reaaalllly makes me wish for a delegation mechanism though
:(
2019-08-06 22:14:16 -04:00
Eric Huss
0f56c09d3a Merge pull request #996 from lzutao/temporary-disable-sccache
build(CI): Temporary disable sccache
2019-08-05 18:00:35 -07:00
Lzu Tao
63ad3d9340 build(CI): Temporary disable sccache 2019-08-03 23:19:04 +07:00
WofWca
1c5dc1e310 ui: Improve next/prev chapter links' style 2019-08-03 22:56:25 +08:00
Steve Klabnik
77af889a2e Merge pull request #991 from lbeckman314/patch-1
Add `--path .` to cargo install commands.
2019-08-01 18:37:33 -05:00
Liam Beckman
e48fed74bf Add --path . to cargo install commands.
Changing `cargo install` to `cargo install --path .` prevents the following error:

<span style="color: red;">error:</span> Using `cargo install` to install the binaries for the package in current working directory is no longer supported, use `cargo install --path .` instead. Use `cargo build` if you want to simply build the package.
2019-07-31 14:49:25 -07:00
Sorin Davidoi
e512850c13 fix(css/chrome): Use standard property for scrollbar (#816)
This was recently standardized and is currently implemented in Firefox Nightly.
2019-07-28 21:01:33 +02:00
Michael Bryan
bb412edf53 Made sure the tests pass 2019-07-21 04:32:28 +08:00
Michael Bryan
5b0a23ebab Updated the documentation 2019-07-21 02:40:58 +08:00
Michael Bryan
e56c41a1c2 The HTML renderer now cleans its own build directory 2019-07-21 02:37:09 +08:00
Michael Bryan
d1b5a8f982 The MDBook::build() method no longer cleans the renderer's build directory 2019-07-21 02:35:18 +08:00
Eric Huss
f396623b63 Merge pull request #983 from meichstedt/me/fix-config-docs
Remove redundant occurrence of 'in the'
2019-07-17 09:56:01 -07:00
Matthias Eichstedt
9ec43b6c6d Remove redundant occurrence of 'in the' 2019-07-17 17:20:18 +02:00
Eric Huss
7c4d2070f7 Merge pull request #981 from ehuss/release-0.3.1
Release 0.3.1
2019-07-15 14:58:07 -07:00
Eric Huss
50d5917530 Release 0.3.1 2019-07-15 14:56:59 -07:00
Eric Huss
9cd47eb80f Merge pull request #979 from ehuss/fix-links
Fix some broken links.
2019-07-15 14:55:21 -07:00
Eric Huss
4932df2570 Merge pull request #980 from ehuss/appveyor-one-job
Only run 1 job on appveyor when a PR is opened.
2019-07-15 14:54:55 -07:00
Eric Huss
11d31c989c Only run 1 job on appveyor when a PR is opened. 2019-07-15 13:16:55 -07:00
Eric Huss
e5ace6d6a4 Fix some broken links. 2019-07-15 12:51:46 -07:00
Eric Huss
e7c3d02c61 Merge pull request #851 from CBenoit/master
Add include by anchor in preprocessor (partial include)
2019-07-15 12:31:59 -07:00
Benoît CORTIER
d8a68ba3f6 Document anchor-based partial include feature in the book 2019-07-14 21:55:51 -04:00
Benoît CORTIER
d29a79349c Add include by anchor in preprocessor. 2019-07-14 21:55:51 -04:00
Eric Huss
d6088c8a57 Merge pull request #978 from ehuss/bump-elasticlunr
Bump elasticlunr.
2019-07-12 21:59:39 -07:00
Eric Huss
b91e5c8807 Merge pull request #977 from sunng87/feature/handlebars-2.0
(feat) update handlebars to 2.0
2019-07-12 09:56:51 -07:00
Eric Huss
6199e4df79 Bump elasticlunr. 2019-07-12 09:53:11 -07:00
Ning Sun
2d11eb05fe (feat) update handlebars to 2.0 2019-07-13 00:11:05 +08:00
Carol (Nichols || Goulding)
3d45e40693 Small cleanups of variable/field names (#970)
* Rename a variable from playpen to link

Links can now be more than only playpen links

* Rename a field to match the enum type it holds

Also so that link.link.stuff doesn't happen when a variable link holds a
Link instance
2019-07-04 11:31:04 +02:00
Eric Huss
228e99ba11 Fix even more print page links. (#963) 2019-07-01 17:52:25 +02:00
Eric Huss
4b569edadd Fix memory leak and warning (#967) 2019-07-01 17:49:57 +02:00
Eric Huss
3e652b5bfc Merge pull request #965 from lzutao/missed-sccache
Fix broken cache by updating sccache version
2019-06-29 13:42:43 -07:00
Lzu Tao
ba41d73dc3 build: Fix stale builds 2019-06-28 23:14:26 +07:00
Lzu Tao
1ce1401263 travis: Include cargo registry when cache
This reduces the crates downloading time upto 30 seconds
on fast network.
2019-06-28 11:13:54 +07:00
Lzu Tao
00b3d9cf86 build: Fix broken cache by updating sccache 2019-06-28 11:13:54 +07:00
Eric Huss
bb3398bdbb Merge pull request #941 from rnitta/configurable-language
Change language attribute of the book to configurable
2019-06-24 08:56:22 -07:00
Eric Huss
19c26217c0 Don't keep gh-pages history. (#964) 2019-06-21 07:17:03 +02:00
Eric Huss
a2029f0a78 Merge pull request #959 from jeremystucki/refactoring
Minor Refactoring
2019-06-20 20:05:02 -07:00
Eric Huss
7c33ac800c Merge pull request #962 from integer32llc/rangebounds
Use stdlib RangeBounds
2019-06-20 20:01:52 -07:00
Eric Huss
d371001ab8 Merge pull request #961 from integer32llc/fs-read-to-string
Switch to the standard library's fs::read_to_string
2019-06-20 20:00:35 -07:00
Eric Huss
d73504eb23 Merge pull request #960 from integer32llc/update-min-rust-version
Update specified minimum Rust version and test it in travis
2019-06-20 19:59:15 -07:00
Carol (Nichols || Goulding)
abddd7c6f7 Use stdlib RangeBounds 2019-06-20 21:56:31 -04:00
Carol (Nichols || Goulding)
31e36f85e7 Update specified minimum Rust version and test it in travis 2019-06-20 15:04:55 -04:00
Jeremy Stucki
92a7b0cdcd Use iterator instead of for loop 2019-06-20 15:12:56 +02:00
Jeremy Stucki
592140db5b Remove redundant closure 2019-06-20 14:56:47 +02:00
Jeremy Stucki
3a0eeb4bbb Remove needless scope 2019-06-20 14:29:14 +02:00
Jeremy Stucki
a9dae326fa Use unwrap_or instead of match on Result 2019-06-20 14:27:57 +02:00
Jeremy Stucki
abba959add Remove needless lifetime 2019-06-20 14:18:31 +02:00
Jeremy Stucki
ea15e55829 Use map instead of match on Option 2019-06-20 14:18:17 +02:00
j143-bot
d07bd9fed4 Update the master-docs link to ../mdBook (#958) 2019-06-20 08:54:38 +02:00
Carol (Nichols || Goulding)
b83c55f7ef Switch to the standard library's fs::read_to_string 2019-06-19 22:49:18 -04:00
Eric Huss
69a08ef390 Merge pull request #956 from ehuss/rel-0.3.0
Release 0.3.0
2019-06-18 16:02:43 -07:00
Eric Huss
1cd1151790 Release 0.3.0 2019-06-18 15:24:26 -07:00
Eric Zubriski
84d4063e4a Fix-681 (#954)
* Adding section for code snippets.

* Reformatting sections.

* Update mdbook.md
2019-06-13 07:38:12 +02:00
Eric Huss
07830f7f11 Merge pull request #891 from integer32llc/include-before-test
Write preprocessed content to file before testing with rustdoc
2019-06-12 15:01:58 -07:00
Eric Huss
828b7d05c5 Merge pull request #953 from ehuss/update-changelog
Update changelog.
2019-06-12 09:31:45 -07:00
Eric Huss
379004efcb Update changelog. 2019-06-12 09:30:58 -07:00
Eric Huss
2497e77bf1 Support strikethrough and tasklists. (#952) 2019-06-12 17:02:03 +02:00
Eric Huss
0c2292b9aa Update css to support diff syntax highlighting. (#943)
This adds the rules to highlight diff lines with highlight.js.
2019-06-12 16:59:55 +02:00
lzutao
4386a10e87 Explicitly mark fonts and images files as binary (#951) 2019-06-11 21:44:15 +02:00
Eric Huss
3cfed10098 Update to pulldown-cmark 0.5. (#898)
* Update to pulldown-cmark 0.4.1.

* Update to pulldown-cmark 0.5.2.

* Remove pulldown-cmark-to-cmark dependency.

Since it is not compatible with the new pulldown-cmark. This example isn't
directly usable, anyways, and I think the no-op example sufficiently shows how
to make a preprocessor.

* cargo fmt

* Fix example link.
2019-06-11 18:26:24 +02:00
rnitta
a655d5d241 Header elements wrap links (#948)
* swap hierarchy of header for that of link

* fix comment
2019-06-03 14:31:15 +02:00
Eric Huss
f8c3a2deea Update highlight.js (#942)
Updates to v9.15.8.

My main motivation is to fix a minor issue with TOML highlighting.

This keeps the same language list as before with the addition of a new "common"
language `properties` and added `julia` because someone asked for it and I like
julia. The full list from building:

	:common armasm d go handlebars haskell julia rust scala swift x86asm yaml

- apache
- armasm
- bash
- coffeescript
- cpp
- cs
- css
- d
- diff
- go
- xml
- handlebars
- haskell
- http
- ini
- java
- javascript
- json
- julia
- makefile
- markdown
- nginx
- objectivec
- perl
- php
- properties
- python
- ruby
- rust
- scala
- shell
- sql
- swift
- x86asm
- yaml
2019-06-03 14:22:32 +02:00
Eric Huss
b226d2fc55 cargo fmt 2019-05-31 09:19:46 -07:00
lzutao
53ba0d6655 Remove 'static lifetime from static vars (#947) 2019-05-31 18:01:02 +02:00
Eric Huss
43ead86ecc Update toml. (#945)
Just keeping up-to-date.
2019-05-31 18:00:15 +02:00
Eric Huss
1d3ec7e0c7 Support rust edition in playground. (#946)
The endpoint was recently updated to support the edition param.
2019-05-31 17:59:44 +02:00
Eric Huss
f4017376a9 Merge pull request #944 from lzutao/formatting
Minor formatting fixes
2019-05-30 11:12:57 -07:00
Lzu Tao
672cf456eb Remove unnecessary ::<crate>
Find and replace with `git grep -E '\W::[a-z]'` command.
2019-05-30 23:12:33 +07:00
Lzu Tao
8dce00d54d Cargo.toml: Sort dependencies fields 2019-05-30 22:23:42 +07:00
rnitta
4f7c299de7 update language attribute to configurable 2019-05-30 11:53:49 +09:00
Eric Huss
04e74bfa1b Merge pull request #931 from ehuss/update-deps
cargo update
2019-05-26 11:23:00 -07:00
Eric Huss
4026a586a1 cargo update 2019-05-26 09:23:23 -07:00
lzutao
71281bff10 Update some updatable dependencies (#934)
* Update ammonia dependency

* Update env_logger

* Update itertools

* Update ws dep

* Update pretty_assertions dep
2019-05-26 14:05:42 +02:00
lzutao
8542f7f29d Transition to 2018 edition (#933)
* Transition to 2018 edition

* Update Travis CI badge in README

* Remove non-idiomatic `extern crate` lines
2019-05-25 20:50:41 +02:00
Eric Huss
fe492d1cb9 Merge pull request #937 from ehuss/fix-changelog-typo
Fix changelog typo
2019-05-25 09:02:09 -07:00
Eric Huss
481c2f2194 Fix changelog typo 2019-05-25 09:00:33 -07:00
lzutao
882014860c Update ace editor to v1.4.4 (#935) 2019-05-25 14:39:16 +02:00
Bas Bossink
e3ec751a3f Issue 703 (#929)
* Replace all occurances of altenate backend with alternative backed

Rename test for consistency of the terminology.

* Use better sed command
2019-05-19 22:16:10 +02:00
Eric Huss
fc565df86b Some documentation fixes. (#925) 2019-05-19 00:05:57 +02:00
Eric Huss
ec8e63145c Add a changelog. (#926) 2019-05-18 23:56:01 +02:00
Bas Bossink
2752c88c46 Fix issue #703. (#928) 2019-05-18 23:38:08 +02:00
Eric Huss
e7befd19bc Merge pull request #924 from ehuss/fix-appveyor-token
Fix appveyor github token.
2019-05-16 13:20:42 -07:00
Eric Huss
644b8e132c Fix appveyor github token. 2019-05-16 13:19:39 -07:00
Eric Huss
8e82ae534a Merge pull request #923 from ehuss/new-deploy
Fix automatic deploy.
2019-05-16 09:34:39 -07:00
Eric Huss
6a8a5b7642 Fix automatic deploy.
This should fix deploying release artifacts and gh-pages for the documentation.
Secrets should now be configured in the UI for travis and appveyor.

This also removes some of the appveyor jobs, since they aren't really
necessary, and there are limited jobs on rust-lang-libs.
2019-05-15 15:50:01 -07:00
Roman Proskuryakov
c3284a2ae9 Update mdbook 0.1 -> 0.2 in docs for CI (#918) 2019-05-11 20:30:21 +02:00
Allen
df12cc55c8 Revert "Merge pull request #889 from s3bk/master" (#917)
* Revert "Merge pull request #889 from s3bk/master"

This reverts commit b30b58b565, reversing
changes made to c6220fba83.

* format tests :P
2019-05-09 20:18:28 +02:00
Eric Huss
cb4a3e0711 Fix more print.html links. (#871) 2019-05-08 23:50:59 +02:00
lzutao
9194a40acd CI: Check Rust formatting for each commit (#909) 2019-05-08 21:20:38 +02:00
Bas Bossink
506996808b Fix issue 832 (#841)
* Add if around stub summary creation

Check if an existing SUMMARY.md is present to prevent overwriting it
with the stub SUMMARY.md.

[#832]

* Add test for existing SUMMARY.md
2019-05-08 21:13:20 +02:00
Philipp Hansch
5163c5ab75 Don't let robots index the print.html (#844) 2019-05-08 00:32:43 +02:00
Stefanie Jäger
ecfaed1e02 Change overflow-x to initial (#818)
Change overflow-x from auto to initial.
This resolves weird rendering behavior in Chrome and Safari on macOS.

With help from @bash

Co-authored-by: Ruben Schmidmeister <ruben.schmidmeister@icloud.com>
2019-05-08 00:30:28 +02:00
Eric Huss
8bb5426441 Fix keyboard chapter navigation for file urls. (#915) 2019-05-08 00:29:46 +02:00
Eric Huss
a674c9eff1 Fix "next" navigation on index.html. (#916) 2019-05-08 00:27:48 +02:00
lzutao
7ab939f8f2 CI: Enable sccache build (#913) 2019-05-06 23:41:44 +02:00
lzutao
581187098c Deny 2018 edition idioms globally (#911) 2019-05-06 22:50:34 +02:00
lzutao
ab7802a9a9 Fix most of clippy warnings (#914)
* Fix clippy: cast_lossless

* Fix clippy: match_ref_pats

* Fix clippy: extra_unused_lifetimes

* Fix clippy: needless_lifetimes

* Fix clippy: new_without_default

* Fix clippy: or_fun_call

* Fix clippy: should_implement_trait

* Fix clippy: redundant_closure

* Fix clippy: const_static_lifetime

* Fix clippy: redundant_pattern_matching

* Fix clippy: unused_io_amount

* Fix clippy: string_lit_as_bytes

* Fix clippy: needless_update

* Fix clippy: blacklisted_name

* Fix clippy: collapsible_if

* Fix clippy: match_wild_err_arm

* Fix clippy: single_match

* Fix clippy: useless_vec

* Fix clippy: single_char_pattern

* Fix clippy: float_cmp

* Fix clippy: approx_constant
2019-05-06 20:20:58 +02:00
Dylan DPC
345acb8597 Merge pull request #908 from lzutao/fmt
Formatting whole project
2019-05-06 00:26:50 +02:00
Dylan DPC
6380526e93 Merge pull request #907 from lzutao/lock
Update Cargo.lock
2019-05-05 22:57:41 +02:00
Lzu Tao
4560bdeb47 Update Cargo.lock 2019-05-06 00:12:53 +07:00
Lzu Tao
0aa3a9045a cargo fmt 2019-05-05 22:00:24 +07:00
Dylan DPC
b30b58b565 Merge pull request #889 from s3bk/master
new "Book" theme
2019-05-04 17:45:37 +02:00
Dylan DPC
c6220fba83 Merge pull request #906 from rust-lang-nursery/fix/typo
fixes typo in code
2019-05-04 17:33:05 +02:00
Dylan DPC
652eab6e7e Update custom_preprocessors.rs 2019-05-03 20:32:56 +02:00
Dylan DPC
5726a8afd6 Update build_process.rs 2019-05-03 20:00:43 +02:00
Dylan DPC
7e26a8430d Update mod.rs 2019-05-03 19:59:58 +02:00
Dylan DPC
07a64b110a Merge pull request #905 from ehuss/fix-code-inline-color
Fix color of `code spans` that are links.
2019-05-02 21:14:28 +02:00
Eric Huss
dd69e03ff5 Fix color of code spans that are links. 2019-05-02 11:07:24 -07:00
Dylan DPC
7f3a0ff6a0 Merge pull request #886 from bluejekyll/master
add docs for publishing to github pages manually
2019-04-30 00:50:58 +02:00
Dylan DPC
aea317e173 Update continuous-integration.md 2019-04-30 00:50:35 +02:00
Dylan DPC
f9454615b1 Update book-example/src/continuous-integration.md
Co-Authored-By: bluejekyll <benjaminfry@me.com>
2019-04-29 15:33:31 -07:00
Dylan DPC
39211291d9 Update book-example/src/continuous-integration.md
Co-Authored-By: bluejekyll <benjaminfry@me.com>
2019-04-29 15:33:25 -07:00
Dylan DPC
f01fe854fa Merge pull request #883 from anp/custom_summary
Expose API for building a book with a custom Summary.
2019-04-30 00:25:03 +02:00
Dylan DPC
6eeaaaa44d Merge pull request #819 from StefanieJaeger/css-comment
Update comment in editor.js
2019-04-28 23:56:13 +02:00
Dylan DPC
357ebcf7ce Merge pull request #849 from gentoo90/sidebar-resize
Add sidebar resize
2019-04-28 23:29:07 +02:00
Dylan DPC
1a4f38eace Merge pull request #903 from FreeMasen/master
remove walkdir from lib via handlebars
2019-04-28 22:19:30 +02:00
rfm
1d3f83eede remove walkdir from lib via handlebars 2019-04-28 12:54:01 -05:00
Dylan DPC
9712347b9c Merge pull request #796 from badboy/dont-trim-external-js-path
Don't strip relative path of additional javascript files
2019-04-26 09:01:39 +02:00
Dylan DPC
f73d42d994 Merge pull request #878 from shen390s/master
Cleanup build directory before preprocessors run to keep files genera…
2019-04-26 00:17:09 +02:00
Dylan DPC
a647017e4b Merge pull request #861 from k-nasa/add_colored_help
Add colored help
2019-04-25 23:19:18 +02:00
Dylan DPC
a66d44190e Merge pull request #900 from felixrabe/patch-2
Typo
2019-04-23 22:13:07 +02:00
Felix Rabe
01fd7a76f0 Typo 2019-04-23 22:11:12 +02:00
Dylan DPC
99dc62f9c3 Merge pull request #867 from ffissore/master
Fixed loss of focus when clicking the Copy button
2019-04-23 20:25:58 +02:00
Dylan DPC
b891fd5a12 Merge pull request #834 from donald-pinckney/master
Fixes #826
2019-04-23 11:36:43 +02:00
Federico Fissore
02fa7b0a11 When the Copy button is pressed, focus is lost and scrolling the page
with the arrow keys stops working. Fixed by upgrading ClipboardJS to
the latest version
2019-04-23 09:29:22 +02:00
Dylan DPC
8b2e1c2daa Merge pull request #870 from cauebs/group-events
Group file changes and rebuild book only once
2019-04-23 00:20:54 +02:00
Dylan DPC
88d2f69138 Merge pull request #860 from k-nasa/fix_duplication
Fix duplication with Cargo.toml
2019-04-22 12:31:50 +02:00
Dylan DPC
cb94053779 Merge pull request #892 from integer32llc/fix-warnings
Fix deprecation warnings for trim left/right matches
2019-04-21 21:20:39 +02:00
Dylan DPC
0a8707b1e6 Merge pull request #872 from ehuss/travis-deploy
Fix deploy on Travis.
2019-04-20 19:11:38 +02:00
Donald Pinckney
0dc2728fa9 Merge branch 'master' of github.com:rust-lang-nursery/mdBook 2019-04-18 12:29:32 -04:00
Dylan DPC
9b02cd7496 Merge pull request #897 from ji-zhou/master
Correct grammar: remove the redundancy
2019-04-17 23:47:08 +02:00
ji.zhou
11f86f4511 Correct grammar: remove the redundancy 2019-04-12 22:53:21 +08:00
Carol (Nichols || Goulding)
4abac12c04 Fix deprecation warnings for trim left/right matches 2019-03-23 08:47:10 -04:00
Carol (Nichols || Goulding)
d7c7d91005 Write preprocessed content to file before testing with rustdoc
Fixes #855.
2019-03-23 08:35:44 -04:00
Sebastian Köln
9243cf9d95 well. typo 2019-03-21 07:56:48 +01:00
Sebastian Köln
d2470730fc center book title 2019-03-20 22:13:12 +01:00
Sebastian Köln
6a2e2461fb quote inline code 2019-03-20 17:02:05 +01:00
Sebastian Köln
3faa3e42f0 switch to Source font family 2019-03-20 16:34:19 +01:00
Sebastian Köln
9c8fae4704 fix forgotten rename 2019-03-20 15:36:40 +01:00
Sebastian Köln
9b6f5a9840 add Book theme (Liberation fonts need to be in /fonts/ on the server) 2019-03-20 15:21:36 +01:00
Benjamin Fry
62af2367bb add docs for publishing to github pages manually 2019-03-12 11:22:00 -07:00
Adam Perry
37808b7e08 Expose API for building a book with a custom Summary.
This is useful for situations where you'd like to
supplement or replace the existing Summary parsing with
custom filesystem traversal code or other similar changes.
2019-03-04 11:44:00 -08:00
Rongsong Shen
b37f21a09b Cleanup build directory before preprocessors run to keep files generated by preprocessors 2019-02-16 12:17:26 +08:00
Eric Huss
966632a724 Fix deploy on Travis.
The deploy scripts require TARGET env var to be set, but it wasn't set anywhere, causing it to fail: https://travis-ci.com/rust-lang-nursery/mdBook/builds/97850452.

This also reduces the number of mac builders, since they aren't necessary, and capacity is limited.

The api key will likely need to be updated, too, since the transition to travis.com.

Appveyor also seems to be broken, but I suspect it is also a key issue.
2019-02-03 15:45:02 -08:00
Cauê Baasch de Souza
c7281459f9 Group file changes and rebuild book only once 2019-01-31 16:31:02 -02:00
nasa
ae3f87ad0c fix: Fix Cargo.toml description 2019-01-20 15:12:51 +09:00
Steve Klabnik
c068703028 start next development iteration 0.2.4-alpha.0 2019-01-18 10:43:59 -05:00
Steve Klabnik
6cbc41d413 version 0.2.3 2019-01-18 10:39:51 -05:00
Steve Klabnik
25c1ca1275 Merge pull request #866 from rust-lang-nursery/gh828
Fixing links in print.html
2019-01-18 09:54:17 -05:00
Steve Klabnik
acbb951240 Merge pull request #865 from sderosiaux/patch-1
Fix websocket hostname usage
2019-01-17 15:08:18 -05:00
Steve Klabnik
9e96165d8f Merge pull request #845 from phansch/update_contributing
Update CONTRIBUTING.md (Clippy is on stable now)
2019-01-17 15:07:21 -05:00
Steve Klabnik
5c5ef2f86b Merge pull request #853 from sshplendid/broken-link
Update broken link at the user guide.
2019-01-17 15:06:45 -05:00
Steve Klabnik
23ac06e2eb Fix a bug in link re-writing
In a regex, `.` means `[0-9a-zA-Z]`, not a period character. This means
that this regex worked, unless a link anchor contained `md` inside of
it. This fixes the regex to match a literal period.

Reported by @ehuss in
https://github.com/rust-lang-nursery/mdBook/pull/866#issuecomment-454595806
2019-01-16 15:44:51 -05:00
Steve Klabnik
2ddbb37f49 Fix the bug 2019-01-15 14:10:02 -05:00
Steve Klabnik
a481735fa2 failing test 2019-01-15 14:08:53 -05:00
Stéphane Derosiaux
954cfa86e5 Fix websocket hostname usage
The livereload url was using an unknown property "websocket-address" instead of "websocket-hostname", hence it was always fallback onto the hostname (which can be different).
2019-01-09 17:59:45 +01:00
k-nasa
7e52da3c1b Add colored help 2018-12-25 21:35:37 +09:00
k-nasa
4e8d051bd1 Delete extra space 2018-12-25 21:10:45 +09:00
k-nasa
78ee8e43bb Fix duplication with Cargo.toml 2018-12-25 21:10:07 +09:00
Shawn
b675b91980 Update broken link
https://www.rust-lang.org/downloads.html is the outdated link.
2018-12-15 10:09:23 +09:00
gentoo90
3d8db7f25c Disable text selection and CSS transition while resizing sidebar 2018-12-12 21:55:50 +02:00
gentoo90
3d37e24c14 Add sidebar resizing 2018-12-12 21:55:50 +02:00
Philipp Hansch
eb19d2d654 Update CONTRIBUTING.md (Clippy is on stable now) 2018-12-08 13:48:59 +01:00
Michael Bryan
1052ee92e1 Merge pull request #837 from basbossink/master
Random acts of kindness
2018-12-08 15:44:40 +08:00
Bas Bossink
3598e905aa Make failing_alternate_backend test more platform specific
Use the suggestion from @Michael-F-Bryan to make the passing_ and
failing_alternate_backend test more reliable across platforms.
2018-12-05 22:26:53 +01:00
Bas Bossink
3f002979c4 Suppress dead_code warning in test 2018-12-04 00:57:15 +01:00
Bas Bossink
742dbbc917 Run rustfmt. 2018-12-04 00:11:41 +01:00
Bas Bossink
991a725c26 Solve the simplest clippy warnings and run rustfmt 2018-12-04 00:10:09 +01:00
Donald Pinckney
317c7731da Remove extra comment 2018-11-28 20:07:37 -05:00
Donald Pinckney
4c17b11ed0 Fix #826 2018-11-28 20:05:37 -05:00
Michael Bryan
005dfc55bf Merge pull request #829 from kingdido999/patch-1
Fix invalid url in renderer documentation
2018-11-22 20:12:19 +08:00
Desmond
8c86031384 Fix invalid url in renderer documentation 2018-11-21 08:59:53 +08:00
Stefanie Jäger
5151aae07e Update comment
Comment in editor.js referenced file called "general.styl", changed that
to "general.css".
2018-11-09 17:34:57 +01:00
Michael Bryan
42b87e0fbc Merge pull request #804 from Bassetts/default-theme-option
Default theme option
2018-10-30 21:18:48 +08:00
Matt Ickstadt
33add4b532 Merge pull request #782 from mattico/document-dest-dir-rel
Document dest-dir relative path behavior
2018-10-23 12:16:37 -05:00
Michael Bryan
b0513ee771 Merge pull request #788 from weihanglo/feat/non-ascii-heading-anchor
Allow non alphabetic initial in heading anchor
2018-10-23 19:21:44 +08:00
Michael Bryan
b4538da9c3 Merge pull request #802 from Bassetts/git-button
Implement a git repository button
2018-10-23 19:16:45 +08:00
Michael Bryan
7ac3e50b37 Merge pull request #809 from hikarinotomadoi/fix-documentation-comments-for-HtmlConfig
Improve documentation comments
2018-10-23 19:15:51 +08:00
yoshimura masataka
13a9aab2b2 Improve documentation comments 2018-10-23 10:34:14 +09:00
Jason Liquorish
eccec9bb52 Update documentation for 2018-10-21 13:16:59 +01:00
Michael Bryan
e63f53fe47 (cargo-release) start next development iteration 0.2.3-alpha.0 2018-10-20 14:22:47 +08:00
Michael Bryan
2c20c99d4a (cargo-release) version 0.2.2 2018-10-20 14:21:21 +08:00
Michael Bryan
c6125b184f Merge pull request #807 from rust-lang-nursery/update-readme
Update the readme to mention plugins
2018-10-20 14:17:57 +08:00
Michael Bryan
dfb6e3cb10 Merge pull request #806 from rust-lang-nursery/serialize-checks
Add some round-trip checks for preprocessor input serialization
2018-10-20 14:17:03 +08:00
Michael Bryan
cffc385b0c Updated the user guide's config section to mention specifying plugin commands 2018-10-20 14:16:07 +08:00
Michael Bryan
e73928f933 Mentioned plugins in the README 2018-10-20 14:01:51 +08:00
Michael Bryan
41071a5dd9 Bumped dependencies 2018-10-20 12:51:45 +08:00
Michael Bryan
f6a7432569 Added a round-trip test to make sure parse_input() is always correct 2018-10-20 12:50:35 +08:00
Michael Bryan
89ea60e7a5 Made __non_exhaustive fields #[serde(skip)] 2018-10-20 11:21:24 +08:00
Jason Liquorish
10b69e60c8 Add documentation and tests 2018-10-15 21:40:59 +01:00
Jason Liquorish
336e08fe50 Update FontAwesome version to 4.7.0 2018-10-15 20:01:36 +01:00
Jason Liquorish
5bfdf9fcc8 Added git-repository-icon option
Updated documentation and added tests.
2018-10-15 19:48:54 +01:00
Michael Bryan
29f8b791f1 Merge pull request #792 from rust-lang-nursery/custom-preprocessor
WIP: Custom Preprocessors
2018-10-16 00:02:12 +08:00
xyh
877bf37d18 avoid using cd in example travis-ci script (#803) 2018-10-15 18:52:37 +08:00
Jason Liquorish
d2565af000 Add helper to format theme name for theme changer 2018-10-13 14:44:10 +01:00
Jason Liquorish
599e47f1f1 Initial implementation of a git repository button 2018-10-13 12:17:33 +01:00
Jason Liquorish
0c31ab2953 Initial implementation of default theme option 2018-10-12 19:57:59 +01:00
Michael Bryan
b1c7c54108 Rewrote a large proportion of the Preprocessor docs to be up-to-date 2018-09-25 19:48:20 +08:00
Jan-Erik Rediger
f654c42426 Don't strip relative path of additional javascript files
Previously, additional JavaScript files inside a directory were
correctly copied (with their parent created), but the link to it was
stripped of that parent.
There's no need for that (and it was not done for CSS)
2018-09-19 20:36:32 +02:00
Jan-Erik Rediger
0c926b3e88 Ensure section numbers are correctly incremented after a horizontal separator (#790)
Fixes #779
2018-09-19 23:33:28 +08:00
mwilbur
e4eddb3f26 Fix broken link to API pages (#795) 2018-09-19 23:32:37 +08:00
Michael Bryan
adec78e7f5 Forgot to implement 3rd party preprocessor discovery 2018-09-19 23:16:11 +08:00
Michael Bryan
5cd5e4764c Fleshed out the api docs 2018-09-16 23:44:52 +08:00
Michael Bryan
132f4fd358 Fixed a bug where the tests use the wrong dummy book 2018-09-16 23:33:58 +08:00
Michael Bryan
1d72cea972 The example preprocessor works 2018-09-16 23:28:01 +08:00
Michael Bryan
1aa1194d79 We can shell out to the preprocessor 2018-09-16 23:23:03 +08:00
Michael Bryan
304234c122 The example can now tell mdbook if renderers are supported 2018-09-16 23:00:19 +08:00
Michael Bryan
729c94a7e4 Started working on a custom preprocessor 2018-09-16 22:49:52 +08:00
Ramon Buckland
df874cdbdb Resolves #270. details for Clippy and Rustmft usage (#776)
* Resolves #270. details for Clippy and Rustmft usage

* Fix Typo
2018-09-16 14:39:18 +08:00
Michael Bryan
5dce539928 Notify preprocessors of the mdbook version and add __non_exhaustive elements 2018-09-16 14:27:37 +08:00
Michael Bryan
206a00915b Export the mdbook version from the crate root 2018-09-16 14:22:50 +08:00
Ramon Buckland
ced74ca4dd Updated the documentation for new preprocessor format (#787)
* Updated the documentation for new preprocessor format

* adjusted the descriptions for preprocessors
2018-09-10 19:51:18 +08:00
Michael Bryan
09667c9956 Configurable preprocessor (#658)
* The preprocessor trait now returns a modified book instead of editing in place

* A preprocessor is told which render it's running for

* Made sure preprocessors get their renderer's name

* Users can now manually specify whether a preprocessor should run for a renderer

* You can normally use default preprocessors by default

* Got my logic around the wrong way

* Fixed the `build.use-default-preprocessors` flag
2018-09-10 18:55:58 +08:00
Weihang Lo
d729a762fe Remove insertion on non alphabetic initial headings 2018-09-09 12:00:28 +08:00
Weihang Lo
43b3d157d9 (test) validate id from non ascii headings 2018-09-09 12:00:25 +08:00
Matt Ickstadt
a9f3be6f44 Make serve command note more prominent 2018-09-06 10:24:56 -05:00
Matt Ickstadt
34356b87a0 Document dest-dir relative path behavior 2018-09-06 10:24:42 -05:00
Matt Ickstadt
48c97dadd0 Merge pull request #777 from wirelyre/fix-additional-css-and-js
Fix paths to additional CSS and JavaScript files
2018-09-04 14:16:42 -04:00
wirelyre
65198a7632 Fix paths to additional CSS and JavaScript files
Expressions in an `#each` block need to begin with "../" to reference
values in the main context.
2018-08-31 20:03:34 -05:00
Matt Ickstadt
a0e7b19784 (cargo-release) start next development iteration 0.2.2-alpha.0 2018-08-22 08:51:07 -05:00
Matt Ickstadt
7e2e095c26 (cargo-release) version 0.2.1 2018-08-22 08:48:25 -05:00
Matt Ickstadt
5baaf55abc Merge pull request #770 from mattico/improve-travis
Improve CI
2018-08-22 08:55:06 -05:00
Matt Ickstadt
9157f6e32d Improve CI
- Try to get OSX releases working
- Use LTO for appveyor releases
- Remove unused install.sh
2018-08-21 11:18:20 -05:00
Matt Ickstadt
3688f73052 rustfmt
using rustfmt 0.99.2-nightly
2018-08-21 10:58:44 -05:00
Matt Ickstadt
3e89e8b1bd Update dependencies 2018-08-21 10:51:34 -05:00
Matt Ickstadt
e08fc148b1 Merge pull request #769 from dguo/patch-1
Fix a typo
2018-08-21 09:44:35 -05:00
Danny Guo
d9c1c77aae Fix a typo 2018-08-20 23:48:18 -04:00
Matt Ickstadt
bb2ca4f938 Merge pull request #765 from weihanglo/fix/quote-table-colors
Fix table and quote related color
2018-08-13 11:08:29 -05:00
Weihang Lo
42aded9577 Fix table and quote related color 2018-08-12 02:47:06 +08:00
Matt Ickstadt
7fb2d5437a Merge pull request #761 from sunng87/feature/handlebars-1.0
Update to handlebars 1.0
2018-08-07 15:39:09 -05:00
Ning Sun
4cc3a1333b (feat) update to handlebars 1.0 2018-08-05 15:08:47 +08:00
Matt Ickstadt
322e8fcf77 Rewrap guide markdown at 80 columns
Much of the book was wrapped at around 80 columns, but let's make this consistent.
2018-08-02 21:34:26 -05:00
Matt Ickstadt
a8a460545f Update installation instructions in guide 2018-08-02 21:19:47 -05:00
Matt Ickstadt
1d69ccae48 Run rustfmt
using 0.8.2-stable included in rustc 1.28.0
2018-08-02 20:22:49 -05:00
Matt Ickstadt
762d89ebbf (cargo-release) start next development iteration 0.2.1-alpha.0 2018-08-02 19:20:05 -05:00
Matt Ickstadt
91ffca1bbc (cargo-release) version 0.2.0 2018-08-02 19:17:40 -05:00
Matt Ickstadt
6f963bbe3c Run AppVeyor on version tags 2018-08-02 19:10:41 -05:00
Matt Ickstadt
93af92910a Remove travis github_pages deploy
Since it doesn't work at the moment and it takes a lot of time.
2018-08-02 19:10:11 -05:00
Matt Ickstadt
f30ce0184d Fix escaped link preprocessor 2018-08-02 19:04:35 -05:00
Matt Ickstadt
ccb2340fbe Update dependencies 2018-08-02 17:33:56 -05:00
Matt Ickstadt
bbe6e324d0 Merge pull request #757 from mattico/improve-argparse
Improve command-line argument parsing
2018-08-02 17:28:45 -05:00
Matt Ickstadt
a776aa9783 Update documentation for commands 2018-08-02 17:01:47 -05:00
Matt Ickstadt
b8f8e76899 Improve command-line argument parsing 2018-08-02 16:52:41 -05:00
Matt Ickstadt
ac4e00c7c6 Begin development for the 0.2.0 release 2018-08-02 13:31:58 -05:00
Matt Ickstadt
67fde37030 Merge pull request #756 from mattico/test-dir
Fix relative paths in index.html
2018-08-02 13:12:34 -05:00
Matt Ickstadt
b2eb1ace08 Fix relative paths in index.html 2018-08-02 12:43:40 -05:00
Matt Ickstadt
b5fd170008 Merge pull request #755 from mattico/test-dir
Add directory argument to `mdbook test`
2018-08-01 23:11:32 -05:00
Matt Ickstadt
b3665c287d Add directory argument to mdbook test 2018-08-01 17:59:40 -05:00
Matt Ickstadt
436c084b9e Merge pull request #754 from mattico/playpen-api
Use stable rust playground API
2018-08-01 15:22:06 -05:00
Matt Ickstadt
47f85e71a8 Use stable rust playground API
and also add timeouts to these fetches.
2018-08-01 12:38:36 -05:00
Matt Ickstadt
1d448fc8cc (cargo-release) start next development iteration 0.1.11-alpha.0 2018-07-30 19:23:20 -05:00
Matt Ickstadt
add23a43c2 (cargo-release) version 0.1.10 2018-07-30 19:21:18 -05:00
Matt Ickstadt
8ba1830750 Merge pull request #752 from mattico/icon-margin-padding
Icon button hit-test dead-space fix
2018-07-30 19:19:31 -05:00
Matt Ickstadt
76c1c9e0a8 Merge pull request #751 from mattico/update-playpen-bt
Fix rust playground execute API
2018-07-30 19:18:18 -05:00
Matt Ickstadt
d054140117 Fix rust playground execute API 2018-07-30 19:17:53 -05:00
Matt Ickstadt
512826c465 Fix hiding theme menu 2018-07-30 19:06:50 -05:00
Matt Ickstadt
99019b74aa Remove hit-test dead space between buttons 2018-07-30 19:03:29 -05:00
Matt Ickstadt
d87e77edd0 Merge pull request #744 from eminence/noplaypen_class
Add a "noplaypen" class for rust code samples.
2018-07-28 19:18:30 -05:00
Andrew Chin
abfc3009fc Add a "noplaypen" class for rust code samples.
This class will supress the "play" button in the html backend (which you
can also do with the "ignore" class), but it will still let the code be
tested via `mdbook test` (which is not possible with the "ignore" class).

This is useful for code examples that don't really do much (and so the
user doesn't gain much from running them), but as an author you still
want to test them to guard against syntax errors and typos and the like.
2018-07-26 17:55:14 -04:00
Matt Ickstadt
028c8b0f75 Merge pull request #743 from mattico/remove-stylus-redux
Convert Stylus to CSS
2018-07-26 15:37:52 -05:00
Matt Ickstadt
05f3c693a7 Ensure theme selection button is the full-width of the menu 2018-07-26 15:28:09 -05:00
Matt Ickstadt
8b3038e3ef Remove unconditional searchindex.js load
which should have been included in #707
2018-07-26 15:28:09 -05:00
Matt Ickstadt
bc432c8f42 Fix favicon in subfolders 2018-07-26 15:28:09 -05:00
Matt Ickstadt
e88970d172 Don't use CSS variables in media queries
since they don't work :'(
2018-07-26 15:28:09 -05:00
Matt Ickstadt
4c87a0b5f0 Remove stylus support 2018-07-26 15:28:09 -05:00
Matt Ickstadt
ac38f05bb6 Change template to use new CSS 2018-07-26 15:28:05 -05:00
Matt Ickstadt
3119a7e4bf Fix several CSS bugs 2018-07-26 13:49:57 -05:00
Matt Ickstadt
cc745d04f2 Merge css files 2018-07-26 13:37:22 -05:00
Matt Ickstadt
d1a23109e2 Convert stylus files to CSS 2018-07-26 13:37:22 -05:00
Matt Ickstadt
b3e0942bc9 Fix travis deploy out of disk space error 2018-07-26 13:18:33 -05:00
Matt Ickstadt
3e998eb766 (cargo-release) start next development iteration 0.1.10-alpha.0 2018-07-26 12:50:07 -05:00
Matt Ickstadt
2bbabdcd62 (cargo-release) version 0.1.9 2018-07-26 12:44:29 -05:00
Matt Ickstadt
a287a0dcc8 Merge pull request #742 from mattico/narrow-device-style-fixes
Reduce the margins on header icons on narrow devices
2018-07-25 13:41:33 -05:00
Matt Ickstadt
e7afb3340c Reduce the margins on header icons on narrow devices 2018-07-25 13:28:05 -05:00
Matt Ickstadt
b4e15e5357 Merge pull request #741 from mattico/fix-mdbook-test
Fix `mdbook test`
2018-07-25 12:56:28 -05:00
Matt Ickstadt
1d1d4d7c30 Update tests to handle added dummy_book chapter 2018-07-25 12:45:20 -05:00
Matt Ickstadt
35c2d1ff91 Merge pull request #740 from mattico/fix-ios-text-size
Prevent certain mobile browsers from enlarging fonts in landscape ori…
2018-07-25 12:27:16 -05:00
Matt Ickstadt
fd9d27e082 rustfmt 2018-07-25 12:20:48 -05:00
Matt Ickstadt
0e1787c617 Don't run index preprocessor on mdbook test 2018-07-25 12:19:01 -05:00
Matt Ickstadt
e5563182fc Add readme to cause test to fail
The test for `mdbook test` fails due to the index preprocessor which only runs on README files.
2018-07-25 12:14:27 -05:00
Matt Ickstadt
a08255316a Prevent certain mobile browsers from enlarging fonts in landscape orientation 2018-07-25 11:25:09 -05:00
Matt Ickstadt
ebea8c2337 Fix travis cache configuration 2018-07-24 17:29:44 -05:00
Matt Ickstadt
a7342230d5 Merge pull request #739 from mattico/travis-cache-timeout
Increase the travis-ci cache timeout
2018-07-24 17:25:36 -05:00
Matt Ickstadt
15b18a682b Increase the travis-ci cache timeout 2018-07-24 17:23:51 -05:00
Matt Ickstadt
a8590928cf Update env_logger version 2018-07-24 16:55:02 -05:00
Matt Ickstadt
c9a9987aec Merge pull request #738 from mattico/fix-cargo-bin
Move subcommand modules to match cargo conventions
2018-07-24 16:46:57 -05:00
Matt Ickstadt
775377acce Merge pull request #737 from mattico/update-deps-easy
Update dependencies
2018-07-24 16:43:14 -05:00
Matt Ickstadt
5dd0496a4f Update dependencies
`crossbeam` and `time` are removed since they're no longer used.
2018-07-24 16:40:34 -05:00
Matt Ickstadt
f300a21a47 Move subcommand modules to match cargo conventions 2018-07-24 16:34:49 -05:00
Matt Ickstadt
0f89abb1c7 Merge pull request #735 from mattico/fix-travis-deploy
Fix travis deploy script
2018-07-24 14:13:39 -05:00
Matt Ickstadt
b5e32f57dc Fix travis deploy script 2018-07-24 14:12:27 -05:00
Matt Ickstadt
b88abb171c Merge pull request #710 from Eyenseo/master
Fix different font sizes in editor and code
2018-07-24 13:34:34 -05:00
Matt Ickstadt
7c8dd5085b Merge pull request #732 from mattico/sidebar-overflow
Allow sidebar section titles to wrap
2018-07-24 12:11:46 -05:00
Matt Ickstadt
4f793af53b Allow sidebar section titles to overflow 2018-07-24 12:00:48 -05:00
Matt Ickstadt
29b3ff14c7 Merge pull request #719 from lucasem/patch-1
rustdoc codeblock hash escape
2018-07-24 11:09:28 -05:00
Matt Ickstadt
0ac36f2183 Merge pull request #690 from mattico/rustfmt
Remove rustfmt.toml
2018-07-23 12:47:33 -05:00
Matt Ickstadt
5835da2432 Run rustfmt 2018-07-23 12:47:04 -05:00
Matt Ickstadt
646e3f8fd2 Remove rustfmt.toml 2018-07-23 12:39:18 -05:00
Matt Ickstadt
da9be67516 Merge pull request #707 from mattico/search-index-opt
Optimize search index
2018-07-23 12:32:05 -05:00
Matt Ickstadt
d9dbba49ea Fix for relative paths 2018-07-23 12:19:59 -05:00
Matt Ickstadt
384582aeba Cleanup add_doc 2018-07-23 12:08:04 -05:00
Matt Ickstadt
e94078cc9c Fix tests 2018-07-23 12:08:04 -05:00
Matt Ickstadt
e1a46d213e Use JSON search index with JS fallback
This allows the search index to be loaded asynchronously, and should
use fewer resources as it doesn't have to execute the JS.
JS loading is kept as a fallback for CORS issues with file:// URIs in Chrome.
2018-07-23 12:08:04 -05:00
Matt Ickstadt
62c8311301 Don't copy search js when disabled 2018-07-23 12:08:04 -05:00
Matt Ickstadt
b8011de3e8 Warn when search index is >10MB 2018-07-23 12:08:04 -05:00
Matt Ickstadt
019e74041d Use integer doc_refs to shrink the search index
This change reduced the searchindex.js of book_example from 508KB to 317KB.
2018-07-23 12:08:04 -05:00
Matt Ickstadt
8cd7061ff2 Add search.enable config field 2018-07-23 12:08:04 -05:00
Matt Ickstadt
96b99472fd Merge pull request #709 from mattico/no-playpen-no-fetch
Only fetch crates list on pages with playpens
2018-07-23 12:06:02 -05:00
Matt Ickstadt
4d357b6779 Merge pull request #728 from kggp1995/master
- Fixed token type.
2018-07-23 12:01:35 -05:00
Corey Farwell
1e6328c112 Merge pull request #727 from waywardmonkeys/update-toml-query
Update to toml-query 0.7
2018-07-22 17:26:16 -04:00
Ganesh Prasad Kumble
21c24c2815 - Fix token type
OAuth token type doesn't work. Have to change it to PAT.
2018-07-21 19:23:47 +05:30
Corey Farwell
bb8b43d396 Merge pull request #726 from waywardmonkeys/update-elasticlunr
Update to elasticlunr 2.3.
2018-07-19 06:38:58 -04:00
Bruce Mitchener
f07e734efc Update to toml-query 0.7
This was just released with an update to its dependencies, so that
mdBook could update its dependencies.
2018-07-19 17:35:34 +07:00
Bruce Mitchener
db2c16102e Update to elasticlunr 2.3.
This removes one of the remaining dependencies upon pre-1.0 regex.
2018-07-19 11:11:54 +07:00
Matt Ickstadt
cae8a8ffe2 Only fetch crates list on pages with playpens 2018-07-16 19:17:00 -05:00
cetra3
bdb37ec117 Use relative links and translate internal references (#603)
* Relative links for 0.1.8

* Compat for IE11 search
2018-07-11 21:33:44 +08:00
Michael Bryan
01656b610f (cargo-release) start next development iteration 0.1.9-alpha.0 2018-07-09 18:40:43 +08:00
Michael Bryan
b9ff0e8a77 (cargo-release) version 0.1.8 2018-07-09 18:39:35 +08:00
Michael Bryan
0bda57175d Metadata for cargo-release should be in release.toml (#722) 2018-07-09 18:36:55 +08:00
Lucas Morales
374e1d3f94 rustdoc codeblock hash escape
pending merge of rust-lang/rust#51803
2018-07-04 16:54:55 +04:00
Steve Klabnik
6287e6a44f Merge pull request #715 from fitzgen/continuous-integration-instructions
user guide: Add instructions for running `mdbook` in CI
2018-07-03 10:01:19 -07:00
Corey Farwell
953d3821b6 Merge pull request #701 from kubo39/saturating_sub
Use `saturating_sub` instead of `checked_sub.unwrap_or`
2018-06-27 07:40:20 -04:00
Nick Fitzgerald
488ace15ff user guide: Add instructions for running mdbook in CI
This adds instructions for building and testing books in CI on every PR and
push, as well as instructions for how to automatically deploy to gh-pages on
successful CI runs on `master`.

Fixes #714
2018-06-22 10:31:49 -07:00
eyenseo
b452d5e0c7 Fix different font sizes in editor and code
Fixes #705
2018-06-19 22:28:23 +02:00
Hiroki Noda
289028850f Use saturating_sub instead of checked_sub.unwrap_or 2018-06-04 01:42:09 +09:00
Andrew Gauger
2a55ff62f3 Recursively apply preprocessor (#682) 2018-05-20 18:36:19 +08:00
Mathieu David
6bf86806e4 Merge pull request #691 from phansch/update_regex
Update Regex to 1.0.0
2018-05-18 16:58:01 +02:00
Philipp Hansch
90bd7207ec Add minimum required Rust version to README 2018-05-18 09:04:41 +02:00
Mathieu David
27b29fdaf2 Merge pull request #696 from mattico/fix-theme-dir
Fix default theme dir selection
2018-05-16 21:58:41 +02:00
Matt Ickstadt
154e0fb308 Rustfmt 2018-05-16 12:08:23 -05:00
Matt Ickstadt
0de177a344 Add a warning for possible theme directories which will no longer be used 2018-05-16 12:06:55 -05:00
Mathieu David
f154b2fb65 Merge pull request #697 from mattico/fix-fontawesome-gh-pages
Fix FontAwesome directory missing from Github Pages sites
2018-05-15 23:23:09 +02:00
Matt Ickstadt
d7759fbf4d Remove underscore from FontAwesome directory 2018-05-15 12:34:44 -05:00
Matt Ickstadt
f84e670edd Add a .nojekyll file
to allow users to have other files with leading underscore names.
2018-05-15 12:34:44 -05:00
Matt Ickstadt
9a9c625319 Fix default theme dir selection 2018-05-14 14:52:29 -05:00
Tim Ryan
b9ca108fca Support reproducible builds by forcing window.search to use stable key ordering. (#692) 2018-05-14 18:22:21 +08:00
Philipp Hansch
e99dc51fb3 Update Regex to 1.0.0 2018-05-13 12:35:17 +02:00
Mathieu David
7ee5b6643b Merge pull request #689 from gnzlbg/patch-1
remove removed rustfmt options
2018-05-10 10:31:15 +02:00
gnzlbg
42781bcd6b remove removed rustfmt options
Closes #688
2018-05-09 15:29:46 +02:00
Weihang Lo
41d372de26 Update documentation for preprocessor (#686) 2018-05-06 23:48:11 +08:00
Weihang Lo
69599646e7 Add index preprocessor (#685)
* Add index preprocessor

README.md is a de facto index file in markdown-based documentation.
Hence, we respect to README.md and convert it into index.html.

* Fix warning for unused variables

* Update tests for config

* Match file stem case-insensitively for IndexPreprocessor

* Add tests for IndexPreprocessor

* Update book example to fit index preprocessor
2018-05-04 19:41:28 +08:00
Matt Ickstadt
69fef40e57 Improve print output (#680)
* Update print styles for new sidebar behavior

* Hide copy icons in print output

* Wait for mathjax rendering to complete before printing

* Remove old wrapping css
Browsers this old are already hilariously broken, so we don't need these fallbacks.

* Change mathjax script type
Chrome won't execute this if it's not marked as js

* Ensure page has rendered before printing
In certain situations Chrome willl fire window.onLoad before it's
done rendering. Add a 100ms delay to work around this.
2018-05-01 20:29:34 +08:00
Michael Bryan
a323620e02 (cargo-release) start next development iteration 0.1.8-alpha.0 2018-04-23 07:42:43 +08:00
Michael Bryan
ea0b835b38 (cargo-release) version 0.1.7 2018-04-23 07:41:31 +08:00
Mathieu David
58f0f3b0f2 Merge pull request #672 from mattico/update-deps
Update deps
2018-04-22 22:58:35 +02:00
Matt Ickstadt
e7a61efb39 Fix warning 2018-04-22 13:01:10 -05:00
Matt Ickstadt
d48bc29373 Update dependencies 2018-04-22 13:01:05 -05:00
Michael Bryan
72f154bee4 (cargo-release) start next development iteration 0.1.7-alpha.0 2018-04-22 00:45:07 +08:00
Matt Brubeck
1c71eaa964 Put the search bar into an HTML form (#669)
This enables "Add a keyword for this search" in the contex menu for the
search field, in Firefox and other browsers.
2018-04-21 23:27:51 +08:00
Matt Ickstadt
c195aa990d Update dependencies (#670) 2018-04-21 23:22:05 +08:00
Mathieu David
34bdcaf8b3 Merge pull request #660 from THeK3nger/doc-mathjax-note
Add a note in MathJax documentation
2018-04-16 02:24:49 +02:00
Michael Bryan
41399fc29c Revert "Fixes the search box overlapping with content when first shown (#666)" (#667)
This reverts commit 7f82a197b9.
2018-04-11 10:23:56 +08:00
Michael Bryan
7f82a197b9 Fixes the search box overlapping with content when first shown (#666) 2018-04-10 22:02:27 +08:00
Gwen Lofman
71d44933f0 Replace his with their in reference to reader (#665)
The reader should not be assumed male; I'm a developer and user,
I'm not male.  Makes documentation's language gender neutral to
make it more welcoming to people that do not use he/him pronouns.
2018-04-10 07:02:53 +08:00
Matt Ickstadt
f01bf88e69 Fix several theme issues (#648)
* Don't hide page content when displaying search

* Decrease sidebar animation time

* Fix search key event handler
which wasn't completely de-jqueryified.

* Avoid reflowing page content on small screens
This reduces jank caused by reflowing the page text while animating the
sidebar, and it looks nicer.

* Don't use HTMLParentNode.prepend()
since edge doesn't support it yet

* Don't animate menu border bottom color
since it's the same color as the background, which isn't animated.

* Small CSS improvments
- Remove invalid `pointer: cursor` style
- Disable transitions for noscript to stop page from spazzing on every load
- Add `cursor: pointer` to mark
- Disable `cursor: pointer` on noscript menu-title

* JS fixes

- Load MathJax async
- Always use local fontawesome and clipboard.js
- Move js class to html element to make theme switching easier

* Give the print button a bit more margin
2018-04-09 12:10:44 +08:00
Michael Bryan
b5ea84c60d Remove unnecessary travis jobs (#664)
* Removed all the unnecessary CI jobs

* Updated dependencies

* Removed a deprecation warning
2018-04-07 15:47:08 +08:00
Nils
148c806e34 Prevent search from triggering when editing code (#653) 2018-04-07 06:31:51 +08:00
Davide Aversa
38279deed7 Add a note in MathJax documentation 2018-04-03 10:17:06 +02:00
Bastien Orivel
55f7ed1c37 Replace tempdir by tempfile (#650)
The former has been deprecated in favor of the latter
2018-03-27 07:47:37 +08:00
Anders Rasmussen
eb0f7179ab Use git config to get author name in mdbook init (#649)
* Use `git config` to get author name in `mdbook init`

* Return `None` if `git` command fails

* Use `.ok()?` to convert from Result to Option and return early if `None`
2018-03-26 22:37:11 +08:00
Matt Ickstadt
5fb3675151 Update elasticlunr-rs (#646)
* Update dependencies

* Use config structs from elasticlunr-rs

* Update searchindex fixture
2018-03-20 20:22:35 +08:00
Michael Bryan
77b4f6a940 (cargo-release) start next development iteration 0.1.6-alpha.0 2018-03-16 07:38:05 +08:00
Michael Bryan
6308da699a (cargo-release) version 0.1.5 2018-03-16 07:36:40 +08:00
Guillaume Gomez
62a727c041 Fix search (#645) 2018-03-16 07:37:08 +08:00
Michael Bryan
3bc5d907f4 Merge pull request #641 from rust-lang-nursery/pre-release
Pre release
2018-03-15 09:24:26 +08:00
Michael Bryan
3cd12e7092 (cargo-release) start next development iteration 0.1.5-alpha.0 2018-03-15 09:22:59 +08:00
202 changed files with 18322 additions and 8195 deletions

8
.gitattributes vendored
View File

@@ -2,7 +2,7 @@
* text=auto eol=lf
*.rs rust
*.woff -text
*.ttf -text
*.otf -text
*.png -text
*.woff binary
*.ttf binary
*.otf binary
*.png binary

42
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Deploy
on:
release:
types: [created]
jobs:
release:
name: Deploy Release
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@master
- name: Install hub
run: ci/install-hub.sh ${{ matrix.os }}
shell: bash
- name: Install Rust
run: ci/install-rust.sh stable
shell: bash
- name: Build and deploy artifacts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ci/make-release.sh ${{ matrix.os }}
shell: bash
pages:
name: GitHub Pages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable
- name: Build book
run: cargo run -- build guide
- name: Deploy to GitHub
env:
GITHUB_DEPLOY_KEY: ${{ secrets.GITHUB_DEPLOY_KEY }}
run: |
touch guide/book/.nojekyll
curl -LsSf https://raw.githubusercontent.com/rust-lang/simpleinfra/master/setup-deploy-keys/src/deploy.rs | rustc - -o /tmp/deploy
cd guide/book
/tmp/deploy

51
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: CI
on:
# Only run when merging to master, or open/synchronize/reopen a PR.
push:
branches:
- master
pull_request:
jobs:
test:
name: Test
runs-on: ${{ matrix.os }}
strategy:
matrix:
build: [stable, beta, nightly, macos, windows, msrv]
include:
- build: stable
os: ubuntu-latest
rust: stable
- build: beta
os: ubuntu-latest
rust: beta
- build: nightly
os: ubuntu-latest
rust: nightly
- build: macos
os: macos-latest
rust: stable
- build: windows
os: windows-latest
rust: stable
- build: msrv
os: ubuntu-latest
rust: 1.39.0
steps:
- uses: actions/checkout@master
- name: Install Rust
run: bash ci/install-rust.sh ${{ matrix.rust }}
- name: Build and run tests
run: cargo test
- name: Test no default
run: cargo test --no-default-features
rustfmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Install Rust
run: rustup update stable && rustup default stable && rustup component add rustfmt
- run: cargo fmt -- --check

8
.gitignore vendored
View File

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

View File

@@ -1,88 +0,0 @@
# Based on the "trust" template v0.1.1
# https://github.com/japaric/trust/tree/v0.1.1
dist: trusty
language: rust
services: docker
sudo: required
cache: cargo
before_cache:
- chmod -R a+r $HOME/.cargo
env:
global:
- CRATE_NAME=mdbook
- secure: DPzSRXyfRIVTibv1wOKFeGekXlL8sumGEZxpeq911MpLlrndOKmOo5Ibi3JD8fbUOsE9A/5spj4B2KQNjhbplH+Cp26oEikjuNAA6cA/b2+/TMoC3i0klAYpVopBBV3FFna0gLP+q6t6fzG2v9TJrvmmVav6KVX6ylPNvD/LoReCjrkpgLIQuAQ6dSQNor9uV+EVt4plKhhkiS28DlYdgmTvNb5g4dzOhs8hoWty72J765VYWEDDC8qXn6N9GyrhsC3dhjASGn+1QDSCADYdbG9nrRlb4CZhrfcgOnHhAFva363kshg9HtCphigMgQy2oZXk4nLWK90/HuaPPkVj+N/lpIYjtiHOunToZJfIb0MWzyVI+7+I7WR6n6XbhLCPMe/sPXHHQ3HhQhZZ9xv7CDx9IkYJQBcF3LC+9kzJRi4QT0UTqrxcO3ncgXwvholP8Vg2KKPqFcbuyLPzbvr/o8zIilvLUFAEoDPfTEwSAC4BCzaGkFQVWzhWkgw8Pe1ckOEYFkZ0VLBuCpEiz+x45sbBL1SnnO5xhpjmdc572ZyW7ZmAABw1VfiWhhBWg4WGSf8lLnDHhNA36Qon34pnME/xpJQtWoo7ZZkkzvzYP/oW88/0UIMWDSOYKz7MijXlbNUggwAwUhrLzXDuB71HUKfPreFubfUxbOpu+OtTcOQ=
matrix:
include:
# Android
- env: TARGET=arm-linux-androideabi DISABLE_TESTS=1
# Linux
- env: TARGET=aarch64-unknown-linux-gnu
- env: TARGET=arm-unknown-linux-gnueabi
- env: TARGET=i686-unknown-linux-gnu
- env: TARGET=x86_64-unknown-linux-gnu
- env: TARGET=x86_64-unknown-linux-musl
# Mac
- env: TARGET=i686-apple-darwin
os: osx
- env: TARGET=x86_64-apple-darwin
os: osx
# BSD
- env: TARGET=i686-unknown-freebsd DISABLE_TESTS=1
- env: TARGET=x86_64-unknown-freebsd DISABLE_TESTS=1
- env: TARGET=x86_64-unknown-netbsd DISABLE_TESTS=1
# Other channels
- env: TARGET=x86_64-unknown-linux-gnu
rust: beta
- env: TARGET=x86_64-apple-darwin
os: osx
rust: beta
- env: TARGET=x86_64-unknown-linux-gnu
rust: nightly
- env: TARGET=x86_64-apple-darwin
os: osx
rust: nightly
before_install:
- set -e
- rustup self update
install:
- sh ci/install.sh
- source ~/.cargo/env || true
script:
- bash ci/script.sh
after_success:
- bash ci/github_pages.sh
before_deploy:
- sh ci/before_deploy.sh
deploy:
api_key:
- secure: cURRWBr034iqBz/ifD7uOunBfNR30YxIXfgLX0osWz+iafkVbhDGYYz9sBmRraqO2P7L2koEXMADVb/md1kI2+ykiq/ml+l9zuEAZPVmvSGUN7ZD+7s+lu3l5OBPG5z175T+b2q2q2m8XVR7TW20ra4QbE0bq06KAoOyjSgQVBTSCYsL9uTsGwiVRMEqqJT/BmKhKJNkpGsTKyBSKkOXvfeAAbE260vXUDEN9TYdJ3fvteRrpwLX56ee64gIZUq0RjDc4SKIEqilM6iUtNMvurqaewYNGkiXKRruV6BPCHxEHo6NNT46kOJLBJTf7gZw//dWhSoWpg9P0gdAnPWm407kSa3F7aJ1eRShAFQ4BLyfz9efTqm+jP3fOp7Mm7igSh9w6caSRuOnSsUf5+raRQ8E5Y9HsWGzzpZQk24Fx9EGZ04EeDSdpZAFz+jcbMpHf8t2p4CEx0CCNwYvKx6EydMKbMF5QteQ8SQkXNLhv7Rz2OgtXWYZPRVCMfQfOplsi2InsLCrQxTgwh+6u654SqVSgaHG+IncEAxBrdWy4rHcg7qereUcKfcY3k96vaDxdn/T2c00Ig0aNFR91YnixGMd6J6tQgDcRK9jh6fUm1CCBE9hT+pNUmtgYKuWBoLZexUZFFnfuBed0WciBot1bGDDamndqKq0jJiAzg+GMHk=
file_glob: true
file: "$CRATE_NAME-$TRAVIS_TAG-$TARGET.*"
on:
condition: "$TRAVIS_RUST_VERSION = stable"
tags: true
provider: releases
skip_cleanup: true
branches:
only:
- "/^v\\d+\\.\\d+\\.\\d+.*$/"
- master
notifications:
email:
on_success: never

538
CHANGELOG.md Normal file
View File

@@ -0,0 +1,538 @@
# Changelog
## mdBook 0.4.4
[4df9ec9...01836ba](https://github.com/rust-lang/mdBook/compare/4df9ec9...01836ba)
### Added
- Added the `output.html.print.enable` configuration value to disable the
"print" page.
[#1169](https://github.com/rust-lang/mdBook/pull/1169)
- Added a list of supported languages for syntax-highlighting to the
documentation.
[#1345](https://github.com/rust-lang/mdBook/pull/1345)
### Fixed
- Now supports symbolic links for files in the `src` directory.
[#1323](https://github.com/rust-lang/mdBook/pull/1323)
## mdBook 0.4.3
[9278b83...4df9ec9](https://github.com/rust-lang/mdBook/compare/9278b83...4df9ec9)
### Added
- Added `output.html.cname` option to emit a `CNAME` file which is used by
GitHub Pages to know which domain is being used.
[#1311](https://github.com/rust-lang/mdBook/pull/1311)
### Changed
- `mdbook test` no longer stops on the first test failure, but instead will
run all the tests.
[#1313](https://github.com/rust-lang/mdBook/pull/1313)
- Removed the `local` font source for Source Code Pro, as the locally
installed font may not render properly on FireFox on macOS.
[#1307](https://github.com/rust-lang/mdBook/pull/1307)
### Fixed
- Added newline to end of `.nojekyll` file.
[#1310](https://github.com/rust-lang/mdBook/pull/1310)
- Fixed missing space before draft chapter titles.
[#1309](https://github.com/rust-lang/mdBook/pull/1309)
## mdBook 0.4.2
[649f355...9278b83](https://github.com/rust-lang/mdBook/compare/649f355...9278b83)
### Changed
- The "show hidden lines" icon has changed from the "expand" icon to an "eye".
[#1281](https://github.com/rust-lang/mdBook/pull/1281)
- Updated highlight.js. This adds several languages: c, c-like (effectively
cpp), csharp (replaces cs), kotlin, less, lua, php-template, plaintext,
python-repl, r, scss, typescript.
[#1277](https://github.com/rust-lang/mdBook/pull/1277)
### Fixed
- Fixed SUMMARY links that contained newlines.
[#1291](https://github.com/rust-lang/mdBook/pull/1291)
- Fixed SUMMARY links that contain `%20` spaces.
[#1293](https://github.com/rust-lang/mdBook/pull/1293)
- Fixed favicon so that if only the png or svg is overridden, the other is not
automatically included in the `<link>` tag.
[#1272](https://github.com/rust-lang/mdBook/pull/1272)
## mdBook 0.4.1
[d4df7e7...649f355](https://github.com/rust-lang/mdBook/compare/d4df7e7...649f355)
### Changed
- Removed several outdated dev-dependencies.
[#1267](https://github.com/rust-lang/mdBook/pull/1267)
### Fixed
- Fixed sidebar scrolling if the book includes part titles.
[#1265](https://github.com/rust-lang/mdBook/pull/1265)
- Don't include the default favicon if only one of the PNG or SVG is overridden.
[#1266](https://github.com/rust-lang/mdBook/pull/1266)
## mdBook 0.4.0
[99ecd4f...d4df7e7](https://github.com/rust-lang/mdBook/compare/99ecd4f...d4df7e7)
### Breaking Changes
- Several of the changes in the release have altered the public API of the
mdbook library.
- Many dependencies have been updated or replaced.
This also removes the `--websocket-hostname` and `--websocket-port` from
the `serve` command.
[#1211](https://github.com/rust-lang/mdBook/pull/1211)
- A new "404" page is now automatically rendered. This requires knowledge of
the base URL of your site to work properly. If you decide to use this as
your 404 page, you should set the `site-url` setting in the book
configuration so mdbook can generate the links correctly. Alternatively you
can disable the 404 page generation, or set up your own 404 handling in your
web server.
[#1221](https://github.com/rust-lang/mdBook/pull/1221)
- The `debug` and `output` features have been removed as they were unused.
[#1211](https://github.com/rust-lang/mdBook/pull/1211)
- If you are using customized themes, you may want to consider setting the
`preferred-dark-theme` config setting, as it now defaults to "navy".
[#1199](https://github.com/rust-lang/mdBook/pull/1199)
- "Playpen" has been renamed to "playground". This is generally backwards
compatible for users, but `{{#playpen}}` will now display warnings. This may
impact books that have modified the "playpen" elements in the theme.
[#1241](https://github.com/rust-lang/mdBook/pull/1241)
- If a renderer is not installed, it is now treated as an error. If you want
the old behavior of ignoring missing renderers, set the `optional` setting
for that renderer.
[#1122](https://github.com/rust-lang/mdBook/pull/1122)
- If you have a custom favicon, you may need to look into adding an SVG
version, otherwise the default SVG icon will be displayed.
[#1230](https://github.com/rust-lang/mdBook/pull/1230)
### Added
- Added a new `[rust]` configuration section to `book.toml`, which allows
setting the default edition with `edition = "2018"`.
[#1163](https://github.com/rust-lang/mdBook/pull/1163)
- Renderers can now be marked as `optional`, so that they will be ignored if
the renderer is not installed.
[#1122](https://github.com/rust-lang/mdBook/pull/1122)
- Added `head.hbs` to allow adding content to the `<head>` section in HTML.
[#1206](https://github.com/rust-lang/mdBook/pull/1206)
- Added "draft chapters". These are chapters listed without a link to indicate
content yet to be written.
[#1153](https://github.com/rust-lang/mdBook/pull/1153)
- Added "parts" to split a book into different sections. Headers can be added
to `SUMMARY.md` to signify different sections.
[#1171](https://github.com/rust-lang/mdBook/pull/1171)
- Added generation of a "404" page for handling missing pages and broken links.
[#1221](https://github.com/rust-lang/mdBook/pull/1221)
- Added configuration section for specifying URL redirects.
[#1237](https://github.com/rust-lang/mdBook/pull/1237)
- Added an SVG favicon that works with light and dark colors schemes.
[#1230](https://github.com/rust-lang/mdBook/pull/1230)
### Changed
- Changed default Rust attribute of `allow(unused_variables)` to `allow(unused)`.
[#1195](https://github.com/rust-lang/mdBook/pull/1195)
- Fonts are now served locally instead of from the Google Fonts CDN. The
`copy-fonts` option was added to disable this if you want to supply your own
fonts.
[#1188](https://github.com/rust-lang/mdBook/pull/1188)
- Switched the built-in webserver for the `serve` command to a new
implementation. This results in some internal differences in how websockets
are handled, which removes the separate websocket options. This should also
make it easier to serve multiple books at once.
[#1211](https://github.com/rust-lang/mdBook/pull/1211)
- The default dark theme is now "navy".
[#1199](https://github.com/rust-lang/mdBook/pull/1199)
- "Playpen" has been renamed to "playground", matching the actual name of the
service which was renamed many years ago.
[#1241](https://github.com/rust-lang/mdBook/pull/1241)
### Fixed
- Links with the `+` symbol should now work.
[#1208](https://github.com/rust-lang/mdBook/pull/1208)
- The `MDBOOK_BOOK` environment variable now correctly allows overriding the
entire book configuration.
[#1207](https://github.com/rust-lang/mdBook/pull/1207)
- The sidebar can no longer be dragged outside of the window.
[#1229](https://github.com/rust-lang/mdBook/pull/1229)
- Hide the Rust Playground "play" button for `no_run` code samples.
[#1249](https://github.com/rust-lang/mdBook/pull/1249)
- Fixed the `--dest-dir` command-line option for the `serve` and `watch`
commands.
[#1228](https://github.com/rust-lang/mdBook/pull/1228)
- Hotkey handlers are now disabled in `text` input fields (for example, typing
`S` in a custom text input field).
[#1244](https://github.com/rust-lang/mdBook/pull/1244)
## mdBook 0.3.7
[88684d8...99ecd4f](https://github.com/rust-lang/mdBook/compare/88684d8...99ecd4f)
### Changed
- Code spans in headers are no longer highlighted as code.
[#1162](https://github.com/rust-lang/mdBook/pull/1162)
- The sidebar will now scroll the activate page to the middle instead of the top.
[#1161](https://github.com/rust-lang/mdBook/pull/1161)
- Reverted change to reject build output within the `src` directory, and
instead add a check that prevents infinite copies.
[#1181](https://github.com/rust-lang/mdBook/pull/1181)
[#1026](https://github.com/rust-lang/mdBook/pull/1026)
### Fixed
- Fixed sidebar line-height jumping for collapsed chapters.
[#1182](https://github.com/rust-lang/mdBook/pull/1182)
- Fixed theme selector focus.
[#1170](https://github.com/rust-lang/mdBook/pull/1170)
## mdBook 0.3.6
[efdb832...88684d8](https://github.com/rust-lang/mdBook/compare/efdb832...88684d8)
### Added
- `MDBook::execute_build_process` is now publicly accessible in the API so
that plugins can more easily initiate the build process.
[#1099](https://github.com/rust-lang/mdBook/pull/1099)
### Changed
- Use a different color for Ayu theme's highlighting for Rust attributes (uses
a bright color instead of the comment color).
[#1133](https://github.com/rust-lang/mdBook/pull/1133)
- Adjusted spacing of sidebar entries.
[#1137](https://github.com/rust-lang/mdBook/pull/1137)
- Slightly adjusted line-height of `<p>`, `<ul>`, and `<ol>`.
[#1136](https://github.com/rust-lang/mdBook/pull/1136)
- Handlebars updated to 3.0.
[#1130](https://github.com/rust-lang/mdBook/pull/1130)
### Fixed
- Fix an issue with sidebar scroll position on reload.
[#1108](https://github.com/rust-lang/mdBook/pull/1108)
- `mdbook serve` will retain the current scroll position when the page is reloaded.
[#1097](https://github.com/rust-lang/mdBook/pull/1097)
- Fixed the page name if the book didn't have a title to not be prefixed with ` - `.
[#1145](https://github.com/rust-lang/mdBook/pull/1145)
- HTML attributes `rel=next` and `rel=previous` are now supported in "wide"
mode (previously they were only set in narrow mode).
[#1150](https://github.com/rust-lang/mdBook/pull/1150)
- Prevent recursive copies when the destination directory is contained in the
source directory.
[#1135](https://github.com/rust-lang/mdBook/pull/1135)
- Adjusted the menu bar animation to not immediately obscure the top content.
[#989](https://github.com/rust-lang/mdBook/pull/989)
- Fix for comments in SUMMARY.md that appear between items.
[#1167](https://github.com/rust-lang/mdBook/pull/1167)
## mdBook 0.3.5
[6e0d0fa...efdb832](https://github.com/rust-lang/mdBook/compare/6e0d0fa...efdb832)
### Changed
- The `default-theme` config setting is now case-insensitive.
[#1079](https://github.com/rust-lang/mdBook/pull/1079)
### Fixed
- Fixed `#` hidden Rust code lines not rendering properly.
[#1088](https://github.com/rust-lang/mdBook/pull/1088)
- Updated pulldown-cmark to 0.6.1, fixing several issues.
[#1021](https://github.com/rust-lang/mdBook/pull/1021)
## mdBook 0.3.4
[e5f77aa...6e0d0fa](https://github.com/rust-lang/mdBook/compare/e5f77aa...6e0d0fa)
### Changed
- Switch to relative `rem` font sizes from `px`.
[#894](https://github.com/rust-lang/mdBook/pull/894)
- Migrated repository to https://github.com/rust-lang/mdBook/
[#1083](https://github.com/rust-lang/mdBook/pull/1083)
## mdBook 0.3.3
[2b649fe...e5f77aa](https://github.com/rust-lang/mdBook/compare/2b649fe...e5f77aa)
### Changed
- Improvements to the automatic dark theme selection.
[#1069](https://github.com/rust-lang/mdBook/pull/1069)
- Fragment links now prevent scrolling the header behind the menu bar.
[#1077](https://github.com/rust-lang/mdBook/pull/1077)
### Fixed
- Fixed error when building a book that has a spacer immediately after the
first chapter.
[#1075](https://github.com/rust-lang/mdBook/pull/1075)
## mdBook 0.3.2
[9cd47eb...2b649fe](https://github.com/rust-lang/mdBook/compare/9cd47eb...2b649fe)
### Added
- Added a markdown renderer, which is off by default. This may be useful for
debugging preprocessors.
[#1018](https://github.com/rust-lang/mdBook/pull/1018)
- Code samples may now include line numbers with the
`output.html.playpen.line-numbers` configuration value.
[#1035](https://github.com/rust-lang/mdBook/pull/1035)
- The `watch` and `serve` commands will now ignore files listed in
`.gitignore`.
[#1044](https://github.com/rust-lang/mdBook/pull/1044)
- Added automatic dark-theme detection based on the CSS `prefers-color-scheme`
feature. This may be enabled by setting `output.html.preferred-dark-theme`
to your preferred dark theme.
[#1037](https://github.com/rust-lang/mdBook/pull/1037)
- Added `rustdoc_include` preprocessor. This makes it easier to include
portions of an external Rust source file. The rest of the file is hidden,
but the user may expand it to see the entire file, and will continue to work
with `mdbook test`.
[#1003](https://github.com/rust-lang/mdBook/pull/1003)
- Added Ctrl-Enter shortcut to the playpen editor to automatically run the
sample.
[#1066](https://github.com/rust-lang/mdBook/pull/1066)
- Added `output.html.playpen.copyable` configuration option to disable
the copy button.
[#1050](https://github.com/rust-lang/mdBook/pull/1050)
- Added ability to dynamically expand and fold sections within the sidebar.
See the `output.html.fold` configuration to enable this feature.
[#1027](https://github.com/rust-lang/mdBook/pull/1027)
### Changed
- Use standard `scrollbar-color` CSS along with webkit extension
[#816](https://github.com/rust-lang/mdBook/pull/816)
- The renderer build directory is no longer deleted before the renderer is
run. This allows a backend to cache results between runs.
[#985](https://github.com/rust-lang/mdBook/pull/985)
- Next/prev links now highlight on hover to indicate it is clickable.
[#994](https://github.com/rust-lang/mdBook/pull/994)
- Increase padding of table headers.
[#824](https://github.com/rust-lang/mdBook/pull/824)
- Errors in `[output.html]` config are no longer ignored.
[#1033](https://github.com/rust-lang/mdBook/pull/1033)
- Updated highlight.js for syntax highlighting updates (primarily to add
async/await to Rust highlighting).
[#1041](https://github.com/rust-lang/mdBook/pull/1041)
- Raised minimum supported rust version to 1.35.
[#1003](https://github.com/rust-lang/mdBook/pull/1003)
- Hidden code lines are no longer dynamically removed via JavaScript, but
instead managed with CSS.
[#846](https://github.com/rust-lang/mdBook/pull/846)
[#1065](https://github.com/rust-lang/mdBook/pull/1065)
- Changed the default font set for the ACE editor, giving preference to
"Source Code Pro".
[#1062](https://github.com/rust-lang/mdBook/pull/1062)
- Windows 32-bit releases are no longer published.
[#1071](https://github.com/rust-lang/mdBook/pull/1071)
### Fixed
- Fixed sidebar auto-scrolling.
[#1052](https://github.com/rust-lang/mdBook/pull/1052)
- Fixed error message when running `clean` multiple times.
[#1055](https://github.com/rust-lang/mdBook/pull/1055)
- Actually fix the "next" link on index.html. The previous fix didn't work.
[#1005](https://github.com/rust-lang/mdBook/pull/1005)
- Stop using `inline-block` for `inline code`, fixing selection highlighting
and some rendering issues.
[#1058](https://github.com/rust-lang/mdBook/pull/1058)
- Fix header auto-hide on browsers with momentum scrolling that allows
negative `scrollTop`.
[#1070](https://github.com/rust-lang/mdBook/pull/1070)
## mdBook 0.3.1
[69a08ef...9cd47eb](https://github.com/rust-lang/mdBook/compare/69a08ef...9cd47eb)
### Added
- 🔥 Added ability to include files using anchor points instead of line numbers.
[#851](https://github.com/rust-lang/mdBook/pull/851)
- Added `language` configuration value to set the language of the book, which
will affect things like the `<html lang="en">` tag.
[#941](https://github.com/rust-lang/mdBook/pull/941)
### Changed
- Updated to handlebars 2.0.
[#977](https://github.com/rust-lang/mdBook/pull/977)
### Fixed
- Fixed memory leak warning.
[#967](https://github.com/rust-lang/mdBook/pull/967)
- Fix more print.html links.
[#963](https://github.com/rust-lang/mdBook/pull/963)
- Fixed crash on some unicode input.
[#978](https://github.com/rust-lang/mdBook/pull/978)
## mdBook 0.3.0
[6cbc41d...69a08ef](https://github.com/rust-lang/mdBook/compare/6cbc41d...69a08ef)
### Added
- Added ability to resize the sidebar.
[#849](https://github.com/rust-lang/mdBook/pull/849)
- Added `load_with_config_and_summary` function to `MDBook` to be able to
build a book with a custom `Summary`.
[#883](https://github.com/rust-lang/mdBook/pull/883)
- Set `noindex` on `print.html` page to prevent robots from indexing it.
[#844](https://github.com/rust-lang/mdBook/pull/844)
- Added support for ~~strikethrough~~ and GitHub-style tasklists.
[#952](https://github.com/rust-lang/mdBook/pull/952)
### Changed
- Command-line help output is now colored.
[#861](https://github.com/rust-lang/mdBook/pull/861)
- The build directory is now deleted before rendering starts, instead of after
if finishes.
[#878](https://github.com/rust-lang/mdBook/pull/878)
- Removed dependency on `same-file` crate.
[#903](https://github.com/rust-lang/mdBook/pull/903)
- 💥 Renamed `with_preprecessor` to `with_preprocessor`.
[#906](https://github.com/rust-lang/mdBook/pull/906)
- Updated ACE editor to 1.4.4, should remove a JavaScript console warning.
[#935](https://github.com/rust-lang/mdBook/pull/935)
- Dependencies have been updated.
[#934](https://github.com/rust-lang/mdBook/pull/934)
[#945](https://github.com/rust-lang/mdBook/pull/945)
- Highlight.js has been updated. This fixes some TOML highlighting, and adds
Julia support.
[#942](https://github.com/rust-lang/mdBook/pull/942)
- 🔥 Updated to pulldown-cmark 0.5. This may have significant changes to the
formatting of existing books, as the newer version has more accurate
interpretation of the CommonMark spec and a large number of bug fixes and
changes.
[#898](https://github.com/rust-lang/mdBook/pull/898)
- The `diff` language should now highlight correctly.
[#943](https://github.com/rust-lang/mdBook/pull/943)
- Make the blank region of a header not clickable.
[#948](https://github.com/rust-lang/mdBook/pull/948)
- Rustdoc tests now use the preprocessed content instead of the raw,
unpreprocessed content.
[#891](https://github.com/rust-lang/mdBook/pull/891)
### Fixed
- Fixed file change detection so that `mdbook serve` only reloads once when
multiple files are changed at once.
[#870](https://github.com/rust-lang/mdBook/pull/870)
- Fixed on-hover color highlighting for links in sidebar.
[#834](https://github.com/rust-lang/mdBook/pull/834)
- Fixed loss of focus when clicking the "Copy" button in code blocks.
[#867](https://github.com/rust-lang/mdBook/pull/867)
- Fixed incorrectly stripping the path for `additional-js` files.
[#796](https://github.com/rust-lang/mdBook/pull/796)
- Fixed color of `code spans` that are links.
[#905](https://github.com/rust-lang/mdBook/pull/905)
- Fixed "next" navigation on index.html.
[#916](https://github.com/rust-lang/mdBook/pull/916)
- Fixed keyboard chapter navigation for `file` urls.
[#915](https://github.com/rust-lang/mdBook/pull/915)
- Fixed bad wrapping for inline code on some browsers.
[#818](https://github.com/rust-lang/mdBook/pull/818)
- Properly load an existing `SUMMARY.md` in `mdbook init`.
[#841](https://github.com/rust-lang/mdBook/pull/841)
- Fixed some broken links in `print.html`.
[#871](https://github.com/rust-lang/mdBook/pull/871)
- The Rust Playground link now supports the 2018 edition.
[#946](https://github.com/rust-lang/mdBook/pull/946)
## mdBook 0.2.3 (2018-01-18)
[2c20c99...6cbc41d](https://github.com/rust-lang/mdBook/compare/2c20c99...6cbc41d)
### Added
- Added an optional button to the top of the page which will link to a git
repository. Use the `git-repository-url` and `git-repository-icon` options
in the `[output.html]` section to enable it and set its appearance.
[#802](https://github.com/rust-lang/mdBook/pull/802)
- Added a `default-theme` option to the `[output.html]` section.
[#804](https://github.com/rust-lang/mdBook/pull/804)
### Changed
- 💥 Header ID anchors no longer add an arbitrary `a` character for headers
that start with a non-ascii-alphabetic character.
[#788](https://github.com/rust-lang/mdBook/pull/788)
### Fixed
- Fix websocket hostname usage
[#865](https://github.com/rust-lang/mdBook/pull/865)
- Fixing links in print.html
[#866](https://github.com/rust-lang/mdBook/pull/866)
## mdBook 0.2.2 (2018-10-19)
[7e2e095...2c20c99](https://github.com/rust-lang/mdBook/compare/7e2e095...2c20c99)
### Added
- 🎉 Process-based custom preprocessors. See [the
docs](https://rust-lang.github.io/mdBook/for_developers/preprocessors.html)
for more.
[#792](https://github.com/rust-lang/mdBook/pull/792)
- 🎉 Configurable preprocessors.
Added `build.use-default-preprocessors` boolean TOML key to allow disabling
the built-in `links` and `index` preprocessors.
Added `[preprocessor]` TOML tables to configure each preprocessor.
Specifying `[preprocessor.links]` or `[preprocessor.index]` will enable the
respective built-in preprocessor if `build.use-default-preprocessors` is
`false`.
Added `fn supports_renderer(&self, renderer: &str) -> bool` to the
`Preprocessor` trait to specify if the preprocessor supports the given
renderer. The default implementation always returns `true`.
`Preprocessor::run` now takes a book by value instead of a mutable
reference. It should return a `Book` value with the intended modifications.
Added `PreprocessorContext::renderer` to indicate the renderer being used.
[#658](https://github.com/rust-lang/mdBook/pull/658)
[#787](https://github.com/rust-lang/mdBook/pull/787)
### Fixed
- Fix paths to additional CSS and JavaScript files
[#777](https://github.com/rust-lang/mdBook/pull/777)
- Ensure section numbers are correctly incremented after a horizontal
separator
[#790](https://github.com/rust-lang/mdBook/pull/790)
## mdBook 0.2.1 (2018-08-22)
[91ffca1...7e2e095](https://github.com/rust-lang/mdBook/compare/91ffca1...7e2e095)
### Changed
- Update to handlebars-rs 1.0
[#761](https://github.com/rust-lang/mdBook/pull/761)
### Fixed
- Fix table colors, broken by Stylus -> CSS transition
[#765](https://github.com/rust-lang/mdBook/pull/765)
## mdBook 0.2.0 (2018-08-02)
### Changed
- 💥 This release changes how links are handled in mdBook. Previously, relative
links were interpreted relative to the book's root. In `0.2.0`+ links are
relative to the page they are in, and use the `.md` extension. This has [several
advantages](https://github.com/rust-lang/mdBook/pull/603#issue-166701447),
such as making links work in other markdown viewers like GitHub. You will
likely have to change links in your book to accommodate this change. For
example, a book with this layout:
```
chapter_1/
section_1.md
section_2.md
SUMMARY.md
```
Previously a link in `section_1.md` to `section_2.md` would look like this:
```markdown
[section_2](chapter_1/section_2.html)
```
Now it must be changed to this:
```markdown
[section_2](section_2.md)
```
- 💥 `mdbook test --library-path` now accepts a comma-delimited list of
arguments rather than taking all following arguments. This makes it easier
to handle the trailing book directory argument without always needing to put
` -- ` before it. Multiple instances of the option continue to be accepted:
`mdbook test -L foo -L bar`.
- 💥 `mdbook serve` has some of its options renamed for clarity. See `mdbook
help serve` for details.
- Embedded rust playpens now use the "stable" playground API.
[#754](https://github.com/rust-lang/mdBook/pull/754)
### Fixed
- Escaped includes (`\{{#include file.rs}}`) will now render correctly.
[f30ce01](https://github.com/rust-lang/mdBook/commit/f30ce0184d71e342141145472bf816419d30a2c5)
- `index.html` will now render correctly when the book's first section is
inside a subdirectory.
[#756](https://github.com/rust-lang/mdBook/pull/756)

View File

@@ -5,22 +5,22 @@ Welcome stranger!
If you have come here to learn how to contribute to mdBook, we have some tips for you!
First of all, don't hesitate to ask questions!
Use the [issue tracker](https://github.com/rust-lang-nursery/mdBook/issues), no question is too simple.
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
Any issue is up for the grabbing, but if you are starting out, you might be interested in the
[E-Easy issues](https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AE-Easy).
[E-Easy issues](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AE-Easy).
Those are issues that are considered more straightforward for beginners to Rust or the codebase itself.
These issues can be a good launching pad for more involved issues. Easy tasks for a first time contribution
include documentation improvements, new tests, examples, updating dependencies, etc.
If you come from a web development background, you might be interested in issues related to web technologies tagged
[A-JavaScript](https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-JavaScript),
[A-Style](https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-Style),
[A-HTML](https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-HTML) or
[A-Mobile](https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-Mobile).
[A-JavaScript](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-JavaScript),
[A-Style](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-Style),
[A-HTML](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-HTML) or
[A-Mobile](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-Mobile).
When you decide you want to work on a specific issue, ping us on that issue so that we can assign it to you.
Again, do not hesitate to ask questions. We will gladly mentor anyone that want to tackle an issue.
@@ -41,36 +41,60 @@ mdBook builds on stable Rust, if you want to build mdBook from source, here are
0. Clone this repository with git.
```
git clone https://github.com/rust-lang-nursery/mdBook.git
git clone https://github.com/rust-lang/mdBook.git
```
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`.
### Code Quality
### Making changes to the style
We love code quality and Rust has some excellent tools to assist you with contributions.
mdBook doesn't use CSS directly but uses [Stylus](http://stylus-lang.com/), a CSS-preprocessor which compiles to CSS.
#### Formatting Code with rustfmt
When you want to change the style, it is important to not change the CSS directly because any manual modification to
the CSS files will be overwritten when compiling the stylus files. Instead, you should make your changes directly in the
[stylus files](https://github.com/rust-lang-nursery/mdBook/tree/master/src/theme/stylus) and regenerate the CSS.
Before you make your Pull Request to the project, please run it through the `rustfmt` utility.
This will ensure we have good quality source code that is better for us all to maintain.
For this to work, you first need [Node and NPM](https://nodejs.org/en/) installed on your machine.
Then run the following command to install both [stylus](http://stylus-lang.com/) and [nib](https://tj.github.io/nib/), you might need `sudo` to install successfully.
[rustfmt](https://github.com/rust-lang/rustfmt) has a lot more information on the project.
The quick guide is
```
npm install -g stylus nib
```
1. Install it
```
rustup component add rustfmt
```
1. You can now run `rustfmt` on a single file simply by...
```
rustfmt src/path/to/your/file.rs
```
... or you can format the entire project with
```
cargo fmt
```
When run through `cargo` it will format all bin and lib files in the current crate.
When that finished, you can simply regenerate the CSS files by building mdBook with the following command:
For more information, such as running it from your favourite editor, please see the `rustfmt` project. [rustfmt](https://github.com/rust-lang/rustfmt)
```
cargo build --features=regenerate-css
```
This should automatically call the appropriate stylus command to recompile the files to CSS and include them in the project.
#### Finding Issues with Clippy
Clippy is a code analyser/linter detecting mistakes, and therfore helps to improve your code.
Like formatting your code with `rustfmt`, running clippy regularly and before your Pull Request will
help us maintain awesome code.
The best documentation can be found over at [rust-clippy](https://github.com/rust-lang/rust-clippy)
1. To install
```
rustup component add clippy
```
2. Running clippy
```
cargo clippy
```
Clippy has an ever growing list of checks, that are managed in [lint files](https://rust-lang.github.io/rust-clippy/master/index.html).
### Making a pull-request
@@ -78,7 +102,7 @@ When you feel comfortable that your changes could be integrated into mdBook, you
One of the core maintainers will then approve the changes or request some changes before it gets merged.
If you want to make your pull-request even better, you might want to run [Clippy](https://github.com/Manishearth/rust-clippy)
and [rustfmt](https://github.com/rust-lang-nursery/rustfmt) on the code first.
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:

2004
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,78 +1,63 @@
[package]
name = "mdbook"
version = "0.1.4"
authors = ["Mathieu David <mathieudavid@mathieudavid.org>", "Michael-F-Bryan <michaelfbryan@gmail.com>"]
description = "Create books from markdown files"
documentation = "http://rust-lang-nursery.github.io/mdBook/index.html"
repository = "https://github.com/rust-lang-nursery/mdBook"
version = "0.4.5"
authors = [
"Mathieu David <mathieudavid@mathieudavid.org>",
"Michael-F-Bryan <michaelfbryan@gmail.com>",
"Matt Ickstadt <mattico8@gmail.com>"
]
documentation = "http://rust-lang.github.io/mdBook/index.html"
edition = "2018"
exclude = ["/guide/*"]
keywords = ["book", "gitbook", "rustbook", "markdown"]
license = "MPL-2.0"
readme = "README.md"
build = "build.rs"
exclude = [
"book-example/*",
"src/theme/stylus/**",
]
[package.metadata.release]
sign-commit = true
push-remote = "origin"
tag-prefix = "v"
repository = "https://github.com/rust-lang/mdBook"
description = "Creates a book from markdown files"
[dependencies]
clap = "2.24"
anyhow = "1.0.28"
chrono = "0.4"
handlebars = "0.32"
serde = "1.0"
serde_derive = "1.0"
error-chain = "0.11"
serde_json = "1.0"
pulldown-cmark = "0.1.2"
clap = "2.24"
env_logger = "0.7.1"
handlebars = "3.0"
lazy_static = "1.0"
log = "0.4"
env_logger = "0.5.0-rc.1"
toml = "0.4"
memchr = "2.0"
open = "1.1"
regex = "0.2.1"
tempdir = "0.3.4"
itertools = "0.7"
pulldown-cmark = "0.7.0"
regex = "1.0.0"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
shlex = "0.1"
toml-query = "0.6"
tempfile = "3.0"
toml = "0.5.1"
# Watch feature
notify = { version = "4.0", optional = true }
time = { version = "0.1.34", optional = true }
crossbeam = { version = "0.3", optional = true }
gitignore = { version = "1.0", optional = true }
# Serve feature
iron = { version = "0.5", optional = true }
staticfile = { version = "0.4", optional = true }
ws = { version = "0.7", optional = true}
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 }
# Search feature
elasticlunr-rs = { version = "1.0", optional = true }
ammonia = { version = "1.1", optional = true }
[build-dependencies]
error-chain = "0.11"
elasticlunr-rs = { version = "2.3", optional = true, default-features = false }
ammonia = { version = "3", optional = true }
[dev-dependencies]
select = "0.4"
pretty_assertions = "0.5"
select = "0.5"
pretty_assertions = "0.6"
walkdir = "2.0"
pulldown-cmark-to-cmark = "1.1.0"
[features]
default = ["output", "watch", "serve", "search"]
debug = []
output = []
regenerate-css = []
watch = ["notify", "time", "crossbeam"]
serve = ["iron", "staticfile", "ws"]
default = ["watch", "serve", "search"]
watch = ["notify", "gitignore"]
serve = ["futures-util", "tokio", "warp"]
search = ["elasticlunr-rs", "ammonia"]
[[bin]]
doc = false
name = "mdbook"
path = "src/bin/mdbook.rs"

119
README.md
View File

@@ -1,25 +1,8 @@
# mdBook
<table>
<tr>
<td><strong>Linux / OS X</strong></td>
<td>
<a href="https://travis-ci.org/rust-lang-nursery/mdBook"><img src="https://travis-ci.org/rust-lang-nursery/mdBook.svg?branch=master"></a>
</td>
</tr>
<tr>
<td><strong>Windows</strong></td>
<td>
<a href="https://ci.appveyor.com/project/rust-lang-libs/mdbook"><img src="https://ci.appveyor.com/api/projects/status/ysyke2rvo85sni55?svg=true"></a>
</td>
</tr>
<tr>
<td colspan="2">
<a href="https://crates.io/crates/mdbook"><img src="https://img.shields.io/crates/v/mdbook.svg"></a>
<a href="LICENSE"><img src="https://img.shields.io/github/license/rust-lang-nursery/mdBook.svg"></a>
</td>
</tr>
</table>
[![Build Status](https://github.com/rust-lang/mdBook/workflows/CI/badge.svg?event=push)](https://github.com/rust-lang/mdBook/actions?workflow=CI)
[![crates.io](https://img.shields.io/crates/v/mdbook.svg)](https://crates.io/crates/mdbook)
[![LICENSE](https://img.shields.io/github/license/rust-lang/mdBook.svg)](LICENSE)
mdBook is a utility to create modern online books from Markdown files.
@@ -41,7 +24,7 @@ There are multiple ways to install mdBook.
2. **From Crates.io**
This requires [Rust] and Cargo to be installed. Once you have installed
This requires at least [Rust] 1.39 and Cargo to be installed. Once you have installed
Rust, type the following in the terminal:
```
@@ -57,33 +40,37 @@ There are multiple ways to install mdBook.
another CI server, we recommend that you specify a semver version range for
mdBook when you install it through your script!
This will constrain the server to install the latests **non-breaking**
This will constrain the server to install the latest **non-breaking**
version of mdBook and will prevent your books from failing to build because
we released a new version. For example:
we released a new version.
You can also disable default features to speed up compile time.
Example:
```
cargo install mdbook --vers "^0.1.0"
cargo install mdbook --no-default-features --features output --vers "^0.1.0"
```
3. **From Git**
3. **From Git**
The version published to crates.io will ever so slightly be behind the
version hosted here on GitHub. If you need the latest version you can build
the git version of mdBook yourself. Cargo makes this ***super easy***!
```
cargo install --git https://github.com/rust-lang-nursery/mdBook.git mdbook
cargo install --git https://github.com/rust-lang/mdBook.git mdbook
```
Again, make sure to add the Cargo bin directory to your `PATH`.
4. **For Contributions**
4. **For Contributions**
If you want to contribute to mdBook you will have to clone the repository on
your local machine:
```
git clone https://github.com/rust-lang-nursery/mdBook.git
git clone https://github.com/rust-lang/mdBook.git
```
`cd` into `mdBook/` and run
@@ -98,7 +85,7 @@ There are multiple ways to install mdBook.
## Usage
mdBook will primarily be used as a command line tool, even though it exposes
mdBook is primarily used as a command line tool, even though it exposes
all its functionality as a Rust crate for integration in other projects.
Here are the main commands you will want to run. For a more exhaustive
@@ -145,6 +132,60 @@ explanation, check out the [User Guide].
Delete directory in which generated book is located.
### 3rd Party Plugins
The way a book is loaded and rendered can be configured by the user via third
party plugins. These plugins are just programs which will be invoked during the
build process and are split into roughly two categories, *preprocessors* and
*renderers*.
Preprocessors are used to transform a book before it is sent to a renderer.
One example would be to replace all occurrences of
`{{#include some_file.ext}}` with the contents of that file. Some existing
preprocessors are:
- `index` - a built-in preprocessor (enabled by default) which will transform
all `README.md` chapters to `index.md` so `foo/README.md` can be accessed via
the url `foo/` when published to a browser
- `links` - a built-in preprocessor (enabled by default) for expanding the
`{{# playground}}` and `{{# include}}` helpers in a chapter.
Renderers are given the final book so they can do something with it. This is
typically used for, as the name suggests, rendering the document in a particular
format, however there's nothing stopping a renderer from doing static analysis
of a book in order to validate links or run tests. Some existing renderers are:
- `html` - the built-in renderer which will generate a HTML version of the book
- `markdown` - the built-in renderer (disabled by default) which will run
preprocessors then output the resulting Markdown. Useful for debugging
preprocessors.
- [`linkcheck`] - a backend which will check that all links are valid
- [`epub`] - an experimental EPUB generator
> **Note for Developers:** Feel free to send us a PR if you've developed your
> own plugin and want it mentioned here.
A preprocessor or renderer is enabled by installing the appropriate program and
then mentioning it in the book's `book.toml` file.
```console
$ cargo install mdbook-linkcheck
$ edit book.toml && cat book.toml
[book]
title = "My Awesome Book"
authors = ["Michael-F-Bryan"]
[output.html]
[output.linkcheck] # enable the "mdbook-linkcheck" renderer
$ mdbook build
2018-10-20 13:57:51 [INFO] (mdbook::book): Book building has started
2018-10-20 13:57:51 [INFO] (mdbook::book): Running the html backend
2018-10-20 13:57:53 [INFO] (mdbook::book): Running the linkcheck backend
```
For more information on the plugin system, consult the [User Guide].
### As a library
@@ -163,12 +204,12 @@ Contributions are highly appreciated and encouraged! Don't hesitate to
participate to discussions in the issues, propose new features and ask for
help.
If you are just starting out with Rust, there are a series of issus that are
If you are just starting out with Rust, there are a series of issues that are
tagged [E-Easy] and **we will gladly mentor you** so that you can successfully
go through the process of fixing a bug or adding a new feature! Let us know if
you need any help.
For more info about contributing, check out our [contribution guide] who helps
For more info about contributing, check out our [contribution guide] which helps
you go through the build and contribution process!
There is also a [rendered version][master-docs] of the latest API docs
@@ -180,12 +221,14 @@ available, for those hacking on `master`.
All the code in this repository is released under the ***Mozilla Public License v2.0***, for more information take a look at the [LICENSE] file.
[User Guide]: https://rust-lang-nursery.github.io/mdBook/
[User Guide]: https://rust-lang.github.io/mdBook/
[API docs]: https://docs.rs/mdbook/*/mdbook/
[E-Easy]: https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AE-Easy
[contribution guide]: https://github.com/rust-lang-nursery/mdBook/blob/master/CONTRIBUTING.md
[LICENSE]: https://github.com/rust-lang-nursery/mdBook/blob/master/LICENSE
[releases]: https://github.com/rust-lang-nursery/mdBook/releases
[E-Easy]: https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AE-Easy
[contribution guide]: https://github.com/rust-lang/mdBook/blob/master/CONTRIBUTING.md
[LICENSE]: https://github.com/rust-lang/mdBook/blob/master/LICENSE
[releases]: https://github.com/rust-lang/mdBook/releases
[Rust]: https://www.rust-lang.org/
[CLI docs]: http://rust-lang-nursery.github.io/mdBook/cli/init.html
[master-docs]: http://rust-lang-nursery.github.io/mdBook/mdbook/
[CLI docs]: http://rust-lang.github.io/mdBook/cli/init.html
[master-docs]: http://rust-lang.github.io/mdBook/
[`linkcheck`]: https://crates.io/crates/mdbook-linkcheck
[`epub`]: https://crates.io/crates/mdbook-epub

View File

@@ -1,69 +0,0 @@
environment:
global:
PROJECT_NAME: mdBook
nodejs_version: "6"
matrix:
# Stable channel
- TARGET: i686-pc-windows-msvc
RUST_CHANNEL: stable
- TARGET: x86_64-pc-windows-msvc
RUST_CHANNEL: stable
# Beta channel
- TARGET: i686-pc-windows-msvc
RUST_CHANNEL: beta
- TARGET: x86_64-pc-windows-msvc
RUST_CHANNEL: beta
# Nightly channel
- TARGET: i686-pc-windows-msvc
RUST_CHANNEL: nightly
- TARGET: x86_64-pc-windows-msvc
RUST_CHANNEL: nightly
# Install Rust and Cargo
install:
- ps: >-
If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') {
$Env:PATH += ';C:\msys64\mingw64\bin'
} ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') {
$Env:PATH += ';C:\msys64\mingw32\bin'
}
- curl -sSf -o rustup-init.exe https://win.rustup.rs/
- rustup-init.exe -y --default-host %TARGET% --default-toolchain %RUST_CHANNEL%
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- rustc -Vv
- cargo -V
- ps: Install-Product node $env:nodejs_version
- node --version
- npm --version
- npm install -g stylus nib
build: false
# Equivalent to Travis' `script` phase
test_script:
- cargo build --verbose
- cargo build --verbose --features=regenerate-css
- cargo test --verbose
before_deploy:
# Generate artifacts for release
- cargo build --release
- mkdir staging
- copy target\release\mdbook.exe staging
- cd staging
- 7z a ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip *
- appveyor PushArtifact ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip
deploy:
description: 'Windows release'
artifact: /.*\.zip/
auth_token:
secure: QQhjKVyz7mpjlyGhlXytbFQQfKFQWTahHkD+B0NzIUoEVqO7ZLWjnoWasvLqW4nE
provider: GitHub
on:
RUST_CHANNEL: stable
appveyor_repo_tag: true
branches:
only:
- master

View File

@@ -1,15 +0,0 @@
# mdBook
**mdBook** is a command line tool and Rust crate to create books using Markdown files. It's very similar to Gitbook but written in [Rust](http://www.rust-lang.org).
What you are reading serves as an example of the output of mdBook and at the same time as a high-level documentation.
mdBook is free and open source, you can find the source code on [Github](https://github.com/rust-lang-nursery/mdBook). Issues and feature requests can be posted on the [Github Issue tracker](https://github.com/rust-lang-nursery/mdBook/issues).
## API docs
Alongside this book you can also read the [API docs](https://docs.rs/mdbook/*/mdbook/) generated by Rustdoc if you would like to use mdBook as a crate or write a new renderer and need a more low-level overview.
## License
mdBook, all the source code, is released under the [Mozilla Public License v2.0](https://www.mozilla.org/MPL/2.0/)

View File

@@ -1,35 +0,0 @@
# The build command
The build command is used to render your book:
```bash
mdbook build
```
It will try to parse your `SUMMARY.md` file to understand the structure of your book
and fetch the corresponding files.
The rendered output will maintain the same directory structure as the source for
convenience. Large books will therefore remain structured when rendered.
#### Specify a directory
Like `init`, the `build` command can take a directory as an argument to use
instead of the current working directory.
```bash
mdbook build path/to/book
```
#### --open
When you use the `--open` (`-o`) option, mdbook will open the rendered book in
your default web browser after building it.
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for your book.
-------------------
***note:*** *make sure to run the build command in the root directory and not in the source directory*

View File

@@ -1,21 +0,0 @@
# The clean command
The clean command is used to delete the generated book and any other build
artifacts.
```bash
mdbook clean
```
It will try to delete the built book. If a path is provided, it will be used.
#### Specify a directory
Like `init`, the `clean` command can take a directory as an argument to use
instead of the normal build directory.
```bash
mdbook clean --dest-dir=path/to/book
```
`path/to/book` could be absolute or relative.

View File

@@ -1,35 +0,0 @@
# Command Line Tool
mdBook can be used either as a command line tool or a [Rust crate](https://crates.io/crates/mdbook).
Let's focus on the command line tool capabilities first.
## Install
### Pre-requisite
mdBook is written in **[Rust](https://www.rust-lang.org/)** and therefore needs to be compiled with **Cargo**, because we don't yet offer ready-to-go binaries. If you haven't already installed Rust, please go ahead and [install it](https://www.rust-lang.org/downloads.html) now.
### Install Crates.io version
Installing mdBook is relatively easy if you already have Rust and Cargo installed. You just have to type this snippet in your terminal:
```bash
cargo install mdbook
```
This will fetch the source code from [Crates.io](https://crates.io/) and compile it. You will have to add Cargo's `bin` directory to your `PATH`.
Run `mdbook help` in your terminal to verify if it works. Congratulations, you have installed mdBook!
### Install Git version
The **[git version](https://github.com/rust-lang-nursery/mdBook)** contains all the latest bug-fixes and features, that will be released in the next version on **Crates.io**, if you can't wait until the next release. You can build the git version yourself. Open your terminal and navigate to the directory of you choice. We need to clone the git repository and then build it with Cargo.
```bash
git clone --depth=1 https://github.com/rust-lang-nursery/mdBook.git
cd mdBook
cargo build --release
```
The executable `mdbook` will be in the `./target/release` folder, this should be added to the path.

View File

@@ -1,45 +0,0 @@
# The init command
There is some minimal boilerplate that is the same for every new book. It's for this purpose that mdBook includes an `init` command.
The `init` command is used like this:
```bash
mdbook init
```
When using the `init` command for the first time, a couple of files will be set up for you:
```bash
book-test/
├── book
└── src
├── chapter_1.md
└── SUMMARY.md
```
- The `src` directory is were you write your book in markdown. It contains all the source files,
configuration files, etc.
- 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.html).
#### Tip & Trick: Hidden Feature
When a `SUMMARY.md` file already exists, the `init` command will first parse it and generate the missing files according to the paths used in the `SUMMARY.md`. This allows you to think and create the whole structure of your book and then let mdBook generate it for you.
#### Specify a directory
When using the `init` command, you can also specify a directory, instead of using the current working directory,
by appending a path to the command:
```bash
mdbook init path/to/book
```
## --theme
When you use the `--theme` argument, the default theme will be copied into a 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.

View File

@@ -1,40 +0,0 @@
# The serve command
The `serve` command is useful when you want to preview your book. It also does hot reloading of the webpage whenever a file changes.
It achieves this by serving the books content over `localhost:3000` (unless otherwise configured, see below) and runs a websocket server on `localhost:3001` which triggers the reloads.
This preferred by many for writing books with mdbook because it allows for you to see the result of your work instantly after every file change.
#### Specify a directory
Like `watch`, `serve` can take a directory as an argument to use instead of
the current working directory.
```bash
mdbook serve path/to/book
```
#### Server options
`serve` has four options: the http port, the websocket port, the interface to serve on, and the public address of the server so that the browser may reach the websocket server.
For example: suppose you had an nginx server for SSL termination which has a public address of 192.168.1.100 on port 80 and proxied that to 127.0.0.1 on port 8000. To run use the nginx proxy do:
```bash
mdbook serve path/to/book -p 8000 -i 127.0.0.1 -a 192.168.1.100
```
If you were to want live reloading for this you would need to proxy the websocket calls through nginx as well from `192.168.1.100:<WS_PORT>` to `127.0.0.1:<WS_PORT>`. The `-w` flag allows for the websocket port to be configured.
#### --open
When you use the `--open` (`-o`) option, mdbook will open the book in your
your default web browser after starting the server.
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for your book.
-----
***note:*** *the `serve` command has not gotten a lot of testing yet, there could be some rough edges. If you discover a problem, please report it [on Github](https://github.com/rust-lang-nursery/mdBook/issues)*

View File

@@ -1,19 +0,0 @@
# The test command
When writing a book, you sometimes need to automate some tests. For example, [The Rust Programming Book](https://doc.rust-lang.org/stable/book/) uses a lot of code examples that could get outdated.
Therefore it is very important for them to be able to automatically test these code examples.
mdBook supports a `test` command that will run all available tests in mdBook. At the moment, only one test is available:
*"Test Rust code examples using Rustdoc"*, but I hope this will be expanded in the future to include more tests like:
- checking for broken links
- checking for unused files
- ...
In the future I would like the user to be able to enable / disable test from the `book.toml` configuration file and support custom tests.
**How to use it:**
```bash
$ mdbook test
[*]: Testing file: "/mdBook/book-example/src/README.md”
```

View File

@@ -1,26 +0,0 @@
# The watch command
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.
#### Specify a directory
Like `init` and `build`, `watch` can take a directory as an argument to use
instead of the current working directory.
```bash
mdbook watch path/to/book
```
#### --open
When you use the `--open` (`-o`) option, mdbook will open the rendered book in
your default web browser.
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for your book.
-----
***note:*** *the `watch` command has not gotten a lot of testing yet, there could be some rough edges. If you discover a problem, please report it [on Github](https://github.com/rust-lang-nursery/mdBook/issues)*

View File

@@ -1,96 +0,0 @@
# Preprocessors
A *preprocessor* is simply a bit of code which gets run immediately after the
book is loaded and before it gets rendered, allowing you to update and mutate
the book. Possible use cases are:
- Creating custom helpers like `\{{#include /path/to/file.md}}`
- Updating links so `[some chapter](some_chapter.md)` is automatically changed
to `[some chapter](some_chapter.html)` for the HTML renderer
- Substituting in latex-style expressions (`$$ \frac{1}{3} $$`) with their
mathjax equivalents
## Implementing a Preprocessor
A preprocessor is represented by the `Preprocessor` trait.
```rust
pub trait Preprocessor {
fn name(&self) -> &str;
fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()>;
}
```
Where the `PreprocessorContext` is defined as
```rust
pub struct PreprocessorContext {
pub root: PathBuf,
pub config: Config,
}
```
## A complete Example
The magic happens within the `run(...)` method of the [`Preprocessor`][preprocessor-docs] trait implementation.
As direct access to the chapters is not possible, you will probably end up iterating
them using `for_each_mut(...)`:
```rust
book.for_each_mut(|item: &mut BookItem| {
if let BookItem::Chapter(ref mut chapter) = *item {
eprintln!("{}: processing chapter '{}'", self.name(), chapter.name);
res = Some(
match Deemphasize::remove_emphasis(&mut num_removed_items, chapter) {
Ok(md) => {
chapter.content = md;
Ok(())
}
Err(err) => Err(err),
},
);
}
});
```
The `chapter.content` is just a markdown formatted string, and you will have to
process it in some way. Even though it's entirely possible to implement some sort of
manual find & replace operation, if that feels too unsafe you can use [`pulldown-cmark`][pc]
to parse the string into events and work on them instead.
Finally you can use [`pulldown-cmark-to-cmark`][pctc] to transform these events back to
a string.
The following code block shows how to remove all emphasis from markdown, and do so
safely.
```rust
fn remove_emphasis(num_removed_items: &mut i32, chapter: &mut Chapter) -> Result<String> {
let mut buf = String::with_capacity(chapter.content.len());
let events = Parser::new(&chapter.content).filter(|e| {
let should_keep = match *e {
Event::Start(Tag::Emphasis)
| Event::Start(Tag::Strong)
| Event::End(Tag::Emphasis)
| Event::End(Tag::Strong) => false,
_ => true,
};
if !should_keep {
*num_removed_items += 1;
}
should_keep
});
cmark(events, &mut buf, None)
.map(|_| buf)
.map_err(|err| Error::from(format!("Markdown serialization failed: {}", err)))
}
```
For everything else, have a look [at the complete example][example].
[preprocessor-docs]: https://docs.rs/mdbook/0.1.3/mdbook/preprocess/trait.Preprocessor.html
[pc]: https://crates.io/crates/pulldown-cmark
[pctc]: https://crates.io/crates/pulldown-cmark-to-cmark
[example]: https://github.com/rust-lang-nursery/mdBook/blob/master/examples/de-emphasize.rs

View File

@@ -1,197 +0,0 @@
# 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."
[build]
build-dir = "my-example-book"
create-missing = false
[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 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.
**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`
```
### 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.
**book.toml**
```toml
[build]
build-dir = "build"
create-missing = false
```
### HTML renderer options
The HTML renderer has a couple of options as well. All the options for the
renderer need to be specified under the TOML table `[output.html]`.
The following configuration options are available:
- **theme:** mdBook comes with a default theme and all the resource files
needed for it. But if this option is set, mdBook will selectively overwrite
the theme files with the ones found in the specified folder.
- **curly-quotes:** Convert straight quotes to curly quotes, except for
those that occur in code blocks and code spans. Defaults to `false`.
- **google-analytics:** If you use Google Analytics, this option lets you
enable it by simply specifying your ID in the configuration file.
- **additional-css:** If you need to slightly change the appearance of your
book without overwriting the whole style, you can specify a set of
stylesheets that will be loaded after the default ones where you can
surgically change the style.
- **additional-js:** If you need to add some behaviour to your book without
removing the current behaviour, you can specify a set of JavaScript files
that will be loaded alongside the default one.
- **no-section-label:** mdBook by defaults adds section label in table of
contents column. For example, "1.", "2.1". Set this option to true to
disable those labels. Defaults to `false`.
- **playpen:** A subtable for configuring various playpen settings.
- **search:** A subtable for configuring the in-browser search
functionality. mdBook must be compiled with the `search` feature enabled
(on by default).
Available configuration options for the `[output.html.playpen]` table:
- **editable:** Allow editing the source code. Defaults to `false`.
- **copy-js:** Copy JavaScript files for the editor to the output directory.
Defaults to `true`.
[Ace]: https://ace.c9.io/
Available configuration options for the `[output.html.search]` table:
- **limit-results:** The maximum number of search results. Defaults to `30`.
- **teaser-word-count:** The number of words used for a search result teaser.
Defaults to `30`.
- **use-boolean-and:** Define the logical link between multiple search words.
If true, all search words must appear in each result. Defaults to `true`.
- **boost-title:** Boost factor for the search result score if a search word
appears in the header. Defaults to `2`.
- **boost-hierarchy:** Boost factor for the search result score if a search
word appears in the hierarchy. The hierarchy contains all titles of the
parent documents and all parent headings. Defaults to `1`.
- **boost-paragraph:** Boost factor for the search result score if a search
word appears in the text. Defaults to `1`.
- **expand:** True if search should match longer results e.g. search `micro`
should match `microwave`. Defaults to `true`.
- **heading-split-level:** Search results will link to a section of the document
which contains the result. Documents are split into sections by headings
this level or less.
Defaults to `3`. (`### This is a level 3 heading`)
- **copy-js:** Copy JavaScript files for the search implementation to the
output directory. Defaults to `true`.
This shows all available options in the **book.toml**:
```toml
[book]
title = "Example book"
authors = ["John Doe", "Jane Doe"]
description = "The example book covers examples."
[output.html]
theme = "my-theme"
curly-quotes = true
google-analytics = "123456"
additional-css = ["custom.css", "custom2.css"]
additional-js = ["custom.js"]
[output.html.playpen]
editor = "./path/to/editor"
editable = false
[output.html.search]
enable = true
searcher = "./path/to/searcher"
limit-results = 30
teaser-word-count = 30
use-boolean-and = true
boost-title = 2
boost-hierarchy = 1
boost-paragraph = 1
expand = true
heading-split-level = 3
```
## 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
>
> ```text
> $ export MDBOOK_BOOK="{'title': 'My Awesome Book', authors: ['Michael-F-Bryan']}"
> $ mdbook build
> ```
The latter case may be useful in situations where `mdbook` is invoked
from a script or CI, where it sometimes isn't possible to update the
`book.toml` before building.

View File

@@ -1,31 +0,0 @@
# MathJax Support
mdBook has optional support for math equations through [MathJax](https://www.mathjax.org/).
To enable MathJax, you need to add the `mathjax-support` key to your `book.toml` under the `output.html` section.
```toml
[output.html]
mathjax-support = true
```
>**Note:**
The usual delimiters MathJax uses are not yet supported. You can't currently use `$$ ... $$` as delimiters and the `\[ ... \]` delimiters need an extra backslash to work. Hopefully this limitation will be lifted soon.
### Inline equations
Inline equations are delimited by `\\(` and `\\)`. So for example, to render the following inline equation \\( \int x dx = \frac{x^2}{2} + C \\) you would write the following:
```
\\( \int x dx = \frac{x^2}{2} + C \\)
```
### Block equations
Block equations are delimited by `\\[` and `\\]`. To render the following equation
\\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\]
you would write:
```bash
\\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\]
```

View File

@@ -1,64 +0,0 @@
# mdBook-specific markdown
## Hiding code lines
There is a feature in mdBook that lets you hide code lines by prepending them with a `#`.
```bash
# fn main() {
let x = 5;
let y = 6;
println!("{}", x + y);
# }
```
Will render as
```rust
# fn main() {
let x = 5;
let y = 7;
println!("{}", x + y);
# }
```
## Including files
With the following syntax, you can include files into your book:
```hbs
\{{#include file.rs}}
```
The path to the file has to be relative from the current source file.
Usually, this command is used for including code snippets and examples. In this case, oftens one would include a specific part of the file e.g. which only contains the relevant lines for the example. We support four different modes of partial includes:
```hbs
\{{#include file.rs:2}}
\{{#include file.rs::10}}
\{{#include file.rs:2:}}
\{{#include file.rs:2:10}}
```
The first command only includes the second line from file `file.rs`. The second command includes all lines up to line 10, i.e. the lines from 11 till the end of the file are omitted. The third command includes all lines from line 2, i.e. the first line is omitted. The last command includes the excerpt of `file.rs` consisting of lines 2 to 10.
## Inserting runnable Rust files
With the following syntax, you can insert runnable Rust files into your book:
```hbs
\{{#playpen file.rs}}
```
The path to the Rust file has to be relative from the current source file.
When play is clicked, the code snippet will be sent to the [Rust Playpen] to be compiled and run. The result is sent back and displayed directly underneath the code.
Here is what a rendered code snippet looks like:
{{#playpen example.rs}}
[Rust Playpen]: https://play.rust-lang.org/

View File

@@ -1,32 +0,0 @@
# SUMMARY.md
The summary file is used by mdBook to know what chapters to include,
in what order they should appear, what their hierarchy is and where the source files are.
Without this file, there is no book.
Even though `SUMMARY.md` is a markdown file, the formatting is very strict to
allow for easy parsing. Let's see how you should format your `SUMMARY.md` file.
#### Allowed elements
1. ***Title*** It's common practice to begin with a title, generally
<code class="language-markdown"># Summary</code>.
But it is not mandatory, the parser just ignores it. So you can too
if you feel like it.
2. ***Prefix Chapter*** Before the main numbered chapters you can add a couple of elements that will not be numbered. This is useful for
forewords, introductions, etc. There are however some constraints. You can not nest prefix chapters, they should all be on the root level. And you can not add prefix chapters once you have added numbered chapters.
```markdown
[Title of prefix element](relative/path/to/markdown.md)
```
3. ***Numbered Chapter*** Numbered chapters are the main content of the book, they will be numbered and can be nested,
resulting in a nice hierarchy (chapters, sub-chapters, etc.)
```markdown
- [Title of the Chapter](relative/path/to/markdown.md)
```
You can either use `-` or `*` to indicate a numbered chapter.
4. ***Suffix Chapter*** After the numbered chapters you can add a couple of non-numbered chapters. They are the same as prefix chapters but come after the numbered chapters instead of before.
All other elements are unsupported and will be ignored at best or result in an error.

View File

@@ -1,40 +0,0 @@
# Editor
In addition to providing runnable code playpens, mdBook optionally allows them to be editable. In order to enable editable code blocks, the following needs to be added to the ***book.toml***:
```toml
[output.html.playpen]
editable = true
```
To make a specific block available for editing, the attribute `editable` needs to be added to it:
<pre><code class="language-markdown">```rust,editable
fn main() {
let number = 5;
print!("{}", number);
}
```</code></pre>
The above will result in this editable playpen:
```rust,editable
fn main() {
let number = 5;
print!("{}", number);
}
```
Note the new `Undo Changes` button in the editable playpens.
## 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:
```toml
[output.html.playpen]
editable = true
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 default Ace editor.

View File

@@ -1,91 +0,0 @@
# index.hbs
`index.hbs` is the handlebars template that is used to render the book.
The markdown files are processed to html and then injected in that template.
If you want to change the layout or style of your book, chances are that you will
have to modify this template a little bit. Here is what you need to know.
## Data
A lot of data is exposed to the handlebars template with the "context".
In the handlebars template you can access this information by using
```handlebars
{{name_of_property}}
```
Here is a list of the properties that are exposed:
- ***language*** Language of the book in the form `en`. To use in <code class="language-html">\<html lang="{{ language }}"></code> for example.
At the moment it is hardcoded.
- ***title*** Title of the book, as specified in `book.toml`
- ***chapter_title*** Title of the current chapter, as listed in `SUMMARY.md`
- ***path*** Relative path to the original markdown file from the source directory
- ***content*** This is the rendered markdown.
- ***path_to_root*** This is a path containing exclusively `../`'s that points to the root of the book from the current file.
Since the original directory structure is maintained, it is useful to prepend relative links with this `path_to_root`.
- ***chapters*** Is an array of dictionaries of the form
```json
{"section": "1.2.1", "name": "name of this chapter", "path": "dir/markdown.md"}
```
containing all the chapters of the book. It is used for example to construct the table of contents (sidebar).
## Handlebars Helpers
In addition to the properties you can access, there are some handlebars helpers at your disposal.
1. ### toc
The toc helper is used like this
```handlebars
{{#toc}}{{/toc}}
```
and outputs something that looks like this, depending on the structure of your book
```html
<ul class="chapter">
<li><a href="link/to/file.html">Some chapter</a></li>
<li>
<ul class="section">
<li><a href="link/to/other_file.html">Some other Chapter</a></li>
</ul>
</li>
</ul>
```
If you would like to make a toc with another structure, you have access to the chapters property containing all the data.
The only limitation at the moment is that you would have to do it with JavaScript instead of with a handlebars helper.
```html
<script>
var chapters = {{chapters}};
// Processing here
</script>
```
2. ### previous / next
The previous and next helpers expose a `link` and `name` property to the previous and next chapters.
They are used like this
```handlebars
{{#previous}}
<a href="{{link}}" class="nav-chapters previous">
<i class="fa fa-angle-left"></i>
</a>
{{/previous}}
```
The inner html will only be rendered if the previous / next chapter exists.
Of course the inner html can be changed to your liking.
------
*If you would like me to expose other properties or helpers, please [create a new issue](https://github.com/rust-lang-nursery/mdBook/issues)
and I will consider it.*

View File

@@ -1,60 +0,0 @@
# Syntax Highlighting
For syntax highlighting I use [Highlight.js](https://highlightjs.org) with a custom theme.
Automatic language detection has been turned off, so you will probably want to
specify the programming language you use like this
<pre><code class="language-markdown">```rust
fn main() {
// Some code
}
```</code></pre>
## Custom theme
Like the rest of the theme, the files used for syntax highlighting can be overridden with your own.
- ***highlight.js*** normally you shouldn't have to overwrite this file, unless you want to use a more recent version.
- ***highlight.css*** theme used by highlight.js for syntax highlighting.
If you want to use another theme for `highlight.js` download it from their website, or make it yourself,
rename it to `highlight.css` and put it in `src/theme` (or the equivalent if you changed your source folder)
Now your theme will be used instead of the default theme.
## Hiding code lines
There is a feature in mdBook that let's you hide code lines by prepending them with a `#`.
```bash
# fn main() {
let x = 5;
let y = 6;
println!("{}", x + y);
# }
```
Will render as
```rust
# fn main() {
let x = 5;
let y = 7;
println!("{}", x + y);
# }
```
**At the moment, this only works for code examples that are annotated with `rust`. Because it would collide with semantics of some programming languages. In the future, we want to make this configurable through the `book.toml` so that 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 issue](https://github.com/rust-lang-nursery/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.

View File

@@ -1,22 +0,0 @@
# Theme
The default renderer uses a [handlebars](http://handlebarsjs.com/) template to render your markdown files and comes with a default theme
included in the mdBook binary.
The theme is totally customizable, you can selectively replace every file from the theme by your own by adding a
`theme` directory next to `src` folder in your project root. Create a new file with the name of the file you want to override
and now that file will be used instead of the default file.
Here are the files you can override:
- ***index.hbs*** is the handlebars template.
- ***book.css*** is the style used in the output. If you want to change the design of your book, this is probably the file you want to modify. Sometimes in conjunction with `index.hbs` when you want to radically change the layout.
- ***book.js*** is mostly used to add client side functionality, like hiding / un-hiding the sidebar, changing the theme, ...
- ***highlight.js*** is the JavaScript that is used to highlight code snippets, you should not need to modify this.
- ***highlight.css*** is the theme used for the code highlighting
- ***favicon.png*** the favicon that will be used
Generally, when you want to tweak the theme, you don't need to override all the files. If you only need changes in the stylesheet,
there is no point in overriding all the other files. Because custom files take precedence over built-in ones, they will not get updated with new fixes / features.
**Note:** When you override a file, it is possible that you break some functionality. Therefore I recommend to use the file from the default theme as template and only add / modify what you need. You can copy the default theme into your source directory automatically by using `mdbook init --theme` just remove the files you don't want to override.

View File

@@ -1,98 +0,0 @@
// build.rs
use std::env;
use std::path::Path;
#[macro_use]
extern crate error_chain;
#[cfg(windows)]
mod execs {
use std::process::Command;
pub fn cmd(program: &str) -> Command {
let mut cmd = Command::new("cmd");
cmd.args(&["/c", program]);
cmd
}
}
#[cfg(not(windows))]
mod execs {
use std::process::Command;
pub fn cmd(program: &str) -> Command {
Command::new(program)
}
}
error_chain!{
foreign_links {
Io(std::io::Error);
}
}
fn program_exists(program: &str) -> Result<()> {
execs::cmd(program).arg("-v")
.output()
.chain_err(|| format!("Please install '{}'!", program))?;
Ok(())
}
fn npm_package_exists(package: &str) -> Result<()> {
let status = execs::cmd("npm").args(&["list", "-g"])
.arg(package)
.output();
match status {
Ok(ref out) if out.status.success() => Ok(()),
_ => {
bail!("Missing npm package '{0}' install with: 'npm -g install {0}'",
package)
}
}
}
pub enum Resource<'a> {
Program(&'a str),
Package(&'a str),
}
use Resource::{Package, Program};
impl<'a> Resource<'a> {
pub fn exists(&self) -> Result<()> {
match *self {
Program(name) => program_exists(name),
Package(name) => npm_package_exists(name),
}
}
}
fn run() -> Result<()> {
if let Ok(_) = env::var("CARGO_FEATURE_REGENERATE_CSS") {
// Check dependencies
Program("npm").exists()?;
Program("node").exists().or(Program("nodejs").exists())?;
Package("nib").exists()?;
Package("stylus").exists()?;
// Compile stylus stylesheet to css
let manifest_dir = env::var("CARGO_MANIFEST_DIR")
.chain_err(|| "Please run the script with: 'cargo build'!")?;
let theme_dir = Path::new(&manifest_dir).join("src/theme/");
let stylus_dir = theme_dir.join("stylus/book.styl");
if !execs::cmd("stylus").arg(stylus_dir)
.arg("--out")
.arg(theme_dir)
.arg("--use")
.arg("nib")
.status()?
.success()
{
bail!("Stylus encountered an error");
}
}
Ok(())
}
quick_main!(run);

View File

@@ -1,31 +0,0 @@
# This script takes care of building your crate and packaging it for release
set -ex
main() {
local src=$(pwd) \
stage=
case $TRAVIS_OS_NAME in
linux)
stage=$(mktemp -d)
;;
osx)
stage=$(mktemp -d -t tmp)
;;
esac
test -f Cargo.lock || cargo generate-lockfile
cross rustc --bin mdbook --target $TARGET --release -- -C lto
cp target/$TARGET/release/mdbook $stage/
cd $stage
tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz *
cd $src
rm -rf $stage
}
main

View File

@@ -1,50 +0,0 @@
#!/bin/bash
# Deploys the `book-example` to GitHub Pages
set -ex
# Only run this on the master branch for stable
if [ "$TRAVIS_PULL_REQUEST" != "false" ] ||
[ "$TRAVIS_BRANCH" != "master" ] ||
[ "$TRAVIS_RUST_VERSION" != "stable" ] ||
[ "$TARGET" != "x86_64-unknown-linux-gnu" ]; then
exit 0
fi
# Make sure we have the css dependencies
npm install -g stylus nib
NC='\033[39m'
CYAN='\033[36m'
GREEN='\033[32m'
rev=$(git rev-parse --short HEAD)
echo -e "${CYAN}Running cargo doc${NC}"
cargo doc --features regenerate-css > /dev/null
echo -e "${CYAN}Running mdbook build${NC}"
cargo run -- build book-example/
echo -e "${CYAN}Copying book to target/doc${NC}"
cp -R book-example/book/* target/doc/
cd target/doc
echo -e "${CYAN}Initializing Git${NC}"
git init
git config user.name "Michael Bryan"
git config user.email "michaelfbryan@gmail.com"
git remote add upstream "https://$GH_TOKEN@github.com/rust-lang-nursery/mdBook.git"
git fetch upstream --quiet
git reset upstream/gh-pages --quiet
touch .
echo -e "${CYAN}Pushing changes to gh-pages${NC}"
git add -A .
git commit -m "rebuild pages at ${rev}" --quiet
git push -q upstream HEAD:gh-pages --quiet
echo -e "${GREEN}Deployed docs to GitHub Pages${NC}"

24
ci/install-hub.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Installs the `hub` executable into hub/bin
set -ex
case $1 in
ubuntu*)
curl -LsSf https://github.com/github/hub/releases/download/v2.12.8/hub-linux-amd64-2.12.8.tgz -o hub.tgz
mkdir hub
tar -xzvf hub.tgz --strip=1 -C hub
;;
macos*)
curl -LsSf https://github.com/github/hub/releases/download/v2.12.8/hub-darwin-amd64-2.12.8.tgz -o hub.tgz
mkdir hub
tar -xzvf hub.tgz --strip=1 -C hub
;;
windows*)
curl -LsSf https://github.com/github/hub/releases/download/v2.12.8/hub-windows-amd64-2.12.8.zip -o hub.zip
7z x hub.zip -ohub
;;
*)
echo "OS should be first parameter, was: $1"
;;
esac
echo "$PWD/hub/bin" >> $GITHUB_PATH

19
ci/install-rust.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Install/update rust.
# The first argument should be the toolchain to install.
set -ex
if [ -z "$1" ]
then
echo "First parameter must be toolchain to install."
exit 1
fi
TOOLCHAIN="$1"
rustup set profile minimal
rustup component remove --toolchain=$TOOLCHAIN rust-docs || echo "already removed"
rustup update --no-self-update $TOOLCHAIN
rustup default $TOOLCHAIN
rustup -V
rustc -Vv
cargo -V

View File

@@ -1,47 +0,0 @@
set -ex
main() {
local target=
if [ $TRAVIS_OS_NAME = linux ]; then
target=x86_64-unknown-linux-musl
sort=sort
else
target=x86_64-apple-darwin
sort=gsort # for `sort --sort-version`, from brew's coreutils.
fi
# Builds for iOS are done on OSX, but require the specific target to be
# installed.
case $TARGET in
aarch64-apple-ios)
rustup target install aarch64-apple-ios
;;
armv7-apple-ios)
rustup target install armv7-apple-ios
;;
armv7s-apple-ios)
rustup target install armv7s-apple-ios
;;
i386-apple-ios)
rustup target install i386-apple-ios
;;
x86_64-apple-ios)
rustup target install x86_64-apple-ios
;;
esac
# This fetches latest stable release
local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \
| cut -d/ -f3 \
| grep -E '^v[0.1.0-9.]+$' \
| $sort --version-sort \
| tail -n1)
curl -LSfs https://japaric.github.io/trust/install.sh | \
sh -s -- \
--force \
--git japaric/cross \
--tag $tag \
--target $target
}
main

47
ci/make-release.sh Executable file
View File

@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# Builds the release and creates an archive and optionally deploys to GitHub.
set -ex
if [[ -z "$GITHUB_REF" ]]
then
echo "GITHUB_REF must be set"
exit 1
fi
# Strip mdbook-refs/tags/ from the start of the ref.
TAG=${GITHUB_REF#*/tags/}
host=$(rustc -Vv | grep ^host: | sed -e "s/host: //g")
export CARGO_PROFILE_RELEASE_LTO=true
cargo build --bin mdbook --release
cd target/release
case $1 in
ubuntu*)
asset="mdbook-$TAG-$host.tar.gz"
tar czf ../../$asset mdbook
;;
macos*)
asset="mdbook-$TAG-$host.tar.gz"
# There is a bug with BSD tar on macOS where the first 8MB of the file are
# sometimes all NUL bytes. See https://github.com/actions/cache/issues/403
# and https://github.com/rust-lang/cargo/issues/8603 for some more
# information. An alternative solution here is to install GNU tar, but
# flushing the disk cache seems to work, too.
sudo /usr/sbin/purge
tar czf ../../$asset mdbook
;;
windows*)
asset="mdbook-$TAG-$host.zip"
7z a ../../$asset mdbook.exe
;;
*)
echo "OS should be first parameter, was: $1"
;;
esac
cd ../..
if [[ -z "$GITHUB_TOKEN" ]]
then
echo "$GITHUB_TOKEN not set, skipping deploy."
else
hub release edit -m "" --attach $asset $TAG
fi

View File

@@ -1,22 +0,0 @@
# This script takes care of testing your crate
set -ex
main() {
cross build --target $TARGET --all --no-default-features
cross build --target $TARGET --all
cross build --target $TARGET --all --release
if [ ! -z $DISABLE_TESTS ]; then
return
fi
cross test --target $TARGET --no-default-features
cross test --target $TARGET
cross test --target $TARGET --release
}
# we don't run the "test phase" when doing deploys
if [ -z $TRAVIS_TAG ]; then
main
fi

View File

@@ -1,94 +0,0 @@
//! This program removes all forms of emphasis from the markdown of the book.
extern crate mdbook;
extern crate pulldown_cmark;
extern crate pulldown_cmark_to_cmark;
use mdbook::errors::{Error, Result};
use mdbook::MDBook;
use mdbook::book::{Book, BookItem, Chapter};
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
use pulldown_cmark::{Event, Parser, Tag};
use pulldown_cmark_to_cmark::fmt::cmark;
use std::ffi::OsString;
use std::env::{args, args_os};
use std::process;
struct Deemphasize;
impl Preprocessor for Deemphasize {
fn name(&self) -> &str {
"md-links-to-html-links"
}
fn run(&self, _ctx: &PreprocessorContext, book: &mut Book) -> Result<()> {
eprintln!("Running '{}' preprocessor", self.name());
let mut res: Option<_> = None;
let mut num_removed_items = 0;
book.for_each_mut(|item: &mut BookItem| {
if let Some(Err(_)) = res {
return;
}
if let BookItem::Chapter(ref mut chapter) = *item {
eprintln!("{}: processing chapter '{}'", self.name(), chapter.name);
res = Some(
match Deemphasize::remove_emphasis(&mut num_removed_items, chapter) {
Ok(md) => {
chapter.content = md;
Ok(())
}
Err(err) => Err(err),
},
);
}
});
eprintln!(
"{}: removed {} events from markdown stream.",
self.name(),
num_removed_items
);
match res {
Some(res) => res,
None => Ok(()),
}
}
}
fn do_it(book: OsString) -> Result<()> {
let mut book = MDBook::load(book)?;
book.with_preprecessor(Deemphasize);
book.build()
}
fn main() {
if args_os().count() != 2 {
eprintln!("USAGE: {} <book>", args().next().expect("executable"));
return;
}
if let Err(e) = do_it(args_os().skip(1).next().expect("one argument")) {
eprintln!("{}", e);
process::exit(1);
}
}
impl Deemphasize {
fn remove_emphasis(num_removed_items: &mut i32, chapter: &mut Chapter) -> Result<String> {
let mut buf = String::with_capacity(chapter.content.len());
let events = Parser::new(&chapter.content).filter(|e| {
let should_keep = match *e {
Event::Start(Tag::Emphasis)
| Event::Start(Tag::Strong)
| Event::End(Tag::Emphasis)
| Event::End(Tag::Strong) => false,
_ => true,
};
if !should_keep {
*num_removed_items += 1;
}
should_keep
});
cmark(events, &mut buf, None)
.map(|_| buf)
.map_err(|err| Error::from(format!("Markdown serialization failed: {}", err)))
}
}

View File

@@ -0,0 +1,102 @@
use crate::nop_lib::Nop;
use clap::{App, Arg, ArgMatches, SubCommand};
use mdbook::book::Book;
use mdbook::errors::Error;
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
use std::io;
use std::process;
pub fn make_app() -> App<'static, 'static> {
App::new("nop-preprocessor")
.about("A mdbook preprocessor which does precisely nothing")
.subcommand(
SubCommand::with_name("supports")
.arg(Arg::with_name("renderer").required(true))
.about("Check whether a renderer is supported by this preprocessor"),
)
}
fn main() {
let matches = make_app().get_matches();
// Users will want to construct their own preprocessor here
let preprocessor = Nop::new();
if let Some(sub_args) = matches.subcommand_matches("supports") {
handle_supports(&preprocessor, sub_args);
} else if let Err(e) = handle_preprocessing(&preprocessor) {
eprintln!("{}", e);
process::exit(1);
}
}
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...
eprintln!(
"Warning: The {} plugin was built against version {} of mdbook, \
but we're being called from version {}",
pre.name(),
mdbook::MDBOOK_VERSION,
ctx.mdbook_version
);
}
let processed_book = pre.run(&ctx, book)?;
serde_json::to_writer(io::stdout(), &processed_book)?;
Ok(())
}
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);
// Signal whether the renderer is supported by exiting with 1 or 0.
if supported {
process::exit(0);
} else {
process::exit(1);
}
}
/// The actual implementation of the `Nop` preprocessor. This would usually go
/// in your main `lib.rs` file.
mod nop_lib {
use super::*;
/// A no-op preprocessor.
pub struct Nop;
impl Nop {
pub fn new() -> Nop {
Nop
}
}
impl Preprocessor for Nop {
fn name(&self) -> &str {
"nop-preprocessor"
}
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book, Error> {
// In testing we want to tell the preprocessor to blow up by setting a
// particular config value
if let Some(nop_cfg) = ctx.config.get_preprocessor(self.name()) {
if nop_cfg.contains_key("blow-up") {
anyhow::bail!("Boom!!1!");
}
}
// we *are* a no-op preprocessor after all
Ok(book)
}
fn supports_renderer(&self, renderer: &str) -> bool {
renderer != "not-supported"
}
}
}

View File

@@ -2,12 +2,18 @@
title = "mdBook Documentation"
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
authors = ["Mathieu David", "Michael-F-Bryan"]
language = "en"
[rust]
edition = "2018"
[output.html]
mathjax-support = true
site-url = "/mdBook/"
[output.html.playpen]
[output.html.playground]
editable = true
line-numbers = true
[output.html.search]
limit-results = 20

3
guide/src/404.md Normal file
View File

@@ -0,0 +1,3 @@
# Document not found (404)
This URL is invalid, sorry. Try the search instead!

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

@@ -0,0 +1,25 @@
# mdBook
**mdBook** is a command line tool and Rust crate to create books using Markdown
(as by the [CommonMark](https://commonmark.org/) specification) files. It's very
similar to Gitbook but written in [Rust](http://www.rust-lang.org).
What you are reading serves as an example of the output of mdBook and at the
same time as a high-level documentation.
mdBook is free and open source, you can find the source code on
[GitHub](https://github.com/rust-lang/mdBook). Issues and feature
requests can be posted on the [GitHub issue
tracker](https://github.com/rust-lang/mdBook/issues).
## API docs
Alongside this book you can also read the [API
docs](https://docs.rs/mdbook/*/mdbook/) generated by Rustdoc if you would like
to use mdBook as a crate or write a new renderer and need a more low-level
overview.
## License
mdBook, all the source code, is released under the [Mozilla Public License
v2.0](https://www.mozilla.org/MPL/2.0/).

View File

@@ -1,24 +1,28 @@
# Summary
- [mdBook](README.md)
- [Command Line Tool](cli/cli-tool.md)
- [Command Line Tool](cli/README.md)
- [init](cli/init.md)
- [build](cli/build.md)
- [watch](cli/watch.md)
- [serve](cli/serve.md)
- [test](cli/test.md)
- [clean](cli/clean.md)
- [Format](format/format.md)
- [Format](format/README.md)
- [SUMMARY.md](format/summary.md)
- [Draft chapter]()
- [Configuration](format/config.md)
- [Theme](format/theme/theme.md)
- [Theme](format/theme/README.md)
- [index.hbs](format/theme/index-hbs.md)
- [Syntax highlighting](format/theme/syntax-highlighting.md)
- [Editor](format/theme/editor.md)
- [MathJax Support](format/mathjax.md)
- [mdBook specific features](format/mdbook.md)
- [For Developers](for_developers/index.md)
- [mdBook-specific features](format/mdbook.md)
- [Continuous Integration](continuous-integration.md)
- [For Developers](for_developers/README.md)
- [Preprocessors](for_developers/preprocessors.md)
- [Alternate Backends](for_developers/backends.md)
- [Alternative Backends](for_developers/backends.md)
-----------
[Contributors](misc/contributors.md)

55
guide/src/cli/README.md Normal file
View File

@@ -0,0 +1,55 @@
# Command Line Tool
mdBook can be used either as a command line tool or a [Rust
crate](https://crates.io/crates/mdbook). Let's focus on the command line tool
capabilities first.
## Install From Binaries
Precompiled binaries are provided for major platforms on a best-effort basis.
Visit [the releases page](https://github.com/rust-lang/mdBook/releases)
to download the appropriate version for your platform.
## Install From Source
mdBook can also be installed from source
### Pre-requisite
mdBook is written in **[Rust](https://www.rust-lang.org/)** and therefore needs
to be compiled with **Cargo**. If you haven't already installed Rust, please go
ahead and [install it](https://www.rust-lang.org/tools/install) now.
### Install Crates.io version
Installing mdBook is relatively easy if you already have Rust and Cargo
installed. You just have to type this snippet in your terminal:
```bash
cargo install mdbook
```
This will fetch the source code for the latest release from
[Crates.io](https://crates.io/) and compile it. You will have to add Cargo's
`bin` directory to your `PATH`.
Run `mdbook help` in your terminal to verify if it works. Congratulations, you
have installed mdBook!
### Install Git version
The **[git version](https://github.com/rust-lang/mdBook)** contains all
the latest bug-fixes and features, that will be released in the next version on
**Crates.io**, if you can't wait until the next release. You can build the git
version yourself. Open your terminal and navigate to the directory of you
choice. We need to clone the git repository and then build it with Cargo.
```bash
git clone --depth=1 https://github.com/rust-lang/mdBook.git
cd mdBook
cargo build --release
```
The executable `mdbook` will be in the `./target/release` folder, this should be
added to the path.

39
guide/src/cli/build.md Normal file
View File

@@ -0,0 +1,39 @@
# The build command
The build command is used to render your book:
```bash
mdbook build
```
It will try to parse your `SUMMARY.md` file to understand the structure of your
book and fetch the corresponding files.
The rendered output will maintain the same directory structure as the source for
convenience. Large books will therefore remain structured when rendered.
#### Specify a directory
The `build` command can take a directory as an argument to use as the book's
root instead of the current working directory.
```bash
mdbook build path/to/book
```
#### --open
When you use the `--open` (`-o`) flag, mdbook will open the rendered book in
your default web browser after building it.
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for the
book. Relative paths are interpreted relative to the book's root directory. If
not specified it will default to the value of the `build.build-dir` key in
`book.toml`, or to `./book`.
-------------------
***Note:*** *The build command copies all files (excluding files with `.md` extension) from the source directory
into the build directory.*

30
guide/src/cli/clean.md Normal file
View File

@@ -0,0 +1,30 @@
# The clean command
The clean command is used to delete the generated book and any other build
artifacts.
```bash
mdbook clean
```
#### Specify a directory
The `clean` command can take a directory as an argument to use as the book's
root instead of the current working directory.
```bash
mdbook clean path/to/book
```
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to override the book's output
directory, which will be deleted by this command. Relative paths are interpreted
relative to the book's root directory. If not specified it will default to the
value of the `build.build-dir` key in `book.toml`, or to `./book`.
```bash
mdbook clean --dest-dir=path/to/book
```
`path/to/book` could be absolute or relative.

54
guide/src/cli/init.md Normal file
View File

@@ -0,0 +1,54 @@
# The init command
There is some minimal boilerplate that is the same for every new book. It's for
this purpose that mdBook includes an `init` command.
The `init` command is used like this:
```bash
mdbook init
```
When using the `init` command for the first time, a couple of files will be set
up for you:
```bash
book-test/
├── book
└── src
├── chapter_1.md
└── SUMMARY.md
```
- The `src` directory is where you write your book in markdown. It contains all
the source files, configuration files, etc.
- 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)
#### Tip: Generate chapters from SUMMARY.md
When a `SUMMARY.md` file already exists, the `init` command will first parse it
and generate the missing files according to the paths used in the `SUMMARY.md`.
This allows you to think and create the whole structure of your book and then
let mdBook generate it for you.
#### Specify a directory
The `init` command can take a directory as an argument to use as the book's root
instead of the current working directory.
```bash
mdbook init path/to/book
```
#### --theme
When you use the `--theme` flag, the default theme will be copied into a
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.

59
guide/src/cli/serve.md Normal file
View File

@@ -0,0 +1,59 @@
# 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
connection is used to trigger the client-side refresh.
***Note:*** *The `serve` command is for testing a book's HTML output, and is not
intended to be a complete HTTP server for a website.*
#### Specify a directory
The `serve` command can take a directory as an argument to use as the book's
root instead of the current working directory.
```bash
mdbook serve path/to/book
```
#### 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:
```bash
mdbook serve path/to/book -p 8000 -n 127.0.0.1 --websocket-hostname 192.168.1.100
```
If you were to want live reloading for this you would need to proxy the
websocket calls through nginx as well from `192.168.1.100:<WS_PORT>` to
`127.0.0.1:<WS_PORT>`. The `-w` flag allows for the websocket port to be
configured.
#### --open
When you use the `--open` (`-o`) flag, mdbook will open the book in your
default web browser after starting the server.
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for the
book. Relative paths are interpreted relative to the book's root directory. If
not specified it will default to the value of the `build.build-dir` key in
`book.toml`, or to `./book`.
#### Specify exclude patterns
The `serve` command will not automatically trigger a build for files listed in
the `.gitignore` file in the book root directory. The `.gitignore` file may
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._

53
guide/src/cli/test.md Normal file
View File

@@ -0,0 +1,53 @@
# The test command
When writing a book, you sometimes need to automate some tests. For example,
[The Rust Programming Book](https://doc.rust-lang.org/stable/book/) uses a lot
of code examples that could get outdated. Therefore it is very important for
them to be able to automatically test these code examples.
mdBook supports a `test` command that will run all available tests in a book. At
the moment, only rustdoc tests are supported, but this may be expanded upon in
the future.
#### Disable tests on a code block
rustdoc doesn't test code blocks which contain the `ignore` attribute:
```rust,ignore
fn main() {}
```
rustdoc also doesn't test code blocks which specify a language other than Rust:
```markdown
**Foo**: _bar_
```
rustdoc *does* test code blocks which have no language specified:
```
This is going to cause an error!
```
#### Specify a directory
The `test` command can take a directory as an argument to use as the book's root
instead of the current working directory.
```bash
mdbook test path/to/book
```
#### --library-path
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`).
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for the
book. Relative paths are interpreted relative to the book's root directory. If
not specified it will default to the value of the `build.build-dir` key in
`book.toml`, or to `./book`.

39
guide/src/cli/watch.md Normal file
View File

@@ -0,0 +1,39 @@
# The watch command
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.
#### Specify a directory
The `watch` command can take a directory as an argument to use as the book's
root instead of the current working directory.
```bash
mdbook watch path/to/book
```
#### --open
When you use the `--open` (`-o`) option, mdbook will open the rendered book in
your default web browser.
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for the
book. Relative paths are interpreted relative to the book's root directory. If
not specified it will default to the value of the `build.build-dir` key in
`book.toml`, or to `./book`.
#### Specify exclude patterns
The `watch` command will not automatically trigger a build for files listed in
the `.gitignore` file in the book root directory. The `.gitignore` file may
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._

View File

@@ -0,0 +1,89 @@
# Running `mdbook` in Continuous Integration
While the following examples use Travis CI, their principles should
straightforwardly transfer to other continuous integration providers as well.
## Ensuring Your Book Builds and Tests Pass
Here is a sample Travis CI `.travis.yml` configuration that ensures `mdbook
build` and `mdbook test` run successfully. The key to fast CI turnaround times
is caching `mdbook` installs, so that you aren't compiling `mdbook` on every CI
run.
```yaml
language: rust
sudo: false
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 path/to/mybook && mdbook test path/to/mybook
```
## Deploying Your Book to GitHub Pages
Following these instructions will result in your book being published to GitHub
pages after a successful CI run on your repository's `master` branch.
First, create a new GitHub "Personal Access Token" with the "public_repo"
permissions (or "repo" for private repositories). Go to your repository's Travis
CI settings page and add an environment variable named `GITHUB_TOKEN` that is
marked secure and *not* shown in the logs.
Then, append this snippet to your `.travis.yml` and update the path to the
`book` directory:
```yaml
deploy:
provider: pages
skip-cleanup: true
github-token: $GITHUB_TOKEN
local-dir: path/to/mybook/book
keep-history: false
on:
branch: master
```
That's it!
### Deploying to GitHub Pages manually
If your CI doesn't support GitHub pages, or you're deploying somewhere else
with integrations such as Github Pages:
*note: you may want to use different tmp dirs*:
```console
$> git worktree add /tmp/book gh-pages
$> mdbook build
$> rm -rf /tmp/book/* # this won't delete the .git directory
$> cp -rp book/* /tmp/book/
$> cd /tmp/book
$> git add -A
$> git commit 'new book message'
$> git push origin gh-pages
$> cd -
```
Or put this into a Makefile rule:
```makefile
.PHONY: deploy
deploy: book
@echo "====> deploying to github"
git worktree add /tmp/book gh-pages
rm -rf /tmp/book/*
cp -rp book/* /tmp/book/
cd /tmp/book && \
git add -A && \
git commit -m "deployed on $(shell date) by ${USER}" && \
git push origin gh-pages
```

View File

@@ -1,25 +1,25 @@
# For Developers
While `mdbook` is mainly used as a command line tool, you can also import the
While `mdbook` is mainly used as a command line tool, you can also import the
underlying library directly and use that to manage a book. It also has a fairly
flexible plugin mechanism, allowing you to create your own custom tooling and
flexible plugin mechanism, allowing you to create your own custom tooling and
consumers (often referred to as *backends*) if you need to do some analysis of
the book or render it in a different format.
The *For Developers* chapters are here to show you the more advanced usage of
The *For Developers* chapters are here to show you the more advanced usage of
`mdbook`.
The two main ways a developer can hook into the book's build process is via,
- [Preprocessors](for_developers/preprocessors.html)
- [Alternate Backends](for_developers/backends.html)
- [Preprocessors](preprocessors.md)
- [Alternative Backends](backends.md)
## The Build Process
The process of rendering a book project goes through several steps.
1. Load the book
1. Load the book
- Parse the `book.toml`, falling back to the default `Config` if it doesn't
exist
- Load the book chapters into memory
@@ -32,15 +32,15 @@ The process of rendering a book project goes through several steps.
The `mdbook` binary is just a wrapper around the `mdbook` crate, exposing its
functionality as a command-line program. As such it is quite easy to create your
own programs which use `mdbook` internally, adding your own functionality (e.g.
own programs which use `mdbook` internally, adding your own functionality (e.g.
a custom preprocessor) or tweaking the build process.
The easiest way to find out how to use the `mdbook` crate is by looking at the
[API Docs]. The top level documentation explains how one would use the
[API Docs]. The top level documentation explains how one would use the
[`MDBook`] type to load and build a book, while the [config] module gives a good
explanation on the configuration system.
[`MDBook`]: http://rust-lang-nursery.github.io/mdBook/mdbook/book/struct.MDBook.html
[API Docs]: http://rust-lang-nursery.github.io/mdBook/mdbook/
[config]: file:///home/michael/Documents/forks/mdBook/target/doc/mdbook/config/index.html
[`MDBook`]: https://docs.rs/mdbook/*/mdbook/book/struct.MDBook.html
[API Docs]: https://docs.rs/mdbook/*/mdbook/
[config]: https://docs.rs/mdbook/*/mdbook/config/index.html

View File

@@ -1,11 +1,11 @@
# Alternate Backends
# Alternative Backends
A "backend" is simply a program which `mdbook` will invoke during the book
A "backend" is simply a program which `mdbook` will invoke during the book
rendering process. This program is passed a JSON representation of the book and
configuration information via `stdin`. Once the backend receives this
configuration information via `stdin`. Once the backend receives this
information it is free to do whatever it wants.
There are already several alternate backends on GitHub which can be used as a
There are already several alternative backends on GitHub which can be used as a
rough example of how this is accomplished in practice.
- [mdbook-linkcheck] - a simple program for verifying the book doesn't contain
@@ -14,24 +14,24 @@ rough example of how this is accomplished in practice.
- [mdbook-test] - a program to run the book's contents through [rust-skeptic] to
verify everything compiles and runs correctly (similar to `rustdoc --test`)
This page will step you through creating your own alternate backend in the form
This page will step you through creating your own alternative backend in the form
of a simple word counting program. Although it will be written in Rust, there's
no reason why it couldn't be accomplished using something like Python or Ruby.
## Setting Up
First you'll want to create a new binary program and add `mdbook` as a
First you'll want to create a new binary program and add `mdbook` as a
dependency.
```
```shell
$ cargo new --bin mdbook-wordcount
$ cd mdbook-wordcount
$ cd mdbook-wordcount
$ cargo add mdbook
```
When our `mdbook-wordcount` plugin is invoked, `mdbook` will send it a JSON
version of [`RenderContext`] via our plugin's `stdin`. For convenience, there's
When our `mdbook-wordcount` plugin is invoked, `mdbook` will send it a JSON
version of [`RenderContext`] via our plugin's `stdin`. For convenience, there's
a [`RenderContext::from_json()`] constructor which will load a `RenderContext`.
This is all the boilerplate necessary for our backend to load the book.
@@ -49,18 +49,18 @@ fn main() {
}
```
> **Note:** The `RenderContext` contains a `version` field. This lets backends
> **Note:** The `RenderContext` contains a `version` field. This lets backends
figure out whether they are compatible with the version of `mdbook` it's being
called by. This `version` comes directly from the corresponding field in
`mdbook`'s `Cargo.toml`.
called by. This `version` comes directly from the corresponding field in
`mdbook`'s `Cargo.toml`.
It is recommended that backends use the [`semver`] crate to inspect this field
and emit a warning if there may be a compatibility issue.
## Inspecting the Book
Now our backend has a copy of the book, lets count how many words are in each
Now our backend has a copy of the book, lets count how many words are in each
chapter!
Because the `RenderContext` contains a [`Book`] field (`book`), and a `Book` has
@@ -89,15 +89,15 @@ fn count_words(ch: &Chapter) -> usize {
## Enabling the Backend
Now we've got the basics running, we want to actually use it. First, install
the program.
Now we've got the basics running, we want to actually use it. First, install the
program.
```
$ cargo install
```shell
$ cargo install --path .
```
Then `cd` to the particular book you'd like to count the words of and update its
`book.toml` file.
`book.toml` file.
```diff
[book]
@@ -110,18 +110,17 @@ Then `cd` to the particular book you'd like to count the words of and update its
+ [output.wordcount]
```
When it loads a book into memory, `mdbook` will inspect your `book.toml` file
to try and figure out which backends to use by looking for all `output.*`
tables. If none are provided it'll fall back to using the default HTML
renderer.
When it loads a book into memory, `mdbook` will inspect your `book.toml` file to
try and figure out which backends to use by looking for all `output.*` tables.
If none are provided it'll fall back to using the default HTML renderer.
Notably, this means if you want to add your own custom backend you'll also
need to make sure to add the HTML backend, even if its table just stays empty.
Notably, this means if you want to add your own custom backend you'll also need
to make sure to add the HTML backend, even if its table just stays empty.
Now you just need to build your book like normal, and everything should *Just
Now you just need to build your book like normal, and everything should *Just
Work*.
```
```shell
$ mdbook build
...
2018-01-16 07:31:15 [INFO] (mdbook::renderer): Invoking the "mdbook-wordcount" renderer
@@ -141,15 +140,15 @@ Syntax highlighting: 314
MathJax Support: 153
Rust code specific features: 148
For Developers: 788
Alternate Backends: 710
Alternative Backends: 710
Contributors: 85
```
The reason we didn't need to specify the full name/path of our `wordcount`
backend is because `mdbook` will try to *infer* the program's name via
convention. The executable for the `foo` backend is typically called
The reason we didn't need to specify the full name/path of our `wordcount`
backend is because `mdbook` will try to *infer* the program's name via
convention. The executable for the `foo` backend is typically called
`mdbook-foo`, with an associated `[output.foo]` entry in the `book.toml`. To
explicitly tell `mdbook` what command to invoke (it may require command-line
explicitly tell `mdbook` what command to invoke (it may require command-line
arguments or be an interpreted script), you can use the `command` field.
```diff
@@ -168,16 +167,16 @@ arguments or be an interpreted script), you can use the `command` field.
## Configuration
Now imagine you don't want to count the number of words on a particular chapter
(it might be generated text/code, etc). The canonical way to do this is via
the usual `book.toml` configuration file by adding items to your `[output.foo]`
table.
(it might be generated text/code, etc). The canonical way to do this is via the
usual `book.toml` configuration file by adding items to your `[output.foo]`
table.
The `Config` can be treated roughly as a nested hashmap which lets you call
methods like `get()` to access the config's contents, with a
`get_deserialized()` convenience method for retrieving a value and
automatically deserializing to some arbitrary type `T`.
`get_deserialized()` convenience method for retrieving a value and automatically
deserializing to some arbitrary type `T`.
To implement this, we'll create our own serializable `WordcountConfig` struct
To implement this, we'll create our own serializable `WordcountConfig` struct
which will encapsulate all configuration for this backend.
First add `serde` and `serde_derive` to your `Cargo.toml`,
@@ -212,13 +211,13 @@ and then add a check to make sure we skip ignored chapters.
+ let cfg: WordcountConfig = ctx.config
+ .get_deserialized("output.wordcount")
+ .unwrap_or_default();
for item in ctx.book.iter() {
if let BookItem::Chapter(ref ch) = *item {
+ if cfg.ignores.contains(&ch.name) {
+ continue;
+ }
+
+
let num_words = count_words(ch);
println!("{}: {}", ch.name, num_words);
}
@@ -229,7 +228,7 @@ and then add a check to make sure we skip ignored chapters.
## Output and Signalling Failure
While it's nice to print word counts to the terminal when a book is built, it
While it's nice to print word counts to the terminal when a book is built, it
might also be a good idea to output them to a file somewhere. `mdbook` tells a
backend where it should place any generated output via the `destination` field
in [`RenderContext`].
@@ -240,17 +239,17 @@ in [`RenderContext`].
- use std::io;
use mdbook::renderer::RenderContext;
use mdbook::book::{BookItem, Chapter};
fn main() {
...
+ let _ = fs::create_dir_all(&ctx.destination);
+ let mut f = File::create(ctx.destination.join("wordcounts.txt")).unwrap();
+
+
for item in ctx.book.iter() {
if let BookItem::Chapter(ref ch) = *item {
...
let num_words = count_words(ch);
println!("{}: {}", ch.name, num_words);
+ writeln!(f, "{}: {}", ch.name, num_words).unwrap();
@@ -262,9 +261,13 @@ in [`RenderContext`].
> **Note:** There is no guarantee that the destination directory exists or is
> empty (`mdbook` may leave the previous contents to let backends do caching),
> so it's always a good idea to create it with `fs::create_dir_all()`.
>
> If the destination directory already exists, don't assume it will be empty.
> To allow backends to cache the results from previous runs, `mdbook` may leave
> old content in the directory.
There's always the possibility that an error will occur while processing a book
(just look at all the `unwrap()`'s we've written already), so `mdbook` will
(just look at all the `unwrap()`'s we've written already), so `mdbook` will
interpret a non-zero exit code as a rendering failure.
For example, if we wanted to make sure all chapters have an *even* number of
@@ -277,11 +280,11 @@ like this:
fn main() {
...
for item in ctx.book.iter() {
if let BookItem::Chapter(ref ch) = *item {
...
let num_words = count_words(ch);
println!("{}: {}", ch.name, num_words);
writeln!(f, "{}: {}", ch.name, num_words).unwrap();
@@ -304,8 +307,8 @@ like this:
Now, if we reinstall the backend and build a book,
```
$ cargo install --force
```shell
$ cargo install --path . --force
$ mdbook build /path/to/book
...
2018-01-16 21:21:39 [INFO] (mdbook::renderer): Invoking the "wordcount" renderer
@@ -315,27 +318,59 @@ init: 283
init has an odd number of words!
2018-01-16 21:21:39 [ERROR] (mdbook::renderer): Renderer exited with non-zero return code.
2018-01-16 21:21:39 [ERROR] (mdbook::utils): Error: Rendering failed
2018-01-16 21:21:39 [ERROR] (mdbook::utils): Caused By: The "mdbook-wordcount" renderer failed
2018-01-16 21:21:39 [ERROR] (mdbook::utils): Caused By: The "mdbook-wordcount" renderer failed
```
As you've probably already noticed, output from the plugin's subprocess is
immediately passed through to the user. It is encouraged for plugins to
follow the "rule of silence" and only generate output when necessary (e.g. an
error in generation or a warning).
immediately passed through to the user. It is encouraged for plugins to follow
the "rule of silence" and only generate output when necessary (e.g. an error in
generation or a warning).
All environment variables are passed through to the backend, allowing you to
use the usual `RUST_LOG` to control logging verbosity.
All environment variables are passed through to the backend, allowing you to use
the usual `RUST_LOG` to control logging verbosity.
## Handling missing backends
If you enable a backend that isn't installed, the default behavior is to throw an error:
```text
The command `mdbook-wordcount` wasn't found, is the "wordcount" backend installed?
If you want to ignore this error when the "wordcount" backend is not installed,
set `optional = true` in the `[output.wordcount]` section of the book.toml configuration file.
```
This behavior can be changed by marking the backend as optional.
```diff
[book]
title = "mdBook Documentation"
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
authors = ["Mathieu David", "Michael-F-Bryan"]
[output.html]
[output.wordcount]
command = "python /path/to/wordcount.py"
+ optional = true
```
This demotes the error to a warning, and it will instead look like this:
```text
The command was not found, but was marked as optional.
Command: wordcount
```
## Wrapping Up
Although contrived, hopefully this example was enough to show how you'd create
an alternate backend for `mdbook`. If you feel it's missing something, don't
an alternative backend for `mdbook`. If you feel it's missing something, don't
hesitate to create an issue in the [issue tracker] so we can improve the user
guide.
The existing backends mentioned towards the start of this chapter should serve
as a good example of how it's done in real life, so feel free to skim through
as a good example of how it's done in real life, so feel free to skim through
the source code or ask questions.
@@ -343,10 +378,10 @@ the source code or ask questions.
[mdbook-epub]: https://github.com/Michael-F-Bryan/mdbook-epub
[mdbook-test]: https://github.com/Michael-F-Bryan/mdbook-test
[rust-skeptic]: https://github.com/budziq/rust-skeptic
[`RenderContext`]: http://rust-lang-nursery.github.io/mdBook/mdbook/renderer/struct.RenderContext.html
[`RenderContext::from_json()`]: http://rust-lang-nursery.github.io/mdBook/mdbook/renderer/struct.RenderContext.html#method.from_json
[`RenderContext`]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html
[`RenderContext::from_json()`]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html#method.from_json
[`semver`]: https://crates.io/crates/semver
[`Book`]: http://rust-lang-nursery.github.io/mdBook/mdbook/book/struct.Book.html
[`Book::iter()`]: http://rust-lang-nursery.github.io/mdBook/mdbook/book/struct.Book.html#method.iter
[`Config`]: http://rust-lang-nursery.github.io/mdBook/mdbook/config/struct.Config.html
[issue tracker]: https://github.com/rust-lang-nursery/mdBook/issues
[`Book`]: https://docs.rs/mdbook/*/mdbook/book/struct.Book.html
[`Book::iter()`]: https://docs.rs/mdbook/*/mdbook/book/struct.Book.html#method.iter
[`Config`]: https://docs.rs/mdbook/*/mdbook/config/struct.Config.html
[issue tracker]: https://github.com/rust-lang/mdBook/issues

View File

@@ -0,0 +1,115 @@
# Preprocessors
A *preprocessor* is simply a bit of code which gets run immediately after the
book is loaded and before it gets rendered, allowing you to update and mutate
the book. Possible use cases are:
- Creating custom helpers like `\{{#include /path/to/file.md}}`
- Updating links so `[some chapter](some_chapter.md)` is automatically changed
to `[some chapter](some_chapter.html)` for the HTML renderer
- Substituting in latex-style expressions (`$$ \frac{1}{3} $$`) with their
mathjax equivalents
## Hooking Into MDBook
MDBook uses a fairly simple mechanism for discovering third party plugins.
A new table is added to `book.toml` (e.g. `preprocessor.foo` for the `foo`
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.
```toml
[book]
title = "My Book"
authors = ["Michael-F-Bryan"]
[preprocessor.foo]
# The command can also be specified manually
command = "python3 /path/to/foo.py"
# Only run the `foo` preprocessor for the HTML and EPUB renderer
renderer = ["html", "epub"]
```
In typical unix style, all inputs to the plugin will be written to `stdin` as
JSON and `mdbook` will read from `stdout` if it is expecting output.
The easiest way to get started is by creating your own implementation of the
`Preprocessor` trait (e.g. in `lib.rs`) and then creating a shell binary which
translates inputs to the correct `Preprocessor` method. For convenience, there
is [an example no-op preprocessor] in the `examples/` directory which can easily
be adapted for other preprocessors.
<details>
<summary>Example no-op preprocessor</summary>
```rust
// nop-preprocessors.rs
{{#include ../../../examples/nop-preprocessor.rs}}
```
</details>
## Hints For Implementing A Preprocessor
By pulling in `mdbook` as a library, preprocessors can have access to the
existing infrastructure for dealing with books.
For example, a custom preprocessor could use the
[`CmdPreprocessor::parse_input()`] function to deserialize the JSON written to
`stdin`. Then each chapter of the `Book` can be mutated in-place via
[`Book::for_each_mut()`], and then written to `stdout` with the `serde_json`
crate.
Chapters can be accessed either directly (by recursively iterating over
chapters) or via the `Book::for_each_mut()` convenience method.
The `chapter.content` is just a string which happens to be markdown. While it's
entirely possible to use regular expressions or do a manual find & replace,
you'll probably want to process the input into something more computer-friendly.
The [`pulldown-cmark`][pc] crate implements a production-quality event-based
Markdown parser, with the [`pulldown-cmark-to-cmark`][pctc] allowing you to
translate events back into markdown text.
The following code block shows how to remove all emphasis from markdown,
without accidentally breaking the document.
```rust
fn remove_emphasis(
num_removed_items: &mut usize,
chapter: &mut Chapter,
) -> Result<String> {
let mut buf = String::with_capacity(chapter.content.len());
let events = Parser::new(&chapter.content).filter(|e| {
let should_keep = match *e {
Event::Start(Tag::Emphasis)
| Event::Start(Tag::Strong)
| Event::End(Tag::Emphasis)
| Event::End(Tag::Strong) => false,
_ => true,
};
if !should_keep {
*num_removed_items += 1;
}
should_keep
});
cmark(events, &mut buf, None).map(|_| buf).map_err(|err| {
Error::from(format!("Markdown serialization failed: {}", err))
})
}
```
For everything else, have a look [at the complete example][example].
[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
[example]: https://github.com/rust-lang/mdBook/blob/master/examples/nop-preprocessor.rs
[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

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

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

View File

@@ -0,0 +1,43 @@
# MathJax Support
mdBook has optional support for math equations through
[MathJax](https://www.mathjax.org/).
To enable MathJax, you need to add the `mathjax-support` key to your `book.toml`
under the `output.html` section.
```toml
[output.html]
mathjax-support = true
```
>**Note:** The usual delimiters MathJax uses are not yet supported. You can't
currently use `$$ ... $$` as delimiters and the `\[ ... \]` delimiters need an
extra backslash to work. Hopefully this limitation will be lifted soon.
>**Note:** When you use double backslashes in MathJax blocks (for example in
> commands such as `\begin{cases} \frac 1 2 \\ \frac 3 4 \end{cases}`) you need
> to add _two extra_ backslashes (e.g., `\begin{cases} \frac 1 2 \\\\ \frac 3 4
> \end{cases}`).
### Inline equations
Inline equations are delimited by `\\(` and `\\)`. So for example, to render the
following inline equation \\( \int x dx = \frac{x^2}{2} + C \\) you would write
the following:
```
\\( \int x dx = \frac{x^2}{2} + C \\)
```
### Block equations
Block equations are delimited by `\\[` and `\\]`. To render the following
equation
\\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\]
you would write:
```bash
\\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\]
```

194
guide/src/format/mdbook.md Normal file
View File

@@ -0,0 +1,194 @@
# mdBook-specific features
## Hiding code lines
There is a feature in mdBook that lets you hide code lines by prepending them
with a `#` [like you would with Rustdoc][rustdoc-hide].
[rustdoc-hide]: https://doc.rust-lang.org/stable/rustdoc/documentation-tests.html#hiding-portions-of-the-example
```bash
# fn main() {
let x = 5;
let y = 6;
println!("{}", x + y);
# }
```
Will render as
```rust
# fn main() {
let x = 5;
let y = 7;
println!("{}", x + y);
# }
```
## Including files
With the following syntax, you can include files into your book:
```hbs
\{{#include file.rs}}
```
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.
````hbs
```
\{{#include file.rs}}
```
````
## Including portions of a file
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
\{{#include file.rs:2}}
\{{#include file.rs::10}}
\{{#include file.rs:2:}}
\{{#include file.rs:2:10}}
```
The first command only includes the second line from file `file.rs`. The second
command includes all lines up to line 10, i.e. the lines from 11 till the end of
the file are omitted. The third command includes all lines from line 2, i.e. the
first line is omitted. The last command includes the excerpt of `file.rs`
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
any kind of commented line.
Consider the following file to include:
```rs
/* ANCHOR: all */
// ANCHOR: component
struct Paddle {
hello: f32,
}
// ANCHOR_END: component
////////// ANCHOR: system
impl System for MySystem { ... }
////////// ANCHOR_END: system
/* ANCHOR_END: all */
```
Then in the book, all you have to do is:
````hbs
Here is a component:
```rust,no_run,noplayground
\{{#include file.rs:component}}
```
Here is a system:
```rust,no_run,noplayground
\{{#include file.rs:system}}
```
This is the full file.
```rust,no_run,noplayground
\{{#include file.rs:all}}
```
````
Lines containing anchor patterns inside the included anchor are ignored.
## Including a file but initially hiding all except specified lines
The `rustdoc_include` helper is for including code from external Rust files that contain complete
examples, but only initially showing particular lines specified with line numbers or anchors in the
same way as with `include`.
The lines not in the line number range or between the anchors will still be included, but they will
be prefaced with `#`. This way, a reader can expand the snippet to see the complete example, and
Rustdoc will use the complete example when you run `mdbook test`.
For example, consider a file named `file.rs` that contains this Rust program:
```rust
fn main() {
let x = add_one(2);
assert_eq!(x, 3);
}
fn add_one(num: i32) -> i32 {
num + 1
}
```
We can include a snippet that initially shows only line 2 by using this syntax:
````hbs
To call the `add_one` function, we pass it an `i32` and bind the returned value to `x`:
```rust
\{{#rustdoc_include file.rs:2}}
```
````
This would have the same effect as if we had manually inserted the code and hidden all but line 2
using `#`:
````hbs
To call the `add_one` function, we pass it an `i32` and bind the returned value to `x`:
```rust
# fn main() {
let x = add_one(2);
# assert_eq!(x, 3);
# }
#
# fn add_one(num: i32) -> i32 {
# num + 1
#}
```
````
That is, it looks like this (click the "expand" icon to see the rest of the file):
```rust
# fn main() {
let x = add_one(2);
# assert_eq!(x, 3);
# }
#
# fn add_one(num: i32) -> i32 {
# num + 1
#}
```
## Inserting runnable Rust files
With the following syntax, you can insert runnable Rust files into your book:
```hbs
\{{#playground file.rs}}
```
The path to the Rust file has to be relative from the current source file.
When play is clicked, the code snippet will be sent to the [Rust Playground] to be
compiled and run. The result is sent back and displayed directly underneath the
code.
Here is what a rendered code snippet looks like:
{{#playground example.rs}}
[Rust Playground]: https://play.rust-lang.org/

View File

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

View File

@@ -0,0 +1,44 @@
# Theme
The default renderer uses a [handlebars](http://handlebarsjs.com/) template to
render your markdown files and comes with a default theme included in the mdBook
binary.
The theme is totally customizable, you can selectively replace every file from
the theme by your own by adding a `theme` directory next to `src` folder in your
project root. Create a new file with the name of the file you want to override
and now that file will be used instead of the default file.
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.
- **_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,
you should not need to modify this.
- **_highlight.css_** is the theme used for the code highlighting.
- **_favicon.svg_** and **_favicon.png_** the favicon that will be used. The SVG
version is used by [newer browsers].
Generally, when you want to tweak the theme, you don't need to override all the
files. If you only need changes in the stylesheet, there is no point in
overriding all the other files. Because custom files take precedence over
built-in ones, they will not get updated with new fixes / features.
**Note:** When you override a file, it is possible that you break some
functionality. Therefore I recommend to use the file from the default theme as
template and only add / modify what you need. You can copy the default theme
into your source directory automatically by using `mdbook init --theme` just
remove the files you don't want to override.
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
[newer browsers]: https://caniuse.com/#feat=link-icon-svg

View File

@@ -0,0 +1,46 @@
# Editor
In addition to providing runnable code playgrounds, mdBook optionally allows them
to be editable. In order to enable editable code blocks, the following needs to
be added to the ***book.toml***:
```toml
[output.html.playground]
editable = true
```
To make a specific block available for editing, the attribute `editable` needs
to be added to it:
<pre><code class="language-markdown">```rust,editable
fn main() {
let number = 5;
print!("{}", number);
}
```</code></pre>
The above will result in this editable playground:
```rust,editable
fn main() {
let number = 5;
print!("{}", number);
}
```
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:
```toml
[output.html.playground]
editable = true
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
default Ace editor.

View File

@@ -0,0 +1,101 @@
# index.hbs
`index.hbs` is the handlebars template that is used to render the book. The
markdown files are processed to html and then injected in that template.
If you want to change the layout or style of your book, chances are that you
will have to modify this template a little bit. Here is what you need to know.
## Data
A lot of data is exposed to the handlebars template with the "context". In the
handlebars template you can access this information by using
```handlebars
{{name_of_property}}
```
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`.
- ***book_title*** Title of the book, as specified in `book.toml`
- ***chapter_title*** Title of the current chapter, as listed in `SUMMARY.md`
- ***path*** Relative path to the original markdown file from the source
directory
- ***content*** This is the rendered markdown.
- ***path_to_root*** This is a path containing exclusively `../`'s that points
to the root of the book from the current file. Since the original directory
structure is maintained, it is useful to prepend relative links with this
`path_to_root`.
- ***chapters*** Is an array of dictionaries of the form
```json
{"section": "1.2.1", "name": "name of this chapter", "path": "dir/markdown.md"}
```
containing all the chapters of the book. It is used for example to construct
the table of contents (sidebar).
## Handlebars Helpers
In addition to the properties you can access, there are some handlebars helpers
at your disposal.
### 1. toc
The toc helper is used like this
```handlebars
{{#toc}}{{/toc}}
```
and outputs something that looks like this, depending on the structure of your
book
```html
<ul class="chapter">
<li><a href="link/to/file.html">Some chapter</a></li>
<li>
<ul class="section">
<li><a href="link/to/other_file.html">Some other Chapter</a></li>
</ul>
</li>
</ul>
```
If you would like to make a toc with another structure, you have access to the
chapters property containing all the data. The only limitation at the moment
is that you would have to do it with JavaScript instead of with a handlebars
helper.
```html
<script>
var chapters = {{chapters}};
// Processing here
</script>
```
### 2. previous / next
The previous and next helpers expose a `link` and `name` property to the
previous and next chapters.
They are used like this
```handlebars
{{#previous}}
<a href="{{link}}" class="nav-chapters previous">
<i class="fa fa-angle-left"></i>
</a>
{{/previous}}
```
The inner html will only be rendered if the previous / next chapter exists.
Of course the inner html can be changed to your liking.
------
*If you would like other properties or helpers exposed, please [create a new
issue](https://github.com/rust-lang/mdBook/issues)*

View File

@@ -0,0 +1,121 @@
# Syntax Highlighting
mdBook uses [Highlight.js](https://highlightjs.org) with a custom theme
for syntax highlighting.
Automatic language detection has been turned off, so you will probably want to
specify the programming language you use like this:
~~~markdown
```rust
fn main() {
// Some code
}
```
~~~
## Supported languages
These languages are supported by default, but you can add more by supplying
your own `highlight.js` file:
- apache
- armasm
- bash
- c
- coffeescript
- cpp
- csharp
- css
- d
- diff
- go
- handlebars
- haskell
- http
- ini
- java
- javascript
- json
- julia
- kotlin
- less
- lua
- makefile
- markdown
- nginx
- objectivec
- perl
- php
- plaintext
- properties
- python
- r
- ruby
- rust
- scala
- scss
- shell
- sql
- swift
- typescript
- x86asm
- xml
- yaml
## Custom theme
Like the rest of the theme, the files used for syntax highlighting can be
overridden with your own.
- ***highlight.js*** normally you shouldn't have to overwrite this file, unless
you want to use a more recent version.
- ***highlight.css*** theme used by highlight.js for syntax highlighting.
If you want to use another theme for `highlight.js` download it from their
website, or make it yourself, rename it to `highlight.css` and put it in
the `theme` folder of your book.
Now your theme will be used instead of the default theme.
## Hiding code lines
There is a feature in mdBook that lets you hide code lines by prepending them
with a `#`.
```bash
# fn main() {
let x = 5;
let y = 6;
println!("{}", x + y);
# }
```
Will render as
```rust
# fn main() {
let x = 5;
let y = 7;
println!("{}", x + y);
# }
```
**At the moment, this only works for code examples that are annotated with
`rust`. Because it would collide with semantics of some programming languages.
In the future, we want to make this configurable through the `book.toml` so that
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
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.

View File

@@ -1,8 +1,7 @@
# Contributors
Here is a list of the contributors who have helped improving mdBook. Big shout-out to them!
If you have contributed to mdBook and I forgot to add you, don't hesitate to add yourself to the list. If you are in the list, feel free to add your real name & contact information if you wish.
Here is a list of the contributors who have helped improving mdBook. Big
shout-out to them!
- [mdinger](https://github.com/mdinger)
- Kevin ([kbknapp](https://github.com/kbknapp))
@@ -12,7 +11,10 @@ If you have contributed to mdBook and I forgot to add you, don't hesitate to add
- [funnkill](https://github.com/funkill)
- Fu Gangqiang ([FuGangqiang](https://github.com/FuGangqiang))
- [Michael-F-Bryan](https://github.com/Michael-F-Bryan)
- [Chris Spiegel](https://github.com/cspiegel)
- Chris Spiegel ([cspiegel](https://github.com/cspiegel))
- [projektir](https://github.com/projektir)
- [Phaiax](https://github.com/Phaiax)
- [Matt Ickstadt](https://github.com/mattico)
- Matt Ickstadt ([mattico](https://github.com/mattico))
- Weihang Lo ([@weihanglo](https://github.com/weihanglo))
If you feel you're missing from this list, feel free to add yourself in a PR.

3
release.toml Normal file
View File

@@ -0,0 +1,3 @@
sign-commit = true
push-remote = "origin"
tag-prefix = "v"

View File

@@ -1,7 +0,0 @@
array_layout = "Visual"
chain_indent = "Visual"
fn_args_layout = "Visual"
fn_call_style = "Visual"
format_strings = true
generics_indent = "Visual"

View File

@@ -1,30 +0,0 @@
use std::fs;
use std::path::PathBuf;
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use mdbook::errors::*;
use get_book_dir;
// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("clean")
.about("Delete built book")
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'The directory of built book{n}(Defaults to ./book when \
omitted)'",
)
}
// Clean command implementation
pub fn execute(args: &ArgMatches) -> ::mdbook::errors::Result<()> {
let book_dir = get_book_dir(args);
let book = MDBook::load(&book_dir)?;
let dir_to_remove = match args.value_of("dest-dir") {
Some(dest_dir) => PathBuf::from(dest_dir),
None => book.root.join(&book.config.build.build_dir),
};
fs::remove_dir_all(&dir_to_remove).chain_err(|| "Unable to remove the build directory")?;
Ok(())
}

View File

@@ -1,117 +0,0 @@
extern crate iron;
extern crate staticfile;
extern crate ws;
use std;
use self::iron::{status, AfterMiddleware, Chain, Iron, IronError, IronResult, Request, Response,
Set};
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use mdbook::utils;
use mdbook::errors::*;
use {get_book_dir, open};
#[cfg(feature = "watch")]
use watch;
struct ErrorRecover;
// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("serve")
.about("Serve the book at http://localhost:3000. Rebuild and reload on change.")
.arg_from_usage(
"[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'",
)
.arg_from_usage("-p, --port=[port] 'Use another port{n}(Defaults to 3000)'")
.arg_from_usage(
"-w, --websocket-port=[ws-port] 'Use another port for the websocket connection \
(livereload){n}(Defaults to 3001)'",
)
.arg_from_usage(
"-i, --interface=[interface] 'Interface to listen on{n}(Defaults to localhost)'",
)
.arg_from_usage(
"-a, --address=[address] 'Address that the browser can reach the websocket server \
from{n}(Defaults to the interface address)'",
)
.arg_from_usage("-o, --open 'Open the book server in a web browser'")
}
// Watch command implementation
pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args);
let mut book = MDBook::load(&book_dir)?;
let port = args.value_of("port").unwrap_or("3000");
let ws_port = args.value_of("websocket-port").unwrap_or("3001");
let interface = args.value_of("interface").unwrap_or("localhost");
let public_address = args.value_of("address").unwrap_or(interface);
let open_browser = args.is_present("open");
let address = format!("{}:{}", interface, port);
let ws_address = format!("{}:{}", interface, ws_port);
let livereload_url = format!("ws://{}:{}", public_address, ws_port);
book.config
.set("output.html.livereload-url", &livereload_url)?;
book.build()?;
let mut chain = Chain::new(staticfile::Static::new(book.build_dir_for("html")));
chain.link_after(ErrorRecover);
let _iron = Iron::new(chain)
.http(&*address)
.chain_err(|| "Unable to launch the server")?;
let ws_server =
ws::WebSocket::new(|_| |_| Ok(())).chain_err(|| "Unable to start the websocket")?;
let broadcaster = ws_server.broadcaster();
std::thread::spawn(move || {
ws_server.listen(&*ws_address).unwrap();
});
let serving_url = format!("http://{}", address);
info!("Serving on: {}", serving_url);
if open_browser {
open(serving_url);
}
#[cfg(feature = "watch")]
watch::trigger_on_change(&mut book, move |path, book_dir| {
info!("File changed: {:?}", path);
info!("Building book...");
// FIXME: This area is really ugly because we need to re-set livereload :(
let livereload_url = livereload_url.clone();
let result = MDBook::load(&book_dir)
.and_then(move |mut b| {
b.config.set("output.html.livereload-url", &livereload_url)?;
Ok(b)
})
.and_then(|b| b.build());
if let Err(e) = result {
error!("Unable to load the book");
utils::log_backtrace(&e);
} else {
let _ = broadcaster.send("reload");
}
});
Ok(())
}
impl AfterMiddleware for ErrorRecover {
fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
match err.response.status {
// each error will result in 404 response
Some(_) => Ok(err.response.set(status::NotFound)),
_ => Err(err),
}
}
}

View File

@@ -1,26 +0,0 @@
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use mdbook::errors::Result;
use get_book_dir;
// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("test")
.about("Test that code samples compile")
.arg_from_usage(
"-L, --library-path [DIR]... 'directory to add to crate search path'",
)
}
// test command implementation
pub fn execute(args: &ArgMatches) -> Result<()> {
let library_paths: Vec<&str> = args.values_of("library-path")
.map(|v| v.collect())
.unwrap_or_default();
let book_dir = get_book_dir(args);
let mut book = MDBook::load(&book_dir)?;
book.test(library_paths)?;
Ok(())
}

View File

@@ -1,87 +0,0 @@
extern crate notify;
use std::path::Path;
use self::notify::Watcher;
use std::time::Duration;
use std::sync::mpsc::channel;
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use mdbook::utils;
use mdbook::errors::Result;
use {get_book_dir, open};
// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("watch")
.about("Watch the files for changes")
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
.arg_from_usage(
"[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'",
)
}
// Watch command implementation
pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args);
let book = MDBook::load(&book_dir)?;
if args.is_present("open") {
book.build()?;
open(book.build_dir_for("html").join("index.html"));
}
trigger_on_change(&book, |path, book_dir| {
info!("File changed: {:?}\nBuilding book...\n", path);
let result = MDBook::load(&book_dir).and_then(|b| b.build());
if let Err(e) = result {
error!("Unable to build the book");
utils::log_backtrace(&e);
}
});
Ok(())
}
/// Calls the closure when a book source file is changed, blocking indefinitely.
pub fn trigger_on_change<F>(book: &MDBook, closure: F)
where
F: Fn(&Path, &Path),
{
use self::notify::RecursiveMode::*;
use self::notify::DebouncedEvent::*;
// Create a channel to receive the events.
let (tx, rx) = channel();
let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) {
Ok(w) => w,
Err(e) => {
error!("Error while trying to watch the files:\n\n\t{:?}", e);
::std::process::exit(1)
}
};
// Add the source directory to the watcher
if let Err(e) = watcher.watch(book.source_dir(), Recursive) {
error!("Error while watching {:?}:\n {:?}", book.source_dir(), e);
::std::process::exit(1);
};
let _ = watcher.watch(book.theme_dir(), Recursive);
// Add the book.toml file to the watcher if it exists
let _ = watcher.watch(book.root.join("book.toml"), NonRecursive);
info!("Listening for changes...");
for event in rx.iter() {
debug!("Received filesystem event: {:?}", event);
match event {
Create(path) | Write(path) | Remove(path) | Rename(_, path) => {
closure(&path, &book.root);
}
_ => {}
}
}
}

View File

@@ -1,12 +1,12 @@
use std::fmt::{self, Display, Formatter};
use std::path::{Path, PathBuf};
use std::collections::VecDeque;
use std::fmt::{self, Display, Formatter};
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
use config::BuildConfig;
use errors::*;
use crate::config::BuildConfig;
use crate::errors::*;
/// Load a book into memory from its `src/` directory.
pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book> {
@@ -15,13 +15,13 @@ pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book>
let mut summary_content = String::new();
File::open(summary_md)
.chain_err(|| "Couldn't open SUMMARY.md")?
.with_context(|| "Couldn't open SUMMARY.md")?
.read_to_string(&mut summary_content)?;
let summary = parse_summary(&summary_content).chain_err(|| "Summary parsing failed")?;
let summary = parse_summary(&summary_content).with_context(|| "Summary parsing failed")?;
if cfg.create_missing {
create_missing(&src_dir, &summary).chain_err(|| "Unable to create missing chapters")?;
create_missing(&src_dir, &summary).with_context(|| "Unable to create missing chapters")?;
}
load_book_from_disk(&summary, src_dir)
@@ -39,17 +39,19 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
let next = items.pop().expect("already checked");
if let SummaryItem::Link(ref link) = *next {
let filename = src_dir.join(&link.location);
if !filename.exists() {
if let Some(parent) = filename.parent() {
if !parent.exists() {
fs::create_dir_all(parent)?;
if let Some(ref location) = link.location {
let filename = src_dir.join(location);
if !filename.exists() {
if let Some(parent) = filename.parent() {
if !parent.exists() {
fs::create_dir_all(parent)?;
}
}
}
debug!("Creating missing file {}", filename.display());
debug!("Creating missing file {}", filename.display());
let mut f = File::create(&filename)?;
writeln!(f, "# {}", link.name)?;
let mut f = File::create(&filename)?;
writeln!(f, "# {}", link.name)?;
}
}
items.extend(&link.nested_items);
@@ -82,7 +84,7 @@ impl Book {
}
/// Get a depth-first iterator over the items in the book.
pub fn iter(&self) -> BookItems {
pub fn iter(&self) -> BookItems<'_> {
BookItems {
items: self.sections.iter().collect(),
}
@@ -116,7 +118,7 @@ where
I: IntoIterator<Item = &'a mut BookItem>,
{
for item in items {
if let &mut BookItem::Chapter(ref mut ch) = item {
if let BookItem::Chapter(ch) = item {
for_each_mut(func, &mut ch.sub_items);
}
@@ -131,6 +133,8 @@ pub enum BookItem {
Chapter(Chapter),
/// A section separator.
Separator,
/// A part title.
PartTitle(String),
}
impl From<Chapter> for BookItem {
@@ -152,7 +156,7 @@ pub struct Chapter {
/// Nested items.
pub sub_items: Vec<BookItem>,
/// The chapter's location, relative to the `SUMMARY.md` file.
pub path: PathBuf,
pub path: Option<PathBuf>,
/// An ordered list of the names of each chapter above this one, in the hierarchy.
pub parent_names: Vec<String>,
}
@@ -167,19 +171,39 @@ impl Chapter {
) -> Chapter {
Chapter {
name: name.to_string(),
content: content,
path: path.into(),
parent_names: parent_names,
content,
path: Some(path.into()),
parent_names,
..Default::default()
}
}
/// Create a new draft chapter that is not attached to a source markdown file and has
/// thus no content.
pub fn new_draft(name: &str, parent_names: Vec<String>) -> Self {
Chapter {
name: name.to_string(),
content: String::new(),
path: None,
parent_names,
..Default::default()
}
}
/// 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,
}
}
}
/// Use the provided `Summary` to load a `Book` from disk.
///
/// You need to pass in the book's source directory because all the links in
/// `SUMMARY.md` give the chapter locations relative to it.
fn load_book_from_disk<P: AsRef<Path>>(summary: &Summary, src_dir: P) -> Result<Book> {
pub(crate) fn load_book_from_disk<P: AsRef<Path>>(summary: &Summary, src_dir: P) -> Result<Book> {
debug!("Loading the book from disk");
let src_dir = src_dir.as_ref();
@@ -202,16 +226,17 @@ fn load_book_from_disk<P: AsRef<Path>>(summary: &Summary, src_dir: P) -> Result<
})
}
fn load_summary_item<P: AsRef<Path>>(
fn load_summary_item<P: AsRef<Path> + Clone>(
item: &SummaryItem,
src_dir: P,
parent_names: Vec<String>,
) -> Result<BookItem> {
match *item {
match item {
SummaryItem::Separator => Ok(BookItem::Separator),
SummaryItem::Link(ref link) => {
load_chapter(link, src_dir, parent_names).map(|c| BookItem::Chapter(c))
load_chapter(link, src_dir, parent_names).map(BookItem::Chapter)
}
SummaryItem::PartTitle(title) => Ok(BookItem::PartTitle(title.clone())),
}
}
@@ -220,32 +245,41 @@ fn load_chapter<P: AsRef<Path>>(
src_dir: P,
parent_names: Vec<String>,
) -> Result<Chapter> {
debug!("Loading {} ({})", link.name, link.location.display());
let src_dir = src_dir.as_ref();
let location = if link.location.is_absolute() {
link.location.clone()
let mut ch = if let Some(ref link_location) = link.location {
debug!("Loading {} ({})", link.name, link_location.display());
let location = if link_location.is_absolute() {
link_location.clone()
} else {
src_dir.join(link_location)
};
let mut f = File::open(&location)
.with_context(|| format!("Chapter file not found, {}", link_location.display()))?;
let mut content = String::new();
f.read_to_string(&mut content).with_context(|| {
format!("Unable to read \"{}\" ({})", link.name, location.display())
})?;
let stripped = location
.strip_prefix(&src_dir)
.expect("Chapters are always inside a book");
Chapter::new(&link.name, content, stripped, parent_names.clone())
} else {
src_dir.join(&link.location)
Chapter::new_draft(&link.name, parent_names.clone())
};
let mut f = File::open(&location)
.chain_err(|| format!("Chapter file not found, {}", link.location.display()))?;
let mut content = String::new();
f.read_to_string(&mut content)
.chain_err(|| format!("Unable to read \"{}\" ({})", link.name, location.display()))?;
let stripped = location
.strip_prefix(&src_dir)
.expect("Chapters are always inside a book");
let mut sub_item_parents = parent_names.clone();
let mut ch = Chapter::new(&link.name, content, stripped, parent_names);
ch.number = link.number.clone();
sub_item_parents.push(link.name.clone());
let sub_items = link.nested_items
let sub_items = link
.nested_items
.iter()
.map(|i| load_summary_item(i, src_dir, sub_item_parents.clone()))
.collect::<Result<Vec<_>>>()?;
@@ -285,7 +319,7 @@ impl<'a> Iterator for BookItems<'a> {
}
impl Display for Chapter {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if let Some(ref section_number) = self.number {
write!(f, "{} ", section_number)?;
}
@@ -297,10 +331,10 @@ impl Display for Chapter {
#[cfg(test)]
mod tests {
use super::*;
use tempdir::TempDir;
use std::io::Write;
use tempfile::{Builder as TempFileBuilder, TempDir};
const DUMMY_SRC: &'static str = "
const DUMMY_SRC: &str = "
# Dummy Chapter
this is some dummy text.
@@ -311,12 +345,12 @@ And here is some \
/// Create a dummy `Link` in a temporary directory.
fn dummy_link() -> (Link, TempDir) {
let temp = TempDir::new("book").unwrap();
let temp = TempFileBuilder::new().prefix("book").tempdir().unwrap();
let chapter_path = temp.path().join("chapter_1.md");
File::create(&chapter_path)
.unwrap()
.write(DUMMY_SRC.as_bytes())
.write_all(DUMMY_SRC.as_bytes())
.unwrap();
let link = Link::new("Chapter 1", chapter_path);
@@ -332,7 +366,7 @@ And here is some \
File::create(&second_path)
.unwrap()
.write_all("Hello World!".as_bytes())
.write_all(b"Hello World!")
.unwrap();
let mut second = Link::new("Nested Chapter 1", &second_path);
@@ -375,7 +409,7 @@ And here is some \
name: String::from("Nested Chapter 1"),
content: String::from("Hello World!"),
number: Some(SectionNumber(vec![1, 2])),
path: PathBuf::from("second.md"),
path: Some(PathBuf::from("second.md")),
parent_names: vec![String::from("Chapter 1")],
sub_items: Vec::new(),
};
@@ -383,7 +417,7 @@ And here is some \
name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC),
number: None,
path: PathBuf::from("chapter_1.md"),
path: Some(PathBuf::from("chapter_1.md")),
parent_names: Vec::new(),
sub_items: vec![
BookItem::Chapter(nested.clone()),
@@ -404,14 +438,12 @@ And here is some \
..Default::default()
};
let should_be = Book {
sections: vec![
BookItem::Chapter(Chapter {
name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC),
path: PathBuf::from("chapter_1.md"),
..Default::default()
}),
],
sections: vec![BookItem::Chapter(Chapter {
name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC),
path: Some(PathBuf::from("chapter_1.md")),
..Default::default()
})],
..Default::default()
};
@@ -449,7 +481,7 @@ And here is some \
name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC),
number: None,
path: PathBuf::from("Chapter_1/index.md"),
path: Some(PathBuf::from("Chapter_1/index.md")),
parent_names: Vec::new(),
sub_items: vec![
BookItem::Chapter(Chapter::new(
@@ -477,7 +509,8 @@ And here is some \
assert_eq!(got.len(), 5);
// checking the chapter names are in the order should be sufficient here...
let chapter_names: Vec<String> = got.into_iter()
let chapter_names: Vec<String> = got
.into_iter()
.filter_map(|i| match *i {
BookItem::Chapter(ref ch) => Some(ch.name.clone()),
_ => None,
@@ -500,7 +533,7 @@ And here is some \
name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC),
number: None,
path: PathBuf::from("Chapter_1/index.md"),
path: Some(PathBuf::from("Chapter_1/index.md")),
parent_names: Vec::new(),
sub_items: vec![
BookItem::Chapter(Chapter::new(
@@ -535,13 +568,12 @@ And here is some \
fn cant_load_chapters_with_an_empty_path() {
let (_, temp) = dummy_link();
let summary = Summary {
numbered_chapters: vec![
SummaryItem::Link(Link {
name: String::from("Empty"),
location: PathBuf::from(""),
..Default::default()
}),
],
numbered_chapters: vec![SummaryItem::Link(Link {
name: String::from("Empty"),
location: Some(PathBuf::from("")),
..Default::default()
})],
..Default::default()
};
@@ -556,13 +588,11 @@ And here is some \
fs::create_dir(&dir).unwrap();
let summary = Summary {
numbered_chapters: vec![
SummaryItem::Link(Link {
name: String::from("nested"),
location: dir,
..Default::default()
}),
],
numbered_chapters: vec![SummaryItem::Link(Link {
name: String::from("nested"),
location: Some(dir),
..Default::default()
})],
..Default::default()
};

View File

@@ -1,12 +1,11 @@
use std::fs::{self, File};
use std::path::PathBuf;
use std::io::Write;
use toml;
use std::path::PathBuf;
use config::Config;
use super::MDBook;
use theme;
use errors::*;
use crate::config::Config;
use crate::errors::*;
use crate::theme;
/// A helper for setting up a new book and its directory structure.
#[derive(Debug, Clone, PartialEq)]
@@ -65,19 +64,19 @@ impl BookBuilder {
info!("Creating a new book with stub content");
self.create_directory_structure()
.chain_err(|| "Unable to create directory structure")?;
.with_context(|| "Unable to create directory structure")?;
self.create_stub_files()
.chain_err(|| "Unable to create stub files")?;
.with_context(|| "Unable to create stub files")?;
if self.create_gitignore {
self.build_gitignore()
.chain_err(|| "Unable to create .gitignore")?;
.with_context(|| "Unable to create .gitignore")?;
}
if self.copy_theme {
self.copy_across_theme()
.chain_err(|| "Unable to copy across the theme")?;
.with_context(|| "Unable to copy across the theme")?;
}
self.write_book_toml()?;
@@ -98,21 +97,21 @@ impl BookBuilder {
fn write_book_toml(&self) -> Result<()> {
debug!("Writing book.toml");
let book_toml = self.root.join("book.toml");
let cfg = toml::to_vec(&self.config).chain_err(|| "Unable to serialize the config")?;
let cfg = toml::to_vec(&self.config).with_context(|| "Unable to serialize the config")?;
File::create(book_toml)
.chain_err(|| "Couldn't create book.toml")?
.with_context(|| "Couldn't create book.toml")?
.write_all(&cfg)
.chain_err(|| "Unable to write config to book.toml")?;
.with_context(|| "Unable to write config to book.toml")?;
Ok(())
}
fn copy_across_theme(&self) -> Result<()> {
debug!("Copying theme");
let themedir = self.config
.html_config()
.and_then(|html| html.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);
@@ -127,11 +126,28 @@ impl BookBuilder {
let mut index = File::create(themedir.join("index.hbs"))?;
index.write_all(theme::INDEX)?;
let mut css = File::create(themedir.join("book.css"))?;
css.write_all(theme::CSS)?;
let cssdir = themedir.join("css");
fs::create_dir(&cssdir)?;
let mut general_css = File::create(cssdir.join("general.css"))?;
general_css.write_all(theme::GENERAL_CSS)?;
let mut chrome_css = File::create(cssdir.join("chrome.css"))?;
chrome_css.write_all(theme::CHROME_CSS)?;
if html_config.print.enable {
let mut print_css = File::create(cssdir.join("print.css"))?;
print_css.write_all(theme::PRINT_CSS)?;
}
let mut variables_css = File::create(cssdir.join("variables.css"))?;
variables_css.write_all(theme::VARIABLES_CSS)?;
let mut favicon = File::create(themedir.join("favicon.png"))?;
favicon.write_all(theme::FAVICON)?;
favicon.write_all(theme::FAVICON_PNG)?;
let mut favicon = File::create(themedir.join("favicon.svg"))?;
favicon.write_all(theme::FAVICON_SVG)?;
let mut js = File::create(themedir.join("book.js"))?;
js.write_all(theme::JS)?;
@@ -160,15 +176,20 @@ impl BookBuilder {
let src_dir = self.root.join(&self.config.book.src);
let summary = src_dir.join("SUMMARY.md");
let mut f = File::create(&summary).chain_err(|| "Unable to create SUMMARY.md")?;
writeln!(f, "# Summary")?;
writeln!(f, "")?;
writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
let chapter_1 = src_dir.join("chapter_1.md");
let mut f = File::create(&chapter_1).chain_err(|| "Unable to create chapter_1.md")?;
writeln!(f, "# Chapter 1")?;
if !summary.exists() {
trace!("No summary found creating stub summary and chapter_1.md.");
let mut f = File::create(&summary).with_context(|| "Unable to create SUMMARY.md")?;
writeln!(f, "# Summary")?;
writeln!(f)?;
writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
let chapter_1 = src_dir.join("chapter_1.md");
let mut f =
File::create(&chapter_1).with_context(|| "Unable to create chapter_1.md")?;
writeln!(f, "# Chapter 1")?;
} else {
trace!("Existing summary found, no need to create stub files.");
}
Ok(())
}

View File

@@ -5,26 +5,30 @@
//!
//! [1]: ../index.html
mod summary;
#[allow(clippy::module_inception)]
mod book;
mod init;
mod summary;
pub use self::book::{load_book, Book, BookItem, BookItems, Chapter};
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
pub use self::init::BookBuilder;
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
use std::path::PathBuf;
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use tempdir::TempDir;
use std::string::ToString;
use tempfile::Builder as TempFileBuilder;
use toml::Value;
use utils;
use renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer};
use preprocess::{LinkPreprocessor, Preprocessor, PreprocessorContext};
use errors::*;
use crate::errors::*;
use crate::preprocess::{
CmdPreprocessor, IndexPreprocessor, LinkPreprocessor, Preprocessor, PreprocessorContext,
};
use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderContext, Renderer};
use crate::utils;
use config::Config;
use crate::config::{Config, RustEdition};
/// The object used to manage and build a book.
pub struct MDBook {
@@ -34,10 +38,10 @@ pub struct MDBook {
pub config: Config,
/// A representation of the book's contents in memory.
pub book: Book,
renderers: Vec<Box<Renderer>>,
renderers: Vec<Box<dyn Renderer>>,
/// List of pre-processors to be run on the book
preprocessors: Vec<Box<Preprocessor>>,
preprocessors: Vec<Box<dyn Preprocessor>>,
}
impl MDBook {
@@ -53,7 +57,7 @@ impl MDBook {
warn!("This format is no longer used, so you should migrate to the");
warn!("book.toml format.");
warn!("Check the user guide for migration information:");
warn!("\thttps://rust-lang-nursery.github.io/mdBook/format/config.html");
warn!("\thttps://rust-lang.github.io/mdBook/format/config.html");
}
let mut config = if config_location.exists() {
@@ -65,7 +69,7 @@ impl MDBook {
config.update_from_env();
if log_enabled!(::log::Level::Trace) {
if log_enabled!(log::Level::Trace) {
for line in format!("Config: {:#?}", config).lines() {
trace!("{}", line);
}
@@ -93,21 +97,42 @@ impl MDBook {
})
}
/// 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,
summary: Summary,
) -> Result<MDBook> {
let root = book_root.into();
let src_dir = root.join(&config.book.src);
let book = book::load_book_from_disk(&summary, &src_dir)?;
let renderers = determine_renderers(&config);
let preprocessors = determine_preprocessors(&config)?;
Ok(MDBook {
root,
config,
book,
renderers,
preprocessors,
})
}
/// Returns a flat depth-first iterator over the elements of the book,
/// it returns an [BookItem enum](bookitem.html):
/// `(section: String, bookitem: &BookItem)`
///
/// ```no_run
/// # extern crate mdbook;
/// # use mdbook::MDBook;
/// # use mdbook::book::BookItem;
/// # #[allow(unused_variables)]
/// # fn main() {
/// # let book = MDBook::load("mybook").unwrap();
/// for item in book.iter() {
/// match *item {
/// BookItem::Chapter(ref chapter) => {},
/// BookItem::Separator => {},
/// BookItem::PartTitle(ref title) => {}
/// }
/// }
///
@@ -118,9 +143,8 @@ impl MDBook {
/// // 2. Chapter 2
/// //
/// // etc.
/// # }
/// ```
pub fn iter(&self) -> BookItems {
pub fn iter(&self) -> BookItems<'_> {
self.book.iter()
}
@@ -149,35 +173,38 @@ impl MDBook {
pub fn build(&self) -> Result<()> {
info!("Book building has started");
let mut preprocessed_book = self.book.clone();
let preprocess_ctx = PreprocessorContext::new(self.root.clone(), self.config.clone());
for preprocessor in &self.preprocessors {
debug!("Running the {} preprocessor.", preprocessor.name());
preprocessor.run(&preprocess_ctx, &mut preprocessed_book)?;
}
for renderer in &self.renderers {
info!("Running the {} backend", renderer.name());
self.run_renderer(&preprocessed_book, renderer.as_ref())?;
self.execute_build_process(&**renderer)?;
}
Ok(())
}
fn run_renderer(&self, preprocessed_book: &Book, renderer: &Renderer) -> Result<()> {
/// 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(
self.root.clone(),
self.config.clone(),
renderer.name().to_string(),
);
for preprocessor in &self.preprocessors {
if preprocessor_should_run(&**preprocessor, renderer, &self.config) {
debug!("Running the {} preprocessor.", preprocessor.name());
preprocessed_book = preprocessor.run(&preprocess_ctx, preprocessed_book)?;
}
}
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);
if build_dir.exists() {
debug!(
"Cleaning build dir for the \"{}\" renderer ({})",
name,
build_dir.display()
);
utils::fs::remove_dir_content(&build_dir)
.chain_err(|| "Unable to clear output directory")?;
}
let render_context = RenderContext::new(
self.root.clone(),
@@ -188,7 +215,7 @@ impl MDBook {
renderer
.render(&render_context)
.chain_err(|| "Rendering failed")
.with_context(|| "Rendering failed")
}
/// You can change the default renderer to another one by using this method.
@@ -200,7 +227,7 @@ impl MDBook {
}
/// Register a [`Preprocessor`](../preprocess/trait.Preprocessor.html) to be used when rendering the book.
pub fn with_preprecessor<P: Preprocessor + 'static>(&mut self, preprocessor: P) -> &mut Self {
pub fn with_preprocessor<P: Preprocessor + 'static>(&mut self, preprocessor: P) -> &mut Self {
self.preprocessors.push(Box::new(preprocessor));
self
}
@@ -213,39 +240,62 @@ impl MDBook {
.flat_map(|x| vec![x.0, x.1])
.collect();
let temp_dir = TempDir::new("mdbook")?;
let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
let preprocess_context = PreprocessorContext::new(self.root.clone(), self.config.clone());
// FIXME: Is "test" the proper renderer name to use here?
let preprocess_context =
PreprocessorContext::new(self.root.clone(), self.config.clone(), "test".to_string());
LinkPreprocessor::new().run(&preprocess_context, &mut self.book)?;
let book = LinkPreprocessor::new().run(&preprocess_context, self.book.clone())?;
// Index Preprocessor is disabled so that chapter paths continue to point to the
// actual markdown files.
for item in self.iter() {
let mut failed = false;
for item in book.iter() {
if let BookItem::Chapter(ref ch) = *item {
if !ch.path.as_os_str().is_empty() {
let path = self.source_dir().join(&ch.path);
let content = utils::fs::file_to_string(&path)?;
info!("Testing file: {:?}", path);
let chapter_path = match ch.path {
Some(ref path) if !path.as_os_str().is_empty() => path,
_ => continue,
};
// write preprocessed file to tempdir
let path = temp_dir.path().join(&ch.path);
let mut tmpf = utils::fs::create_file(&path)?;
tmpf.write_all(content.as_bytes())?;
let path = self.source_dir().join(&chapter_path);
info!("Testing file: {:?}", path);
let output = Command::new("rustdoc")
.arg(&path)
.arg("--test")
.args(&library_args)
.output()?;
// write preprocessed file to tempdir
let path = temp_dir.path().join(&chapter_path);
let mut tmpf = utils::fs::create_file(&path)?;
tmpf.write_all(ch.content.as_bytes())?;
if !output.status.success() {
bail!(ErrorKind::Subprocess(
"Rustdoc returned an error".to_string(),
output
));
let mut cmd = Command::new("rustdoc");
cmd.arg(&path).arg("--test").args(&library_args);
if let Some(edition) = self.config.rust.edition {
match edition {
RustEdition::E2015 => {
cmd.args(&["--edition", "2015"]);
}
RustEdition::E2018 => {
cmd.args(&["--edition", "2018"]);
}
}
}
let output = cmd.output()?;
if !output.status.success() {
failed = true;
error!(
"rustdoc returned an error:\n\
\n--- stdout\n{}\n--- stderr\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
}
}
if failed {
bail!("One or more tests failed");
}
Ok(())
}
@@ -298,19 +348,19 @@ impl MDBook {
}
/// Look at the `Config` and try to figure out what renderers to use.
fn determine_renderers(config: &Config) -> Vec<Box<Renderer>> {
let mut renderers: Vec<Box<Renderer>> = Vec::new();
fn determine_renderers(config: &Config) -> Vec<Box<dyn Renderer>> {
let mut renderers = Vec::new();
if let Some(output_table) = config.get("output").and_then(|o| o.as_table()) {
for (key, table) in output_table.iter() {
// the "html" backend has its own Renderer
if let Some(output_table) = config.get("output").and_then(Value::as_table) {
renderers.extend(output_table.iter().map(|(key, table)| {
if key == "html" {
renderers.push(Box::new(HtmlHandlebars::new()));
Box::new(HtmlHandlebars::new()) as Box<dyn Renderer>
} else if key == "markdown" {
Box::new(MarkdownRenderer::new()) as Box<dyn Renderer>
} else {
let renderer = interpret_custom_renderer(key, table);
renderers.push(renderer);
interpret_custom_renderer(key, table)
}
}
}));
}
// if we couldn't find anything, add the HTML renderer as a default
@@ -321,47 +371,98 @@ fn determine_renderers(config: &Config) -> Vec<Box<Renderer>> {
renderers
}
fn default_preprocessors() -> Vec<Box<Preprocessor>> {
vec![Box::new(LinkPreprocessor::new())]
fn default_preprocessors() -> Vec<Box<dyn Preprocessor>> {
vec![
Box::new(LinkPreprocessor::new()),
Box::new(IndexPreprocessor::new()),
]
}
fn is_default_preprocessor(pre: &dyn Preprocessor) -> bool {
let name = pre.name();
name == LinkPreprocessor::NAME || name == IndexPreprocessor::NAME
}
/// Look at the `MDBook` and try to figure out what preprocessors to run.
fn determine_preprocessors(config: &Config) -> Result<Vec<Box<Preprocessor>>> {
let preprocess_list = match config.build.preprocess {
Some(ref p) => p,
// If no preprocessor field is set, default to the LinkPreprocessor. This allows you
// to disable the LinkPreprocessor by setting "preprocess" to an empty list.
None => return Ok(default_preprocessors()),
};
fn determine_preprocessors(config: &Config) -> Result<Vec<Box<dyn Preprocessor>>> {
let mut preprocessors = Vec::new();
let mut preprocessors: Vec<Box<Preprocessor>> = Vec::new();
if config.build.use_default_preprocessors {
preprocessors.extend(default_preprocessors());
}
for key in preprocess_list {
match key.as_ref() {
"links" => preprocessors.push(Box::new(LinkPreprocessor::new())),
_ => bail!("{:?} is not a recognised preprocessor", key),
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],
)),
}
}
}
Ok(preprocessors)
}
fn interpret_custom_renderer(key: &str, table: &Value) -> Box<Renderer> {
fn interpret_custom_preprocessor(key: &str, table: &Value) -> Box<CmdPreprocessor> {
let command = table
.get("command")
.and_then(Value::as_str)
.map(ToString::to_string)
.unwrap_or_else(|| format!("mdbook-{}", key));
Box::new(CmdPreprocessor::new(key.to_string(), command))
}
fn interpret_custom_renderer(key: &str, table: &Value) -> Box<CmdRenderer> {
// look for the `command` field, falling back to using the key
// prepended by "mdbook-"
let table_dot_command = table
.get("command")
.and_then(|c| c.as_str())
.map(|s| s.to_string());
.and_then(Value::as_str)
.map(ToString::to_string);
let command = table_dot_command.unwrap_or_else(|| format!("mdbook-{}", key));
Box::new(CmdRenderer::new(key.to_string(), command.to_string()))
Box::new(CmdRenderer::new(key.to_string(), command))
}
/// Check whether we should run a particular `Preprocessor` in combination
/// with the renderer, falling back to `Preprocessor::supports_renderer()`
/// method if the user doesn't say anything.
///
/// The `build.use-default-preprocessors` config option can be used to ensure
/// default preprocessors always run if they support the renderer.
fn preprocessor_should_run(
preprocessor: &dyn Preprocessor,
renderer: &dyn Renderer,
cfg: &Config,
) -> bool {
// default preprocessors should be run by default (if supported)
if cfg.build.use_default_preprocessors && is_default_preprocessor(preprocessor) {
return preprocessor.supports_renderer(renderer.name());
}
let key = format!("preprocessor.{}.renderers", preprocessor.name());
let renderer_name = renderer.name();
if let Some(Value::Array(ref explicit_renderers)) = cfg.get(&key) {
return explicit_renderers
.iter()
.filter_map(Value::as_str)
.any(|name| name == renderer_name);
}
preprocessor.supports_renderer(renderer_name)
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
use toml::value::{Table, Value};
#[test]
@@ -403,61 +504,120 @@ mod tests {
}
#[test]
fn config_defaults_to_link_preprocessor_if_not_set() {
fn config_defaults_to_link_and_index_preprocessor_if_not_set() {
let cfg = Config::default();
// make sure we haven't got anything in the `output` table
assert!(cfg.build.preprocess.is_none());
// make sure we haven't got anything in the `preprocessor` table
assert!(cfg.get("preprocessor").is_none());
let got = determine_preprocessors(&cfg);
assert!(got.is_ok());
assert_eq!(got.as_ref().unwrap().len(), 1);
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");
}
#[test]
fn config_doesnt_default_if_empty() {
let cfg_str: &'static str = r#"
fn use_default_preprocessors_works() {
let mut cfg = Config::default();
cfg.build.use_default_preprocessors = false;
let got = determine_preprocessors(&cfg).unwrap();
assert_eq!(got.len(), 0);
}
#[test]
fn can_determine_third_party_preprocessors() {
let cfg_str = r#"
[book]
title = "Some Book"
[preprocessor.random]
[build]
build-dir = "outputs"
create-missing = false
preprocess = []
"#;
let cfg = Config::from_str(cfg_str).unwrap();
// make sure we have something in the `output` table
assert!(cfg.build.preprocess.is_some());
// make sure the `preprocessor.random` table exists
assert!(cfg.get_preprocessor("random").is_some());
let got = determine_preprocessors(&cfg);
let got = determine_preprocessors(&cfg).unwrap();
assert!(got.is_ok());
assert!(got.unwrap().is_empty());
assert!(got.into_iter().any(|p| p.name() == "random"));
}
#[test]
fn config_complains_if_unimplemented_preprocessor() {
let cfg_str: &'static str = r#"
[book]
title = "Some Book"
[build]
build-dir = "outputs"
create-missing = false
preprocess = ["random"]
fn preprocessors_can_provide_their_own_commands() {
let cfg_str = r#"
[preprocessor.random]
command = "python random.py"
"#;
let cfg = Config::from_str(cfg_str).unwrap();
// make sure we have something in the `output` table
assert!(cfg.build.preprocess.is_some());
// 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 got = determine_preprocessors(&cfg);
assert_eq!(random.cmd(), "python random.py");
}
assert!(got.is_err());
#[test]
fn config_respects_preprocessor_selection() {
let cfg_str = r#"
[preprocessor.links]
renderers = ["html"]
"#;
let cfg = Config::from_str(cfg_str).unwrap();
// double-check that we can access preprocessor.links.renderers[0]
let html = cfg
.get_preprocessor("links")
.and_then(|links| links.get("renderers"))
.and_then(Value::as_array)
.and_then(|renderers| renderers.get(0))
.and_then(Value::as_str)
.unwrap();
assert_eq!(html, "html");
let html_renderer = HtmlHandlebars::default();
let pre = LinkPreprocessor::new();
let should_run = preprocessor_should_run(&pre, &html_renderer, &cfg);
assert!(should_run);
}
struct BoolPreprocessor(bool);
impl Preprocessor for BoolPreprocessor {
fn name(&self) -> &str {
"bool-preprocessor"
}
fn run(&self, _ctx: &PreprocessorContext, _book: Book) -> Result<Book> {
unimplemented!()
}
fn supports_renderer(&self, _renderer: &str) -> bool {
self.0
}
}
#[test]
fn preprocessor_should_run_falls_back_to_supports_renderer_method() {
let cfg = Config::default();
let html = HtmlHandlebars::new();
let should_be = true;
let got = preprocessor_should_run(&BoolPreprocessor(should_be), &html, &cfg);
assert_eq!(got, should_be);
let should_be = false;
let got = preprocessor_should_run(&BoolPreprocessor(should_be), &html, &cfg);
assert_eq!(got, should_be);
}
}

View File

@@ -1,10 +1,10 @@
use crate::errors::*;
use memchr::{self, Memchr};
use pulldown_cmark::{self, Event, Tag};
use std::fmt::{self, Display, Formatter};
use std::iter::FromIterator;
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
use memchr::{self, Memchr};
use pulldown_cmark::{self, Event, Tag};
use errors::*;
/// Parse the text from a `SUMMARY.md` file into a sort of "recipe" to be
/// used when loading a book from disk.
@@ -25,12 +25,17 @@ use errors::*;
/// [Title of prefix element](relative/path/to/markdown.md)
/// ```
///
/// **Part Title:** An optional title for the next collect of numbered chapters. The numbered
/// chapters can be broken into as many parts as desired.
///
/// **Numbered Chapter:** Numbered chapters are the main content of the book,
/// they
/// will be numbered and can be nested, resulting in a nice hierarchy (chapters,
/// sub-chapters, etc.)
///
/// ```markdown
/// # Title of Part
///
/// - [Title of the Chapter](relative/path/to/markdown.md)
/// ```
///
@@ -55,7 +60,7 @@ pub struct Summary {
pub title: Option<String>,
/// Chapters before the main text (e.g. an introduction).
pub prefix_chapters: Vec<SummaryItem>,
/// The main chapters in the document.
/// The main numbered chapters of the book, broken into one or more possibly named parts.
pub numbered_chapters: Vec<SummaryItem>,
/// Items which come after the main document (e.g. a conclusion).
pub suffix_chapters: Vec<SummaryItem>,
@@ -71,7 +76,7 @@ pub struct Link {
pub name: String,
/// The location of the chapter's source file, taking the book's `src`
/// directory as the root.
pub location: PathBuf,
pub location: Option<PathBuf>,
/// The section number, if this chapter is in the numbered section.
pub number: Option<SectionNumber>,
/// Any nested items this chapter may contain.
@@ -83,7 +88,7 @@ impl Link {
pub fn new<S: Into<String>, P: AsRef<Path>>(name: S, location: P) -> Link {
Link {
name: name.into(),
location: location.as_ref().to_path_buf(),
location: Some(location.as_ref().to_path_buf()),
number: None,
nested_items: Vec::new(),
}
@@ -94,7 +99,7 @@ impl Default for Link {
fn default() -> Self {
Link {
name: String::new(),
location: PathBuf::new(),
location: Some(PathBuf::new()),
number: None,
nested_items: Vec::new(),
}
@@ -108,6 +113,8 @@ pub enum SummaryItem {
Link(Link),
/// A separator (`---`).
Separator,
/// A part title.
PartTitle(String),
}
impl SummaryItem {
@@ -134,12 +141,13 @@ impl From<Link> for SummaryItem {
///
/// ```text
/// summary ::= title prefix_chapters numbered_chapters
/// suffix_chapters
/// suffix_chapters
/// title ::= "# " TEXT
/// | EPSILON
/// prefix_chapters ::= item*
/// suffix_chapters ::= item*
/// numbered_chapters ::= dotted_item+
/// numbered_chapters ::= part+
/// part ::= title dotted_item+
/// dotted_item ::= INDENT* DOT_POINT item
/// item ::= link
/// | separator
@@ -153,7 +161,12 @@ impl From<Link> for SummaryItem {
/// > match the following regex: "[^<>\n[]]+".
struct SummaryParser<'a> {
src: &'a str,
stream: pulldown_cmark::Parser<'a>,
stream: pulldown_cmark::OffsetIter<'a>,
offset: usize,
/// We can't actually put an event back into the `OffsetIter` stream, so instead we store it
/// here until somebody calls `next_event` again.
back: Option<Event<'a>>,
}
/// Reads `Events` from the provided stream until the corresponding
@@ -164,54 +177,55 @@ struct SummaryParser<'a> {
/// use pattern matching and you won't get errors because `take_while()`
/// moves `$stream` out of self.
macro_rules! collect_events {
($stream:expr, start $delimiter:pat) => {
($stream:expr,start $delimiter:pat) => {
collect_events!($stream, Event::Start($delimiter))
};
($stream:expr, end $delimiter:pat) => {
($stream:expr,end $delimiter:pat) => {
collect_events!($stream, Event::End($delimiter))
};
($stream:expr, $delimiter:pat) => {
{
let mut events = Vec::new();
($stream:expr, $delimiter:pat) => {{
let mut events = Vec::new();
loop {
let event = $stream.next();
trace!("Next event: {:?}", event);
loop {
let event = $stream.next().map(|(ev, _range)| ev);
trace!("Next event: {:?}", event);
match event {
Some($delimiter) => break,
Some(other) => events.push(other),
None => {
debug!("Reached end of stream without finding the closing pattern, {}", stringify!($delimiter));
break;
}
match event {
Some($delimiter) => break,
Some(other) => events.push(other),
None => {
debug!(
"Reached end of stream without finding the closing pattern, {}",
stringify!($delimiter)
);
break;
}
}
events
}
}
events
}};
}
impl<'a> SummaryParser<'a> {
fn new(text: &str) -> SummaryParser {
let pulldown_parser = pulldown_cmark::Parser::new(text);
fn new(text: &str) -> SummaryParser<'_> {
let pulldown_parser = pulldown_cmark::Parser::new(text).into_offset_iter();
SummaryParser {
src: text,
stream: pulldown_parser,
offset: 0,
back: None,
}
}
/// Get the current line and column to give the user more useful error
/// messages.
fn current_location(&self) -> (usize, usize) {
let byte_offset = self.stream.get_offset();
let previous_text = self.src[..byte_offset].as_bytes();
let previous_text = self.src[..self.offset].as_bytes();
let line = Memchr::new(b'\n', previous_text).count() + 1;
let start_of_line = memchr::memrchr(b'\n', previous_text).unwrap_or(0);
let col = self.src[start_of_line..byte_offset].chars().count();
let col = self.src[start_of_line..self.offset].chars().count();
(line, col)
}
@@ -220,12 +234,15 @@ impl<'a> SummaryParser<'a> {
fn parse(mut self) -> Result<Summary> {
let title = self.parse_title();
let prefix_chapters = self.parse_affix(true)
.chain_err(|| "There was an error parsing the prefix chapters")?;
let numbered_chapters = self.parse_numbered()
.chain_err(|| "There was an error parsing the numbered chapters")?;
let suffix_chapters = self.parse_affix(false)
.chain_err(|| "There was an error parsing the suffix chapters")?;
let prefix_chapters = self
.parse_affix(true)
.with_context(|| "There was an error parsing the prefix chapters")?;
let numbered_chapters = self
.parse_parts()
.with_context(|| "There was an error parsing the numbered chapters")?;
let suffix_chapters = self
.parse_affix(false)
.with_context(|| "There was an error parsing the suffix chapters")?;
Ok(Summary {
title,
@@ -235,8 +252,7 @@ impl<'a> SummaryParser<'a> {
})
}
/// Parse the affix chapters. This expects the first event (start of
/// paragraph) to have already been consumed by the previous parser.
/// Parse the affix chapters.
fn parse_affix(&mut self, is_prefix: bool) -> Result<Vec<SummaryItem>> {
let mut items = Vec::new();
debug!(
@@ -246,20 +262,22 @@ impl<'a> SummaryParser<'a> {
loop {
match self.next_event() {
Some(Event::Start(Tag::List(..))) => {
Some(ev @ Event::Start(Tag::List(..)))
| Some(ev @ Event::Start(Tag::Heading(1))) => {
if is_prefix {
// we've finished prefix chapters and are at the start
// of the numbered section.
self.back(ev);
break;
} else {
bail!(self.parse_error("Suffix chapters cannot be followed by a list"));
}
}
Some(Event::Start(Tag::Link(href, _))) => {
let link = self.parse_link(href.to_string())?;
Some(Event::Start(Tag::Link(_type, href, _title))) => {
let link = self.parse_link(href.to_string());
items.push(SummaryItem::Link(link));
}
Some(Event::Start(Tag::Rule)) => items.push(SummaryItem::Separator),
Some(Event::Rule) => items.push(SummaryItem::Separator),
Some(_) => {}
None => break,
}
@@ -268,54 +286,112 @@ impl<'a> SummaryParser<'a> {
Ok(items)
}
fn parse_link(&mut self, href: String) -> Result<Link> {
fn parse_parts(&mut self) -> Result<Vec<SummaryItem>> {
let mut parts = vec![];
// We want the section numbers to be continues through all parts.
let mut root_number = SectionNumber::default();
let mut root_items = 0;
loop {
// Possibly match a title or the end of the "numbered chapters part".
let title = match self.next_event() {
Some(ev @ Event::Start(Tag::Paragraph)) => {
// we're starting the suffix chapters
self.back(ev);
break;
}
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))
}
Some(ev) => {
self.back(ev);
None
}
None => break, // EOF, bail...
};
// Parse the rest of the part.
let numbered_chapters = self
.parse_numbered(&mut root_items, &mut root_number)
.with_context(|| "There was an error parsing the numbered chapters")?;
if let Some(title) = title {
parts.push(SummaryItem::PartTitle(title));
}
parts.extend(numbered_chapters);
}
Ok(parts)
}
/// Finishes parsing a link once the `Event::Start(Tag::Link(..))` has been opened.
fn parse_link(&mut self, href: String) -> Link {
let href = href.replace("%20", " ");
let link_content = collect_events!(self.stream, end Tag::Link(..));
let name = stringify_events(link_content);
if href.is_empty() {
Err(self.parse_error("You can't have an empty link."))
let path = if href.is_empty() {
None
} else {
Ok(Link {
name: name,
location: PathBuf::from(href.to_string()),
number: None,
nested_items: Vec::new(),
})
Some(PathBuf::from(href))
};
Link {
name,
location: path,
number: None,
nested_items: Vec::new(),
}
}
/// Parse the numbered chapters. This assumes the opening list tag has
/// already been consumed by a previous parser.
fn parse_numbered(&mut self) -> Result<Vec<SummaryItem>> {
/// Parse the numbered chapters.
fn parse_numbered(
&mut self,
root_items: &mut u32,
root_number: &mut SectionNumber,
) -> Result<Vec<SummaryItem>> {
let mut items = Vec::new();
let root_number = SectionNumber::default();
// we need to do this funny loop-match-if-let dance because a rule will
// close off any currently running list. Therefore we try to read the
// list items before the rule, then if we encounter a rule we'll add a
// separator and try to resume parsing numbered chapters if we start a
// list immediately afterwards.
//
// If you can think of a better way to do this then please make a PR :)
// For the first iteration, we want to just skip any opening paragraph tags, as that just
// marks the start of the list. But after that, another opening paragraph indicates that we
// have started a new part or the suffix chapters.
let mut first = true;
loop {
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
// them
update_section_numbers(&mut bunch_of_items, 0, items.len() as u32);
items.extend(bunch_of_items);
match self.next_event() {
Some(Event::Start(Tag::Paragraph)) => {
// we're starting the suffix chapters
Some(ev @ Event::Start(Tag::Paragraph)) => {
if !first {
// we're starting the suffix chapters
self.back(ev);
break;
}
}
// The expectation is that pulldown cmark will terminate a paragraph before a new
// heading, so we can always count on this to return without skipping headings.
Some(ev @ Event::Start(Tag::Heading(1))) => {
// we're starting a new part
self.back(ev);
break;
}
Some(ev @ Event::Start(Tag::List(..))) => {
self.back(ev);
let mut bunch_of_items = self.parse_nested_numbered(&root_number)?;
// 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
// them
update_section_numbers(&mut bunch_of_items, 0, *root_items);
*root_items += bunch_of_items.len() as u32;
items.extend(bunch_of_items);
}
Some(Event::Start(other_tag)) => {
if other_tag == Tag::Rule {
items.push(SummaryItem::Separator);
}
trace!("Skipping contents of {:?}", other_tag);
// Skip over the contents of this tag
@@ -324,29 +400,42 @@ impl<'a> SummaryParser<'a> {
break;
}
}
}
Some(Event::Rule) => {
items.push(SummaryItem::Separator);
}
if let Some(Event::Start(Tag::List(..))) = self.next_event() {
continue;
} else {
break;
}
}
Some(_) => {
// something else... ignore
continue;
}
// something else... ignore
Some(_) => {}
// EOF, bail...
None => {
// EOF, bail...
break;
}
}
// From now on, we cannot accept any new paragraph opening tags.
first = false;
}
Ok(items)
}
/// Push an event back to the tail of the stream.
fn back(&mut self, ev: Event<'a>) {
assert!(self.back.is_none());
trace!("Back: {:?}", ev);
self.back = Some(ev);
}
fn next_event(&mut self) -> Option<Event<'a>> {
let next = self.stream.next();
let next = self.back.take().or_else(|| {
self.stream.next().map(|(ev, range)| {
self.offset = range.start;
ev
})
});
trace!("Next event: {:?}", next);
next
@@ -363,6 +452,10 @@ impl<'a> SummaryParser<'a> {
items.push(item);
}
Some(Event::Start(Tag::List(..))) => {
// Skip this tag after comment bacause it is not nested.
if items.is_empty() {
continue;
}
// recurse to parse the nested list
let (_, last_item) = get_last_link(&mut items)?;
let last_item_number = last_item
@@ -391,8 +484,8 @@ impl<'a> SummaryParser<'a> {
loop {
match self.next_event() {
Some(Event::Start(Tag::Paragraph)) => continue,
Some(Event::Start(Tag::Link(href, _))) => {
let mut link = self.parse_link(href.to_string())?;
Some(Event::Start(Tag::Link(_type, href, _title))) => {
let mut link = self.parse_link(href.to_string());
let mut number = parent.clone();
number.0.push(num_existing_items as u32 + 1);
@@ -400,7 +493,10 @@ impl<'a> SummaryParser<'a> {
"Found chapter: {} {} ({})",
number,
link.name,
link.location.display()
link.location
.as_ref()
.map(|p| p.to_str().unwrap_or(""))
.unwrap_or("[draft]")
);
link.number = Some(number);
@@ -419,19 +515,24 @@ impl<'a> SummaryParser<'a> {
fn parse_error<D: Display>(&self, msg: D) -> Error {
let (line, col) = self.current_location();
ErrorKind::ParseError(line, col, msg.to_string()).into()
anyhow::anyhow!(
"failed to parse SUMMARY.md line {}, column {}: {}",
line,
col,
msg
)
}
/// Try to parse the title line.
fn parse_title(&mut self) -> Option<String> {
if let Some(Event::Start(Tag::Header(1))) = self.next_event() {
debug!("Found a h1 in the SUMMARY");
match self.next_event() {
Some(Event::Start(Tag::Heading(1))) => {
debug!("Found a h1 in the SUMMARY");
let tags = collect_events!(self.stream, end Tag::Header(1));
Some(stringify_events(tags))
} else {
None
let tags = collect_events!(self.stream, end Tag::Heading(1));
Some(stringify_events(tags))
}
_ => None,
}
}
}
@@ -457,19 +558,19 @@ fn get_last_link(links: &mut [SummaryItem]) -> Result<(usize, &mut Link)> {
.filter_map(|(i, item)| item.maybe_link_mut().map(|l| (i, l)))
.rev()
.next()
.ok_or_else(|| {
"Unable to get last link because the list of SummaryItems doesn't contain any Links"
.into()
})
.ok_or_else(||
anyhow::anyhow!("Unable to get last link because the list of SummaryItems doesn't contain any Links")
)
}
/// Removes the styling from a list of Markdown events and returns just the
/// plain text.
fn stringify_events(events: Vec<Event>) -> String {
fn stringify_events(events: Vec<Event<'_>>) -> String {
events
.into_iter()
.filter_map(|t| match t {
Event::Text(text) => Some(text.into_owned()),
Event::Text(text) | Event::Code(text) => Some(text.into_string()),
Event::SoftBreak => Some(String::from(" ")),
_ => None,
})
.collect()
@@ -481,7 +582,7 @@ fn stringify_events(events: Vec<Event>) -> String {
pub struct SectionNumber(pub Vec<u32>);
impl Display for SectionNumber {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if self.0.is_empty() {
write!(f, "0")
} else {
@@ -571,17 +672,16 @@ mod tests {
let should_be = vec![
SummaryItem::Link(Link {
name: String::from("First"),
location: PathBuf::from("./first.md"),
location: Some(PathBuf::from("./first.md")),
..Default::default()
}),
SummaryItem::Link(Link {
name: String::from("Second"),
location: PathBuf::from("./second.md"),
location: Some(PathBuf::from("./second.md")),
..Default::default()
}),
];
let _ = parser.stream.next(); // step past first event
let got = parser.parse_affix(true).unwrap();
assert_eq!(got, should_be);
@@ -592,7 +692,6 @@ mod tests {
let src = "[First](./first.md)\n\n---\n\n[Second](./second.md)\n";
let mut parser = SummaryParser::new(src);
let _ = parser.stream.next(); // step past first event
let got = parser.parse_affix(true).unwrap();
assert_eq!(got.len(), 3);
@@ -604,7 +703,6 @@ mod tests {
let src = "[First](./first.md)\n- [Second](./second.md)\n";
let mut parser = SummaryParser::new(src);
let _ = parser.stream.next(); // step past first event
let got = parser.parse_affix(false);
assert!(got.is_err());
@@ -615,19 +713,19 @@ mod tests {
let src = "[First](./first.md)";
let should_be = Link {
name: String::from("First"),
location: PathBuf::from("./first.md"),
location: Some(PathBuf::from("./first.md")),
..Default::default()
};
let mut parser = SummaryParser::new(src);
let _ = parser.stream.next(); // skip past start of paragraph
let _ = parser.stream.next(); // Discard opening paragraph
let href = match parser.stream.next() {
Some(Event::Start(Tag::Link(href, _))) => href.to_string(),
Some((Event::Start(Tag::Link(_type, href, _title)), _range)) => href.to_string(),
other => panic!("Unreachable, {:?}", other),
};
let got = parser.parse_link(href).unwrap();
let got = parser.parse_link(href);
assert_eq!(got, should_be);
}
@@ -636,16 +734,16 @@ mod tests {
let src = "- [First](./first.md)\n";
let link = Link {
name: String::from("First"),
location: PathBuf::from("./first.md"),
location: Some(PathBuf::from("./first.md")),
number: Some(SectionNumber(vec![1])),
..Default::default()
};
let should_be = vec![SummaryItem::Link(link)];
let mut parser = SummaryParser::new(src);
let _ = parser.stream.next();
let got = parser.parse_numbered().unwrap();
let got = parser
.parse_numbered(&mut 0, &mut SectionNumber::default())
.unwrap();
assert_eq!(got, should_be);
}
@@ -657,29 +755,92 @@ mod tests {
let should_be = vec![
SummaryItem::Link(Link {
name: String::from("First"),
location: PathBuf::from("./first.md"),
location: Some(PathBuf::from("./first.md")),
number: Some(SectionNumber(vec![1])),
nested_items: vec![
SummaryItem::Link(Link {
name: String::from("Nested"),
location: PathBuf::from("./nested.md"),
number: Some(SectionNumber(vec![1, 1])),
nested_items: Vec::new(),
}),
],
nested_items: vec![SummaryItem::Link(Link {
name: String::from("Nested"),
location: Some(PathBuf::from("./nested.md")),
number: Some(SectionNumber(vec![1, 1])),
nested_items: Vec::new(),
})],
}),
SummaryItem::Link(Link {
name: String::from("Second"),
location: PathBuf::from("./second.md"),
location: Some(PathBuf::from("./second.md")),
number: Some(SectionNumber(vec![2])),
nested_items: Vec::new(),
}),
];
let mut parser = SummaryParser::new(src);
let _ = parser.stream.next();
let got = parser
.parse_numbered(&mut 0, &mut SectionNumber::default())
.unwrap();
let got = parser.parse_numbered().unwrap();
assert_eq!(got, should_be);
}
#[test]
fn parse_numbered_chapters_separated_by_comment() {
let src = "- [First](./first.md)\n<!-- this is a comment -->\n- [Second](./second.md)";
let should_be = vec![
SummaryItem::Link(Link {
name: String::from("First"),
location: Some(PathBuf::from("./first.md")),
number: Some(SectionNumber(vec![1])),
nested_items: Vec::new(),
}),
SummaryItem::Link(Link {
name: String::from("Second"),
location: Some(PathBuf::from("./second.md")),
number: Some(SectionNumber(vec![2])),
nested_items: Vec::new(),
}),
];
let mut parser = SummaryParser::new(src);
let got = parser
.parse_numbered(&mut 0, &mut SectionNumber::default())
.unwrap();
assert_eq!(got, should_be);
}
#[test]
fn parse_titled_parts() {
let src = "- [First](./first.md)\n- [Second](./second.md)\n\
# Title 2\n- [Third](./third.md)\n\t- [Fourth](./fourth.md)";
let should_be = vec![
SummaryItem::Link(Link {
name: String::from("First"),
location: Some(PathBuf::from("./first.md")),
number: Some(SectionNumber(vec![1])),
nested_items: Vec::new(),
}),
SummaryItem::Link(Link {
name: String::from("Second"),
location: Some(PathBuf::from("./second.md")),
number: Some(SectionNumber(vec![2])),
nested_items: Vec::new(),
}),
SummaryItem::PartTitle(String::from("Title 2")),
SummaryItem::Link(Link {
name: String::from("Third"),
location: Some(PathBuf::from("./third.md")),
number: Some(SectionNumber(vec![3])),
nested_items: vec![SummaryItem::Link(Link {
name: String::from("Fourth"),
location: Some(PathBuf::from("./fourth.md")),
number: Some(SectionNumber(vec![3, 1])),
nested_items: Vec::new(),
})],
}),
];
let mut parser = SummaryParser::new(src);
let got = parser.parse_parts().unwrap();
assert_eq!(got, should_be);
}
@@ -694,33 +855,122 @@ mod tests {
let should_be = vec![
SummaryItem::Link(Link {
name: String::from("First"),
location: PathBuf::from("./first.md"),
location: Some(PathBuf::from("./first.md")),
number: Some(SectionNumber(vec![1])),
nested_items: Vec::new(),
}),
SummaryItem::Link(Link {
name: String::from("Second"),
location: PathBuf::from("./second.md"),
location: Some(PathBuf::from("./second.md")),
number: Some(SectionNumber(vec![2])),
nested_items: Vec::new(),
}),
];
let mut parser = SummaryParser::new(src);
let _ = parser.stream.next();
let got = parser.parse_numbered().unwrap();
let got = parser
.parse_numbered(&mut 0, &mut SectionNumber::default())
.unwrap();
assert_eq!(got, should_be);
}
#[test]
fn an_empty_link_location_is_an_error() {
fn an_empty_link_location_is_a_draft_chapter() {
let src = "- [Empty]()\n";
let mut parser = SummaryParser::new(src);
parser.stream.next();
let got = parser.parse_numbered();
assert!(got.is_err());
let got = parser.parse_numbered(&mut 0, &mut SectionNumber::default());
let should_be = vec![SummaryItem::Link(Link {
name: String::from("Empty"),
location: None,
number: Some(SectionNumber(vec![1])),
nested_items: Vec::new(),
})];
assert!(got.is_ok());
assert_eq!(got.unwrap(), should_be);
}
/// Regression test for https://github.com/rust-lang/mdBook/issues/779
/// Ensure section numbers are correctly incremented after a horizontal separator.
#[test]
fn keep_numbering_after_separator() {
let src =
"- [First](./first.md)\n---\n- [Second](./second.md)\n---\n- [Third](./third.md)\n";
let should_be = vec![
SummaryItem::Link(Link {
name: String::from("First"),
location: Some(PathBuf::from("./first.md")),
number: Some(SectionNumber(vec![1])),
nested_items: Vec::new(),
}),
SummaryItem::Separator,
SummaryItem::Link(Link {
name: String::from("Second"),
location: Some(PathBuf::from("./second.md")),
number: Some(SectionNumber(vec![2])),
nested_items: Vec::new(),
}),
SummaryItem::Separator,
SummaryItem::Link(Link {
name: String::from("Third"),
location: Some(PathBuf::from("./third.md")),
number: Some(SectionNumber(vec![3])),
nested_items: Vec::new(),
}),
];
let mut parser = SummaryParser::new(src);
let got = parser
.parse_numbered(&mut 0, &mut SectionNumber::default())
.unwrap();
assert_eq!(got, should_be);
}
/// Regression test for https://github.com/rust-lang/mdBook/issues/1218
/// Ensure chapter names spread across multiple lines have spaces between all the words.
#[test]
fn add_space_for_multi_line_chapter_names() {
let src = "- [Chapter\ntitle](./chapter.md)";
let should_be = vec![SummaryItem::Link(Link {
name: String::from("Chapter title"),
location: Some(PathBuf::from("./chapter.md")),
number: Some(SectionNumber(vec![1])),
nested_items: Vec::new(),
})];
let mut parser = SummaryParser::new(src);
let got = parser
.parse_numbered(&mut 0, &mut SectionNumber::default())
.unwrap();
assert_eq!(got, should_be);
}
#[test]
fn allow_space_in_link_destination() {
let src = "- [test1](./test%20link1.md)\n- [test2](<./test link2.md>)";
let should_be = vec![
SummaryItem::Link(Link {
name: String::from("test1"),
location: Some(PathBuf::from("./test link1.md")),
number: Some(SectionNumber(vec![1])),
nested_items: Vec::new(),
}),
SummaryItem::Link(Link {
name: String::from("test2"),
location: Some(PathBuf::from("./test link2.md")),
number: Some(SectionNumber(vec![2])),
nested_items: Vec::new(),
}),
];
let mut parser = SummaryParser::new(src);
let got = parser
.parse_numbered(&mut 0, &mut SectionNumber::default())
.unwrap();
assert_eq!(got, should_be);
}
}

View File

@@ -1,21 +1,22 @@
use std::path::PathBuf;
use crate::{get_book_dir, open};
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use mdbook::errors::Result;
use {get_book_dir, open};
use mdbook::MDBook;
// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("build")
.about("Build the book from the markdown files")
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
.about("Builds a book from its markdown files")
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book \
when omitted)'",
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
)
.arg_from_usage(
"[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'",
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",
)
.arg_from_usage("-o, --open 'Opens the compiled book in a web browser'")
}
// Build command implementation
@@ -24,7 +25,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
let mut book = MDBook::load(&book_dir)?;
if let Some(dest_dir) = args.value_of("dest-dir") {
book.config.build.build_dir = PathBuf::from(dest_dir);
book.config.build.build_dir = dest_dir.into();
}
book.build()?;

39
src/cmd/clean.rs Normal file
View File

@@ -0,0 +1,39 @@
use crate::get_book_dir;
use anyhow::Context;
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use std::fs;
// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("clean")
.about("Deletes a built book")
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
Running this command deletes this directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
)
.arg_from_usage(
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",
)
}
// Clean command implementation
pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> {
let book_dir = get_book_dir(args);
let book = MDBook::load(&book_dir)?;
let dir_to_remove = match args.value_of("dest-dir") {
Some(dest_dir) => dest_dir.into(),
None => book.root.join(&book.config.build.build_dir),
};
if dir_to_remove.exists() {
fs::remove_dir_all(&dir_to_remove)
.with_context(|| "Unable to remove the build directory")?;
}
Ok(())
}

View File

@@ -1,22 +1,23 @@
use crate::get_book_dir;
use clap::{App, ArgMatches, SubCommand};
use mdbook::config;
use mdbook::errors::Result;
use mdbook::MDBook;
use std::io;
use std::io::Write;
use std::env;
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use mdbook::errors::Result;
use mdbook::utils;
use mdbook::config;
use get_book_dir;
use std::process::Command;
// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("init")
.about("Create boilerplate structure and files in the directory")
.about("Creates the boilerplate structure and files for a new book")
// the {n} denotes a newline which will properly aligned in all help messages
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory \
when omitted)'")
.arg_from_usage(
"[dir] 'Directory to create the book in{n}\
(Defaults to the Current Directory when omitted)'",
)
.arg_from_usage("--theme 'Copies the default theme into your source folder'")
.arg_from_usage("--force 'skip confirmation prompts'")
.arg_from_usage("--force 'Skips confirmation prompts'")
}
// Init command implementation
@@ -68,20 +69,15 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
Ok(())
}
/// Obtains author name from git config file if it can be located.
/// Obtains author name from git config file by running the `git config` command.
fn get_author_name() -> Option<String> {
if let Some(home) = env::home_dir() {
let git_config_path = home.join(".gitconfig");
let content = utils::fs::file_to_string(git_config_path).unwrap();
let user_name = content
.lines()
.filter(|x| !x.starts_with("#"))
.map(|x| x.trim_left())
.filter(|x| x.starts_with("name"))
.next();
user_name
.and_then(|x| x.rsplit("=").next())
.map(|x| x.trim().to_owned())
let output = Command::new("git")
.args(&["config", "--get", "user.name"])
.output()
.ok()?;
if output.status.success() {
Some(String::from_utf8_lossy(&output.stdout).trim().to_owned())
} else {
None
}

10
src/cmd/mod.rs Normal file
View File

@@ -0,0 +1,10 @@
//! Subcommand modules for the `mdbook` binary.
pub mod build;
pub mod clean;
pub mod init;
#[cfg(feature = "serve")]
pub mod serve;
pub mod test;
#[cfg(feature = "watch")]
pub mod watch;

165
src/cmd/serve.rs Normal file
View File

@@ -0,0 +1,165 @@
#[cfg(feature = "watch")]
use super::watch;
use crate::{get_book_dir, open};
use clap::{App, Arg, ArgMatches, SubCommand};
use futures_util::sink::SinkExt;
use futures_util::StreamExt;
use mdbook::errors::*;
use mdbook::utils;
use mdbook::utils::fs::get_404_output_file;
use mdbook::MDBook;
use std::net::{SocketAddr, ToSocketAddrs};
use std::path::PathBuf;
use tokio::sync::broadcast;
use warp::ws::Message;
use warp::Filter;
/// The HTTP endpoint for the websocket used to trigger reloads when a file changes.
const LIVE_RELOAD_ENDPOINT: &str = "__livereload";
// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("serve")
.about("Serves a book at http://localhost:3000, and rebuilds it on changes")
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
)
.arg_from_usage(
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",
)
.arg(
Arg::with_name("hostname")
.short("n")
.long("hostname")
.takes_value(true)
.default_value("localhost")
.empty_values(false)
.help("Hostname to listen on for HTTP connections"),
)
.arg(
Arg::with_name("port")
.short("p")
.long("port")
.takes_value(true)
.default_value("3000")
.empty_values(false)
.help("Port to use for HTTP connections"),
)
.arg_from_usage("-o, --open 'Opens the book server in a web browser'")
}
// Serve command implementation
pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args);
let mut book = MDBook::load(&book_dir)?;
let port = args.value_of("port").unwrap();
let hostname = args.value_of("hostname").unwrap();
let open_browser = args.is_present("open");
let address = format!("{}:{}", hostname, port);
let livereload_url = format!("ws://{}/{}", address, LIVE_RELOAD_ENDPOINT);
let update_config = |book: &mut MDBook| {
book.config
.set("output.html.livereload-url", &livereload_url)
.expect("livereload-url update failed");
if let Some(dest_dir) = args.value_of("dest-dir") {
book.config.build.build_dir = dest_dir.into();
}
// Override site-url for local serving of the 404 file
book.config.set("output.html.site-url", "/").unwrap();
};
update_config(&mut book);
book.build()?;
let sockaddr: SocketAddr = address
.to_socket_addrs()?
.next()
.ok_or_else(|| anyhow::anyhow!("no address found for {}", address))?;
let build_dir = book.build_dir_for("html");
let input_404 = book
.config
.get("output.html.input-404")
.map(toml::Value::as_str)
.and_then(std::convert::identity) // flatten
.map(ToString::to_string);
let file_404 = get_404_output_file(&input_404);
// A channel used to broadcast to any websockets to reload when a file changes.
let (tx, _rx) = tokio::sync::broadcast::channel::<Message>(100);
let reload_tx = tx.clone();
let thread_handle = std::thread::spawn(move || {
serve(build_dir, sockaddr, reload_tx, &file_404);
});
let serving_url = format!("http://{}", address);
info!("Serving on: {}", serving_url);
if open_browser {
open(serving_url);
}
#[cfg(feature = "watch")]
watch::trigger_on_change(&book, move |paths, book_dir| {
info!("Files changed: {:?}", paths);
info!("Building book...");
// FIXME: This area is really ugly because we need to re-set livereload :(
let result = MDBook::load(&book_dir).and_then(|mut b| {
update_config(&mut b);
b.build()
});
if let Err(e) = result {
error!("Unable to load the book");
utils::log_backtrace(&e);
} else {
let _ = tx.send(Message::text("reload"));
}
});
let _ = thread_handle.join();
Ok(())
}
#[tokio::main]
async fn serve(
build_dir: PathBuf,
address: SocketAddr,
reload_tx: broadcast::Sender<Message>,
file_404: &str,
) {
// A warp Filter which captures `reload_tx` and provides an `rx` copy to
// receive reload messages.
let sender = warp::any().map(move || reload_tx.subscribe());
// A warp Filter to handle the livereload endpoint. This upgrades to a
// websocket, and then waits for any filesystem change notifications, and
// relays them over the websocket.
let livereload = warp::path(LIVE_RELOAD_ENDPOINT)
.and(warp::ws())
.and(sender)
.map(|ws: warp::ws::Ws, mut rx: broadcast::Receiver<Message>| {
ws.on_upgrade(move |ws| async move {
let (mut user_ws_tx, _user_ws_rx) = ws.split();
trace!("websocket got connection");
if let Ok(m) = rx.recv().await {
trace!("notify of reload");
let _ = user_ws_tx.send(m).await;
}
})
});
// A warp Filter that serves from the filesystem.
let book_route = warp::fs::dir(build_dir.clone());
// The fallback route for 404 errors
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);
warp::serve(routes).run(address).await;
}

46
src/cmd/test.rs Normal file
View File

@@ -0,0 +1,46 @@
use crate::get_book_dir;
use clap::{App, Arg, ArgMatches, SubCommand};
use mdbook::errors::Result;
use mdbook::MDBook;
// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("test")
.about("Tests that a book's Rust code samples compile")
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
)
.arg_from_usage(
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",
)
.arg(Arg::with_name("library-path")
.short("L")
.long("library-path")
.value_name("dir")
.takes_value(true)
.require_delimiter(true)
.multiple(true)
.empty_values(false)
.help("A comma-separated list of directories to add to {n}the crate search path when building tests"))
}
// test command implementation
pub fn execute(args: &ArgMatches) -> Result<()> {
let library_paths: Vec<&str> = args
.values_of("library-path")
.map(std::iter::Iterator::collect)
.unwrap_or_default();
let book_dir = get_book_dir(args);
let mut book = MDBook::load(&book_dir)?;
if let Some(dest_dir) = args.value_of("dest-dir") {
book.config.build.build_dir = dest_dir.into();
}
book.test(library_paths)?;
Ok(())
}

164
src/cmd/watch.rs Normal file
View File

@@ -0,0 +1,164 @@
use crate::{get_book_dir, open};
use clap::{App, ArgMatches, SubCommand};
use mdbook::errors::Result;
use mdbook::utils;
use mdbook::MDBook;
use notify::Watcher;
use std::path::{Path, PathBuf};
use std::sync::mpsc::channel;
use std::thread::sleep;
use std::time::Duration;
// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("watch")
.about("Watches a book's files and rebuilds it on changes")
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
)
.arg_from_usage(
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",
)
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
}
// Watch command implementation
pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args);
let mut book = MDBook::load(&book_dir)?;
let update_config = |book: &mut MDBook| {
if let Some(dest_dir) = args.value_of("dest-dir") {
book.config.build.build_dir = dest_dir.into();
}
};
update_config(&mut book);
if args.is_present("open") {
book.build()?;
open(book.build_dir_for("html").join("index.html"));
}
trigger_on_change(&book, |paths, book_dir| {
info!("Files changed: {:?}\nBuilding book...\n", paths);
let result = MDBook::load(&book_dir).and_then(|mut b| {
update_config(&mut b);
b.build()
});
if let Err(e) = result {
error!("Unable to build the book");
utils::log_backtrace(&e);
}
});
Ok(())
}
fn remove_ignored_files(book_root: &PathBuf, paths: &[PathBuf]) -> Vec<PathBuf> {
if paths.is_empty() {
return vec![];
}
match find_gitignore(book_root) {
Some(gitignore_path) => {
match gitignore::File::new(gitignore_path.as_path()) {
Ok(exclusion_checker) => filter_ignored_files(exclusion_checker, paths),
Err(_) => {
// We're unable to read the .gitignore file, so we'll silently allow everything.
// Please see discussion: https://github.com/rust-lang/mdBook/pull/1051
paths.iter().map(|path| path.to_path_buf()).collect()
}
}
}
None => {
// There is no .gitignore file.
paths.iter().map(|path| path.to_path_buf()).collect()
}
}
}
fn find_gitignore(book_root: &PathBuf) -> Option<PathBuf> {
book_root
.ancestors()
.map(|p| p.join(".gitignore"))
.find(|p| p.exists())
}
fn filter_ignored_files(exclusion_checker: gitignore::File, paths: &[PathBuf]) -> Vec<PathBuf> {
paths
.iter()
.filter(|path| match exclusion_checker.is_excluded(path) {
Ok(exclude) => !exclude,
Err(error) => {
warn!(
"Unable to determine if {:?} is excluded: {:?}. Including it.",
&path, error
);
true
}
})
.map(|path| path.to_path_buf())
.collect()
}
/// Calls the closure when a book source file is changed, blocking indefinitely.
pub fn trigger_on_change<F>(book: &MDBook, closure: F)
where
F: Fn(Vec<PathBuf>, &Path),
{
use notify::DebouncedEvent::*;
use notify::RecursiveMode::*;
// Create a channel to receive the events.
let (tx, rx) = channel();
let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) {
Ok(w) => w,
Err(e) => {
error!("Error while trying to watch the files:\n\n\t{:?}", e);
std::process::exit(1)
}
};
// Add the source directory to the watcher
if let Err(e) = watcher.watch(book.source_dir(), Recursive) {
error!("Error while watching {:?}:\n {:?}", book.source_dir(), e);
std::process::exit(1);
};
let _ = watcher.watch(book.theme_dir(), Recursive);
// Add the book.toml file to the watcher if it exists
let _ = watcher.watch(book.root.join("book.toml"), NonRecursive);
info!("Listening for changes...");
loop {
let first_event = rx.recv().unwrap();
sleep(Duration::from_millis(50));
let other_events = rx.try_iter();
let all_events = std::iter::once(first_event).chain(other_events);
let paths = all_events
.filter_map(|event| {
debug!("Received filesystem event: {:?}", event);
match event {
Create(path) | Write(path) | Remove(path) | Rename(_, path) => Some(path),
_ => None,
}
})
.collect::<Vec<_>>();
let paths = remove_ignored_files(&book.root, &paths[..]);
if !paths.is_empty() {
closure(paths, &book.root);
}
}
}

View File

@@ -3,16 +3,15 @@
//! 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
//! for arbitrary data which is exposed to plugins and alternate backends.
//! for arbitrary data which is exposed to plugins and alternative backends.
//!
//!
//! # Examples
//!
//! ```rust
//! # extern crate mdbook;
//! # use mdbook::errors::*;
//! # extern crate toml;
//! use std::path::PathBuf;
//! use std::str::FromStr;
//! use mdbook::Config;
//! use toml::Value;
//!
@@ -41,28 +40,27 @@
//! cfg.set("output.html.theme", "./themes");
//!
//! // then load it again, automatically deserializing to a `PathBuf`.
//! let got: PathBuf = cfg.get_deserialized("output.html.theme")?;
//! assert_eq!(got, PathBuf::from("./themes"));
//! let got: Option<PathBuf> = cfg.get_deserialized_opt("output.html.theme")?;
//! assert_eq!(got, Some(PathBuf::from("./themes")));
//! # Ok(())
//! # }
//! # fn main() { run().unwrap() }
//! # run().unwrap()
//! ```
#![deny(missing_docs)]
use std::path::{Path, PathBuf};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io::Read;
use std::env;
use toml::{self, Value};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use toml::value::Table;
use toml_query::read::TomlValueReadExt;
use toml_query::insert::TomlValueInsertExt;
use toml_query::delete::TomlValueDeleteExt;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json;
use toml::{self, Value};
use errors::*;
use crate::errors::*;
use crate::utils::{self, toml_ext::TomlExt};
/// The overall configuration object for MDBook, essentially an in-memory
/// representation of `book.toml`.
@@ -72,22 +70,28 @@ pub struct Config {
pub book: BookConfig,
/// Information about the build environment.
pub build: BuildConfig,
/// Information about Rust language support.
pub rust: RustConfig,
rest: Value,
}
impl Config {
/// Load a `Config` from some string.
pub fn from_str(src: &str) -> Result<Config> {
toml::from_str(src).chain_err(|| Error::from("Invalid configuration file"))
}
impl FromStr for Config {
type Err = Error;
/// Load a `Config` from some string.
fn from_str(src: &str) -> Result<Self> {
toml::from_str(src).with_context(|| "Invalid configuration file")
}
}
impl Config {
/// Load the configuration file from disk.
pub fn from_disk<P: AsRef<Path>>(config_file: P) -> Result<Config> {
let mut buffer = String::new();
File::open(config_file)
.chain_err(|| "Unable to open the configuration file")?
.with_context(|| "Unable to open the configuration file")?
.read_to_string(&mut buffer)
.chain_err(|| "Couldn't read the file")?;
.with_context(|| "Couldn't read the file")?;
Config::from_str(&buffer)
}
@@ -118,7 +122,7 @@ impl Config {
/// > when building the book with something like
/// >
/// > ```text
/// > $ export MDBOOK_BOOK="{'title': 'My Awesome Book', authors: ['Michael-F-Bryan']}"
/// > $ export MDBOOK_BOOK='{"title": "My Awesome Book", "authors": ["Michael-F-Bryan"]}'
/// > $ mdbook build
/// > ```
///
@@ -128,16 +132,25 @@ impl Config {
pub fn update_from_env(&mut self) {
debug!("Updating the config from environment variables");
let overrides = env::vars().filter_map(|(key, value)| match parse_env(&key) {
Some(index) => Some((index, value)),
None => None,
});
let overrides =
env::vars().filter_map(|(key, value)| parse_env(&key).map(|index| (index, value)));
for (key, value) in overrides {
trace!("{} => {}", key, value);
let parsed_value = serde_json::from_str(&value)
.unwrap_or_else(|_| serde_json::Value::String(value.to_string()));
if key == "book" || key == "build" {
if let serde_json::Value::Object(ref map) = parsed_value {
// To `set` each `key`, we wrap them as `prefix.key`
for (k, v) in map {
let full_key = format!("{}.{}", key, k);
self.set(&full_key, v).expect("unreachable");
}
return;
}
}
self.set(key, parsed_value).expect("unreachable");
}
}
@@ -145,21 +158,15 @@ impl Config {
/// Fetch an arbitrary item from the `Config` as a `toml::Value`.
///
/// You can use dotted indices to access nested items (e.g.
/// `output.html.playpen` will fetch the "playpen" out of the html output
/// `output.html.playground` will fetch the "playground" out of the html output
/// table).
pub fn get(&self, key: &str) -> Option<&Value> {
match self.rest.read(key) {
Ok(inner) => inner,
Err(_) => None,
}
self.rest.read(key)
}
/// Fetch a value from the `Config` so you can mutate it.
pub fn get_mut<'a>(&'a mut self, key: &str) -> Option<&'a mut Value> {
match self.rest.read_mut(key) {
Ok(inner) => inner,
Err(_) => None,
}
pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
self.rest.read_mut(key)
}
/// Convenience method for getting the html renderer's configuration.
@@ -170,22 +177,44 @@ impl Config {
/// HTML renderer is refactored to be less coupled to `mdbook` internals.
#[doc(hidden)]
pub fn html_config(&self) -> Option<HtmlConfig> {
self.get_deserialized("output.html").ok()
match self
.get_deserialized_opt("output.html")
.with_context(|| "Parsing configuration [output.html]")
{
Ok(Some(config)) => Some(config),
Ok(None) => None,
Err(e) => {
utils::log_backtrace(&e);
None
}
}
}
/// Deprecated, use get_deserialized_opt instead.
#[deprecated = "use get_deserialized_opt instead"]
pub fn get_deserialized<'de, T: Deserialize<'de>, S: AsRef<str>>(&self, name: S) -> Result<T> {
let name = name.as_ref();
match self.get_deserialized_opt(name)? {
Some(value) => Ok(value),
None => bail!("Key not found, {:?}", name),
}
}
/// Convenience function to fetch a value from the config and deserialize it
/// into some arbitrary type.
pub fn get_deserialized<'de, T: Deserialize<'de>, S: AsRef<str>>(&self, name: S) -> Result<T> {
pub fn get_deserialized_opt<'de, T: Deserialize<'de>, S: AsRef<str>>(
&self,
name: S,
) -> Result<Option<T>> {
let name = name.as_ref();
if let Some(value) = self.get(name) {
value
.clone()
.try_into()
.chain_err(|| "Couldn't deserialize the value")
} else {
bail!("Key not found, {:?}", name)
}
self.get(name)
.map(|value| {
value
.clone()
.try_into()
.with_context(|| "Couldn't deserialize the value")
})
.transpose()
}
/// Set a config key, clobbering any existing values along the way.
@@ -195,20 +224,32 @@ impl Config {
pub fn set<S: Serialize, I: AsRef<str>>(&mut self, index: I, value: S) -> Result<()> {
let index = index.as_ref();
let value =
Value::try_from(value).chain_err(|| "Unable to represent the item as a JSON Value")?;
let value = Value::try_from(value)
.with_context(|| "Unable to represent the item as a JSON Value")?;
if index.starts_with("book.") {
self.book.update_value(&index[5..], value);
} else if index.starts_with("build.") {
self.build.update_value(&index[6..], value);
} else {
self.rest.insert(index, value)?;
self.rest.insert(index, value);
}
Ok(())
}
/// Get the table associated with a particular renderer.
pub fn get_renderer<I: AsRef<str>>(&self, index: I) -> Option<&Table> {
let key = format!("output.{}", index.as_ref());
self.get(&key).and_then(Value::as_table)
}
/// Get the table associated with a particular preprocessor.
pub fn get_preprocessor<I: AsRef<str>>(&self, index: I) -> Option<&Table> {
let key = format!("preprocessor.{}", index.as_ref());
self.get(&key).and_then(Value::as_table)
}
fn from_legacy(mut table: Value) -> Config {
let mut cfg = Config::default();
@@ -217,9 +258,10 @@ impl Config {
// figure out what try_into() deserializes to.
macro_rules! get_and_insert {
($table:expr, $key:expr => $out:expr) => {
let got = $table.as_table_mut()
.and_then(|t| t.remove($key))
.and_then(|v| v.try_into().ok());
let got = $table
.as_table_mut()
.and_then(|t| t.remove($key))
.and_then(|v| v.try_into().ok());
if let Some(value) = got {
$out = value;
}
@@ -231,7 +273,7 @@ impl Config {
get_and_insert!(table, "source" => cfg.book.src);
get_and_insert!(table, "description" => cfg.book.description);
if let Ok(Some(dest)) = table.delete("output.html.destination") {
if let Some(dest) = table.delete("output.html.destination") {
if let Ok(destination) = dest.try_into() {
cfg.build.build_dir = destination;
}
@@ -247,12 +289,13 @@ impl Default for Config {
Config {
book: BookConfig::default(),
build: BuildConfig::default(),
rust: RustConfig::default(),
rest: Value::Table(Table::default()),
}
}
}
impl<'de> Deserialize<'de> for Config {
fn deserialize<D: Deserializer<'de>>(de: D) -> ::std::result::Result<Self, D::Error> {
fn deserialize<D: Deserializer<'de>>(de: D) -> std::result::Result<Self, D::Error> {
let raw = Value::deserialize(de)?;
if is_legacy_format(&raw) {
@@ -263,7 +306,7 @@ impl<'de> Deserialize<'de> for Config {
warn!("`description` under a table called `[book]`, move the `destination` entry");
warn!("from `[output.html]`, renamed to `build-dir`, under a table called");
warn!("`[build]`, and it should all work.");
warn!("Documentation: http://rust-lang-nursery.github.io/mdBook/format/config.html");
warn!("Documentation: http://rust-lang.github.io/mdBook/format/config.html");
return Ok(Config::from_legacy(raw));
}
@@ -287,28 +330,33 @@ impl<'de> Deserialize<'de> for Config {
.and_then(|value| value.try_into().ok())
.unwrap_or_default();
let rust: RustConfig = table
.remove("rust")
.and_then(|value| value.try_into().ok())
.unwrap_or_default();
Ok(Config {
book: book,
build: build,
book,
build,
rust,
rest: Value::Table(table),
})
}
}
impl Serialize for Config {
fn serialize<S: Serializer>(&self, s: S) -> ::std::result::Result<S::Ok, S::Error> {
use serde::ser::Error;
fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
// TODO: This should probably be removed and use a derive instead.
let mut table = self.rest.clone();
let book_config = match Value::try_from(self.book.clone()) {
Ok(cfg) => cfg,
Err(_) => {
return Err(S::Error::custom("Unable to serialize the BookConfig"));
}
};
let book_config = Value::try_from(&self.book).expect("should always be serializable");
table.insert("book", book_config);
if self.rust != RustConfig::default() {
let rust_config = Value::try_from(&self.rust).expect("should always be serializable");
table.insert("rust", rust_config);
}
table.insert("book", book_config).expect("unreachable");
table.serialize(s)
}
}
@@ -335,7 +383,7 @@ fn is_legacy_format(table: &Value) -> bool {
];
for item in &legacy_items {
if let Ok(Some(_)) = table.read(item) {
if table.read(item).is_some() {
return true;
}
}
@@ -358,6 +406,8 @@ pub struct BookConfig {
pub src: PathBuf,
/// Does this book support more than one language?
pub multilingual: bool,
/// The main language of the book.
pub language: Option<String>,
}
impl Default for BookConfig {
@@ -368,6 +418,7 @@ impl Default for BookConfig {
description: None,
src: PathBuf::from("src"),
multilingual: false,
language: Some(String::from("en")),
}
}
}
@@ -378,11 +429,12 @@ impl Default for BookConfig {
pub struct BuildConfig {
/// Where to put built artefacts relative to the book's root directory.
pub build_dir: PathBuf,
/// Should non-existent markdown files specified in `SETTINGS.md` be created
/// Should non-existent markdown files specified in `SUMMARY.md` be created
/// if they don't exist?
pub create_missing: bool,
/// Which preprocessors should be applied
pub preprocess: Option<Vec<String>>,
/// Should the default preprocessors always be used when they are
/// compatible with the renderer?
pub use_default_preprocessors: bool,
}
impl Default for BuildConfig {
@@ -390,21 +442,47 @@ impl Default for BuildConfig {
BuildConfig {
build_dir: PathBuf::from("book"),
create_missing: true,
preprocess: None,
use_default_preprocessors: true,
}
}
}
/// Configuration for the Rust compiler(e.g., for playground)
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct RustConfig {
/// Rust edition used in playground
pub edition: Option<RustEdition>,
}
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
/// Rust edition to use for the code.
pub enum RustEdition {
/// The 2018 edition of Rust
#[serde(rename = "2018")]
E2018,
/// The 2015 edition of Rust
#[serde(rename = "2015")]
E2015,
}
/// Configuration for the HTML renderer.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct HtmlConfig {
/// The theme directory, if specified.
pub theme: Option<PathBuf>,
/// The default theme to use, defaults to 'light'
pub default_theme: Option<String>,
/// The theme to use if the browser requests the dark version of the site.
/// Defaults to 'navy'.
pub preferred_dark_theme: Option<String>,
/// Use "smart quotes" instead of the usual `"` character.
pub curly_quotes: bool,
/// Should mathjax be enabled?
pub mathjax_support: bool,
/// Whether to fonts.css and respective font files to the output directory.
pub copy_fonts: bool,
/// An optional google analytics code.
pub google_analytics: Option<String>,
/// Additional CSS stylesheets to include in the rendered page's `<head>`.
@@ -412,8 +490,33 @@ pub struct HtmlConfig {
/// Additional JS scripts to include at the bottom of the rendered page's
/// `<body>`.
pub additional_js: Vec<PathBuf>,
/// Playpen settings.
pub playpen: Playpen,
/// Fold settings.
pub fold: Fold,
/// Playground settings.
#[serde(alias = "playpen")]
pub playground: Playground,
/// Print settings.
pub print: Print,
/// Don't render section labels.
pub no_section_label: bool,
/// Search settings. If `None`, the default will be used.
pub search: Option<Search>,
/// Git repository url. If `None`, the git button will not be shown.
pub git_repository_url: Option<String>,
/// FontAwesome icon class to use for the Git repository link.
/// Defaults to `fa-github` if `None`.
pub git_repository_icon: Option<String>,
/// Input path for the 404 file, defaults to 404.md, set to "" to disable 404 file output
pub input_404: Option<String>,
/// Absolute url to site, used to emit correct paths for the 404 page, which might be accessed in a deeply nested directory
pub site_url: Option<String>,
/// The DNS subdomain or apex domain at which your book will be hosted. This
/// string will be written to a file named CNAME in the root of your site,
/// as required by GitHub Pages (see [*Managing a custom domain for your
/// GitHub Pages site*][custom domain]).
///
/// [custom domain]: https://docs.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site
pub cname: Option<String>,
/// 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
@@ -422,10 +525,37 @@ pub struct HtmlConfig {
/// This config item *should not be edited* by the end user.
#[doc(hidden)]
pub livereload_url: Option<String>,
/// Should section labels be rendered?
pub no_section_label: bool,
/// Search settings. If `None`, the default will be used.
pub search: Option<Search>,
/// The mapping from old pages to new pages/URLs to use when generating
/// redirects.
pub redirect: HashMap<String, String>,
}
impl Default for HtmlConfig {
fn default() -> HtmlConfig {
HtmlConfig {
theme: None,
default_theme: None,
preferred_dark_theme: None,
curly_quotes: false,
mathjax_support: false,
copy_fonts: true,
google_analytics: None,
additional_css: Vec::new(),
additional_js: Vec::new(),
fold: Fold::default(),
playground: Playground::default(),
print: Print::default(),
no_section_label: false,
search: None,
git_repository_url: None,
git_repository_icon: None,
input_404: None,
site_url: None,
cname: None,
livereload_url: None,
redirect: HashMap::new(),
}
}
}
impl HtmlConfig {
@@ -439,22 +569,54 @@ impl HtmlConfig {
}
}
/// Configuration for tweaking how the the HTML renderer handles the playpen.
/// Configuration for how to render the print icon, print.html, and print.css.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Print {
/// Whether print support is enabled.
pub enable: bool,
}
impl Default for Print {
fn default() -> Self {
Self { enable: true }
}
}
/// Configuration for how to fold chapters of sidebar.
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct Fold {
/// When off, all folds are open. Default: `false`.
pub enable: bool,
/// The higher the more folded regions are open. When level is 0, all folds
/// are closed.
/// Default: `0`.
pub level: u8,
}
/// Configuration for tweaking how the the HTML renderer handles the playground.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct Playpen {
/// Should playpen snippets be editable? Default: `false`.
pub struct Playground {
/// Should playground snippets be editable? Default: `false`.
pub editable: bool,
/// Display the copy button. Default: `true`.
pub copyable: bool,
/// Copy JavaScript files for the editor to the output directory?
/// Default: `true`.
pub copy_js: bool,
/// Display line numbers on playground snippets. Default: `false`.
pub line_numbers: bool,
}
impl Default for Playpen {
fn default() -> Playpen {
Playpen {
impl Default for Playground {
fn default() -> Playground {
Playground {
editable: false,
copyable: true,
copy_js: true,
line_numbers: false,
}
}
}
@@ -463,12 +625,14 @@ impl Default for Playpen {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct Search {
/// Enable the search feature. Default: `true`.
pub enable: bool,
/// Maximum number of visible results. Default: `30`.
pub limit_results: u32,
/// The number of words used for a search result teaser. Default: `30`,
/// The number of words used for a search result teaser. Default: `30`.
pub teaser_word_count: u32,
/// Define the logical link between multiple search words.
/// If true, all search words must appear in each result. Default: `true`.
/// If true, all search words must appear in each result. Default: `false`.
pub use_boolean_and: bool,
/// Boost factor for the search result score if a search word appears in the header.
/// Default: `2`.
@@ -494,6 +658,7 @@ impl Default for Search {
fn default() -> Search {
// Please update the documentation of `Search` when changing values!
Search {
enable: true,
limit_results: 30,
teaser_word_count: 30,
use_boolean_and: false,
@@ -516,12 +681,10 @@ trait Updateable<'de>: Serialize + Deserialize<'de> {
fn update_value<S: Serialize>(&mut self, key: &str, value: S) {
let mut raw = Value::try_from(&self).expect("unreachable");
{
if let Ok(value) = Value::try_from(value) {
let _ = raw.insert(key, value);
} else {
return;
}
if let Ok(value) = Value::try_from(value) {
let _ = raw.insert(key, value);
} else {
return;
}
if let Ok(updated) = raw.try_into() {
@@ -530,38 +693,47 @@ trait Updateable<'de>: Serialize + Deserialize<'de> {
}
}
impl<'de, T> Updateable<'de> for T
where
T: Serialize + Deserialize<'de>,
{
}
impl<'de, T> Updateable<'de> for T where T: Serialize + Deserialize<'de> {}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::fs::get_404_output_file;
const COMPLEX_CONFIG: &'static str = r#"
const COMPLEX_CONFIG: &str = r#"
[book]
title = "Some Book"
authors = ["Michael-F-Bryan <michaelfbryan@gmail.com>"]
description = "A completely useless book"
multilingual = true
src = "source"
language = "ja"
[build]
build-dir = "outputs"
create-missing = false
preprocess = ["first_preprocessor", "second_preprocessor"]
use-default-preprocessors = true
[output.html]
theme = "./themedir"
default-theme = "rust"
curly-quotes = true
google-analytics = "123456"
additional-css = ["./foo/bar/baz.css"]
git-repository-url = "https://foo.com/"
git-repository-icon = "fa-code-fork"
[output.html.playpen]
[output.html.playground]
editable = true
editor = "ace"
[output.html.redirect]
"index.html" = "overview.html"
"nexted/page.md" = "https://rust-lang.org/"
[preprocessor.first]
[preprocessor.second]
"#;
#[test]
@@ -574,26 +746,38 @@ mod tests {
description: Some(String::from("A completely useless book")),
multilingual: true,
src: PathBuf::from("source"),
..Default::default()
language: Some(String::from("ja")),
};
let build_should_be = BuildConfig {
build_dir: PathBuf::from("outputs"),
create_missing: false,
preprocess: Some(vec![
"first_preprocessor".to_string(),
"second_preprocessor".to_string(),
]),
use_default_preprocessors: true,
};
let playpen_should_be = Playpen {
let rust_should_be = RustConfig { edition: None };
let playground_should_be = Playground {
editable: true,
copyable: true,
copy_js: true,
line_numbers: false,
};
let html_should_be = HtmlConfig {
curly_quotes: true,
google_analytics: Some(String::from("123456")),
additional_css: vec![PathBuf::from("./foo/bar/baz.css")],
theme: Some(PathBuf::from("./themedir")),
playpen: playpen_should_be,
default_theme: Some(String::from("rust")),
playground: playground_should_be,
git_repository_url: Some(String::from("https://foo.com/")),
git_repository_icon: Some(String::from("fa-code-fork")),
redirect: vec![
(String::from("index.html"), String::from("overview.html")),
(
String::from("nexted/page.md"),
String::from("https://rust-lang.org/"),
),
]
.into_iter()
.collect(),
..Default::default()
};
@@ -601,9 +785,62 @@ mod tests {
assert_eq!(got.book, book_should_be);
assert_eq!(got.build, build_should_be);
assert_eq!(got.rust, rust_should_be);
assert_eq!(got.html_config().unwrap(), html_should_be);
}
#[test]
fn edition_2015() {
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 = "2015"
"#;
let book_should_be = BookConfig {
title: Some(String::from("mdBook Documentation")),
description: Some(String::from(
"Create book from markdown files. Like Gitbook but implemented in Rust",
)),
authors: vec![String::from("Mathieu David")],
src: PathBuf::from("./source"),
..Default::default()
};
let got = Config::from_str(src).unwrap();
assert_eq!(got.book, book_should_be);
let rust_should_be = RustConfig {
edition: Some(RustEdition::E2015),
};
let got = Config::from_str(src).unwrap();
assert_eq!(got.rust, rust_should_be);
}
#[test]
fn edition_2018() {
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 = "2018"
"#;
let rust_should_be = RustConfig {
edition: Some(RustEdition::E2018),
};
let got = Config::from_str(src).unwrap();
assert_eq!(got.rust, rust_should_be);
}
#[test]
fn load_arbitrary_output_type() {
#[derive(Debug, Deserialize, PartialEq)]
@@ -627,14 +864,17 @@ mod tests {
};
let cfg = Config::from_str(src).unwrap();
let got: RandomOutput = cfg.get_deserialized("output.random").unwrap();
let got: RandomOutput = cfg.get_deserialized_opt("output.random").unwrap().unwrap();
assert_eq!(got, should_be);
let baz: Vec<bool> = cfg.get_deserialized("output.random.baz").unwrap();
let got_baz: Vec<bool> = cfg
.get_deserialized_opt("output.random.baz")
.unwrap()
.unwrap();
let baz_should_be = vec![true, true, false];
assert_eq!(baz, baz_should_be);
assert_eq!(got_baz, baz_should_be);
}
#[test]
@@ -643,7 +883,7 @@ mod tests {
// is happy...
let src = COMPLEX_CONFIG;
let mut config = Config::from_str(src).unwrap();
let key = "output.html.playpen.editable";
let key = "output.html.playground.editable";
assert_eq!(config.get(key).unwrap(), &Value::Boolean(true));
*config.get_mut(key).unwrap() = Value::Boolean(false);
@@ -684,7 +924,7 @@ mod tests {
let build_should_be = BuildConfig {
build_dir: PathBuf::from("my-book"),
create_missing: true,
preprocess: None,
use_default_preprocessors: true,
};
let html_should_be = HtmlConfig {
@@ -711,7 +951,7 @@ mod tests {
assert!(cfg.get(key).is_none());
cfg.set(key, value).unwrap();
let got: String = cfg.get_deserialized(key).unwrap();
let got: String = cfg.get_deserialized_opt(key).unwrap().unwrap();
assert_eq!(got, value);
}
@@ -726,7 +966,7 @@ mod tests {
for (src, should_be) in inputs {
let got = parse_env(src);
let should_be = should_be.map(|s| s.to_string());
let should_be = should_be.map(ToString::to_string);
assert_eq!(got, should_be);
}
@@ -752,10 +992,14 @@ mod tests {
cfg.update_from_env();
assert_eq!(cfg.get_deserialized::<String, _>(key).unwrap(), value);
assert_eq!(
cfg.get_deserialized_opt::<String, _>(key).unwrap().unwrap(),
value
);
}
#[test]
#[allow(clippy::approx_constant)]
fn update_config_using_env_var_and_complex_value() {
let mut cfg = Config::default();
let key = "foo-bar.baz";
@@ -770,7 +1014,9 @@ mod tests {
cfg.update_from_env();
assert_eq!(
cfg.get_deserialized::<serde_json::Value, _>(key).unwrap(),
cfg.get_deserialized_opt::<serde_json::Value, _>(key)
.unwrap()
.unwrap(),
value
);
}
@@ -787,4 +1033,31 @@ mod tests {
assert_eq!(cfg.book.title, Some(should_be));
}
#[test]
fn file_404_default() {
let src = r#"
[output.html]
destination = "my-book"
"#;
let got = Config::from_str(src).unwrap();
let html_config = got.html_config().unwrap();
assert_eq!(html_config.input_404, None);
assert_eq!(&get_404_output_file(&html_config.input_404), "404.html");
}
#[test]
fn file_404_custom() {
let src = r#"
[output.html]
input-404= "missing.md"
output-404= "missing.html"
"#;
let got = Config::from_str(src).unwrap();
let html_config = got.html_config().unwrap();
assert_eq!(html_config.input_404, Some("missing.md".to_string()));
assert_eq!(&get_404_output_file(&html_config.input_404), "missing.html");
}
}

View File

@@ -75,92 +75,48 @@
//! directly, making deserializing the `RenderContext` easy and giving you
//! access to the various methods for working with the [`Config`].
//!
//! [user guide]: https://rust-lang-nursery.github.io/mdBook/
//! [user guide]: https://rust-lang.github.io/mdBook/
//! [`RenderContext`]: renderer/struct.RenderContext.html
//! [relevant chapter]: https://rust-lang-nursery.github.io/mdBook/for_developers/backends.html
//! [relevant chapter]: https://rust-lang.github.io/mdBook/for_developers/backends.html
//! [`Config`]: config/struct.Config.html
#![deny(missing_docs)]
#![deny(rust_2018_idioms)]
#![allow(clippy::comparison_chain)]
#[macro_use]
extern crate error_chain;
extern crate handlebars;
extern crate itertools;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
extern crate memchr;
extern crate pulldown_cmark;
extern crate regex;
extern crate serde;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate serde_json;
extern crate shlex;
extern crate tempdir;
extern crate toml;
extern crate toml_query;
#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;
pub mod preprocess;
pub mod book;
pub mod config;
pub mod preprocess;
pub mod renderer;
pub mod theme;
pub mod utils;
pub use book::MDBook;
pub use book::BookItem;
pub use renderer::Renderer;
pub use config::Config;
/// The current version of `mdbook`.
///
/// This is provided as a way for custom preprocessors and renderers to do
/// compatibility checks.
pub const MDBOOK_VERSION: &str = env!("CARGO_PKG_VERSION");
pub use crate::book::BookItem;
pub use crate::book::MDBook;
pub use crate::config::Config;
pub use crate::renderer::Renderer;
/// The error types used through out this crate.
pub mod errors {
use std::path::PathBuf;
error_chain!{
foreign_links {
Io(::std::io::Error) #[doc = "A wrapper around `std::io::Error`"];
HandlebarsRender(::handlebars::RenderError) #[doc = "Handlebars rendering failed"];
HandlebarsTemplate(Box<::handlebars::TemplateError>) #[doc = "Unable to parse the template"];
Utf8(::std::string::FromUtf8Error) #[doc = "Invalid UTF-8"];
SerdeJson(::serde_json::Error) #[doc = "JSON conversion failed"];
}
links {
TomlQuery(::toml_query::error::Error, ::toml_query::error::ErrorKind) #[doc = "A TomlQuery error"];
}
errors {
/// A subprocess exited with an unsuccessful return code.
Subprocess(message: String, output: ::std::process::Output) {
description("A subprocess failed")
display("{}: {}", message, String::from_utf8_lossy(&output.stdout))
}
/// An error was encountered while parsing the `SUMMARY.md` file.
ParseError(line: usize, col: usize, message: String) {
description("A SUMMARY.md parsing error")
display("Error at line {}, column {}: {}", line, col, message)
}
/// The user tried to use a reserved filename.
ReservedFilenameError(filename: PathBuf) {
description("Reserved Filename")
display("{} is reserved for internal use", filename.display())
}
}
}
// Box to halve the size of Error
impl From<::handlebars::TemplateError> for Error {
fn from(e: ::handlebars::TemplateError) -> Error {
From::from(Box::new(e))
}
}
pub(crate) use anyhow::{bail, ensure, Context};
pub use anyhow::{Error, Result};
}

View File

@@ -1,75 +1,64 @@
extern crate chrono;
#[macro_use]
extern crate clap;
extern crate env_logger;
extern crate error_chain;
#[macro_use]
extern crate log;
extern crate mdbook;
extern crate open;
use chrono::Local;
use clap::{App, AppSettings, ArgMatches};
use env_logger::Builder;
use log::LevelFilter;
use mdbook::utils;
use std::env;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::io::Write;
use clap::{App, AppSettings, ArgMatches};
use chrono::Local;
use log::LevelFilter;
use env_logger::Builder;
use mdbook::utils;
use std::path::{Path, PathBuf};
pub mod build;
pub mod clean;
pub mod init;
pub mod test;
#[cfg(feature = "serve")]
pub mod serve;
#[cfg(feature = "watch")]
pub mod watch;
mod cmd;
const NAME: &'static str = "mdbook";
const VERSION: &str = concat!("v", crate_version!());
fn main() {
init_logger();
// Create a list of valid arguments and sub-commands
let app = App::new(NAME)
.about("Create a book in form of a static website from markdown files")
.author("Mathieu David <mathieudavid@mathieudavid.org>")
// Get the version from our Cargo.toml using clap's crate_version!() macro
.version(concat!("v",crate_version!()))
.setting(AppSettings::ArgRequiredElseHelp)
.after_help("For more information about a specific command, \
try `mdbook <command> --help`\n\
Source code for mdbook available \
at: https://github.com/rust-lang-nursery/mdBook")
.subcommand(init::make_subcommand())
.subcommand(build::make_subcommand())
.subcommand(test::make_subcommand())
.subcommand(clean::make_subcommand());
let app = App::new(crate_name!())
.about(crate_description!())
.author("Mathieu David <mathieudavid@mathieudavid.org>")
.version(VERSION)
.setting(AppSettings::GlobalVersion)
.setting(AppSettings::ArgRequiredElseHelp)
.setting(AppSettings::ColoredHelp)
.after_help(
"For more information about a specific command, try `mdbook <command> --help`\n\
The source code for mdBook is available at: https://github.com/rust-lang/mdBook",
)
.subcommand(cmd::init::make_subcommand())
.subcommand(cmd::build::make_subcommand())
.subcommand(cmd::test::make_subcommand())
.subcommand(cmd::clean::make_subcommand());
#[cfg(feature = "watch")]
let app = app.subcommand(watch::make_subcommand());
let app = app.subcommand(cmd::watch::make_subcommand());
#[cfg(feature = "serve")]
let app = app.subcommand(serve::make_subcommand());
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)) => init::execute(sub_matches),
("build", Some(sub_matches)) => build::execute(sub_matches),
("clean", Some(sub_matches)) => clean::execute(sub_matches),
("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)) => watch::execute(sub_matches),
("watch", Some(sub_matches)) => cmd::watch::execute(sub_matches),
#[cfg(feature = "serve")]
("serve", Some(sub_matches)) => serve::execute(sub_matches),
("test", Some(sub_matches)) => test::execute(sub_matches),
("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);
std::process::exit(101);
}
}
@@ -88,7 +77,7 @@ fn init_logger() {
});
if let Ok(var) = env::var("RUST_LOG") {
builder.parse(&var);
builder.parse_filters(&var);
} else {
// if no RUST_LOG provided, default to logging at the Info level
builder.filter(None, LevelFilter::Info);

197
src/preprocess/cmd.rs Normal file
View File

@@ -0,0 +1,197 @@
use super::{Preprocessor, PreprocessorContext};
use crate::book::Book;
use crate::errors::*;
use shlex::Shlex;
use std::io::{self, Read, Write};
use std::process::{Child, Command, Stdio};
/// A custom preprocessor which will shell out to a 3rd-party program.
///
/// # Preprocessing Protocol
///
/// When the `supports_renderer()` method is executed, `CmdPreprocessor` will
/// execute the shell command `$cmd supports $renderer`. If the renderer is
/// supported, custom preprocessors should exit with a exit code of `0`,
/// any other exit code be considered as unsupported.
///
/// The `run()` method is implemented by passing a `(PreprocessorContext, Book)`
/// tuple to the spawned command (`$cmd`) as JSON via `stdin`. Preprocessors
/// should then "return" a processed book by printing it to `stdout` as JSON.
/// For convenience, the `CmdPreprocessor::parse_input()` function can be used
/// to parse the input provided by `mdbook`.
///
/// Exiting with a non-zero exit code while preprocessing is considered an
/// error. `stderr` is passed directly through to the user, so it can be used
/// for logging or emitting warnings if desired.
///
/// # Examples
///
/// An example preprocessor is available in this project's `examples/`
/// directory.
#[derive(Debug, Clone, PartialEq)]
pub struct CmdPreprocessor {
name: String,
cmd: String,
}
impl CmdPreprocessor {
/// Create a new `CmdPreprocessor`.
pub fn new(name: String, cmd: String) -> CmdPreprocessor {
CmdPreprocessor { name, cmd }
}
/// A convenience function custom preprocessors can use to parse the input
/// written to `stdin` by a `CmdRenderer`.
pub fn parse_input<R: Read>(reader: R) -> Result<(PreprocessorContext, Book)> {
serde_json::from_reader(reader).with_context(|| "Unable to parse the input")
}
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) {
// 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);
}
}
fn write_input<W: Write>(
&self,
writer: W,
book: &Book,
ctx: &PreprocessorContext,
) -> Result<()> {
serde_json::to_writer(writer, &(ctx, book)).map_err(Into::into)
}
/// The command this `Preprocessor` will invoke.
pub fn cmd(&self) -> &str {
&self.cmd
}
fn command(&self) -> Result<Command> {
let mut words = Shlex::new(&self.cmd);
let executable = match words.next() {
Some(e) => e,
None => bail!("Command string was empty"),
};
let mut cmd = Command::new(executable);
for arg in words {
cmd.arg(arg);
}
Ok(cmd)
}
}
impl Preprocessor for CmdPreprocessor {
fn name(&self) -> &str {
&self.name
}
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book> {
let mut cmd = self.command()?;
let mut child = cmd
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.with_context(|| {
format!(
"Unable to start the \"{}\" preprocessor. Is it installed?",
self.name()
)
})?;
self.write_input_to_child(&mut child, &book, ctx);
let output = child
.wait_with_output()
.with_context(|| "Error waiting for the preprocessor to complete")?;
trace!("{} exited with output: {:?}", self.cmd, output);
ensure!(
output.status.success(),
"The preprocessor exited unsuccessfully"
);
serde_json::from_slice(&output.stdout)
.with_context(|| "Unable to parse the preprocessed book")
}
fn supports_renderer(&self, renderer: &str) -> bool {
debug!(
"Checking if the \"{}\" preprocessor supports \"{}\"",
self.name(),
renderer
);
let mut cmd = match self.command() {
Ok(c) => c,
Err(e) => {
warn!(
"Unable to create the command for the \"{}\" preprocessor, {}",
self.name(),
e
);
return false;
}
};
let outcome = cmd
.arg("supports")
.arg(renderer)
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.map(|status| status.code() == Some(0));
if let Err(ref e) = outcome {
if e.kind() == io::ErrorKind::NotFound {
warn!(
"The command wasn't found, is the \"{}\" preprocessor installed?",
self.name
);
warn!("\tCommand: {}", self.cmd);
}
}
outcome.unwrap_or(false)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::MDBook;
use std::path::Path;
fn guide() -> MDBook {
let example = Path::new(env!("CARGO_MANIFEST_DIR")).join("guide");
MDBook::load(example).unwrap()
}
#[test]
fn round_trip_write_and_parse_input() {
let cmd = CmdPreprocessor::new("test".to_string(), "test".to_string());
let md = guide();
let ctx = PreprocessorContext::new(
md.root.clone(),
md.config.clone(),
"some-renderer".to_string(),
);
let mut buffer = Vec::new();
cmd.write_input(&mut buffer, &md.book, &ctx).unwrap();
let (got_ctx, got_book) = CmdPreprocessor::parse_input(buffer.as_slice()).unwrap();
assert_eq!(got_book, md.book);
assert_eq!(got_ctx, ctx);
}
}

105
src/preprocess/index.rs Normal file
View File

@@ -0,0 +1,105 @@
use regex::Regex;
use std::path::Path;
use crate::errors::*;
use super::{Preprocessor, PreprocessorContext};
use crate::book::{Book, BookItem};
/// A preprocessor for converting file name `README.md` to `index.md` since
/// `README.md` is the de facto index file in markdown-based documentation.
#[derive(Default)]
pub struct IndexPreprocessor;
impl IndexPreprocessor {
pub(crate) const NAME: &'static str = "index";
/// Create a new `IndexPreprocessor`.
pub fn new() -> Self {
IndexPreprocessor
}
}
impl Preprocessor for IndexPreprocessor {
fn name(&self) -> &str {
Self::NAME
}
fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book> {
let source_dir = ctx.root.join(&ctx.config.book.src);
book.for_each_mut(|section: &mut BookItem| {
if let BookItem::Chapter(ref mut ch) = *section {
if let Some(ref mut path) = ch.path {
if is_readme_file(&path) {
let mut index_md = source_dir.join(path.with_file_name("index.md"));
if index_md.exists() {
warn_readme_name_conflict(&path, &&mut index_md);
}
path.set_file_name("index.md");
}
}
}
});
Ok(book)
}
}
fn warn_readme_name_conflict<P: AsRef<Path>>(readme_path: P, index_path: P) {
let file_name = readme_path.as_ref().file_name().unwrap_or_default();
let parent_dir = index_path
.as_ref()
.parent()
.unwrap_or_else(|| index_path.as_ref());
warn!(
"It seems that there are both {:?} and index.md under \"{}\".",
file_name,
parent_dir.display()
);
warn!(
"mdbook converts {:?} into index.html by default. It may cause",
file_name
);
warn!("unexpected behavior if putting both files under the same directory.");
warn!("To solve the warning, try to rearrange the book structure or disable");
warn!("\"index\" preprocessor to stop the conversion.");
}
fn is_readme_file<P: AsRef<Path>>(path: P) -> bool {
lazy_static! {
static ref RE: Regex = Regex::new(r"(?i)^readme$").unwrap();
}
RE.is_match(
path.as_ref()
.file_stem()
.and_then(std::ffi::OsStr::to_str)
.unwrap_or_default(),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn file_stem_exactly_matches_readme_case_insensitively() {
let path = "path/to/Readme.md";
assert!(is_readme_file(path));
let path = "path/to/README.md";
assert!(is_readme_file(path));
let path = "path/to/rEaDmE.md";
assert!(is_readme_file(path));
let path = "path/to/README.markdown";
assert!(is_readme_file(path));
let path = "path/to/README";
assert!(is_readme_file(path));
let path = "path/to/README-README.md";
assert!(!is_readme_file(path));
}
}

View File

@@ -1,20 +1,34 @@
use std::ops::{Range, RangeFrom, RangeFull, RangeTo};
use std::path::{Path, PathBuf};
use crate::errors::*;
use crate::utils::{
take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines,
take_rustdoc_include_lines,
};
use regex::{CaptureMatches, Captures, Regex};
use utils::fs::file_to_string;
use utils::take_lines;
use errors::*;
use std::fs;
use std::ops::{Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeTo};
use std::path::{Path, PathBuf};
use super::{Preprocessor, PreprocessorContext};
use book::{Book, BookItem};
use crate::book::{Book, BookItem};
const ESCAPE_CHAR: char = '\\';
const MAX_LINK_NESTED_DEPTH: usize = 10;
/// A preprocessor for expanding the `{{# playpen}}` and `{{# include}}`
/// helpers in a chapter.
/// A preprocessor for expanding helpers in a chapter. Supported helpers are:
///
/// - `{{# include}}` - Insert an external file of any type. Include the whole file, only particular
///. lines, or only between the specified anchors.
/// - `{{# rustdoc_include}}` - Insert an external Rust file, showing the particular lines
///. specified or the lines between specified anchors, and include the rest of the file behind `#`.
/// 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
#[derive(Default)]
pub struct LinkPreprocessor;
impl LinkPreprocessor {
pub(crate) const NAME: &'static str = "links";
/// Create a new `LinkPreprocessor`.
pub fn new() -> Self {
LinkPreprocessor
@@ -23,49 +37,71 @@ impl LinkPreprocessor {
impl Preprocessor for LinkPreprocessor {
fn name(&self) -> &str {
"links"
Self::NAME
}
fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()> {
fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book> {
let src_dir = ctx.root.join(&ctx.config.book.src);
book.for_each_mut(|section: &mut BookItem| {
if let BookItem::Chapter(ref mut ch) = *section {
let base = ch.path
.parent()
.map(|dir| src_dir.join(dir))
.expect("All book items have a parent");
if let Some(ref chapter_path) = ch.path {
let base = chapter_path
.parent()
.map(|dir| src_dir.join(dir))
.expect("All book items have a parent");
let content = replace_all(&ch.content, base);
ch.content = content;
let content = replace_all(&ch.content, base, chapter_path, 0);
ch.content = content;
}
}
});
Ok(())
Ok(book)
}
}
fn replace_all<P: AsRef<Path>>(s: &str, path: P) -> String {
fn replace_all<P1, P2>(s: &str, path: P1, source: P2, depth: usize) -> String
where
P1: AsRef<Path>,
P2: AsRef<Path>,
{
// When replacing one thing in a string by something with a different length,
// the indices after that will not correspond,
// we therefore have to store the difference to correct this
let path = path.as_ref();
let source = source.as_ref();
let mut previous_end_index = 0;
let mut replaced = String::new();
for playpen in find_links(s) {
replaced.push_str(&s[previous_end_index..playpen.start_index]);
for link in find_links(s) {
replaced.push_str(&s[previous_end_index..link.start_index]);
match playpen.render_with_path(&path) {
match link.render_with_path(&path) {
Ok(new_content) => {
replaced.push_str(&new_content);
previous_end_index = playpen.end_index;
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));
} else {
replaced.push_str(&new_content);
}
} else {
error!(
"Stack depth exceeded in {}. Check for cyclic includes",
source.display()
);
}
previous_end_index = link.end_index;
}
Err(e) => {
error!("Error updating \"{}\", {}", playpen.link_text, e);
error!("Error updating \"{}\", {}", link.link_text, e);
for cause in e.chain().skip(1) {
warn!("Caused By: {}", cause);
}
// This should make sure we include the raw `{{# ... }}` snippet
// in the page content if there are any errors.
previous_end_index = playpen.start_index;
previous_end_index = link.start_index;
}
}
}
@@ -77,57 +113,142 @@ fn replace_all<P: AsRef<Path>>(s: &str, path: P) -> String {
#[derive(PartialEq, Debug, Clone)]
enum LinkType<'a> {
Escaped,
IncludeRange(PathBuf, Range<usize>),
IncludeRangeFrom(PathBuf, RangeFrom<usize>),
IncludeRangeTo(PathBuf, RangeTo<usize>),
IncludeRangeFull(PathBuf, RangeFull),
Playpen(PathBuf, Vec<&'a str>),
Include(PathBuf, RangeOrAnchor),
Playground(PathBuf, Vec<&'a str>),
RustdocInclude(PathBuf, RangeOrAnchor),
}
#[derive(PartialEq, Debug, Clone)]
enum RangeOrAnchor {
Range(LineRange),
Anchor(String),
}
// A range of lines specified with some include directive.
#[derive(PartialEq, Debug, Clone)]
enum LineRange {
Range(Range<usize>),
RangeFrom(RangeFrom<usize>),
RangeTo(RangeTo<usize>),
RangeFull(RangeFull),
}
impl RangeBounds<usize> for LineRange {
fn start_bound(&self) -> Bound<&usize> {
match self {
LineRange::Range(r) => r.start_bound(),
LineRange::RangeFrom(r) => r.start_bound(),
LineRange::RangeTo(r) => r.start_bound(),
LineRange::RangeFull(r) => r.start_bound(),
}
}
fn end_bound(&self) -> Bound<&usize> {
match self {
LineRange::Range(r) => r.end_bound(),
LineRange::RangeFrom(r) => r.end_bound(),
LineRange::RangeTo(r) => r.end_bound(),
LineRange::RangeFull(r) => r.end_bound(),
}
}
}
impl From<Range<usize>> for LineRange {
fn from(r: Range<usize>) -> LineRange {
LineRange::Range(r)
}
}
impl From<RangeFrom<usize>> for LineRange {
fn from(r: RangeFrom<usize>) -> LineRange {
LineRange::RangeFrom(r)
}
}
impl From<RangeTo<usize>> for LineRange {
fn from(r: RangeTo<usize>) -> LineRange {
LineRange::RangeTo(r)
}
}
impl From<RangeFull> for LineRange {
fn from(r: RangeFull) -> LineRange {
LineRange::RangeFull(r)
}
}
impl<'a> LinkType<'a> {
fn relative_path<P: AsRef<Path>>(self, base: P) -> Option<PathBuf> {
let base = base.as_ref();
match self {
LinkType::Escaped => None,
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)),
}
}
}
fn return_relative_path<P: AsRef<Path>>(base: P, relative: P) -> PathBuf {
base.as_ref()
.join(relative)
.parent()
.expect("Included file should not be /")
.to_path_buf()
}
fn parse_range_or_anchor(parts: Option<&str>) -> RangeOrAnchor {
let mut parts = parts.unwrap_or("").splitn(3, ':').fuse();
let next_element = parts.next();
let start = if let Some(value) = next_element.and_then(|s| s.parse::<usize>().ok()) {
// subtract 1 since line numbers usually begin with 1
Some(value.saturating_sub(1))
} else if let Some("") = next_element {
None
} else if let Some(anchor) = next_element {
return RangeOrAnchor::Anchor(String::from(anchor));
} else {
None
};
let end = parts.next();
// If `end` is empty string or any other value that can't be parsed as a usize, treat this
// include as a range with only a start bound. However, if end isn't specified, include only
// the single line specified by `start`.
let end = end.map(|s| s.parse::<usize>());
match (start, end) {
(Some(start), Some(Ok(end))) => RangeOrAnchor::Range(LineRange::from(start..end)),
(Some(start), Some(Err(_))) => RangeOrAnchor::Range(LineRange::from(start..)),
(Some(start), None) => RangeOrAnchor::Range(LineRange::from(start..start + 1)),
(None, Some(Ok(end))) => RangeOrAnchor::Range(LineRange::from(..end)),
(None, None) | (None, Some(Err(_))) => RangeOrAnchor::Range(LineRange::from(RangeFull)),
}
}
fn parse_include_path(path: &str) -> LinkType<'static> {
let mut parts = path.split(':');
let mut parts = path.splitn(2, ':');
let path = parts.next().unwrap().into();
// subtract 1 since line numbers usually begin with 1
let start = parts
.next()
.and_then(|s| s.parse::<usize>().ok())
.map(|val| val.checked_sub(1).unwrap_or(0));
let end = parts.next();
let has_end = end.is_some();
let end = end.and_then(|s| s.parse::<usize>().ok());
match start {
Some(start) => match end {
Some(end) => LinkType::IncludeRange(
path,
Range {
start: start,
end: end,
},
),
None => if has_end {
LinkType::IncludeRangeFrom(path, RangeFrom { start: start })
} else {
LinkType::IncludeRange(
path,
Range {
start: start,
end: start + 1,
},
)
},
},
None => match end {
Some(end) => LinkType::IncludeRangeTo(path, RangeTo { end: end }),
None => LinkType::IncludeRangeFull(path, RangeFull),
},
}
let range_or_anchor = parse_range_or_anchor(parts.next());
LinkType::Include(path, range_or_anchor)
}
fn parse_rustdoc_include_path(path: &str) -> LinkType<'static> {
let mut parts = path.splitn(2, ':');
let path = parts.next().unwrap().into();
let range_or_anchor = parse_range_or_anchor(parts.next());
LinkType::RustdocInclude(path, range_or_anchor)
}
#[derive(PartialEq, Debug, Clone)]
struct Link<'a> {
start_index: usize,
end_index: usize,
link: LinkType<'a>,
link_type: LinkType<'a>,
link_text: &'a str,
}
@@ -141,7 +262,16 @@ impl<'a> Link<'a> {
match (typ.as_str(), file_arg) {
("include", Some(pth)) => Some(parse_include_path(pth)),
("playpen", Some(pth)) => Some(LinkType::Playpen(pth.into(), props)),
("playground", Some(pth)) => Some(LinkType::Playground(pth.into(), props)),
("playpen", Some(pth)) => {
warn!(
"the {{{{#playpen}}}} expression has been \
renamed to {{{{#playground}}}}, \
please update your book to use the new name"
);
Some(LinkType::Playground(pth.into(), props))
}
("rustdoc_include", Some(pth)) => Some(parse_rustdoc_include_path(pth)),
_ => None,
}
}
@@ -151,11 +281,11 @@ impl<'a> Link<'a> {
_ => None,
};
link_type.and_then(|lnk| {
link_type.and_then(|lnk_type| {
cap.get(0).map(|mat| Link {
start_index: mat.start(),
end_index: mat.end(),
link: lnk,
link_type: lnk_type,
link_text: mat.as_str(),
})
})
@@ -163,23 +293,55 @@ impl<'a> Link<'a> {
fn render_with_path<P: AsRef<Path>>(&self, base: P) -> Result<String> {
let base = base.as_ref();
match self.link {
match self.link_type {
// omit the escape char
LinkType::Escaped => Ok((&self.link_text[1..]).to_owned()),
LinkType::IncludeRange(ref pat, ref range) => file_to_string(base.join(pat))
.map(|s| take_lines(&s, range.clone()))
.chain_err(|| format!("Could not read file for link {}", self.link_text)),
LinkType::IncludeRangeFrom(ref pat, ref range) => file_to_string(base.join(pat))
.map(|s| take_lines(&s, range.clone()))
.chain_err(|| format!("Could not read file for link {}", self.link_text)),
LinkType::IncludeRangeTo(ref pat, ref range) => file_to_string(base.join(pat))
.map(|s| take_lines(&s, range.clone()))
.chain_err(|| format!("Could not read file for link {}", self.link_text)),
LinkType::IncludeRangeFull(ref pat, _) => file_to_string(base.join(pat))
.chain_err(|| format!("Could not read file for link {}", self.link_text)),
LinkType::Playpen(ref pat, ref attrs) => {
let contents = file_to_string(base.join(pat))
.chain_err(|| format!("Could not read file for link {}", self.link_text))?;
LinkType::Include(ref pat, ref range_or_anchor) => {
let target = base.join(pat);
fs::read_to_string(&target)
.map(|s| match range_or_anchor {
RangeOrAnchor::Range(range) => take_lines(&s, range.clone()),
RangeOrAnchor::Anchor(anchor) => take_anchored_lines(&s, anchor),
})
.with_context(|| {
format!(
"Could not read file for link {} ({})",
self.link_text,
target.display(),
)
})
}
LinkType::RustdocInclude(ref pat, ref range_or_anchor) => {
let target = base.join(pat);
fs::read_to_string(&target)
.map(|s| match range_or_anchor {
RangeOrAnchor::Range(range) => {
take_rustdoc_include_lines(&s, range.clone())
}
RangeOrAnchor::Anchor(anchor) => {
take_rustdoc_include_anchored_lines(&s, anchor)
}
})
.with_context(|| {
format!(
"Could not read file for link {} ({})",
self.link_text,
target.display(),
)
})
}
LinkType::Playground(ref pat, ref attrs) => {
let target = base.join(pat);
let contents = fs::read_to_string(&target).with_context(|| {
format!(
"Could not read file for link {} ({})",
self.link_text,
target.display()
)
})?;
let ftype = if !attrs.is_empty() { "rust," } else { "rust" };
Ok(format!(
"```{}{}\n{}\n```\n",
@@ -206,19 +368,21 @@ impl<'a> Iterator for LinkIter<'a> {
}
}
fn find_links(contents: &str) -> LinkIter {
fn find_links(contents: &str) -> LinkIter<'_> {
// lazily compute following regex
// r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([a-zA-Z0-9_.\-:/\\\s]+)\}\}")?;
lazy_static! {
static ref RE: Regex = Regex::new(r"(?x) # insignificant whitespace mode
\\\{\{\#.*\}\} # match escaped link
| # or
\{\{\s* # link opening parens and whitespace
\#([a-zA-Z0-9]+) # link type
\s+ # separating whitespace
([a-zA-Z0-9\s_.\-:/\\]+) # link target path and space separated properties
\s*\}\} # whitespace and link closing parens
").unwrap();
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"
)
.unwrap();
}
LinkIter(RE.captures_iter(contents))
}
@@ -227,6 +391,21 @@ fn find_links(contents: &str) -> LinkIter {
mod tests {
use super::*;
#[test]
fn test_replace_all_escaped() {
let start = r"
Some text over here.
```hbs
\{{#include file.rs}} << an escaped link!
```";
let end = r"
Some text over here.
```hbs
{{#include file.rs}} << an escaped link!
```";
assert_eq!(replace_all(start, "", "", 0), end);
}
#[test]
fn test_find_links_no_link() {
let s = "Some random text without link...";
@@ -235,7 +414,7 @@ mod tests {
#[test]
fn test_find_links_partial_link() {
let s = "Some random text with {{#playpen...";
let s = "Some random text with {{#playground...";
assert!(find_links(s).collect::<Vec<_>>() == vec![]);
let s = "Some random text with {{#include...";
assert!(find_links(s).collect::<Vec<_>>() == vec![]);
@@ -245,19 +424,19 @@ mod tests {
#[test]
fn test_find_links_empty_link() {
let s = "Some random text with {{#playpen}} and {{#playpen }} {{}} {{#}}...";
let s = "Some random text with {{#playground}} and {{#playground }} {{}} {{#}}...";
assert!(find_links(s).collect::<Vec<_>>() == vec![]);
}
#[test]
fn test_find_links_unknown_link_type() {
let s = "Some random text with {{#playpenz ar.rs}} and {{#incn}} {{baz}} {{#bar}}...";
let s = "Some random text with {{#playgroundz ar.rs}} and {{#incn}} {{baz}} {{#bar}}...";
assert!(find_links(s).collect::<Vec<_>>() == vec![]);
}
#[test]
fn test_find_links_simple_link() {
let s = "Some random text with {{#playpen file.rs}} and {{#playpen test.rs }}...";
let s = "Some random text with {{#playground file.rs}} and {{#playground test.rs }}...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
@@ -267,20 +446,38 @@ mod tests {
vec![
Link {
start_index: 22,
end_index: 42,
link: LinkType::Playpen(PathBuf::from("file.rs"), vec![]),
link_text: "{{#playpen file.rs}}",
end_index: 45,
link_type: LinkType::Playground(PathBuf::from("file.rs"), vec![]),
link_text: "{{#playground file.rs}}",
},
Link {
start_index: 47,
end_index: 68,
link: LinkType::Playpen(PathBuf::from("test.rs"), vec![]),
link_text: "{{#playpen test.rs }}",
start_index: 50,
end_index: 74,
link_type: LinkType::Playground(PathBuf::from("test.rs"), vec![]),
link_text: "{{#playground test.rs }}",
},
]
);
}
#[test]
fn test_find_links_with_special_characters() {
let s = "Some random text with {{#playground foo-bar\\baz/_c++.rs}}...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
assert_eq!(
res,
vec![Link {
start_index: 22,
end_index: 57,
link_type: LinkType::Playground(PathBuf::from("foo-bar\\baz/_c++.rs"), vec![]),
link_text: "{{#playground foo-bar\\baz/_c++.rs}}",
},]
);
}
#[test]
fn test_find_links_with_range() {
let s = "Some random text with {{#include file.rs:10:20}}...";
@@ -288,14 +485,15 @@ mod tests {
println!("\nOUTPUT: {:?}\n", res);
assert_eq!(
res,
vec![
Link {
start_index: 22,
end_index: 48,
link: LinkType::IncludeRange(PathBuf::from("file.rs"), 9..20),
link_text: "{{#include file.rs:10:20}}",
},
]
vec![Link {
start_index: 22,
end_index: 48,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(9..20))
),
link_text: "{{#include file.rs:10:20}}",
}]
);
}
@@ -306,14 +504,15 @@ mod tests {
println!("\nOUTPUT: {:?}\n", res);
assert_eq!(
res,
vec![
Link {
start_index: 22,
end_index: 45,
link: LinkType::IncludeRange(PathBuf::from("file.rs"), 9..10),
link_text: "{{#include file.rs:10}}",
},
]
vec![Link {
start_index: 22,
end_index: 45,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(9..10))
),
link_text: "{{#include file.rs:10}}",
}]
);
}
@@ -324,14 +523,15 @@ mod tests {
println!("\nOUTPUT: {:?}\n", res);
assert_eq!(
res,
vec![
Link {
start_index: 22,
end_index: 46,
link: LinkType::IncludeRangeFrom(PathBuf::from("file.rs"), 9..),
link_text: "{{#include file.rs:10:}}",
},
]
vec![Link {
start_index: 22,
end_index: 46,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(9..))
),
link_text: "{{#include file.rs:10:}}",
}]
);
}
@@ -342,14 +542,15 @@ mod tests {
println!("\nOUTPUT: {:?}\n", res);
assert_eq!(
res,
vec![
Link {
start_index: 22,
end_index: 46,
link: LinkType::IncludeRangeTo(PathBuf::from("file.rs"), ..20),
link_text: "{{#include file.rs::20}}",
},
]
vec![Link {
start_index: 22,
end_index: 46,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..20))
),
link_text: "{{#include file.rs::20}}",
}]
);
}
@@ -360,14 +561,15 @@ mod tests {
println!("\nOUTPUT: {:?}\n", res);
assert_eq!(
res,
vec![
Link {
start_index: 22,
end_index: 44,
link: LinkType::IncludeRangeFull(PathBuf::from("file.rs"), ..),
link_text: "{{#include file.rs::}}",
},
]
vec![Link {
start_index: 22,
end_index: 44,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..))
),
link_text: "{{#include file.rs::}}",
}]
);
}
@@ -378,41 +580,60 @@ mod tests {
println!("\nOUTPUT: {:?}\n", res);
assert_eq!(
res,
vec![
Link {
start_index: 22,
end_index: 42,
link: LinkType::IncludeRangeFull(PathBuf::from("file.rs"), ..),
link_text: "{{#include file.rs}}",
},
]
vec![Link {
start_index: 22,
end_index: 42,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..))
),
link_text: "{{#include file.rs}}",
}]
);
}
#[test]
fn test_find_links_with_anchor() {
let s = "Some random text with {{#include file.rs:anchor}}...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
assert_eq!(
res,
vec![Link {
start_index: 22,
end_index: 49,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Anchor(String::from("anchor"))
),
link_text: "{{#include file.rs:anchor}}",
}]
);
}
#[test]
fn test_find_links_escaped_link() {
let s = "Some random text with escaped playpen \\{{#playpen file.rs editable}} ...";
let s = "Some random text with escaped playground \\{{#playground file.rs editable}} ...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
assert_eq!(
res,
vec![
Link {
start_index: 38,
end_index: 68,
link: LinkType::Escaped,
link_text: "\\{{#playpen file.rs editable}}",
},
]
vec![Link {
start_index: 41,
end_index: 74,
link_type: LinkType::Escaped,
link_text: "\\{{#playground file.rs editable}}",
}]
);
}
#[test]
fn test_find_playpens_with_properties() {
let s = "Some random text with escaped playpen {{#playpen file.rs editable }} and some \
more\n text {{#playpen my.rs editable no_run should_panic}} ...";
fn test_find_playgrounds_with_properties() {
let s =
"Some random text with escaped playground {{#playground file.rs editable }} and some \
more\n text {{#playground my.rs editable no_run should_panic}} ...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
@@ -420,19 +641,19 @@ mod tests {
res,
vec![
Link {
start_index: 38,
end_index: 68,
link: LinkType::Playpen(PathBuf::from("file.rs"), vec!["editable"]),
link_text: "{{#playpen file.rs editable }}",
start_index: 41,
end_index: 74,
link_type: LinkType::Playground(PathBuf::from("file.rs"), vec!["editable"]),
link_text: "{{#playground file.rs editable }}",
},
Link {
start_index: 89,
end_index: 136,
link: LinkType::Playpen(
start_index: 95,
end_index: 145,
link_type: LinkType::Playground(
PathBuf::from("my.rs"),
vec!["editable", "no_run", "should_panic"],
),
link_text: "{{#playpen my.rs editable no_run should_panic}}",
link_text: "{{#playground my.rs editable no_run should_panic}}",
},
]
);
@@ -440,8 +661,9 @@ mod tests {
#[test]
fn test_find_all_link_types() {
let s = "Some random text with escaped playpen {{#include file.rs}} and \\{{#contents are \
insignifficant in escaped link}} some more\n text {{#playpen my.rs editable \
let s =
"Some random text with escaped playground {{#include file.rs}} and \\{{#contents are \
insignifficant in escaped link}} some more\n text {{#playground my.rs editable \
no_run should_panic}} ...";
let res = find_links(s).collect::<Vec<_>>();
@@ -450,33 +672,215 @@ mod tests {
assert_eq!(
res[0],
Link {
start_index: 38,
end_index: 58,
link: LinkType::IncludeRangeFull(PathBuf::from("file.rs"), ..),
start_index: 41,
end_index: 61,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..))
),
link_text: "{{#include file.rs}}",
}
);
assert_eq!(
res[1],
Link {
start_index: 63,
end_index: 112,
link: LinkType::Escaped,
start_index: 66,
end_index: 115,
link_type: LinkType::Escaped,
link_text: "\\{{#contents are insignifficant in escaped link}}",
}
);
assert_eq!(
res[2],
Link {
start_index: 130,
end_index: 177,
link: LinkType::Playpen(
start_index: 133,
end_index: 183,
link_type: LinkType::Playground(
PathBuf::from("my.rs"),
vec!["editable", "no_run", "should_panic"]
),
link_text: "{{#playpen my.rs editable no_run should_panic}}",
link_text: "{{#playground my.rs editable no_run should_panic}}",
}
);
}
#[test]
fn parse_without_colon_includes_all() {
let link_type = parse_include_path("arbitrary");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(RangeFull))
)
);
}
#[test]
fn parse_with_nothing_after_colon_includes_all() {
let link_type = parse_include_path("arbitrary:");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(RangeFull))
)
);
}
#[test]
fn parse_with_two_colons_includes_all() {
let link_type = parse_include_path("arbitrary::");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(RangeFull))
)
);
}
#[test]
fn parse_with_garbage_after_two_colons_includes_all() {
let link_type = parse_include_path("arbitrary::NaN");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(RangeFull))
)
);
}
#[test]
fn parse_with_one_number_after_colon_only_that_line() {
let link_type = parse_include_path("arbitrary:5");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..5))
)
);
}
#[test]
fn parse_with_one_based_start_becomes_zero_based() {
let link_type = parse_include_path("arbitrary:1");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(0..1))
)
);
}
#[test]
fn parse_with_zero_based_start_stays_zero_based_but_is_probably_an_error() {
let link_type = parse_include_path("arbitrary:0");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(0..1))
)
);
}
#[test]
fn parse_start_only_range() {
let link_type = parse_include_path("arbitrary:5:");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..))
)
);
}
#[test]
fn parse_start_with_garbage_interpreted_as_start_only_range() {
let link_type = parse_include_path("arbitrary:5:NaN");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..))
)
);
}
#[test]
fn parse_end_only_range() {
let link_type = parse_include_path("arbitrary::5");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(..5))
)
);
}
#[test]
fn parse_start_and_end_range() {
let link_type = parse_include_path("arbitrary:5:10");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..10))
)
);
}
#[test]
fn parse_with_negative_interpreted_as_anchor() {
let link_type = parse_include_path("arbitrary:-5");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Anchor("-5".to_string())
)
);
}
#[test]
fn parse_with_floating_point_interpreted_as_anchor() {
let link_type = parse_include_path("arbitrary:-5.7");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Anchor("-5.7".to_string())
)
);
}
#[test]
fn parse_with_anchor_followed_by_colon() {
let link_type = parse_include_path("arbitrary:some-anchor:this-gets-ignored");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Anchor("some-anchor".to_string())
)
);
}
#[test]
fn parse_with_more_than_three_colons_ignores_everything_after_third_colon() {
let link_type = parse_include_path("arbitrary:5:10:17:anything:");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..10))
)
);
}
}

View File

@@ -1,32 +1,49 @@
//! Book preprocessing.
pub use self::cmd::CmdPreprocessor;
pub use self::index::IndexPreprocessor;
pub use self::links::LinkPreprocessor;
mod cmd;
mod index;
mod links;
use book::Book;
use config::Config;
use errors::*;
use crate::book::Book;
use crate::config::Config;
use crate::errors::*;
use std::path::PathBuf;
/// Extra information for a `Preprocessor` to give them more context when
/// Extra information for a `Preprocessor` to give them more context when
/// processing a book.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PreprocessorContext {
/// The location of the book directory on disk.
pub root: PathBuf,
/// The book configuration (`book.toml`).
pub config: Config,
/// The `Renderer` this preprocessor is being used with.
pub renderer: String,
/// The calling `mdbook` version.
pub mdbook_version: String,
#[serde(skip)]
__non_exhaustive: (),
}
impl PreprocessorContext {
/// Create a new `PreprocessorContext`.
pub(crate) fn new(root: PathBuf, config: Config) -> Self {
PreprocessorContext { root, config }
pub(crate) fn new(root: PathBuf, config: Config, renderer: String) -> Self {
PreprocessorContext {
root,
config,
renderer,
mdbook_version: crate::MDBOOK_VERSION.to_string(),
__non_exhaustive: (),
}
}
}
/// An operation which is run immediately after loading a book into memory and
/// An operation which is run immediately after loading a book into memory and
/// before it gets rendered.
pub trait Preprocessor {
/// Get the `Preprocessor`'s name.
@@ -34,5 +51,13 @@ pub trait Preprocessor {
/// Run this `Preprocessor`, allowing it to update the book before it is
/// given to a renderer.
fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()>;
}
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book>;
/// A hint to `MDBook` whether this preprocessor is compatible with a
/// particular renderer.
///
/// By default, always returns `true`.
fn supports_renderer(&self, _renderer: &str) -> bool {
true
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +1,3 @@
pub mod toc;
pub mod navigation;
pub mod theme;
pub mod toc;

View File

@@ -1,8 +1,9 @@
use std::path::Path;
use std::collections::BTreeMap;
use std::path::Path;
use serde_json;
use handlebars::{Context, Handlebars, Helper, RenderContext, RenderError, Renderable};
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError, Renderable};
use crate::utils;
type StringMap = BTreeMap<String, String>;
@@ -16,13 +17,13 @@ impl Target {
/// Returns target if found.
fn find(
&self,
base_path: &String,
current_path: &String,
base_path: &str,
current_path: &str,
current_item: &StringMap,
previous_item: &StringMap,
) -> Result<Option<StringMap>, RenderError> {
match self {
&Target::Next => {
match *self {
Target::Next => {
let previous_path = previous_item
.get("path")
.ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))?;
@@ -32,7 +33,7 @@ impl Target {
}
}
&Target::Previous => {
Target::Previous => {
if current_path == base_path {
return Ok(Some(previous_item.clone()));
}
@@ -43,19 +44,45 @@ impl Target {
}
}
fn find_chapter(rc: &mut RenderContext, target: Target) -> Result<Option<StringMap>, RenderError> {
fn find_chapter(
ctx: &Context,
rc: &mut RenderContext<'_, '_>,
target: Target,
) -> Result<Option<StringMap>, RenderError> {
debug!("Get data from context");
let chapters = rc.evaluate_absolute("chapters", true).and_then(|c| {
serde_json::value::from_value::<Vec<StringMap>>(c.clone())
let chapters = rc.evaluate(ctx, "@root/chapters").and_then(|c| {
serde_json::value::from_value::<Vec<StringMap>>(c.as_json().clone())
.map_err(|_| RenderError::new("Could not decode the JSON data"))
})?;
let base_path = rc.evaluate_absolute("path", true)?
let base_path = rc
.evaluate(ctx, "@root/path")?
.as_json()
.as_str()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", "");
if !rc.evaluate(ctx, "@root/is_index")?.is_missing() {
// Special case for index.md which may be a synthetic page.
// Target::find won't match because there is no page with the path
// "index.md" (unless there really is an index.md in SUMMARY.md).
match target {
Target::Previous => return Ok(None),
Target::Next => match chapters
.iter()
.filter(|chapter| {
// Skip things like "spacer"
chapter.contains_key("path")
})
.nth(1)
{
Some(chapter) => return Ok(Some(chapter.clone())),
None => return Ok(None),
},
}
}
let mut previous: Option<StringMap> = None;
debug!("Search for chapter");
@@ -79,14 +106,27 @@ fn find_chapter(rc: &mut RenderContext, target: Target) -> Result<Option<StringM
}
fn render(
_h: &Helper,
r: &Handlebars,
rc: &mut RenderContext,
_h: &Helper<'_, '_>,
r: &Handlebars<'_>,
ctx: &Context,
rc: &mut RenderContext<'_, '_>,
out: &mut dyn Output,
chapter: &StringMap,
) -> Result<(), RenderError> {
trace!("Creating BTreeMap to inject in context");
let mut context = BTreeMap::new();
let base_path = rc
.evaluate(ctx, "@root/path")?
.as_json()
.as_str()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", "");
context.insert(
"path_to_root".to_owned(),
json!(utils::fs::path_to_root(&base_path)),
);
chapter
.get("name")
@@ -109,28 +149,41 @@ fn render(
_h.template()
.ok_or_else(|| RenderError::new("Error with the handlebars template"))
.and_then(|t| {
let mut local_rc = rc.with_context(Context::wraps(&context)?);
t.render(r, &mut local_rc)
let mut local_rc = rc.clone();
let local_ctx = Context::wraps(&context)?;
t.render(r, &local_ctx, &mut local_rc, out)
})?;
Ok(())
}
pub fn previous(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
pub fn previous(
_h: &Helper<'_, '_>,
r: &Handlebars<'_>,
ctx: &Context,
rc: &mut RenderContext<'_, '_>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
trace!("previous (handlebars helper)");
if let Some(previous) = find_chapter(rc, Target::Previous)? {
render(_h, r, rc, &previous)?;
if let Some(previous) = find_chapter(ctx, rc, Target::Previous)? {
render(_h, r, ctx, rc, out, &previous)?;
}
Ok(())
}
pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
pub fn next(
_h: &Helper<'_, '_>,
r: &Handlebars<'_>,
ctx: &Context,
rc: &mut RenderContext<'_, '_>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
trace!("next (handlebars helper)");
if let Some(next) = find_chapter(rc, Target::Next)? {
render(_h, r, rc, &next)?;
if let Some(next) = find_chapter(ctx, rc, Target::Next)? {
render(_h, r, ctx, rc, out, &next)?;
}
Ok(())
@@ -140,29 +193,29 @@ pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), R
mod tests {
use super::*;
static TEMPLATE: &'static str =
static TEMPLATE: &str =
"{{#previous}}{{title}}: {{link}}{{/previous}}|{{#next}}{{title}}: {{link}}{{/next}}";
#[test]
fn test_next_previous() {
let data = json!({
"name": "two",
"path": "two.path",
"chapters": [
{
"name": "one",
"path": "one.path"
},
{
"name": "two",
"path": "two.path",
},
{
"name": "three",
"path": "three.path"
}
]
});
"name": "two",
"path": "two.path",
"chapters": [
{
"name": "one",
"path": "one.path"
},
{
"name": "two",
"path": "two.path",
},
{
"name": "three",
"path": "three.path"
}
]
});
let mut h = Handlebars::new();
h.register_helper("previous", Box::new(previous));
@@ -177,23 +230,23 @@ mod tests {
#[test]
fn test_first() {
let data = json!({
"name": "one",
"path": "one.path",
"chapters": [
{
"name": "one",
"path": "one.path"
},
{
"name": "two",
"path": "two.path",
},
{
"name": "three",
"path": "three.path"
}
]
});
"name": "one",
"path": "one.path",
"chapters": [
{
"name": "one",
"path": "one.path"
},
{
"name": "two",
"path": "two.path",
},
{
"name": "three",
"path": "three.path"
}
]
});
let mut h = Handlebars::new();
h.register_helper("previous", Box::new(previous));
@@ -207,23 +260,23 @@ mod tests {
#[test]
fn test_last() {
let data = json!({
"name": "three",
"path": "three.path",
"chapters": [
{
"name": "one",
"path": "one.path"
},
{
"name": "two",
"path": "two.path",
},
{
"name": "three",
"path": "three.path"
}
]
});
"name": "three",
"path": "three.path",
"chapters": [
{
"name": "one",
"path": "one.path"
},
{
"name": "two",
"path": "two.path",
},
{
"name": "three",
"path": "three.path"
}
]
});
let mut h = Handlebars::new();
h.register_helper("previous", Box::new(previous));

View File

@@ -0,0 +1,28 @@
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError};
pub fn theme_option(
h: &Helper<'_, '_>,
_r: &Handlebars<'_>,
ctx: &Context,
rc: &mut RenderContext<'_, '_>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
trace!("theme_option (handlebars helper)");
let param = h.param(0).and_then(|v| v.value().as_str()).ok_or_else(|| {
RenderError::new("Param 0 with String type is required for theme_option helper.")
})?;
let default_theme = rc.evaluate(ctx, "@root/default_theme")?;
let default_theme_name = default_theme
.as_json()
.as_str()
.ok_or_else(|| RenderError::new("Type error for `default_theme`, string expected"))?;
out.write(param)?;
if param.to_lowercase() == default_theme_name.to_lowercase() {
out.write(" (default)")?;
}
Ok(())
}

View File

@@ -1,9 +1,10 @@
use std::path::Path;
use std::collections::BTreeMap;
use std::path::Path;
use serde_json;
use handlebars::{Handlebars, Helper, HelperDef, RenderContext, RenderError};
use pulldown_cmark::{html, Event, Parser, Tag};
use crate::utils;
use handlebars::{Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError};
use pulldown_cmark::{html, Event, Parser};
// Handlebars helper to construct TOC
#[derive(Clone, Copy)]
@@ -12,93 +13,136 @@ pub struct RenderToc {
}
impl HelperDef for RenderToc {
fn call(&self, _h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
fn call<'reg: 'rc, 'rc>(
&self,
_h: &Helper<'reg, 'rc>,
_r: &'reg Handlebars<'_>,
ctx: &'rc Context,
rc: &mut RenderContext<'reg, 'rc>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
// get value from context data
// rc.get_path() is current json parent path, you should always use it like this
// param is the key of value you want to display
let chapters = rc.evaluate_absolute("chapters", true).and_then(|c| {
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
let chapters = rc.evaluate(ctx, "@root/chapters").and_then(|c| {
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.as_json().clone())
.map_err(|_| RenderError::new("Could not decode the JSON data"))
})?;
let current = rc.evaluate_absolute("path", true)?
let current_path = rc
.evaluate(ctx, "@root/path")?
.as_json()
.as_str()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", "");
rc.writer.write_all(b"<ol class=\"chapter\">")?;
let current_section = rc
.evaluate(ctx, "@root/section")?
.as_json()
.as_str()
.map(str::to_owned)
.unwrap_or_default();
let fold_enable = rc
.evaluate(ctx, "@root/fold_enable")?
.as_json()
.as_bool()
.ok_or_else(|| RenderError::new("Type error for `fold_enable`, bool expected"))?;
let fold_level = rc
.evaluate(ctx, "@root/fold_level")?
.as_json()
.as_u64()
.ok_or_else(|| RenderError::new("Type error for `fold_level`, u64 expected"))?;
out.write("<ol class=\"chapter\">")?;
let mut current_level = 1;
for item in chapters {
// Spacer
if item.get("spacer").is_some() {
rc.writer.write_all(b"<li class=\"spacer\"></li>")?;
out.write("<li class=\"spacer\"></li>")?;
continue;
}
let level = if let Some(s) = item.get("section") {
s.matches('.').count()
let (section, level) = if let Some(s) = item.get("section") {
(s.as_str(), s.matches('.').count())
} else {
1
("", 1)
};
let is_expanded =
if !fold_enable || (!section.is_empty() && current_section.starts_with(section)) {
// Expand if folding is disabled, or if the section is an
// ancestor or the current section itself.
true
} else {
// Levels that are larger than this would be folded.
level - 1 < fold_level as usize
};
if level > current_level {
while level > current_level {
rc.writer.write_all(b"<li>")?;
rc.writer.write_all(b"<ol class=\"section\">")?;
out.write("<li>")?;
out.write("<ol class=\"section\">")?;
current_level += 1;
}
rc.writer.write_all(b"<li>")?;
write_li_open_tag(out, is_expanded, false)?;
} else if level < current_level {
while level < current_level {
rc.writer.write_all(b"</ol>")?;
rc.writer.write_all(b"</li>")?;
out.write("</ol>")?;
out.write("</li>")?;
current_level -= 1;
}
rc.writer.write_all(b"<li>")?;
write_li_open_tag(out, is_expanded, false)?;
} else {
rc.writer.write_all(b"<li")?;
if item.get("section").is_none() {
rc.writer.write_all(b" class=\"affix\"")?;
}
rc.writer.write_all(b">")?;
write_li_open_tag(out, is_expanded, item.get("section").is_none())?;
}
// Part title
if let Some(title) = item.get("part") {
out.write("<li class=\"part-title\">")?;
out.write(title)?;
out.write("</li>")?;
continue;
}
// Link
let path_exists = if let Some(path) = item.get("path") {
if !path.is_empty() {
rc.writer.write_all(b"<a href=\"")?;
let path_exists = if let Some(path) =
item.get("path")
.and_then(|p| if p.is_empty() { None } else { Some(p) })
{
out.write("<a href=\"")?;
let tmp = Path::new(item.get("path").expect("Error: path should be Some(_)"))
.with_extension("html")
.to_str()
.unwrap()
// Hack for windows who tends to use `\` as separator instead of `/`
.replace("\\", "/");
let tmp = Path::new(item.get("path").expect("Error: path should be Some(_)"))
.with_extension("html")
.to_str()
.unwrap()
// Hack for windows who tends to use `\` as separator instead of `/`
.replace("\\", "/");
// Add link
rc.writer.write_all(tmp.as_bytes())?;
rc.writer.write_all(b"\"")?;
// Add link
out.write(&utils::fs::path_to_root(&current_path))?;
out.write(&tmp)?;
out.write("\"")?;
if path == &current {
rc.writer.write_all(b" class=\"active\"")?;
}
rc.writer.write_all(b">")?;
true
} else {
false
if path == &current_path {
out.write(" class=\"active\"")?;
}
out.write(">")?;
true
} else {
out.write("<div>")?;
false
};
if !self.no_section_label {
// Section does not necessarily exist
if let Some(section) = item.get("section") {
rc.writer.write_all(b"<strong aria-hidden=\"true\">")?;
rc.writer.write_all(section.as_bytes())?;
rc.writer.write_all(b"</strong> ")?;
out.write("<strong aria-hidden=\"true\">")?;
out.write(&section)?;
out.write("</strong> ")?;
}
}
@@ -107,10 +151,7 @@ impl HelperDef for RenderToc {
// filter all events that are not inline code blocks
let parser = Parser::new(name).filter(|event| match *event {
Event::Start(Tag::Code)
| Event::End(Tag::Code)
| Event::InlineHtml(_)
| Event::Text(_) => true,
Event::Code(_) | Event::Html(_) | Event::Text(_) => true,
_ => false,
});
@@ -119,22 +160,47 @@ impl HelperDef for RenderToc {
html::push_html(&mut markdown_parsed_name, parser);
// write to the handlebars template
rc.writer.write_all(markdown_parsed_name.as_bytes())?;
out.write(&markdown_parsed_name)?;
}
if path_exists {
rc.writer.write_all(b"</a>")?;
out.write("</a>")?;
} else {
out.write("</div>")?;
}
rc.writer.write_all(b"</li>")?;
// Render expand/collapse toggle
if let Some(flag) = item.get("has_sub_items") {
let has_sub_items = flag.parse::<bool>().unwrap_or_default();
if fold_enable && has_sub_items {
out.write("<a class=\"toggle\"><div>❱</div></a>")?;
}
}
out.write("</li>")?;
}
while current_level > 1 {
rc.writer.write_all(b"</ol>")?;
rc.writer.write_all(b"</li>")?;
out.write("</ol>")?;
out.write("</li>")?;
current_level -= 1;
}
rc.writer.write_all(b"</ol>")?;
out.write("</ol>")?;
Ok(())
}
}
fn write_li_open_tag(
out: &mut dyn Output,
is_expanded: bool,
is_affix: bool,
) -> Result<(), std::io::Error> {
let mut li = String::from("<li class=\"chapter-item ");
if is_expanded {
li.push_str("expanded ");
}
if is_affix {
li.push_str("affix ");
}
li.push_str("\">");
out.write(&li)
}

View File

@@ -1,33 +1,38 @@
extern crate ammonia;
extern crate elasticlunr;
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use elasticlunr::Index;
use pulldown_cmark::*;
use serde_json;
use self::elasticlunr::Index;
use book::{Book, BookItem};
use config::Search;
use errors::*;
use utils;
use theme::searcher;
use crate::book::{Book, BookItem};
use crate::config::Search;
use crate::errors::*;
use crate::theme::searcher;
use crate::utils;
/// Creates all files required for search.
pub fn create_files(search_config: &Search, destination: &Path, book: &Book) -> Result<()> {
let mut index = Index::new(&["title", "body", "breadcrumbs"]);
let mut doc_urls = Vec::with_capacity(book.sections.len());
for item in book.iter() {
render_item(&mut index, &search_config, item)?;
render_item(&mut index, &search_config, &mut doc_urls, item)?;
}
let index = write_to_js(index, &search_config)?;
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());
}
if search_config.copy_js {
utils::fs::write_file(destination, "searchindex.js", index.as_bytes())?;
utils::fs::write_file(destination, "searchindex.json", index.as_bytes())?;
utils::fs::write_file(
destination,
"searchindex.js",
format!("Object.assign(window.search, {});", index).as_bytes(),
)?;
utils::fs::write_file(destination, "searcher.js", searcher::JS)?;
utils::fs::write_file(destination, "mark.min.js", searcher::MARK_JS)?;
utils::fs::write_file(destination, "elasticlunr.min.js", searcher::ELASTICLUNR_JS)?;
@@ -38,18 +43,22 @@ pub fn create_files(search_config: &Search, destination: &Path, book: &Book) ->
}
/// Uses the given arguments to construct a search document, then inserts it to the given index.
fn add_doc<'a>(
fn add_doc(
index: &mut Index,
anchor_base: &'a str,
doc_urls: &mut Vec<String>,
anchor_base: &str,
section_id: &Option<String>,
items: &[&str],
) {
let doc_ref: Cow<'a, str> = if let &Some(ref id) = section_id {
format!("{}#{}", anchor_base, id).into()
let url = if let Some(ref id) = *section_id {
Cow::Owned(format!("{}#{}", anchor_base, id))
} else {
anchor_base.into()
Cow::Borrowed(anchor_base)
};
let doc_ref = utils::collapse_whitespace(doc_ref.trim());
let url = utils::collapse_whitespace(url.trim());
let doc_ref = doc_urls.len().to_string();
doc_urls.push(url.into());
let items = items.iter().map(|&x| utils::collapse_whitespace(x.trim()));
index.add_doc(&doc_ref, items);
}
@@ -58,40 +67,43 @@ fn add_doc<'a>(
fn render_item(
index: &mut Index,
search_config: &Search,
doc_urls: &mut Vec<String>,
item: &BookItem,
) -> Result<()> {
let chapter = match item {
&BookItem::Chapter(ref ch) => ch,
let chapter = match *item {
BookItem::Chapter(ref ch) if !ch.is_draft_chapter() => ch,
_ => return Ok(()),
};
let filepath = Path::new(&chapter.path).with_extension("html");
let chapter_path = chapter
.path
.as_ref()
.expect("Checked that path exists above");
let filepath = Path::new(&chapter_path).with_extension("html");
let filepath = filepath
.to_str()
.chain_err(|| "Could not convert HTML path to str")?;
.with_context(|| "Could not convert HTML path to str")?;
let anchor_base = utils::fs::normalize_path(filepath);
let mut opts = Options::empty();
opts.insert(OPTION_ENABLE_TABLES);
opts.insert(OPTION_ENABLE_FOOTNOTES);
let p = Parser::new_ext(&chapter.content, opts);
let mut p = utils::new_cmark_parser(&chapter.content).peekable();
let mut in_header = false;
let max_section_depth = search_config.heading_split_level as i32;
let mut in_heading = false;
let max_section_depth = u32::from(search_config.heading_split_level);
let mut section_id = None;
let mut heading = String::new();
let mut body = String::new();
let mut breadcrumbs = chapter.parent_names.clone();
let mut footnote_numbers = HashMap::new();
for event in p {
while let Some(event) = p.next() {
match event {
Event::Start(Tag::Header(i)) if i <= max_section_depth => {
if heading.len() > 0 {
// Section finished, the next header is following now
Event::Start(Tag::Heading(i)) if i <= max_section_depth => {
if !heading.is_empty() {
// Section finished, the next heading is following now
// Write the data to the index, and clear it for the next section
add_doc(
index,
doc_urls,
&anchor_base,
&section_id,
&[&heading, &body, &breadcrumbs.join(" » ")],
@@ -102,10 +114,10 @@ fn render_item(
breadcrumbs.pop();
}
in_header = true;
in_heading = true;
}
Event::End(Tag::Header(i)) if i <= max_section_depth => {
in_header = false;
Event::End(Tag::Heading(i)) if i <= max_section_depth => {
in_heading = false;
section_id = Some(utils::id_from_content(&heading));
breadcrumbs.push(heading.clone());
}
@@ -113,37 +125,49 @@ fn render_item(
let number = footnote_numbers.len() + 1;
footnote_numbers.entry(name).or_insert(number);
}
Event::Start(_) | Event::End(_) | Event::SoftBreak | Event::HardBreak => {
Event::Html(html) => {
let mut html_block = html.into_string();
// As of pulldown_cmark 0.6, html events are no longer contained
// 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);
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
// to ensure words don't get merged together
if in_header {
if in_heading {
heading.push(' ');
} else {
body.push(' ');
}
}
Event::Text(text) => {
if in_header {
Event::Text(text) | Event::Code(text) => {
if in_heading {
heading.push_str(&text);
} else {
body.push_str(&text);
}
}
Event::Html(html) | Event::InlineHtml(html) => {
body.push_str(&clean_html(&html));
}
Event::FootnoteReference(name) => {
let len = footnote_numbers.len() + 1;
let number = footnote_numbers.entry(name).or_insert(len);
body.push_str(&format!(" [{}] ", number));
}
Event::TaskListMarker(_checked) => {}
}
}
if heading.len() > 0 {
if !heading.is_empty() {
// Make sure the last section is added to the index
add_doc(
index,
doc_urls,
&anchor_base,
&section_id,
&[&heading, &body, &breadcrumbs.join(" » ")],
@@ -153,76 +177,70 @@ fn render_item(
Ok(())
}
/// Exports the index and search options to a JS script which stores the index in `window.search`.
/// Using a JS script is a workaround for CORS in `file://` URIs. It also removes the need for
/// downloading/parsing JSON in JS.
fn write_to_js(index: Index, search_config: &Search) -> Result<String> {
// These structs mirror the configuration javascript object accepted by
// http://elasticlunr.com/docs/configuration.js.html
fn write_to_json(index: Index, search_config: &Search, doc_urls: Vec<String>) -> Result<String> {
use elasticlunr::config::{SearchBool, SearchOptions, SearchOptionsField};
use std::collections::BTreeMap;
#[derive(Serialize)]
struct SearchOptionsField {
boost: u8,
}
#[derive(Serialize)]
struct SearchOptionsFields {
title: SearchOptionsField,
body: SearchOptionsField,
breadcrumbs: SearchOptionsField,
}
#[derive(Serialize)]
struct SearchOptions {
bool: String,
expand: bool,
struct ResultsOptions {
limit_results: u32,
teaser_word_count: u32,
fields: SearchOptionsFields,
}
#[derive(Serialize)]
struct SearchindexJson {
/// The options used for displaying search results
results_options: ResultsOptions,
/// The searchoptions for elasticlunr.js
searchoptions: SearchOptions,
search_options: SearchOptions,
/// Used to lookup a document's URL from an integer document ref.
doc_urls: Vec<String>,
/// The index for elasticlunr.js
index: elasticlunr::Index,
}
let searchoptions = SearchOptions {
let mut fields = BTreeMap::new();
let mut opt = SearchOptionsField::default();
opt.boost = Some(search_config.boost_title);
fields.insert("title".into(), opt);
opt.boost = Some(search_config.boost_paragraph);
fields.insert("body".into(), opt);
opt.boost = Some(search_config.boost_hierarchy);
fields.insert("breadcrumbs".into(), opt);
let search_options = SearchOptions {
bool: if search_config.use_boolean_and {
"AND".into()
SearchBool::And
} else {
"OR".into()
SearchBool::Or
},
expand: search_config.expand,
fields,
};
let results_options = ResultsOptions {
limit_results: search_config.limit_results,
teaser_word_count: search_config.teaser_word_count,
fields: SearchOptionsFields {
title: SearchOptionsField {
boost: search_config.boost_title,
},
body: SearchOptionsField {
boost: search_config.boost_paragraph,
},
breadcrumbs: SearchOptionsField {
boost: search_config.boost_hierarchy,
},
},
};
let json_contents = SearchindexJson {
searchoptions: searchoptions,
index: index,
results_options,
search_options,
doc_urls,
index,
};
// By converting to serde_json::Value as an intermediary, we use a
// BTreeMap internally and can force a stable ordering of map keys.
let json_contents = serde_json::to_value(&json_contents)?;
let json_contents = serde_json::to_string(&json_contents)?;
Ok(format!("window.search = {};", json_contents))
Ok(json_contents)
}
fn clean_html(html: &str) -> String {
lazy_static! {
static ref AMMONIA: ammonia::Builder<'static> = {
static ref AMMONIA: ammonia::Builder<'static> = {
let mut clean_content = HashSet::new();
clean_content.insert("script");
clean_content.insert("style");

View File

@@ -0,0 +1,52 @@
use crate::book::BookItem;
use crate::errors::*;
use crate::renderer::{RenderContext, Renderer};
use crate::utils;
use std::fs;
#[derive(Default)]
/// A renderer to output the Markdown after the preprocessors have run. Mostly useful
/// when debugging preprocessors.
pub struct MarkdownRenderer;
impl MarkdownRenderer {
/// Create a new `MarkdownRenderer` instance.
pub fn new() -> Self {
MarkdownRenderer
}
}
impl Renderer for MarkdownRenderer {
fn name(&self) -> &str {
"markdown"
}
fn render(&self, ctx: &RenderContext) -> Result<()> {
let destination = &ctx.destination;
let book = &ctx.book;
if destination.exists() {
utils::fs::remove_dir_content(destination)
.with_context(|| "Unable to remove stale Markdown output")?;
}
trace!("markdown render");
for item in book.iter() {
if let BookItem::Chapter(ref ch) = *item {
if !ch.is_draft_chapter() {
utils::fs::write_file(
&ctx.destination,
&ch.path.as_ref().expect("Checked path exists before"),
ch.content.as_bytes(),
)?;
}
}
}
fs::create_dir_all(&destination)
.with_context(|| "Unexpected error when constructing destination path")?;
Ok(())
}
}

View File

@@ -8,25 +8,25 @@
//!
//! The definition for [RenderContext] may be useful though.
//!
//! [For Developers]: https://rust-lang-nursery.github.io/mdBook/lib/index.html
//! [For Developers]: https://rust-lang.github.io/mdBook/for_developers/index.html
//! [RenderContext]: struct.RenderContext.html
pub use self::html_handlebars::HtmlHandlebars;
pub use self::markdown_renderer::MarkdownRenderer;
mod html_handlebars;
mod markdown_renderer;
use shlex::Shlex;
use std::fs;
use std::io::{self, Read};
use std::io::{self, ErrorKind, Read};
use std::path::PathBuf;
use std::process::{Command, Stdio};
use serde_json;
use shlex::Shlex;
use errors::*;
use config::Config;
use book::Book;
const MDBOOK_VERSION: &str = env!("CARGO_PKG_VERSION");
use crate::book::Book;
use crate::config::Config;
use crate::errors::*;
use toml::Value;
/// An arbitrary `mdbook` backend.
///
@@ -66,6 +66,8 @@ pub struct RenderContext {
/// renderers to cache intermediate results, this directory is not
/// guaranteed to be empty or even exist.
pub destination: PathBuf,
#[serde(skip)]
__non_exhaustive: (),
}
impl RenderContext {
@@ -76,11 +78,12 @@ impl RenderContext {
Q: Into<PathBuf>,
{
RenderContext {
book: book,
config: config,
version: MDBOOK_VERSION.to_string(),
book,
config,
version: crate::MDBOOK_VERSION.to_string(),
root: root.into(),
destination: destination.into(),
__non_exhaustive: (),
}
}
@@ -91,7 +94,7 @@ impl RenderContext {
/// Load a `RenderContext` from its JSON representation.
pub fn from_json<R: Read>(reader: R) -> Result<RenderContext> {
serde_json::from_reader(reader).chain_err(|| "Unable to deserialize the `RenderContext`")
serde_json::from_reader(reader).with_context(|| "Unable to deserialize the `RenderContext`")
}
}
@@ -147,6 +150,40 @@ impl CmdRenderer {
}
}
impl CmdRenderer {
fn handle_render_command_error(&self, ctx: &RenderContext, error: io::Error) -> Result<()> {
if let ErrorKind::NotFound = error.kind() {
// Look for "output.{self.name}.optional".
// If it exists and is true, treat this as a warning.
// Otherwise, fail the build.
let optional_key = format!("output.{}.optional", self.name);
let is_optional = match ctx.config.get(&optional_key) {
Some(Value::Boolean(value)) => *value,
_ => false,
};
if is_optional {
warn!(
"The command `{}` for backend `{}` was not found, \
but was marked as optional.",
self.cmd, self.name
);
return Ok(());
} else {
error!(
"The command `{0}` wasn't found, is the \"{1}\" backend installed? \
If you want to ignore this error when the \"{1}\" backend is not installed, \
set `optional = true` in the `[output.{1}]` section of the book.toml configuration file.",
self.cmd, self.name
);
}
}
Err(error).with_context(|| "Unable to start the backend")?
}
}
impl Renderer for CmdRenderer {
fn name(&self) -> &str {
&self.name
@@ -157,38 +194,31 @@ impl Renderer for CmdRenderer {
let _ = fs::create_dir_all(&ctx.destination);
let mut child = match self.compose_command()?
let mut child = match self
.compose_command()?
.stdin(Stdio::piped())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.current_dir(&ctx.destination)
.spawn() {
Ok(c) => c,
Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
warn!("The command wasn't found, is the \"{}\" backend installed?", self.name);
warn!("\tCommand: {}", self.cmd);
return Ok(());
}
Err(e) => {
return Err(e).chain_err(|| "Unable to start the backend")?;
}
};
.spawn()
{
let mut stdin = child.stdin.take().expect("Child has stdin");
if let Err(e) = serde_json::to_writer(&mut stdin, &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);
}
Ok(c) => c,
Err(e) => return self.handle_render_command_error(ctx, e),
};
// explicitly close the `stdin` file handle
drop(stdin);
let mut stdin = child.stdin.take().expect("Child has stdin");
if let Err(e) = serde_json::to_writer(&mut stdin, &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);
}
// explicitly close the `stdin` file handle
drop(stdin);
let status = child
.wait()
.chain_err(|| "Error waiting for the backend to complete")?;
.with_context(|| "Error waiting for the backend to complete")?;
trace!("{} exited with output: {:?}", self.cmd, status);

File diff suppressed because one or more lines are too long

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