Compare commits

..

1263 Commits

Author SHA1 Message Date
Eric Huss 2420919ca8
Merge pull request #2259 from stevecheckoway/improve-test-output
Color test output and shorten chapter paths
2024-05-10 18:14:32 +00:00
Eric Huss c671c2e904
Merge pull request #2262 from Janik-Haag/master
Add nix to default languages
2024-04-12 15:27:31 +00:00
Janik H. c9df8dd1f3
Add nix to default languages 2024-04-10 21:56:13 +02:00
Eric Huss 8ae86d4310
Merge pull request #2355 from johamster/reduce_allocations_when_copying_files
Reduce allocations in `fs::copy_files_except_ext`
2024-04-08 21:40:54 +00:00
Johannes Gloeckle c144c26dcf Reduce allocations in `fs::copy_files_except_ext`
Above mentioned function copies files (recursively) from a source to a
destination directory. For that, file/directory paths have to be created
repeatedly. This allocates as directory and file names are concatenated
into an owning path structure.

The number of allocations can be reduced by creating file/directory
paths only once and borrowing them instead of cloning/recreating them.

In bigger projects, this reduces execution time noticeably. Please note
that file system operations are dominant from performance POV.
2024-04-07 10:43:23 +02:00
Eric Huss 481f6b1531
Merge pull request #2351 from rust-lang/dependabot/cargo/mio-0.8.11
Bump mio from 0.8.10 to 0.8.11
2024-04-05 19:36:55 +00:00
dependabot[bot] b267d56ba7
Bump mio from 0.8.10 to 0.8.11
Bumps [mio](https://github.com/tokio-rs/mio) from 0.8.10 to 0.8.11.
- [Release notes](https://github.com/tokio-rs/mio/releases)
- [Changelog](https://github.com/tokio-rs/mio/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/mio/compare/v0.8.10...v0.8.11)

---
updated-dependencies:
- dependency-name: mio
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-05 18:10:01 +00:00
Eric Huss dd139f8228
Merge pull request #2350 from rust-lang/dependabot/cargo/h2-0.3.26
Bump h2 from 0.3.24 to 0.3.26
2024-04-05 18:04:09 +00:00
dependabot[bot] be4756e4bf
Bump h2 from 0.3.24 to 0.3.26
Bumps [h2](https://github.com/hyperium/h2) from 0.3.24 to 0.3.26.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.26/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.24...v0.3.26)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-05 16:16:31 +00:00
Eric Huss bd323fb930
Merge pull request #2339 from goodmost/master
chore: remove repetitive word
2024-03-19 15:46:26 +00:00
goodmost aff1070f43 chore: remove repetitive word
Signed-off-by: goodmost <zhaohaiyang@outlook.com>
2024-03-19 22:22:16 +08:00
Eric Huss b6742e90b1
Merge pull request #2338 from max-heller/patch-2
Fix typo in docs
2024-03-18 22:59:48 +00:00
Max Heller 95b6ed7965
Fix typo in docs 2024-03-18 18:38:55 -04:00
Eric Huss 5a35144d4f
Merge pull request #2328 from ehuss/clarify-src-path
Clarify Chapter path and source_path.
2024-02-25 23:26:20 +00:00
Eric Huss 5f5f9d6fd5 Clarify Chapter path and source_path. 2024-02-25 15:20:19 -08:00
Eric Huss c602a2fcd6
Merge pull request #2070 from expikr/testbook-add-mathajx-tests
Added missing tests for MathJax to the test book
2024-02-25 23:00:13 +00:00
_ 821d3c423c Add MathJax tests. 2024-02-25 14:53:10 -08:00
Eric Huss 6b89f5dad8
Merge pull request #2327 from ehuss/smart-punctuation
Rename curly-quotes to smart-punctuation.
2024-02-25 22:30:25 +00:00
Eric Huss d28cf53009 Rename curly-quotes to smart-punctuation. 2024-02-25 13:42:44 -08:00
Eric Huss 504900d7bd
Merge pull request #2324 from ehuss/redundant-imports
Fix redundant imports.
2024-02-24 20:19:28 +00:00
Eric Huss 0cc439eee3 Fix redundant imports. 2024-02-24 12:04:57 -08:00
Eric Huss e8b8f34f2b
Merge pull request #2322 from wilwade/patch-1
Fix incorrect theme documentation: Next/Previous should use `title`
2024-02-21 21:25:18 +00:00
Wil Wade 58a23e06a1
Fix incorrect theme documentation
The theme documentation for next and previous used name instead of title
2024-02-20 15:29:30 -05:00
Eric Huss 5a4ac03c0d
Merge pull request #2312 from ehuss/bump-version
Update to 0.4.37
2024-02-07 03:48:57 +00:00
Eric Huss c5a506e240 Update to 0.4.37 2024-02-06 19:34:15 -08:00
Eric Huss bc5cd13c16
Merge pull request #2311 from sspaeti/fix-search-with-form
fix input `s` into a form without triggering search
2024-02-07 03:21:41 +00:00
sspaeti d406c7c09b fix input `s` into a form without triggering search 2024-02-06 10:15:56 +01:00
Eric Huss 9cf3117636
Merge pull request #2309 from ehuss/update-deps
Update dependencies
2024-02-05 22:41:50 +00:00
Eric Huss 61786ddcdf Update dependencies 2024-02-05 14:37:06 -08:00
Eric Huss f33281fae2
Merge pull request #2310 from ehuss/update-env-logger
Update env_logger to 0.11
2024-02-05 22:26:31 +00:00
Eric Huss 93bd457a54 Update env_logger to 0.11 2024-02-05 14:22:21 -08:00
Eric Huss 600824bed2
Merge pull request #2308 from ehuss/pulldown_cmark-0.10
Update pulldown_cmark to 0.10
2024-02-05 22:21:55 +00:00
Eric Huss 42e635bb9e Update pulldown_cmark to 0.10 2024-02-05 14:11:27 -08:00
Eric Huss d48810f045
Merge pull request #2307 from ehuss/backends_receive_render_context_via_stdin
Clean up test backends_receive_render_context_via_stdin
2024-02-05 20:17:35 +00:00
Eric Huss 3387cf373d Clean up test backends_receive_render_context_via_stdin 2024-02-05 09:53:50 -08:00
Eric Huss 7825bd6c5a
Merge pull request #2306 from jvstme/master
docs: Fix broken link
2024-02-04 14:09:09 +00:00
Jvst Me ba14f4ad53
docs: Fix broken link 2024-02-04 16:47:52 +03:00
Eric Huss 02bbc3f777
Merge pull request #2305 from gibbz00/patch-1
Fix minor sentencing issue in build.md
2024-02-04 12:37:31 +00:00
gibbz00 45a2d0b40e Fix minor sentencing issue in build.md 2024-02-04 08:57:50 +01:00
Eric Huss 53eccf7047
Merge pull request #2303 from infogulch/patch-1
summary.md: clarify that part titles must be h1 headers
2024-02-01 23:45:51 +00:00
Joe Taber 63000bc122
summary.md: clarify that part titles must be h1 headers 2024-01-31 01:47:05 -06:00
Eric Huss 220cb4f0c8
Merge pull request #2302 from GeckoEidechse/fix/missing-plus
docs: Add missing `+` in diff code block
2024-01-30 19:55:05 +00:00
GeckoEidechse 7ce3a41184
docs: Add missing `+` in diff code block
The closing bracket for the `if` statement is also nearly added but the leading `+` to indicate that was forgotten.
2024-01-30 17:43:37 +01:00
Eric Huss 51efaf2e81
Merge pull request #2297 from rust-lang/dependabot/cargo/shlex-1.3.0
Bump shlex from 1.2.0 to 1.3.0
2024-01-22 22:41:27 +00:00
dependabot[bot] f0d6d428dc
Bump shlex from 1.2.0 to 1.3.0
Bumps [shlex](https://github.com/comex/rust-shlex) from 1.2.0 to 1.3.0.
- [Changelog](https://github.com/comex/rust-shlex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/comex/rust-shlex/commits)

---
updated-dependencies:
- dependency-name: shlex
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-22 21:52:27 +00:00
Eric Huss 01778fc90a
Merge pull request #2293 from rust-lang/dependabot/cargo/h2-0.3.24
Bump h2 from 0.3.22 to 0.3.24
2024-01-19 17:43:11 +00:00
dependabot[bot] d9928ad3f9
Bump h2 from 0.3.22 to 0.3.24
Bumps [h2](https://github.com/hyperium/h2) from 0.3.22 to 0.3.24.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.24/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.22...v0.3.24)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-19 16:23:16 +00:00
Eric Huss 77b7876986
Merge pull request #2291 from klensy/watch-me
pathdiff only used with watch feature, so make it optional
2024-01-15 13:37:12 +00:00
klensy 745f7c7313 pathdiff only used with watch feature, so make it optional 2024-01-15 12:49:29 +03:00
Eric Huss 0a96d0e3fa
Merge pull request #2290 from klensy/less-clones
removes few more allocs
2024-01-14 14:09:37 +00:00
klensy e3ad9d097e reduce useless regex allocs
from 474mb to 215mb

==40876== Total:     474,156,323 bytes in 1,521,025 blocks
==40876== At t-gmax: 13,872,954 bytes in 4,655 blocks
==40876== At t-end:  488,516 bytes in 884 blocks
==40876== Reads:     820,933,434 bytes
==40876== Writes:    514,838,350 bytes

to

==57763== Total:     215,292,393 bytes in 1,161,048 blocks
==57763== At t-gmax: 13,872,954 bytes in 4,655 blocks
==57763== At t-end:  1,210,783 bytes in 1,274 blocks
==57763== Reads:     598,542,892 bytes
==57763== Writes:    229,841,910 bytes
2024-01-14 15:17:31 +03:00
klensy 573b6522f9 remove useless alloc
on rust reference book this reduces total allocs from 490mb to 474mb:

==23272== Total:     490,538,699 bytes in 1,760,117 blocks
==23272== At t-gmax: 13,872,954 bytes in 4,655 blocks
==23272== At t-end:  488,516 bytes in 884 blocks
==23272== Reads:     830,509,060 bytes
==23272== Writes:    522,290,614 bytes

to

==40876== Total:     474,156,323 bytes in 1,521,025 blocks
==40876== At t-gmax: 13,872,954 bytes in 4,655 blocks
==40876== At t-end:  488,516 bytes in 884 blocks
==40876== Reads:     820,933,434 bytes
==40876== Writes:    514,838,350 bytes
2024-01-14 15:17:07 +03:00
Eric Huss 59d3717159
Merge pull request #2283 from sunng87/feature/hbd-5
feat: upgrade handlebars to 5.0
2024-01-04 21:00:18 +00:00
Ning Sun a42eafc316
feat: upgrade handlebars to 5.0 2024-01-04 20:16:34 +08:00
Eric Huss 11f839b9e5
Merge pull request #2282 from max-heller/patch-1
Fix typo in guide
2024-01-04 01:11:14 +00:00
Max Heller 721274239a
fix typo in guide 2024-01-03 19:46:10 -05:00
Eric Huss 090eba0db5
Merge pull request #2273 from klensy/useless-clone
remove useless string clone
2023-12-16 14:41:39 +00:00
klensy 88be4ac417 remove useless string clone 2023-12-16 13:29:24 +03:00
Dylan DPC c1d622e56e
Merge pull request #2263 from jhult/theme-dir-warning-check
Remove warning check for theme directory as ./src/theme
2023-12-10 08:33:10 +00:00
Jonathan Hult 91af1c3b54
Remove warning check for theme directory as ./src/theme
This was causing an unnecessary warning if &src_dir was the same as ctx.root
2023-12-09 16:53:57 -06:00
Stephen Checkoway 32687e64fe Color test output and shorten chapter paths
Currently, the output from `rustdoc --test` is not colored because
`rustdoc`'s stdout is not a tty. The output of a failed `rustdoc` run is
sent to `mdbook`'s stderr via the `error!()` macro. This commit checks
if stderr is a tty using the standard `.is_terminal()` and if so, passes
`--color always` to `rustdoc`.

The test output from `rustdoc` includes the full path that `rustdoc` was
called with. This obscures the path of the file with the error. E.g.,
```
---- /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
 --> /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md:4:6
  |
3 | this code has a bug
  |      ^^^^ expected one of 8 possible tokens

error: aborting due to previous error
```

This commit runs `rustdoc` in the temp directory and replaces any
relative library paths with absolute library paths. This leads to
simpler error messages. The one above becomes
```
---- lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
 --> lab0/index.md:4:6
  |
3 | this code has a bug
  |      ^^^^ expected one of 8 possible tokens

error: aborting due to previous error
```
(with colors, of course).
2023-12-06 12:09:07 -05:00
Eric Huss b7f46213c7
Merge pull request #2253 from ehuss/bump-version
Update to 0.4.36
2023-11-29 22:54:44 +00:00
Eric Huss aa8982bdb4 Update to 0.4.36 2023-11-29 14:50:26 -08:00
Eric Huss 14826db606
Merge pull request #2252 from ehuss/update-lockfile
Update dependencies
2023-11-29 22:44:41 +00:00
Eric Huss 847a582022 Update msrv to 1.70 2023-11-29 14:36:39 -08:00
Eric Huss 97cd00faeb Update dependencies 2023-11-29 14:29:31 -08:00
Eric Huss 8d4193fb46
Merge pull request #2229 from leonzchang/fix/normalize-path
Fix `mdbook serve` unexpected panic
2023-11-29 22:16:39 +00:00
leonzchang 8d4ae388fa
update comment 2023-11-29 11:51:23 +08:00
leonzchang 7082689866
add comment 2023-11-29 11:50:07 +08:00
leonzchang 40c034ed3f
apply suggest changes 2023-11-29 11:34:08 +08:00
Dylan DPC 208d5ea7ab
Merge pull request #2250 from DuckDuckWhale/master
Dep: fix Tungstenite DoS (RUSTSEC-2023-0065)
2023-11-26 09:42:25 +00:00
DuckDuckWhale ed51438c8b
Dep: fix Tungstenite DoS (RUSTSEC-2023-0065) 2023-11-26 01:05:20 -08:00
Eric Huss 49fce6673a
Merge pull request #2209 from cyevgeniy/feat/sidebar-resize-indicator
Add resize indicator to the sidebar
2023-11-24 20:31:14 +00:00
Eric Huss a016ac0d2b
Merge pull request #2173 from 0xpr03/master
upgrade to notify 6.1.1 and watcher-mini 4.1
2023-11-24 20:05:37 +00:00
Eric Huss ad55f5367e
Merge pull request #2162 from ISSOtm/padding-hljs
Fix code blocks "indent" without highlight.js
2023-11-24 19:59:55 +00:00
Eric Huss 660cbfa6ce
Merge pull request #2243 from wyfo/patch-1
fix typo
2023-11-19 13:53:15 +00:00
Joseph Perez 982608246e
fix typo 2023-11-19 14:46:50 +01:00
Dylan DPC 6f6de2cf05
Merge pull request #2235 from ardcore/improvement/fix-print-margin
Make sure page wrapper transform is removed in print mode
2023-11-16 13:44:27 +00:00
Szymon Pilkowski ae3e3f8269 Make sure page wrapper transform is removed in print mode 2023-11-10 21:51:00 +01:00
Eric Huss dc21f1497b
Merge pull request #2232 from arnetheduck/add-nim
Add Nim to default languages
2023-11-09 03:32:44 +00:00
Jacek Sieka 5c8941ba16 Add Nim to default languages
Nim is a systems programming language (included in the highlight.js
`system` group), and we're quite happily using `mdBook` in several of
our documentation projects starting with our [style
guide](https://status-im.github.io/nim-style-guide/).

While we can maintain our own highlight.js, including `Nim` in the
default distribution would allow us to promote more mdBook usage in the
Nim community at the cost of a ~2kb increase in the `highlight.js` size.
2023-11-08 15:34:35 +01:00
leonzchang b0a001c6a4
create relative path through ignore root and path 2023-11-08 13:35:35 +08:00
leonzchang 722c55f85f
normalize path to relative 2023-11-01 12:33:13 +08:00
leonzchang 3ab19f3295
Revert "bump version v0.4.36"
This reverts commit 621ffc46c0.
2023-10-31 12:22:08 +08:00
leonzchang 621ffc46c0
bump version v0.4.36 2023-10-31 12:14:28 +08:00
leonzchang fbb629c02e
normalize path in watch cmd 2023-10-31 12:13:25 +08:00
Evgeny Chaban 80d3a86468 fix: hide resize indicator on devices with limited accuracy 2023-10-04 01:55:20 +03:00
Evgeny Chaban 8e8fd2717e fix(style): use calc function 2023-10-04 01:40:16 +03:00
Evgeny Chaban f92d24e89c feat(sidebar): add sidebar indicator 2023-10-02 18:24:55 +03:00
Eric Huss 94e0a44e15
Merge pull request #2206 from ehuss/bump-version
Update to 0.4.35
2023-09-29 22:43:05 +00:00
Eric Huss f25181f68d Update to 0.4.35 2023-09-29 14:33:45 -07:00
Eric Huss cf19eb1386 Fix `text-direction` in documentation. 2023-09-29 14:33:20 -07:00
Eric Huss 0583119698
Merge pull request #2197 from dluschan/patch-1
Update index.hbs
2023-09-22 17:26:27 +00:00
Dmitry Luschan 3389f3db7f
Update index.hbs
Trailing slash on void elements has no effect and interacts badly with unquoted attribute values.
2023-09-19 20:44:39 +06:00
Eric Huss c642f5f8a3
Merge pull request #2187 from notriddle/notriddle/warning-block
Add `.warning` class for doc author use
2023-09-09 20:13:10 +00:00
Michael Howell ceb8b509e2 Add more guides to stock CSS classes 2023-09-08 13:27:02 -07:00
Michael Howell 65dae11e47 Add `.warning` class for doc author use
This is designed to be compatible with rustdoc's version, in
https://github.com/rust-lang/rust/pull/106561
2023-09-08 13:17:21 -07:00
Dylan DPC d5b1676216
Merge pull request #2168 from pickfire/prefetch
Add prefetch to next link
2023-09-04 18:03:38 +00:00
Eric Huss 09f222baf7
Merge pull request #1641 from cN3rd/rtl
Continued work on "Support right-to-left languages"
2023-09-02 23:57:48 +00:00
Eric Huss 802e7bffc3 Make the arrow keys honor RTL.
At least to my understanding, the pages will flip in the opposite direction.
2023-09-02 16:44:47 -07:00
Eric Huss fb272d1afa Fix theme selector for RTL. 2023-09-02 16:43:21 -07:00
Eric Huss b871676def Fix sidebar behavior with RTL. 2023-09-02 16:43:13 -07:00
Eric Huss 869fe2f50d Remove text_drection_from_lang_code
The test above should cover this sufficiently.
2023-09-02 16:42:53 -07:00
Eric Huss db877b1c9b Update language list to include missing rtl languages. 2023-09-02 16:42:14 -07:00
Eric Huss 4749f9d97a Some minor corrections from code review. 2023-09-02 16:41:59 -07:00
cN3rd 8564a7fb51 Add proper test to inline code within the book. 2023-09-02 14:38:02 -07:00
cN3rd 6be98e0bbd Ensure code segments always render in LTR 2023-09-02 14:37:59 -07:00
cN3rd 5e0c68c45e Fix icons when using RTL 2023-09-02 07:50:23 -07:00
cN3rd 7717b9dcf2 Support `text_direction` attribute in HTML output 2023-09-02 07:50:21 -07:00
cN3rd 819a108f07 Add `text_direction` property in general book metadata
Text direction can selected in the config via the `text_direction` attribute in `book.toml`,
or be derived from the book's language.
2023-09-02 07:49:28 -07:00
Tim Crawford 3a99899114 Use CSS selectors to override properties for RTL
Fix behavior of some elements when displaying page in RTL.

Signed-off-by: Tim Crawford <crawfxrd@gmail.com>
2023-09-02 07:49:28 -07:00
Tim Crawford 1088066c69 Move sidebar, js classes from html to body element
This will be necessary for using CSS selectors on root attributes.

Signed-off-by: Tim Crawford <crawfxrd@gmail.com>
2023-09-02 07:49:27 -07:00
Tim Crawford 73d44503fd Use logical CSS properties
Replace phyiscal properties (top/bottom/left/right) with logical
properties (start/end) that can be used in non-LTR contexts (e.g.,
content in Arabic or Hebrew).

Based on the CSS Logical Properties and Values Level 1 specification,
currently an Editor's Draft [1].

Referencing MDN, all major browsers except Internet Explorer support the
margin, padding, and border properties.

[1]: https://drafts.csswg.org/css-logical/

Signed-off-by: Tim Crawford <crawfxrd@gmail.com>
2023-09-02 07:47:12 -07:00
Eric Huss 25aaff0bd6
Merge pull request #2182 from cuishuang/master
remove the repetitive word
2023-09-02 13:41:15 +00:00
cui fliter 29691461c5 remove the repetitive word
Signed-off-by: cui fliter <imcusg@gmail.com>
2023-09-02 14:04:32 +08:00
Dylan DPC a74e4dcec8
Merge pull request #2181 from tshepang/patch-1
docs: future expansion to non-Rust testing already implied
2023-09-01 08:55:38 +00:00
Tshepang Mbambo 0b0b548d7a
docs: future expansion to non-Rust testing already implied 2023-09-01 05:11:18 +02:00
Dylan DPC 02f3823e4c
Merge pull request #2175 from qaqland/sidebar-pure-css
Sidebar but Pure CSS, fix rust-lang/mdBook#859
2023-08-24 13:11:14 +00:00
qaqland 36327efe9d Sidebar but Pure CSS, fix rust-lang/mdBook#859
* change sidebar-toggle button by input:checked and label
* change nav-wrappers' CSS selectors
* remove loading animation when page opens with z-index
2023-08-24 16:44:25 +08:00
Aron Heinecke 079f52a191
upgrade to notify 6.1.1 and watcher-mini 4.1 2023-08-21 20:28:56 +02:00
Ivan Tham c9f1d01346 Add prefetch to next link
Fix #1975
2023-08-17 00:50:47 +08:00
Eldred Habert 9bc68bdd93
Fix code blocks "indent" without highlight.js
The `.hljs` class added by highlight.js adds a `display: block` rule
which makes `padding` apply correctly (left padding on all lines,
not just the first one).
Make sure that rule is applied even if highlight.js isn't run.
2023-08-08 12:31:55 +02:00
Eric Huss 56c225bd34
Merge pull request #2158 from ehuss/bump-version
Update to 0.4.34
2023-08-05 21:54:12 +00:00
Eric Huss 55c017cad1 Update to 0.4.34 2023-08-05 14:34:42 -07:00
Eric Huss 7849d55b99
Merge pull request #2157 from ehuss/macos-notify-copy
Add workaround for macOS notify problem.
2023-08-05 20:35:33 +00:00
Eric Huss c903cc8827 Add workaround for macOS notify problem. 2023-08-05 13:23:17 -07:00
Eric Huss 4a797b9565 Revert "Merge pull request #2152 from ehuss/macos-notify-kqueue"
This reverts commit 347e7886e1, reversing
changes made to a8fd6038f1.
2023-08-05 12:53:23 -07:00
Eric Huss 57b487eaa3
Merge pull request #2154 from ehuss/bump-version
Update to 0.4.33
2023-08-04 00:11:45 +00:00
Eric Huss 891b7c06f2 Update to 0.4.33 2023-08-03 17:03:40 -07:00
Eric Huss f7e212ec9c
Merge pull request #2150 from proski/header-code-background
Don't use distinct background for code in headers when printing
2023-08-03 22:46:50 +00:00
Pavel Roskin 228538ea62 Don't use distinct background for code in headers when printing
Fixes #1933
2023-08-02 23:31:46 -07:00
Eric Huss 347e7886e1
Merge pull request #2152 from ehuss/macos-notify-kqueue
Switch macOS notify to kqueue.
2023-08-02 22:13:09 +00:00
Eric Huss bfa5fb8844 Switch macOS notify to kqueue. 2023-08-02 09:21:25 -07:00
Eric Huss a8fd6038f1
Merge pull request #2151 from ehuss/revert-toml
Revert toml update
2023-08-02 15:49:44 +00:00
Eric Huss fbfe887084 Add note to not update toml. 2023-08-02 08:39:23 -07:00
Eric Huss aed991f75f Revert "Merge pull request #2125 from ehuss/update-toml"
This reverts commit 89797064b8, reversing
changes made to 7824aed878.
2023-08-02 08:32:44 -07:00
Eric Huss ab2cb71c00
Merge pull request #2134 from GiorgioReale/master
Enhancing themes with `color-scheme: light | dark;` implementation in CSS
2023-07-30 22:50:08 +00:00
Giorgio Reale fcfde083e7 Enhancing themes with `color-scheme: light | dark;` implementation in CSS 2023-07-30 15:43:51 -07:00
Eric Huss 4614a3637a
Merge pull request #2146 from riverbl/fix-extra-watch-dirs
Fix issues with extra-watch-dirs
2023-07-30 17:08:46 +00:00
Eric Huss d450544d6b
Merge pull request #2148 from ehuss/fix-merge-queue
Use a better merge-queue success check.
2023-07-29 16:23:04 +00:00
Eric Huss 9340e6a78d Use a better merge-queue success check. 2023-07-29 09:13:55 -07:00
riverbl e00b8835cc Fix issues with extra-watch-dirs
Fix paths specified in extra-watch-dirs being relative to the current working directory rather than the book root

If there is an error canonicalising paths in extra-watch-dirs, log the error and exit rather than panicking
2023-07-28 20:07:20 +01:00
Eric Huss 429ca06289
Merge pull request #2140 from ehuss/merge-queue-workflow
Prepare CI workflows to support merge queues.
2023-07-24 20:35:16 -07:00
Eric Huss 0fbfc90bea Prepare CI workflows to support merge queues. 2023-07-24 20:16:07 -07:00
Eric Huss 581e5025a2
Merge pull request #2139 from tshepang/patch-1
misplaced bracket
2023-07-21 07:32:56 -07:00
Tshepang Mbambo e57fce290b
misplaced bracket 2023-07-21 15:29:56 +02:00
Eric Huss d5a3682de9
Merge pull request #2137 from ehuss/mdbook-case-link
Fix link on case-sensitive filesystems.
2023-07-19 08:06:35 -07:00
Eric Huss 75f5862218 Fix link on case-sensitive filesystems. 2023-07-19 07:54:39 -07:00
Eric Huss aed518f945
Merge pull request #2129 from ehuss/bump-version
Update to 0.4.32
2023-07-16 17:43:49 -07:00
Eric Huss e942d41c1d
Merge pull request #2128 from ehuss/release-token-perms
deploy: Rewrite and update permissions
2023-07-16 17:38:21 -07:00
Eric Huss 38fcfd8732 Update to 0.4.32 2023-07-16 17:35:51 -07:00
Eric Huss 82ec68128d
Merge pull request #2127 from ehuss/auto-publish
Automatically publish to crates.io on new release
2023-07-16 17:28:50 -07:00
Eric Huss 9497354cfd Rewrite asset deploy.
This switches to `gh` which is the more modern CLI, and also
available by default which removes the old installer script.

This also tightens the scope where GITHUB_TOKEN is exposed to just
the step where `gh` is executed.

Finally, it tightens the permissions on the GITHUB_TOKEN (though
`contents: write` is extremely permissive, since that allows writing to
almost anything in the repo).
2023-07-16 17:16:15 -07:00
Eric Huss baa936439d deploy: Set the default shell so it doesn't need to be repeated. 2023-07-16 17:12:55 -07:00
Eric Huss 394061d28d Rename make-release.sh to make-release-asset.sh
This is to better reflect what the script does.
2023-07-16 17:12:29 -07:00
Eric Huss 0f25db67dc Automatically publish to crates.io on new release 2023-07-16 16:29:45 -07:00
Eric Huss 49ba91961f
Merge pull request #2126 from ehuss/update-deps
Update dependencies
2023-07-16 13:32:55 -07:00
Eric Huss 28ce772ae9 Update msrv to 1.66. 2023-07-16 13:21:45 -07:00
Eric Huss 424c2d9f6b Update dependencies 2023-07-16 13:09:52 -07:00
Eric Huss 89797064b8
Merge pull request #2125 from ehuss/update-toml
Update toml to 0.7.6
2023-07-16 13:08:40 -07:00
Eric Huss 7824aed878
Merge pull request #2124 from ehuss/update-predicates
Update predicates to 3.0.3
2023-07-16 12:51:51 -07:00
Eric Huss 8236c43c90
Merge pull request #2123 from ehuss/update-notify
Update notify to 6.0.1
2023-07-16 12:50:17 -07:00
Eric Huss 6df89fbe94
Merge pull request #2122 from ehuss/update-opener
Update opener to 0.6.1
2023-07-16 12:50:10 -07:00
Eric Huss b423bf7ddd Update toml to 0.7.6 2023-07-16 12:48:01 -07:00
Eric Huss cdbdb8248c
Merge pull request #2121 from ehuss/update-clap
Update to clap 4.3.12
2023-07-16 12:43:44 -07:00
Eric Huss db45052d7e Update predicates to 3.0.3 2023-07-16 12:42:44 -07:00
Eric Huss 804bbf6564 Update notify to 6.0.1 2023-07-16 12:40:45 -07:00
Eric Huss bd3b9bacf6 Update opener to 0.6.1 2023-07-16 12:37:23 -07:00
Eric Huss 5505d57066 Update to clap 4.3.12 2023-07-16 12:33:53 -07:00
Eric Huss cf88c4e720
Merge pull request #2116 from Stargateur/patch-1
Add oh-my-zsh quick exemple to shell completions
2023-07-16 10:59:37 -07:00
Eric Huss 9911e86039
Merge pull request #2118 from zica87/zica87-patch-1
Fix theme-color meta tag not syncing with the theme
2023-07-16 10:55:53 -07:00
zica 9eba0f6ab2
Fix theme-color meta tag not syncing with the theme 2023-07-09 08:57:46 +08:00
Antoine 6d265c1cce
Add oh-my-zsh quick exemple to shell completions
I have trouble to find this information, doesn't cost must to add it here.
2023-07-08 13:48:51 +02:00
Eric Huss 904aa530b5
Merge pull request #2111 from ehuss/bump-version
Update to 0.4.31
2023-06-29 13:10:11 -07:00
Eric Huss fa316f3edc Update to 0.4.31 2023-06-29 12:33:55 -07:00
Eric Huss 41d19e7338
Merge pull request #2110 from ehuss/strikethrough-single
Document that strikethrough can also use a single tilde.
2023-06-29 12:31:38 -07:00
Eric Huss 4f15a3f85c Document that strikethrough can also use a single tilde. 2023-06-29 12:27:06 -07:00
Eric Huss 222166ca5a
Merge pull request #2109 from ehuss/update-proc-macro2
Update proc-macro2
2023-06-29 12:22:50 -07:00
Eric Huss ab3eb81e52 Update proc-macro2 2023-06-29 10:57:45 -07:00
Eric Huss f37486a74f
Merge pull request #2106 from ehuss/spelling
Fix some spellings
2023-06-25 11:54:23 -07:00
Eric Huss a38b854338 Fix some spellings 2023-06-25 11:37:53 -07:00
Eric Huss e18113a746
Merge pull request #2104 from zqianem/gh-443/sidebar-scroll
Fix flicker when setting sidebar scroll position
2023-06-25 11:29:27 -07:00
Dylan DPC d4edbd1acf
Merge pull request #2105 from Spartan2909/patch-1
Fix typo
2023-06-24 20:38:35 +05:30
Caleb Robson 056e45a003
Fix typo 2023-06-24 15:35:40 +01:00
Em Zhan 72b3227824 Fix flicker when setting sidebar scroll position
Previously, sidebar scroll was set in an external script which caused a
flicker as the sidebar is initially rendered without any scroll before
being scrolled to the desired location.

Switching to an inline script right after the HTML tags for the sidebar
seems to avoid the flicker in most cases. In addition, logic is added to
avoid scrolling jumps when navigating via links within the sidebar.
2023-06-21 19:25:21 -05:00
Eric Huss a51f8a6b8e
Merge pull request #2101 from zqianem/gh-443/menu-border
Avoid menu border flash during page navigation
2023-06-19 13:21:08 -07:00
Em Zhan 1ef8d70ac4 Avoid menu border flash during page navigation
Partially addresses #443
2023-06-17 21:42:54 -05:00
Eric Huss a204946d39
Merge pull request #2096 from tshepang/patch-1
main branch is not always "master" these days
2023-06-07 10:01:06 -07:00
Tshepang Mbambo 3c7795cf44
main branch is not always "master" these days 2023-06-07 18:47:15 +02:00
Eric Huss 9349204636
Merge pull request #2094 from ehuss/bump-version
Update to 0.4.30
2023-05-28 15:06:19 -07:00
Eric Huss d2bcd04133 Update to 0.4.30 2023-05-28 14:54:05 -07:00
Eric Huss 61708ad0bd
Merge pull request #2093 from ehuss/hiddenlines
Support hidden lines in languages other than Rust
2023-05-28 14:21:54 -07:00
Eric Huss c9cfe22fd6 Apply some code style changes. 2023-05-28 14:04:58 -07:00
Eric Huss 5572d3d4de Expand on hidelines documentation. 2023-05-28 14:04:58 -07:00
Eric Huss 1441fe0b91 Explicitly document the `hidelines` key. 2023-05-28 14:04:58 -07:00
Jannik Obermann 7df1d8c838 Support hidden lines in languages other than Rust
Co-Authored-By: thecodewarrior <5467669+thecodewarrior@users.noreply.github.com>
2023-05-28 14:04:54 -07:00
Eric Huss 3a51abfcad
Merge pull request #2013 from ImUrX/heading-extension
Add heading extension support
2023-05-28 12:16:26 -07:00
Eric Huss 870e9086dc
Merge pull request #2092 from ehuss/update-pulldown-cmark
Update pulldown-cmark to 0.9.3
2023-05-28 12:12:26 -07:00
Eric Huss 1db52ff531 Fix search for custom heading attributes 2023-05-28 12:03:03 -07:00
Eric Huss e3be293420 Add an integration test for heading attributes 2023-05-28 12:02:59 -07:00
Eric Huss bbc32dff82 Update pulldown-cmark to 0.9.3 2023-05-28 12:00:00 -07:00
Eric Huss 861197e61c Add a test to the test_book for custom heading attributes 2023-05-28 11:33:24 -07:00
Eric Huss 34e5ef22a0 Don't include empty class attribute. 2023-05-28 11:33:00 -07:00
Eric Huss b141297651 Update documentation for heading attributes 2023-05-28 11:31:35 -07:00
Uriel 0cb977e603 docs suggestion
Co-authored-by: Eric Huss <eric@huss.org>
2023-05-28 10:47:14 -07:00
ImUrX c8a5adcee9 fix more mistakes 2023-05-28 10:47:14 -07:00
ImUrX ecdb411711 fix mistakes 2023-05-28 10:47:14 -07:00
ImUrX a4e206168d Add working heading extension 2023-05-28 10:47:13 -07:00
Dylan DPC 4f1b5eae54
Merge pull request #2091 from zjj/master
Update test_book highlight.md
2023-05-25 10:37:34 +05:30
zjj 54f14e89cf
Update test_book highlight.md
Add missing bash annotation
2023-05-25 12:04:43 +08:00
Eric Huss 1b3922d466
Fix typo 2023-05-13 12:52:42 -07:00
Eric Huss 00a30a9984
Merge pull request #2087 from ehuss/bump-version
Update to 0.4.29
2023-05-13 12:35:36 -07:00
Eric Huss db6699dae2 Update to 0.4.29 2023-05-13 12:26:29 -07:00
Eric Huss 4d229d7b94
Merge pull request #2086 from ehuss/sync-cargo.toml
Set minimum versions in Cargo.toml
2023-05-13 12:20:46 -07:00
Eric Huss d94c5f8380 Set minimum versions in Cargo.toml 2023-05-13 12:01:03 -07:00
Eric Huss 099217390e
Merge pull request #2085 from ehuss/update-clap
Update clap
2023-05-13 11:05:00 -07:00
Eric Huss 4c4ab8a57d Update clap 2023-05-13 10:53:22 -07:00
Eric Huss d746b23749
Merge pull request #2084 from ehuss/update-indirect
Update some indirect dependencies
2023-05-13 10:49:39 -07:00
Eric Huss f77c597e01 Update some indirect dependencies 2023-05-13 10:16:26 -07:00
Eric Huss 3c54a4d33b
Merge pull request #2083 from ehuss/fix-clippy
Apply some clippy fixes
2023-05-13 10:15:38 -07:00
Eric Huss cf9de82c2a
Merge pull request #2082 from ehuss/update-direct-dependencies
Update some direct dependencies
2023-05-13 09:56:47 -07:00
Eric Huss c3155e2642 Apply clippy::match_like_matches_macro 2023-05-13 09:55:51 -07:00
Eric Huss d8f171a996 Apply clippy::manual_while_let_some 2023-05-13 09:50:32 -07:00
Eric Huss 0ef3bb1cc6 Apply clippy::needless_borrowed_reference 2023-05-13 09:46:30 -07:00
Eric Huss 54df8234ed Apply clippy::let_unit_value 2023-05-13 09:45:46 -07:00
Eric Huss dc08e37320 Apply clippy::borrow_deref_ref 2023-05-13 09:44:50 -07:00
Eric Huss 45a8575b95 Apply clippy::needless_borrow 2023-05-13 09:44:11 -07:00
Eric Huss be966cfe1f Raise MSRV to 1.65 2023-05-13 09:41:10 -07:00
Eric Huss f4507aeb9b
Merge pull request #2080 from t2y/fix-copy-fonts-message
Fix handling of copy-fonts=true when fonts.css is overridden
2023-05-13 09:19:06 -07:00
Eric Huss 0985691fbd Update some direct dependencies 2023-05-13 09:12:23 -07:00
Eric Huss 01047846a9 Don't copy the stock fonts if the user has overridden fonts.css.
This wasn't behaving as I was really intending.
2023-05-13 09:05:25 -07:00
Eric Huss 75a6d65e5a Don't warn on copy-fonts=true (the default) when fonts.css is overridden. 2023-05-13 08:51:04 -07:00
Eric Huss 71ea92bbec
Merge pull request #2081 from cn-liutailin/patch-2
Update renderers.md
2023-05-12 16:01:21 -07:00
liutailin aac6de01de
Update renderers.md
Missing symbol ":"
2023-05-13 00:04:12 +08:00
Eric Huss af036d9f45
Merge pull request #2057 from seanpoulter/init-skip
fix(cli): init --force skips confirmation prompts
2023-04-30 14:18:30 -07:00
Tetsuya Morimoto 04016f3be6 Refactor the warning message related to copy_fonts so that a user simply configures it 2023-04-28 12:46:49 +09:00
Dylan DPC 41567b0456
Merge pull request #2076 from ehuss/gitignore
Switch from gitignore to ignore
2023-04-23 11:12:26 +05:30
Eric Huss 9db3a601ca
Merge pull request #2071 from expikr/patch-3
Added missing documentation for `mdbook init --ignore=<git|none>`
2023-04-22 12:55:46 -07:00
Eric Huss 35fdd00203 Switch from gitignore to ignore 2023-04-22 12:53:54 -07:00
expikr 7a435be018
Update init.md 2023-04-18 01:53:38 +08:00
Eric Huss dec0e24275
Merge pull request #2063 from rust-lang/dependabot/cargo/h2-0.3.17
Bump h2 from 0.3.15 to 0.3.17
2023-04-13 10:40:49 -07:00
dependabot[bot] c624fc078b
Bump h2 from 0.3.15 to 0.3.17
Bumps [h2](https://github.com/hyperium/h2) from 0.3.15 to 0.3.17.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.15...v0.3.17)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-13 17:04:53 +00:00
Sean Poulter b9c6b326b7 style(tests): Fixed issues reported by clippy 2023-04-03 08:36:57 -04:00
Sean Poulter 0003072623 docs(cli): Add docs for --force 2023-04-03 08:36:47 -04:00
Sean Poulter bffdb0b03d fix(cli): init --force skips confirmation prompts 2023-04-03 03:05:24 -04:00
Eric Huss b5ffc734a2
Merge pull request #2056 from deining/http_to_https
Convert links from http to https protocol
2023-04-02 14:52:32 -07:00
Andreas Deininger a2c88ae0f1 Convert links from http to https protocol 2023-04-02 21:35:08 +02:00
Eric Huss efb671aaf2
Merge pull request #2042 from ehuss/bump-version
Update to 0.4.28
2023-03-04 16:27:55 -08:00
Eric Huss a4b4b8f649 Update to 0.4.28 2023-03-04 15:40:56 -08:00
Eric Huss 4c59405e5c
Merge pull request #1986 from mgeisler/preprocessors-for-test
Run preprocessors in `mdbook test`
2023-03-04 14:55:10 -08:00
Eric Huss 703a215ef8
Merge pull request #2039 from Skwodo/master
Change overflow-x hidden to clip
2023-03-04 14:05:01 -08:00
Skwodo f5f96bc4f4 change overflow-x hidden to clip 2023-03-02 21:01:38 +01:00
Eric Huss 1668ab7877
Merge pull request #2025 from tshepang/patch-1
Update continuous-integration.md
2023-02-14 06:34:20 -08:00
Tshepang Mbambo 26fc0da9a9
Update continuous-integration.md
typo
2023-02-14 08:29:40 +02:00
Eric Huss c15220d1a1
Merge pull request #2017 from thanatos/roy/fix-sidebar
Default the sidebar to visible in large screens
2023-02-13 12:42:15 -08:00
Eric Huss 7c4562a8b3
Merge pull request #2023 from ehuss/bump-version
Update to 0.4.27
2023-02-13 08:07:35 -08:00
Eric Huss 6e3176f726 Update to 0.4.27 2023-02-13 07:54:28 -08:00
Eric Huss 958b456873
Also make sure releases use --locked
If it somehow gets out of sync, then the release process otherwise wouldn't catch it.
2023-02-13 07:53:32 -08:00
Eric Huss a43b5b69ab
Merge pull request #2022 from ehuss/ci-locked
Make sure CI runs with --locked
2023-02-13 07:51:26 -08:00
Eric Huss 1517435441
Merge pull request #2021 from rust-lang/revert-2009-deps/toml
Revert "bump 'toml' dependency"
2023-02-13 07:44:45 -08:00
Eric Huss 7abb28cb2e Make sure CI runs with --locked 2023-02-13 07:43:46 -08:00
Eric Huss 112fd4aac3
Merge pull request #2020 from mgeisler/patch-1
Remove spammy `debug!` log from `path_to_root`
2023-02-13 07:33:59 -08:00
Eric Huss 90fbe112af
Revert "bump 'toml' dependency" 2023-02-13 07:31:33 -08:00
Martin Geisler c150529c7c
Remove spammy `debug!` log from `path_to_root`
The log statement is empty and simply fills up the logs when you run `mdbook` with `RUST_LOG=debug`.
2023-02-13 16:17:31 +01:00
Roy Wellington Ⅳ fa6aa2ced8 Default the sidebar to visible in large screens
The code here leads me to believe that the intention is for the sidebar
to be default visible on large screens (where `clientWidth` > 1080) and
hidden otherwise.

However, as previously written, if the `localStorage.getItem` call fails
(for example, if the user agent is not accepting cookies), then we fall
back to `sidebar = sidebar || 'visible';` — but `sidebar` is already set
to `hidden`, so the `|| 'visible'` never happens.

This results in the sidebar hiding itself on every navigation through an
mdBook, meaning if you're just switching between sections trying to find
something that you keep needing to re-open the sidebar.
2023-02-11 18:04:58 -05:00
Eric Huss 39664985ba
Merge pull request #2012 from ehuss/bump-version
Update to 0.4.26
2023-02-08 16:09:45 -08:00
Eric Huss ab1e9694bc Update to 0.4.26 2023-02-08 15:58:51 -08:00
Eric Huss 2c710d3b7d
Merge pull request #1987 from ehuss/theme-fonts
Make fonts part of the theme.
2023-02-08 15:56:40 -08:00
Eric Huss 581ab2c945
Merge pull request #2011 from ehuss/update-deps
Update some direct dependencies
2023-02-08 15:53:35 -08:00
Eric Huss 274b48c82f Update some direct dependencies
Updating anyhow v1.0.66 -> v1.0.69
Updating assert_cmd v2.0.7 -> v2.0.8
Updating handlebars v4.3.5 -> v4.3.6
Updating notify v5.0.0 -> v5.1.0
Updating once_cell v1.16.0 -> v1.17.0
Removing bstr v0.2.17
Removing lazy_static v1.4.0
Updating opener v0.5.0 -> v0.5.2
Updating regex v1.7.0 -> v1.7.1
Updating predicates v2.1.4 -> v2.1.5
Updating semver v1.0.14 -> v1.0.16
Updating serde v1.0.150 -> v1.0.152
Updating serde_derive v1.0.150 -> v1.0.152
Updating serde_json v1.0.89 -> v1.0.93
2023-02-08 15:40:48 -08:00
Eric Huss e352e4f59c
Merge pull request #1994 from Skwodo/master
fix overflow-x on mobile
2023-02-08 15:37:53 -08:00
Eric Huss 734936d819 Add some comments about overflow-x 2023-02-08 15:25:14 -08:00
Eric Huss 0e1384b4d2
Merge pull request #2009 from danieleades/deps/toml
bump 'toml' dependency
2023-02-08 14:08:21 -08:00
Daniel Eades 2160613c6a bump 'toml' dependency 2023-02-08 15:40:15 +00:00
Eric Huss 69bb5c7fba
Merge pull request #2001 from iFreilicht/patch-1
Fix incorrect version command
2023-01-29 09:34:13 -08:00
Felix Uhl f32e1a7773
Fix incorrect version command 2023-01-28 20:21:18 +01:00
Eric Huss 703c2f214b
Merge pull request #1998 from dalance/remove_time
Remove dependency to time 0.1.44
2023-01-28 07:41:34 -08:00
dalance 6de831778a Remove dependency to time 0.1.44 2023-01-26 18:02:43 +09:00
Skwodo ca46086e79 fix overflow-x on mobile 2023-01-21 22:41:01 +01:00
Eric Huss 0079184c16
Merge pull request #1961 from noritada/fix/scrollbar-in-chrome-and-safari
Use default scrollbars on webkit browsers
2023-01-18 09:25:02 -08:00
Noritada Kobayashi dcc9efea0a Remove the WebKit-specific scrollbar styling altogether
It is preferable to remove WebKit-specific styling and use the browser
and OS default scrollbars.
Thanks to comments from @julianfortune, @arniu, and @ehuss.

Closes #1483.
2023-01-18 23:51:09 +09:00
Dylan DPC a3b508fab9
Merge pull request #1988 from ehuss/issue-templates
Add issue templates and update contributor docs
2023-01-16 23:00:22 +05:30
Eric Huss 5359b487f2 Add issue templates and update contributor docs 2023-01-16 09:22:54 -08:00
Eric Huss c2d973997a Make fonts part of the theme. 2023-01-15 11:42:46 -08:00
Martin Geisler b09aa0e65c Run preprocessors in `mdbook test`
While adding support for translations[1] to Comprehensive Rust 🦀, I
noticed that `mdbook test` doesn’t execute preprocessors the same way
as `mdbook build`.

This PR makes the two commands use the same code to find and execute
preprocessors.

[1]: https://github.com/google/comprehensive-rust/pull/130
2023-01-15 11:44:46 +01:00
Eric Huss 41a6f0d43e
Merge pull request #1968 from ehuss/ehuss-patch-1
Fix MDBOOK_BOOK environment variable example
2022-12-28 19:47:20 -08:00
Eric Huss 9764f8886b
Fix MDBOOK_BOOK environment variable example 2022-12-28 19:21:38 -08:00
Noritada Kobayashi 1ba2c063e0 Thin scrollbars in Chrome and Safari to make them less assertive (#1483) 2022-12-22 15:40:03 +09:00
Eric Huss c640294dbf
Merge pull request #1960 from ehuss/bump-version
Update to 0.4.25
2022-12-17 08:19:15 -08:00
Eric Huss dec487c62b Update to 0.4.25 2022-12-17 07:42:00 -08:00
Eric Huss 1ba74a30fc
Merge pull request #1959 from ehuss/test-lib-multiple
Fix test with multiple library paths
2022-12-17 07:39:27 -08:00
Eric Huss fcf0cebf6c Fix test with multiple library paths 2022-12-17 07:11:13 -08:00
Eric Huss e14d38194f
Merge pull request #1956 from ehuss/bump-version
Update to 0.4.24
2022-12-15 07:14:13 -08:00
Eric Huss 294aad092e Update to 0.4.24 2022-12-15 06:55:05 -08:00
Eric Huss 8767ebf835
Merge pull request #1955 from ehuss/ubuntu-20.04
Switch to older ubuntu image
2022-12-15 06:50:44 -08:00
Eric Huss cd907f2edf Switch to older ubuntu image 2022-12-15 05:52:15 -08:00
Eric Huss eb77083d23
Merge pull request #1953 from ehuss/bump-version
Update to 0.4.23
2022-12-14 18:46:05 -08:00
Eric Huss 219362318c Update to 0.4.23 2022-12-14 17:48:46 -08:00
Eric Huss 68a75dae48
Merge pull request #1844 from wendajiang/master
Upgrade clap
2022-12-14 17:26:07 -08:00
David 87a381e0a7 upgrade clap to 4.0 2022-12-14 17:11:08 -08:00
Eric Huss 0b2520f84a
Merge pull request #1952 from ehuss/update-deps-breaking
Update dependencies with semver major changes
2022-12-14 17:08:41 -08:00
Eric Huss 21ab85cd03 Update notify
Update notify from 4.0.17 to 5.0.0
https://github.com/notify-rs/notify/blob/main/UPGRADING_V4_TO_V5.md
https://github.com/notify-rs/notify/blob/main/CHANGELOG.md#notify-500-2022-08-28
2022-12-14 14:01:27 -08:00
Eric Huss 486bf32ac7 Update topological-sort
Update topological-sort from 0.1.0 to 0.2.2
https://github.com/gifnksm/topological-sort-rs/compare/v0.1.0...v0.2.2
2022-12-14 08:06:20 -08:00
Eric Huss 4f6610716a Update select
Update select from 0.5.0 to 0.6.0
https://github.com/utkarshkukreti/select.rs/compare/0.5.0...0.6.0
2022-12-14 08:06:16 -08:00
Eric Huss 6db4ca71da Update env_logger
Update env_logger from 0.9.3 to 0.10.0

https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md#0100---2022-11-24
2022-12-14 08:06:11 -08:00
Eric Huss d5319e2b4f Update assert_cmd
Update from 1.0.8 to 2.0.7
https://github.com/assert-rs/assert_cmd/blob/master/CHANGELOG.md#207---2022-12-02
2022-12-14 08:06:06 -08:00
Eric Huss cda44480b7
Merge pull request #1951 from ehuss/update-deps
Update dependencies
2022-12-14 08:05:17 -08:00
Eric Huss fb0af12433 Bump MSRV to 1.60
Needed for new feature syntax
2022-12-14 07:20:09 -08:00
Eric Huss b5f858da4e Update dependencies
Fairly large update of semver compatible deps:

Updating aho-corasick v0.7.18 -> v0.7.20
Updating ammonia v3.1.2 -> v3.3.0
  Adding android_system_properties v0.1.5
Removing ansi_term v0.12.1
Updating anyhow v1.0.43 -> v1.0.66
Updating assert_cmd v1.0.7 -> v1.0.8
Updating autocfg v1.0.1 -> v1.1.0
Updating base64 v0.13.0 -> v0.13.1
Updating bit-set v0.5.2 -> v0.5.3
Removing block-buffer v0.7.3
Removing block-buffer v0.9.0
  Adding block-buffer v0.10.3
Removing block-padding v0.1.5
  Adding bumpalo v3.11.1
Removing byte-tools v0.3.1
Updating bytes v1.0.1 -> v1.3.0
  Adding cc v1.0.77
Updating chrono v0.4.19 -> v0.4.23
Updating clap v3.0.10 -> v3.2.23
Updating clap_complete v3.0.4 -> v3.2.5
  Adding clap_lex v0.2.4
  Adding codespan-reporting v0.11.1
  Adding core-foundation-sys v0.8.3
Updating cpufeatures v0.1.5 -> v0.2.5
  Adding crypto-common v0.1.6
Updating ctor v0.1.20 -> v0.1.26
  Adding cxx v1.0.83
  Adding cxx-build v1.0.83
  Adding cxxbridge-flags v1.0.83
  Adding cxxbridge-macro v1.0.83
Updating diff v0.1.12 -> v0.1.13
Removing digest v0.8.1
Removing digest v0.9.0
  Adding digest v0.10.6
Updating either v1.6.1 -> v1.8.0
Updating elasticlunr-rs v3.0.0 -> v3.0.1
Updating env_logger v0.9.0 -> v0.9.3
Removing fake-simd v0.1.2
  Adding fastrand v1.8.0
Updating filetime v0.2.15 -> v0.2.19
Updating form_urlencoded v1.0.1 -> v1.1.0
Updating futf v0.1.4 -> v0.1.5
Updating futures-channel v0.3.21 -> v0.3.25
Updating futures-core v0.3.21 -> v0.3.25
Updating futures-macro v0.3.16 -> v0.3.25
Updating futures-sink v0.3.21 -> v0.3.25
Updating futures-task v0.3.16 -> v0.3.25
Updating futures-util v0.3.16 -> v0.3.25
Removing generic-array v0.12.4
Removing generic-array v0.14.4
  Adding generic-array v0.14.6
Updating getrandom v0.2.3 -> v0.2.8
Updating h2 v0.3.4 -> v0.3.15
Updating handlebars v4.1.2 -> v4.3.5
Updating hashbrown v0.11.2 -> v0.12.3
Updating headers v0.3.4 -> v0.3.8
Removing html5ever v0.25.1
  Adding html5ever v0.25.2
  Adding html5ever v0.26.0
Updating http v0.2.4 -> v0.2.8
Updating http-body v0.4.3 -> v0.4.5
Updating httparse v1.5.1 -> v1.8.0
Updating httpdate v1.0.1 -> v1.0.2
Updating hyper v0.14.11 -> v0.14.23
  Adding iana-time-zone v0.1.53
  Adding iana-time-zone-haiku v0.1.1
Updating idna v0.2.3 -> v0.3.0
Updating indexmap v1.7.0 -> v1.9.2
  Adding instant v0.1.12
Updating itertools v0.10.1 -> v0.10.5
Updating itoa v0.4.8 -> v1.0.4
  Adding js-sys v0.3.60
Updating libc v0.2.100 -> v0.2.138
  Adding link-cplusplus v1.0.7
  Adding lock_api v0.4.9
Updating log v0.4.14 -> v0.4.17
  Adding markup5ever v0.11.0
Removing matches v0.1.9
Updating memchr v2.4.1 -> v2.5.0
Updating mime_guess v2.0.3 -> v2.0.4
Updating mio v0.7.13 -> v0.8.5
Removing miow v0.3.7
Updating net2 v0.2.37 -> v0.2.38
Removing ntapi v0.3.6
Updating num-integer v0.1.44 -> v0.1.45
Updating num-traits v0.2.14 -> v0.2.15
Updating num_cpus v1.13.0 -> v1.14.0
Updating once_cell v1.15.0 -> v1.16.0
Removing opaque-debug v0.2.3
Removing opaque-debug v0.3.0
Updating os_str_bytes v6.0.0 -> v6.4.1
Updating output_vt100 v0.1.2 -> v0.1.3
  Adding parking_lot v0.12.1
  Adding parking_lot_core v0.9.5
Updating percent-encoding v2.1.0 -> v2.2.0
Updating pest v2.1.3 -> v2.5.1
Updating pest_derive v2.1.0 -> v2.5.1
Updating pest_generator v2.1.3 -> v2.5.1
Updating pest_meta v2.1.3 -> v2.5.1
  Adding phf v0.10.1
  Adding phf_codegen v0.10.0
  Adding phf_generator v0.10.0
  Adding phf_shared v0.10.0
Updating pin-project v1.0.8 -> v1.0.12
Updating pin-project-internal v1.0.8 -> v1.0.12
Updating pin-project-lite v0.2.7 -> v0.2.9
Updating ppv-lite86 v0.2.10 -> v0.2.17
Updating predicates v2.0.1 -> v2.1.4
Updating predicates-core v1.0.2 -> v1.0.5
Updating predicates-tree v1.0.2 -> v1.0.7
Updating pretty_assertions v1.2.1 -> v1.3.0
Removing proc-macro-hack v0.5.19
Removing proc-macro-nested v0.1.7
Updating proc-macro2 v1.0.28 -> v1.0.47
Updating pulldown-cmark v0.9.1 -> v0.9.2
Removing quick-error v2.0.1
Updating quote v1.0.9 -> v1.0.21
Updating rand v0.8.4 -> v0.8.5
Updating rand_core v0.6.3 -> v0.6.4
Removing rand_hc v0.3.1
Updating redox_syscall v0.2.10 -> v0.2.16
Updating regex v1.5.5 -> v1.7.0
Updating regex-syntax v0.6.25 -> v0.6.28
  Adding rustls-pemfile v0.2.1
Updating ryu v1.0.5 -> v1.0.11
Updating scoped-tls v1.0.0 -> v1.0.1
  Adding scopeguard v1.1.0
  Adding scratch v1.0.2
Updating semver v1.0.4 -> v1.0.14
Updating serde v1.0.129 -> v1.0.150
Updating serde_derive v1.0.129 -> v1.0.150
Updating serde_json v1.0.66 -> v1.0.89
Updating serde_urlencoded v0.7.0 -> v0.7.1
Removing sha-1 v0.8.2
Removing sha-1 v0.9.7
  Adding sha-1 v0.10.1
  Adding sha1 v0.10.5
Updating shlex v1.0.0 -> v1.1.0
Updating siphasher v0.3.6 -> v0.3.10
Updating slab v0.4.4 -> v0.4.7
  Adding smallvec v1.10.0
Updating socket2 v0.4.1 -> v0.4.7
Updating string_cache v0.8.1 -> v0.8.4
Updating string_cache_codegen v0.5.1 -> v0.5.2
Updating syn v1.0.75 -> v1.0.105
Updating tempfile v3.2.0 -> v3.3.0
Updating tendril v0.4.2 -> v0.4.3
Updating termcolor v1.1.2 -> v1.1.3
  Adding termtree v0.4.0
Updating textwrap v0.14.2 -> v0.16.0
Updating thiserror v1.0.31 -> v1.0.37
Updating thiserror-impl v1.0.31 -> v1.0.37
Updating time v0.1.43 -> v0.1.45
Updating tinyvec v1.3.1 -> v1.6.0
Updating tokio v1.16.1 -> v1.23.0
Updating tokio-macros v1.8.0 -> v1.8.2
Updating tokio-stream v0.1.7 -> v0.1.11
Updating tokio-tungstenite v0.15.0 -> v0.17.2
Updating tokio-util v0.6.7 -> v0.7.4
Updating toml v0.5.8 -> v0.5.10
Updating tower-service v0.3.1 -> v0.3.2
Updating tracing v0.1.26 -> v0.1.37
Updating tracing-core v0.1.19 -> v0.1.30
Removing treeline v0.1.0
Updating tungstenite v0.14.0 -> v0.17.3
Updating typenum v1.13.0 -> v1.16.0
Updating ucd-trie v0.1.3 -> v0.1.5
Updating unicode-bidi v0.3.6 -> v0.3.8
  Adding unicode-ident v1.0.5
Updating unicode-normalization v0.1.19 -> v0.1.22
  Adding unicode-width v0.1.10
Removing unicode-xid v0.2.2
Updating url v2.2.2 -> v2.3.1
Updating version_check v0.9.3 -> v0.9.4
Updating warp v0.3.2 -> v0.3.3
Removing wasi v0.10.2+wasi-snapshot-preview1
  Adding wasi v0.10.0+wasi-snapshot-preview1
  Adding wasi v0.11.0+wasi-snapshot-preview1
  Adding wasm-bindgen v0.2.83
  Adding wasm-bindgen-backend v0.2.83
  Adding wasm-bindgen-macro v0.2.83
  Adding wasm-bindgen-macro-support v0.2.83
  Adding wasm-bindgen-shared v0.2.83
  Adding windows-sys v0.42.0
  Adding windows_aarch64_gnullvm v0.42.0
  Adding windows_aarch64_msvc v0.42.0
  Adding windows_i686_gnu v0.42.0
  Adding windows_i686_msvc v0.42.0
  Adding windows_x86_64_gnu v0.42.0
  Adding windows_x86_64_gnullvm v0.42.0
  Adding windows_x86_64_msvc v0.42.0
Updating xml5ever v0.16.1 -> v0.16.2
  Adding yansi v0.5.1
2022-12-14 07:03:48 -08:00
Eric Huss 59bd5db556
Merge pull request #1950 from yoyomo/issue-1949
#1949 update for hidden only on clipboard
2022-12-14 06:57:02 -08:00
armandocumate cf1557e454 update for hidden only on clipboard 2022-12-14 06:54:45 -08:00
Dylan DPC 36e1f01091
Merge pull request #1946 from LePichu/master
fix: random `Array` ref in `src/theme/book.js`
2022-12-06 13:26:45 +05:30
LePichu e3c484af01 fix: random ref in 2022-12-06 12:05:11 +05:30
Dylan DPC 4deb5c7cee
Merge pull request #1941 from klensy/msrv
fix msrv in docs
2022-11-29 23:40:01 +05:30
klensy 21fb329d56 fix msrv in docs 2022-11-29 20:33:03 +03:00
Eric Huss 678b469835
Merge pull request #1938 from ehuss/bump-version
Update to 0.4.22
2022-11-28 10:16:18 -08:00
Eric Huss ded48ddac7 Update to 0.4.22 2022-11-28 09:54:39 -08:00
Eric Huss 8a02fc755f Fix broken doc link 2022-11-28 09:53:47 -08:00
Dylan DPC 4844f72b96
Merge pull request #1935 from ehuss/theme-dropdown-rendering-changes
Show the currently selected theme
2022-11-22 13:32:12 +05:30
Eric Huss f32bd6f945 Show the currently selected theme. 2022-11-21 15:27:39 -08:00
Eric Huss f64fcbc07d Fix clipping in theme popup 2022-11-21 14:00:18 -08:00
Eric Huss c34c3bf730
Merge pull request #1929 from Benjins/patch-2
Update docs link in Cargo.toml to HTTPS version
2022-11-18 18:05:37 -08:00
Benji Smith de4c551363
Update docs link in Cargo.toml to HTTPS version
Currently, the documentation link is specified in Cargo.toml as:

documentation = "http://rust-lang.github.io/mdBook/index.html"

which propagates to crates.io and if users click on the docs link there they get the non-TLS version. Not likely to cause major issues, but still best practice to use the HTTPS version since it's there
2022-11-18 20:53:08 -05:00
Eric Huss d45f02d38c
Merge pull request #1924 from averms-forks/html-conformance
Better HTML5 conformance
2022-11-13 13:29:18 -08:00
Eric Huss 666975a1ef
Merge pull request #1884 from willcrichton/master
Add support for watching additional directories
2022-11-13 12:53:19 -08:00
Will Crichton 144a1e4009 Add documentation for extra-watch-dirs 2022-11-12 14:28:43 -08:00
Will Crichton 8b486dfc71 Fix compilation failure 2022-11-12 14:23:05 -08:00
Aman Verma db092a404e State canonical URL using <link> not <meta>.
The Google SEO docs describe using the link element to specify the canonical
page in a redirect. They don't mention anything about <meta>.
2022-11-12 14:54:28 -06:00
Aman Verma edda3d1b51 Fix formatting of content attr in <meta http-equiv=refresh>
According to the HTML specification[1], there needs to be a space after the
semicolon and the URL must be unquoted.

[1]: https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-refresh
2022-11-12 14:47:21 -06:00
Aman Verma 27a11e7b35 Don't generate obsolete charset attrs in <script>.
According to the HTML specification[1], the charset attribute is obsolete in
script elements.

[1]: https://html.spec.whatwg.org/multipage/obsolete.html#HTMLScriptElement-partial
2022-11-12 14:45:09 -06:00
Aman Verma cfd4c93d88 Don't generate redundant <meta http-equiv=content-type>.
Quoting from the HTML specification[1]:

  A document must not contain both a meta element with an http-equiv attribute
  in the Encoding declaration state and a meta element with the charset
  attribute present.

So we remove the <meta> with the http-equiv attribute from our template.

[1]: https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
2022-11-12 14:27:52 -06:00
Will Crichton b1ca805d2a Fix tests 2022-11-07 14:08:31 -08:00
Will Crichton 852a882fab Improve error handling of extra-watch-dirs watching, fix not watching whitelisted files outside book root 2022-11-07 12:20:59 -08:00
Will Crichton fb0cbc90e3 Add support for watching additional directories 2022-10-28 11:47:33 -07:00
Dylan DPC 3a24f10d7c
Merge pull request #1911 from yoyomo/fix-copying-invisible-text
fix copying invisible text
2022-10-15 11:37:46 +05:30
armandocumate 3fc036e01a fi xcopying invisible text 2022-10-14 16:53:59 -07:00
Eric Huss 056a46cc97
Merge pull request #1862 from gifnksm/add-musl-binary
Deploy {x86_64,aarch64}-unknown-linux-musl binary
2022-10-13 17:17:53 -07:00
Eric Huss f8df8ed72d
Merge pull request #1906 from brettchalupa/kbd-styles-1813
Add styles for <kbd> elements
2022-10-09 13:52:52 -07:00
Brett Chalupa 79c159d123 Add styles for <kbd> elements
Allows for special styles to call them out since they're different than
normal text and different than code. They can make use of styles they
inherit for font style and weight.

Notes on changes:

- Added new CSS variables for reused elements
- The font-* rules are separate for each aspect so that they can inherit
  bold/italic/etc

Closes https://github.com/rust-lang/mdBook/issues/1813
2022-10-06 16:02:29 -04:00
Dylan DPC a8c37ceace
Merge pull request #1900 from kianmeng/fix-typos
Fix typos
2022-09-29 21:00:10 +05:30
Kian-Meng Ang cb01f11ad1 Fix typos
Found via `codespell -S ./src/theme -L crate,nam,varius,phasis`
2022-09-29 22:41:12 +08:00
Noritada Kobayashi 7aaa84853d
Add test code to show preprocessor developers what the interface data looks like (#1897)
This test code will show preprocessor developers what the input data
looks like and how to test the preprocessing results.
2022-09-24 14:32:41 -07:00
David 75857fbf73
feat: use once_cell instead of lazy_static (#1894) 2022-09-22 15:05:39 -07:00
Eric Huss c8db0c8ec6
Merge pull request #1889 from mgeisler/simplify-and-then
Simplify the use of `Option::and_then`
2022-09-12 09:16:21 -07:00
Martin Geisler 3958260353 Simplify the use of `Option::and_then`
I found a few places where `Option::and_then` could be simplified (in
my opinion) with `?` or with `match`.
2022-09-11 00:46:26 +02:00
Eric Huss 8cdb8d0367
Merge pull request #1887 from mgeisler/edition-2021
Require Rust 2021 edition
2022-09-09 13:14:33 -07:00
Dylan DPC 66bf85b14f Require Rust 2021 edition
This allows us to clean up and simplify the code.
2022-09-09 16:00:35 +02:00
Dylan DPC 1a0892745e
Merge pull request #1881 from GuillaumeGomez/unneeded-attribute
Remove unneeded `type` attribute for `<script>`
2022-08-30 10:53:11 +05:30
Guillaume Gomez 76b0493fb0 Remove unneeded type attribute for <script> 2022-08-29 21:07:50 +02:00
Chris Lovett 74eb4059d6
add a --chapter option to mdbook test. (#1741)
Sometimes when working on large books it is handy to be able to run mdbook on a single chapter of a book.
2022-08-25 19:13:51 -07:00
Dylan DPC 13f53eb64f
Merge pull request #1876 from joepio/patch-1
Explain how to use `site-url` with asset URLs
2022-08-23 14:33:02 +05:30
Joep Meindertsma b3941526cb
Explain how to use `site-url`
Make sure people use the right kind of asset-urls. This issue only shows up when deploying, not using `mdbook serve`.
2022-08-23 10:33:25 +02:00
Eric Huss fff067b2a8
Merge pull request #1836 from mgeisler/trim-trailing-whitespace
Avoid empty last line in editable code blocks
2022-08-10 19:55:06 -07:00
Martin Geisler 217546c2a0 Trim trailing whitespace in Rust code blocks
Before, a code block would always end with a final newline. The
newline was added unconditionally by `hide_lines`.

When the code block is syntax highlighted by highlight.js, this is not
a problem, no empty line is added for a final trailing `\n` character.
However, when the code block is editable and thus handled by the ACE
editor, a trailing newline _is_ significant. I believe this issue is
most closely described by https://github.com/ajaxorg/ace/issues/2083
in the upstream repository.

The effect of the way ACE handles newlines is that a code block like

    <pre>
      Some code
    </pre>

will create an editor with _two_ lines, not just one.

By trimming trailing whitespace, we ensure that we don’t accidentally
create more lines in the ACE editor than necessary.
2022-08-10 03:11:08 +02:00
Eric Huss 40c06f5e77
Merge pull request #1863 from ehuss/bump-version
Update to 0.4.21
2022-07-22 11:06:12 -07:00
Eric Huss bb09caa9a3 Update to 0.4.21 2022-07-22 10:54:53 -07:00
gifnksm 4ebefeb43a Deploy {x86_64,aarch64}-unknown-linux-musl binary 2022-07-23 00:37:18 +09:00
Eric Huss 8f01d0234f
Merge pull request #1861 from mitchmindtree/nightly-borrowcheck-err-workaround
Workaround rust nightly borrowcheck error (#1860)
2022-07-22 07:26:28 -07:00
mitchmindtree 13035baeae Workaround rust nightly borrowcheck error (#1860)
Surprisingly, this fixes the error filed at #1860!

This seems suspicious, perhaps indicative of a bug in Rust's non-lexical
lifetime handling?

The lifetimes in the `handlebars::Renderable::render` method signature
are quite complicated, and its unclear to me whether or not Rust is
catching some new safety edge-case that wasn't previously handled
correctly...

Possibly related to `drop` order, which I *think* is related to the
order of binding statements?
2022-07-22 15:15:12 +10:00
Eric Huss 92afe9bd3c
Merge pull request #1857 from ehuss/bump-version
Update to 0.4.20
2022-07-14 14:26:20 -07:00
Eric Huss 4c1aca0abb Update to 0.4.20 2022-07-14 13:46:56 -07:00
Eric Huss da166e051d
Merge pull request #1855 from stevenengler/code-fmt
Fix code padding in headings
2022-07-13 18:06:49 -07:00
Steven Engler 6a4ba95926 Fixed code padding in headings 2022-07-13 19:12:33 -04:00
Eric Huss 6688bd8d7b
Merge pull request #1849 from FauconFan/fix_author(s)_in_user_guide
authors -> author in user guide
2022-07-09 13:55:51 -07:00
Dylan DPC 01313a39cc
Merge pull request #1851 from FauconFan/revert-1848-remove_non_exhaustive
Revert "remove __non_exhaustive members, add non_exhaustive attribute instead"
2022-07-09 21:51:04 +05:30
Joseph Priou f92911b8aa
Revert "remove __non_exhaustive members, add non_exhaustive attribute instead" 2022-07-09 17:43:17 +02:00
Dylan DPC 42d6fd5804
Merge pull request #1848 from FauconFan/remove_non_exhaustive
remove __non_exhaustive members, add non_exhaustive attribute instead
2022-07-09 19:18:25 +05:30
Fauconfan a8a45a5fbe authors -> author in user guide 2022-07-09 15:31:00 +02:00
Fauconfan 11781f0c1b remove __non_exhaustive members, add non_exhaustive attribute instead 2022-07-09 15:05:06 +02:00
Eric Huss 53055e0345
Merge pull request #1840 from ehuss/fix-lock
Fix Cargo.lock for version bump.
2022-07-01 15:46:36 -07:00
Eric Huss 1af6d4b0ec Fix Cargo.lock for version bump. 2022-07-01 15:44:18 -07:00
Eric Huss 3e311dc975
Merge pull request #1838 from ehuss/bump-version
Update to 0.4.19
2022-07-01 15:23:01 -07:00
Eric Huss 04e31eb07b Update to 0.4.19 2022-07-01 14:55:25 -07:00
Eric Huss eb82ddca0b
Merge pull request #1837 from ehuss/serve-info
Always show the "serving on" info for `mdbook serve`.
2022-07-01 14:54:04 -07:00
Eric Huss 1d2b720ebe Always show the "serving on" info for `mdbook serve`. 2022-07-01 14:36:18 -07:00
Eric Huss 4c303c3b1d
Merge pull request #1830 from ISSOtm/serve
Always open index page with `serve --open`
2022-07-01 13:18:47 -07:00
ISSOtm 42129c6181 Always open index page with `serve --open` 2022-07-01 09:01:25 +02:00
Eric Huss a10a57e67d
Merge pull request #1829 from ISSOtm/index
Fix up "index page" functionality
2022-06-29 17:55:11 -07:00
ISSOtm fa5f32c7fd Make link to first chapter active in index page
Makes both pages more consistent, and also the previous test pass

Co-authored-by: Eric Huss <eric@huss.org>
2022-06-29 23:51:18 +02:00
ISSOtm a91e888575 Add test for index page 2022-06-29 08:48:49 +02:00
ISSOtm 8571883923 Mark the first chapter as "index", even if not the first book item 2022-06-29 08:35:41 +02:00
Eric Huss 4cf005d4bd
Merge pull request #1832 from ISSOtm/clippy
Fix Clippy lints
2022-06-27 14:28:39 -07:00
Eric Huss b38792c166
Merge pull request #1833 from mattheww/2022-06_searchindex
Omit words longer than 80 characters from the search index
2022-06-27 14:17:40 -07:00
ISSOtm 248863addf Fix Clippy lints
Also remove `allow(clippy::*)`s where possible
2022-06-27 23:08:45 +02:00
Eric Huss 7e2752e71f
Merge pull request #1834 from ehuss/update-deps
Update some dependencies
2022-06-27 13:58:40 -07:00
Eric Huss cbf0ca027d
Merge pull request #1806 from ehuss/button-overlap
Make code buttons appear on hover (or tap on mobile)
2022-06-27 13:58:17 -07:00
Eric Huss 2c2ba636a9 Update pretty_assertions from 0.6.1 to 1.2.1 2022-06-27 13:29:22 -07:00
Eric Huss 494e6722b2 Update env_logger from 0.7.1 to 0.9.0
This drops quick-error 1.2.3 from the tree
2022-06-27 13:29:22 -07:00
Eric Huss ddf71222c5 Bump tokio from 1.10.0 to 1.16.1 2022-06-27 13:29:18 -07:00
Eric Huss 1d89127d8f
Mention how to uninstall.
Closes #1822.
2022-06-27 11:10:09 -07:00
Eric Huss 5f00625c14
Merge pull request #1617 from lf-/x-overflow
Fix some x overflows
2022-06-27 11:01:48 -07:00
Matthew Woodcraft 000a93dc77 Test that long words are omitted from the search index.
Note they do appear in the 'docs' part of searchindex.json (so they will be
visible in search teasers).
2022-06-26 12:22:52 +01:00
Matthew Woodcraft 1f8c090a5f When creating the search index, omit words longer than 80 characters
This avoids creating deeply nested objects in searchindex.json
2022-06-26 12:22:52 +01:00
Eric Huss 0547868d4d
Merge pull request #1825 from joshrotenberg/update-warp-version
Update warp to 0.3.2
2022-06-22 17:33:56 -07:00
Dylan DPC 1056b8361c
Merge pull request #1828 from rust-lang/revert-1809-2022-05_searchindex
Revert "Omit words longer than 80 characters from the search index"
2022-06-22 13:50:01 +02:00
Dylan DPC a5f861bf2b
Revert "Omit words longer than 80 characters from the search index" 2022-06-22 13:31:16 +02:00
Dylan DPC 93aee6419e
Merge pull request #1809 from mattheww/2022-05_searchindex
Omit words longer than 80 characters from the search index
2022-06-22 13:14:08 +02:00
Josh Rotenberg 4b1a7e9ae7 update warp to 0.3.2 2022-06-20 10:48:03 -07:00
Eric Huss 2f5e89f3ec
Merge pull request #1810 from mattico/el-v3
Update to elasticlunr-rs 3.0.0
2022-06-02 15:02:19 -07:00
Eric Huss 2b903ad057 Make code block icons appear on hover. 2022-06-01 18:48:34 -07:00
Matt Ickstadt fb397e6fa0 Update to elasticlunr-rs 3.0.0 2022-06-01 20:20:14 -05:00
Matthew Woodcraft 00a55b35a8 Test that long words are omitted from the search index.
Note they do appear in the 'docs' part of searchindex.json (so they will be
visible in search teasers).
2022-05-22 14:02:54 +01:00
Matthew Woodcraft d65ce55453 When creating the search index, omit words longer than 80 characters
This avoids creating deeply nested objects in searchindex.json
2022-05-22 14:00:20 +01:00
Eric Huss 37d756ae75 Adjust overlap of code buttons with code blocks. 2022-05-16 19:27:46 -07:00
Eric Huss f8782666ba
Merge pull request #1714 from joshrotenberg/draft-no-index
Check for the index.html file before trying to opener::open it
2022-05-16 08:04:38 -07:00
josh rotenberg c74c682939 call find_chapter when opening browser 2022-05-11 13:14:38 -07:00
josh rotenberg 8b49600673 call first_chapter 2022-05-10 11:19:23 -07:00
josh rotenberg 29c729fd23 call first_chapter 2022-05-10 11:17:20 -07:00
josh rotenberg 5d65967448 add first_chapter function 2022-05-10 11:16:22 -07:00
Eric Huss bf258eeb9b
Merge pull request #1801 from klensy/static-regex
init regexes via lazy_static
2022-05-06 12:05:10 -07:00
klensy af6237015a init regexes via lazy_static, don't recompute it 2022-05-06 10:05:52 +03:00
Eric Huss a462fb63c3
Merge pull request #1798 from klensy/serde_derive
Use serde's `derive` feature instead of directly importing serde_derive
2022-05-05 12:55:01 -07:00
klensy f3332fb0da Use serde's `derive` feature instead of directly importing serde_derive 2022-05-05 09:33:51 +03:00
Eric Huss 5bea83114b
Merge pull request #1791 from clarkfw/master
bail! in render() if specified theme directory does not exist
2022-05-02 11:08:57 -07:00
Clark fe8bb38ec1 add a test: Ensure building fails if `[output.html].theme` points to a non-existent directory 2022-04-28 13:13:58 +08:00
Clark a60571321a bail! in render() if specified theme directory does not exist 2022-04-26 20:20:44 +08:00
Eric Huss e1c2e1a753
Merge pull request #1788 from max-sixty/patch-1
Fix typo in changelog
2022-04-15 12:26:42 -07:00
Maximilian Roos 800dbf2929
Fix typo in changelog 2022-04-15 12:09:31 -07:00
Eric Huss 1880447dce
Merge pull request #1787 from ehuss/bump-version
Update to 0.4.18
2022-04-15 11:33:56 -07:00
Eric Huss 268dbb099f Update to 0.4.18 2022-04-15 11:24:45 -07:00
Eric Huss ae275ad1b1
Merge pull request #1785 from ehuss/summary-escape
Don't try to render summary links as markdown.
2022-04-15 11:20:17 -07:00
Eric Huss ceff050bb4 Don't try to render summary links as markdown. 2022-04-14 20:35:39 -07:00
josh rotenberg 8357811d96 Merge branch 'draft-no-index' of https://github.com/joshrotenberg/mdBook into draft-no-index 2022-04-04 18:59:47 -07:00
josh rotenberg 860a17d85a
Merge branch 'rust-lang:master' into draft-no-index 2022-04-04 18:59:39 -07:00
Eric Huss ba324cddb6
Merge pull request #1778 from sgoudham/fix-typo
User Guide: Fix typo 'mbdook' to 'mdbook'
2022-04-01 14:36:51 -07:00
Eric Huss a5dcd78393
Merge pull request #1777 from trofi/ignore-RUST_LOG
tests/cli: ignore user's RUST_LOG= environment variable in tests
2022-04-01 14:00:44 -07:00
sgoudham 8e1195322a
Fix typo 'mbdook' to 'mdbook' 2022-04-01 13:13:58 +01:00
Sergei Trofimovich 2a2b51c8ab tests/cli: ignore user's RUST_LOG= environment variable in tests
nixpkgs build system sets `RUST_LOG=` (empty value) by default.
This switches `mdBook` into warnings+ mode (instead of info+).

This causes the following tests to fail:

    $ RUST_LOG= cargo test --test cli_tests
    ...
    cli::test::mdbook_cli_can_correctly_test_a_passing_book
    cli::test::mdbook_cli_detects_book_with_failing_tests
    cli::build::mdbook_cli_dummy_book_generates_index_html

The change drops RUST_LOG= entry.
2022-04-01 08:15:18 +01:00
Eric Huss eb5ec2a314
Merge pull request #1776 from ehuss/bump-version
Update to 0.4.17
2022-03-30 10:22:28 -07:00
Eric Huss 445529a68f Update to 0.4.17 2022-03-30 09:33:56 -07:00
Eric Huss 981b79b3b3
Merge pull request #1775 from ehuss/fix-print-config
Fix html print config default.
2022-03-30 09:31:16 -07:00
Eric Huss 78bcda02cb Fix html print config default. 2022-03-30 07:58:27 -07:00
Eric Huss cdfa5ad990
Add missing entries for 0.4.16 2022-03-29 17:50:08 -07:00
Eric Huss 720f502e9d
Merge pull request #1773 from ehuss/bump-version
Update to 0.4.16
2022-03-29 17:45:05 -07:00
Eric Huss adf6129769 Update to 0.4.16 2022-03-29 17:33:01 -07:00
Eric Huss a5fddfa468
Merge pull request #1749 from tommilligan/unique-search-anchors
search: fix anchor ids for duplicate headers
2022-03-28 12:44:20 -07:00
Eric Huss 7c37dd5e85
Merge pull request #1731 from epage/clap3
Port mdBook to clap3
2022-03-28 12:34:17 -07:00
Ed Page 857ca19fe4 refactor: Move from deprecated arg_from_usage 2022-03-28 13:06:50 -05:00
Ed Page a19d91ef37 refactor: Move from deprecated multiple 2022-03-28 13:06:50 -05:00
Ed Page ac8526688a refactor: Move from deprecated empty_values 2022-03-28 13:06:50 -05:00
Ed Page 1e1c99bbdb refactor: Move from deprecated GlobalVersion 2022-03-28 13:06:50 -05:00
Ed Page 0c89293029 refactor: Move from deprecated ColoredHelp 2022-03-28 13:06:50 -05:00
Ed Page 39eb78c88b refactor: Move from deprecated SubCommand 2022-03-28 13:06:50 -05:00
Ed Page 372842aac6 refactor: Move from deprecated Arg::with_name 2022-03-28 13:06:50 -05:00
Ed Page 7934e06668 chore: Upgrade to clap3 2022-03-28 13:06:50 -05:00
Ed Page 44f982f8e5 chore: Upgrade MSRV 2022-03-28 13:06:44 -05:00
Eric Huss 1f04a62648
Merge pull request #1693 from rsapkf/404-title
Add proper title to 404 page
2022-03-27 17:26:45 -07:00
Eric Huss 675c8c3f4e Add book title to 404 page title. 2022-03-27 17:17:20 -07:00
rsapkf 97cb77bbdd Add proper title to 404 page 2022-03-27 17:01:11 -07:00
Eric Huss 46345b8e49
Fix comment typo 2022-03-27 16:39:12 -07:00
Eric Huss 566451e9a7
Merge pull request #1771 from FWYongxing/master
livereload uses host, port and HTTP(S) protocol of current page
2022-03-27 16:33:32 -07:00
Eric Huss 15626294b0
Merge pull request #1744 from ilslv/1743-fix-title-consuming-events
Fix `SummaryParser::parse_title()` consuming events (#1743)
2022-03-27 14:38:00 -07:00
Eric Huss 6cab04554e
Merge pull request #1546 from pineapplehunter/master
Config to toggle the run button on codeblocks
2022-03-27 12:32:35 -07:00
Shogo Takata 2ae7f007cc
add doc in mdbook.md 2022-03-27 17:28:42 +09:00
Shogo Takata 89e37a7751
add docs 2022-03-26 16:11:41 +09:00
Shogo Takata 0dca4d9b9f
add tests 2022-03-26 15:34:07 +09:00
Shogo Takata b85c3035fe
Config to toggle the run button on codeblocks 2022-03-26 14:50:47 +09:00
Clark 6899d94027 livereload uses host&port of current page; livereload works with a HTTPS site 2022-03-19 04:38:16 +08:00
Eric Huss fa0f9df497
Merge pull request #1763 from Dylan-DPC/fix/regex-dep
update regex
2022-03-09 09:10:20 -08:00
Dylan DPC 917df6e97d update regex 2022-03-09 08:04:50 +01:00
Eric Huss 5921f59817
Merge pull request #1754 from PiDelport/patch-1
style: add missing word
2022-02-22 09:12:26 -08:00
Pi Delport 50bad7f983
style: add missing word 2022-02-22 18:00:41 +02:00
Tom Milligan 972c61fa76
search: fix anchor ids for duplicate headers 2022-02-18 16:20:05 +00:00
ilslv b73d02fb8c Remove colon to satisfy msrv 2022-02-11 12:50:22 +03:00
ilslv 6c4974b5c6 Fmt 2022-02-11 12:42:54 +03:00
ilslv 81d661c4f1 Fix no initial title consuming events. 2022-02-11 12:33:22 +03:00
Eric Huss 2213312938
Merge pull request #1728 from dmorawetz/master
Make page-break configurable
2022-01-30 14:25:02 -08:00
Jade Lovelace 4ae7ab5e87 switch to break-word as suggested 2022-01-27 18:42:39 -08:00
Jade 59569984e2 Address review: use overflow-wrap everywhere 2022-01-27 18:41:31 -08:00
Jade 89b580ab52 Add a test for the table div-wrapping 2022-01-27 18:41:31 -08:00
Jade 85df785cd3 Wrap tables in an overflow-x wrapper div 2022-01-27 18:41:30 -08:00
Jade fde88c22a8 Fix an x overflow with long inline code
Spotted on
https://rust-lang.github.io/rfcs/2603-rust-symbol-name-mangling-v0.html
2022-01-27 18:39:09 -08:00
Eric Huss 0ec4b692f4
Merge pull request #1732 from dtolnay-contrib/semver
Update semver dev-dependency in nop-preprocessor example to 1.0
2022-01-24 05:59:31 -08:00
David Tolnay c5c8f1a6d3
Update semver dev-dependency in nop-preprocessor example to 1.0 2022-01-23 00:01:10 -08:00
Eric Huss 336640633b
Merge pull request #1729 from GuillaumeGomez/update-pulldown
Update pulldown-cmark version
2022-01-18 09:08:46 -08:00
Guillaume Gomez 4206739492 Update pulldown-cmark version 2022-01-18 16:01:23 +01:00
Daniel Morawetz 9e6217871e
add page-break option to docs 2022-01-17 18:07:22 +01:00
Daniel Morawetz 7b1241d0f2
Revert "Make page-break not configurable"
This reverts commit 0eb23efd44.
2022-01-17 18:03:52 +01:00
Eric Huss 9acc0debec
Merge pull request #1727 from calebcartwright/cargo-fmt-check
use new cargo fmt option
2022-01-13 16:06:29 -08:00
Caleb Cartwright a226de38b6
ci: use new cargo fmt option 2022-01-13 18:01:42 -06:00
josh rotenberg f5b0b1934a Merge remote-tracking branch 'upstream/master' into draft-no-index 2022-01-11 09:53:43 -08:00
Eric Huss 64838ce07d
Merge pull request #1723 from klensy/pc
don't use additional features for pulldown-cmark
2022-01-07 10:57:01 -08:00
klensy 526a0394b0 don't use additional features for pulldown-cmark 2022-01-06 20:35:13 +03:00
Eric Huss 6ed570940d
Merge pull request #1721 from ehuss/bump-version
Update to 0.4.15
2022-01-04 10:38:19 -08:00
Eric Huss b0511f408d Update to 0.4.15 2022-01-04 10:18:39 -08:00
Eric Huss 68a5c09fdf
Merge pull request #1712 from GuillaumeGomez/pulldown-cmark
Update pulldown-cmark version
2021-12-29 19:53:08 -08:00
Eric Huss 97b6a35afc
Merge pull request #1710 from ehuss/update-guide
Update documentation
2021-12-29 19:26:16 -08:00
josh rotenberg 1cacef025d check for the index.html file first 2021-12-28 21:00:06 -08:00
Guillaume Gomez ddb0d2396f Update pulldown-cmark version 2021-12-25 13:46:27 +01:00
Eric Huss f2fba30786 Update documentation 2021-12-19 20:26:37 -08:00
Eric Huss f3e5fce6bf
Merge pull request #1709 from joshrotenberg/markdown-page
Markdown page
2021-12-19 13:26:41 -08:00
josh rotenberg 2d36cd9263 more markdown 2021-12-18 16:00:46 -08:00
josh rotenberg 09087097b5 add more sections here 2021-12-18 15:50:51 -08:00
josh rotenberg 18b9f42fba add the markdown page 2021-12-17 19:08:58 -08:00
josh rotenberg b38949a408 add a markdown page 2021-12-17 19:08:21 -08:00
josh rotenberg c32869cf10 moving this to the markdown page 2021-12-17 19:08:03 -08:00
Eric Huss e3e170715e
Merge pull request #1706 from vaporup/patch-1
Fix Typo in summary.md
2021-12-16 13:53:22 -08:00
Eric Huss a387f482d8
Merge pull request #1705 from acoglio/patch-1
Fix typo.
2021-12-16 13:53:01 -08:00
Sven Wick 4e03818f3e
Update summary.md 2021-12-16 18:29:47 +01:00
Alessandro Coglio 3f268cb0df
Fix typo. 2021-12-15 20:53:01 -08:00
Eric Huss 25829926e5
Merge pull request #1702 from ehuss/update-readme
Update some outdated info in the readme.
2021-12-09 17:39:24 -08:00
Eric Huss c828002b70 Update some outdated info in the readme. 2021-12-09 17:29:40 -08:00
Eric Huss cdbfb4a5b9
Merge pull request #1699 from joshrotenberg/patch-1
Fix typo
2021-12-06 08:31:27 -08:00
josh rotenberg 191dccab10
fix typo 2021-12-06 08:06:07 -08:00
Eric Huss 5eb7d46a99
Merge pull request #1696 from ehuss/bump-version
Update to 0.4.14
2021-11-27 10:34:17 -08:00
Eric Huss dffcedf031 Update to 0.4.14 2021-11-27 10:10:51 -08:00
Eric Huss c9b6be8660
Merge pull request #1675 from ehuss/deprecate-ga
Deprecate google-analytics
2021-11-27 09:53:44 -08:00
Eric Huss 23af80c506
Merge pull request #1685 from igxactly/fix-ios-text-size-landscape
Prevent iOS Safari from enlarging text in landscape orientation
2021-11-20 10:01:41 -08:00
Eric Huss 857acb9759
Merge pull request #1683 from abdnh/strip-html-from-anchor-ids
Strip HTML tags from anchor IDs
2021-11-20 09:47:18 -08:00
Eric Huss 2ddcb43899
Merge pull request #1668 from joshrotenberg/update-pulldown-cmark
Update pulldown-cmark to 0.8.0 and use Smart Punctuation feature
2021-11-20 08:33:45 -08:00
josh rotenberg 1c0983b811 use pulldown cmarks curly quotes 2021-11-19 17:26:30 -08:00
josh rotenberg 1be69af553 need to actually enable it 2021-11-19 15:14:22 -08:00
josh rotenberg c63000f365 Merge remote-tracking branch 'upstream/master' into update-pulldown-cmark 2021-11-19 15:01:04 -08:00
Ingu Kang bbaa0ea1fa Prevent iOS Safari from enlarging text in landscape orientation
referernces:
  - https://developer.mozilla.org/en-US/docs/Web/CSS/text-size-adjust#basic_disabling_usage
  - https://trac.webkit.org/changeset/261940/webkit
2021-11-13 15:37:20 +09:00
Abdo 58bc92d380 Strip HTML tags from anchor IDs 2021-11-10 00:41:44 +03:00
Eric Huss 17d1ed3716
Merge pull request #1676 from YJDoc2/add_triagebot
Add minimal traigebot config
2021-10-27 09:31:40 -07:00
Yashodhan Joshi 8df8ce063d Add minimal traigebot config 2021-10-27 19:31:08 +05:30
Eric Huss c3ff4a5129 Deprecate google-analytics. 2021-10-24 11:43:37 -07:00
Eric Huss 4d20fa578b
Merge pull request #1642 from ehuss/stabilize-2021
Stabilize 2021 flag.
2021-10-24 10:30:55 -07:00
Eric Huss 9e47498458
Merge pull request #1662 from YJDoc2/test-book
Add a Test Book to verify style changes against
2021-10-24 10:01:22 -07:00
josh rotenberg 903469a45f update pulldown-cmark to 0.8.0 2021-10-13 20:27:00 -07:00
Yashodhan Joshi b8ef89db62 Add rust specific codeblock examples 2021-10-05 12:11:29 +05:30
Yashodhan Joshi c283211a37 Add Languages examples for syntax highlighting 2021-10-05 12:02:33 +05:30
Yashodhan Joshi d5af051d0e Add individual tag examples in test_book 2021-10-04 16:17:52 +05:30
Yashodhan Joshi 68f9afe64b Setup basic structure for test book 2021-10-04 13:14:49 +05:30
Eric Huss ffa8284743
Merge pull request #1661 from ehuss/bump-version
Update to 0.4.13
2021-10-03 15:39:23 -07:00
Eric Huss 3e91f9cd5d Update to 0.4.13 2021-10-03 15:15:21 -07:00
Eric Huss f55028b61a
Merge pull request #1657 from lclc/gitlabPages
Documentation: CI: GitLab Pages Example
2021-10-03 14:51:08 -07:00
Eric Huss 0d887505af
Merge pull request #1607 from ISSOtm/preproc-order
Allow specifying preprocessor order
2021-10-03 14:22:15 -07:00
Eric Huss 6c20736a55
Merge pull request #1658 from pickfire/patch-1
Remove extra semicolon in variables.css
2021-10-03 12:24:11 -07:00
Ivan Tham e4a46c9477
Remove extra semicolon in variables.css 2021-10-03 22:23:09 +08:00
Ivan Tham 6ae5c686d9
Remove extra semicolon in variables.css 2021-10-03 22:19:51 +08:00
Lucas Betschart b862080006 Documentation: CI: GitLab Pages Example
Replace 'only' with 'rules'.
'only' is not actively developed anymore with GitLab (see https://docs.gitlab.com/ee/ci/yaml/#only--except)
2021-09-29 12:42:15 +02:00
ISSOtm 6b790b83ec Warn when preproc order references unknown preprocs 2021-09-28 09:25:17 +02:00
ISSOtm d8ad68c947 Produce an error if `before` or `after` field is not a table 2021-09-27 21:28:21 +02:00
ISSOtm 6b784be616 Stabilize ties in preproc order through name sort 2021-09-27 21:28:01 +02:00
Eric Huss f5598b2eee
Merge pull request #1656 from johannst/switch-to-opener-crate
switch from open to opener
2021-09-26 12:33:23 -07:00
Johannes Stoelp ff4b8e7a8d switch from open to opener
By default, `opener` launches the subprocess without waiting for its
completion, compared to `open` which waits for its completion.

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

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

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

use book_config.src for edit path

fmt

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

restructure guide configuration section

redirect to new config top level

fix broken links

use a relative path for redirect

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

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

add invalid_title_type

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

* Bump MSRV to 1.42.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

After:

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

8
.gitattributes vendored
View File

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

45
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Bug Report
description: Create a report to help us improve
labels: ["C-bug"]
body:
- type: markdown
attributes:
value: Thanks for filing a 🐛 bug report 😄!
- type: textarea
id: problem
attributes:
label: Problem
description: >
Please provide a clear and concise description of what the bug is,
including what currently happens and what you expected to happen.
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps
description: Please list the steps to reproduce the bug.
placeholder: |
1.
2.
3.
- type: textarea
id: possible-solutions
attributes:
label: Possible Solution(s)
description: >
Not obligatory, but suggest a fix/reason for the bug,
or ideas how to implement the addition or change.
- type: textarea
id: notes
attributes:
label: Notes
description: Provide any additional notes that might be helpful.
- type: textarea
id: version
attributes:
label: Version
description: >
Please paste the output of running `mdbook --version` or which version
of the library you are using.
render: text

View File

@ -0,0 +1,28 @@
name: Enhancement
description: Suggest an idea for enhancing mdBook
labels: ["C-enhancement"]
body:
- type: markdown
attributes:
value: |
Thanks for filing a 🙋 feature request 😄!
- type: textarea
id: problem
attributes:
label: Problem
description: >
Please provide a clear description of your use case and the problem
this feature request is trying to solve.
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed Solution
description: >
Please provide a clear and concise description of what you want to happen.
- type: textarea
id: notes
attributes:
label: Notes
description: Provide any additional context or information that might be helpful.

24
.github/ISSUE_TEMPLATE/question.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: Question
description: Have a question on how to use mdBook?
labels: ["C-question"]
body:
- type: markdown
attributes:
value: |
Got a question on how to do something with mdBook?
- type: textarea
id: question
attributes:
label: Question
description: >
Enter your question here. Please try to provide as much detail as possible.
validations:
required: true
- type: textarea
id: version
attributes:
label: Version
description: >
Please paste the output of running `mdbook --version` or which version
of the library you are using.
render: text

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

@ -0,0 +1,73 @@
name: Deploy
on:
release:
types: [created]
defaults:
run:
shell: bash
permissions:
contents: write
jobs:
release:
name: Deploy Release
runs-on: ${{ matrix.os }}
strategy:
matrix:
target:
- aarch64-unknown-linux-musl
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
- x86_64-apple-darwin
- x86_64-pc-windows-msvc
include:
- target: aarch64-unknown-linux-musl
os: ubuntu-20.04
- target: x86_64-unknown-linux-gnu
os: ubuntu-20.04
- target: x86_64-unknown-linux-musl
os: ubuntu-20.04
- target: x86_64-apple-darwin
os: macos-latest
- target: x86_64-pc-windows-msvc
os: windows-latest
steps:
- uses: actions/checkout@master
- name: Install Rust
run: ci/install-rust.sh stable ${{ matrix.target }}
- name: Build asset
run: ci/make-release-asset.sh ${{ matrix.os }} ${{ matrix.target }}
- name: Update release with new asset
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload $MDBOOK_TAG $MDBOOK_ASSET
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
publish:
name: Publish to crates.io
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable
- name: Publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish --no-verify

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

@ -0,0 +1,66 @@
name: CI
on:
pull_request:
merge_group:
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-20.04
# sync MSRV with docs: guide/src/guide/installation.md and Cargo.toml
rust: 1.71.0
steps:
- uses: actions/checkout@v3
- name: Install Rust
run: bash ci/install-rust.sh ${{ matrix.rust }}
- name: Build and run tests
run: cargo test --locked
- name: Test no default
run: cargo test --no-default-features
rustfmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
run: rustup update stable && rustup default stable && rustup component add rustfmt
- run: cargo fmt --check
# The success job is here to consolidate the total success/failure state of
# all other jobs. This job is then included in the GitHub branch protection
# rule which prevents merges unless all other jobs are passing. This makes
# it easier to manage the list of jobs via this yml file and to prevent
# accidentally adding new jobs without also updating the branch protections.
success:
name: Success gate
if: always()
needs:
- test
- rustfmt
runs-on: ubuntu-latest
steps:
- run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'
- name: Done
run: exit 0

9
.gitignore vendored
View File

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

View File

@ -1,48 +0,0 @@
language: rust
rust:
- stable
- beta
- nightly
os:
- linux
- osx
cache:
timeout: 360
cargo: true
before_cache:
- chmod -R a+r $HOME/.cargo
env:
global:
- CRATE_NAME=mdbook
script:
- cargo test --all
- cargo test --all --no-default-features
before_deploy:
- sh ci/before_deploy.sh
deploy:
provider: releases
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
skip_cleanup: true
branches:
only:
- master
- /^v\d+\.\d+\.\d+.*$/
notifications:
email:
on_success: never

1070
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

3
CODE_OF_CONDUCT.md Normal file
View File

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

View File

@ -5,33 +5,41 @@ Welcome stranger!
If you have come here to learn how to contribute to mdBook, we have some tips for you! 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! 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:
### Issue assignment
**:warning: Important :warning:**
Before working on pull request, please ping us on the corresponding issue.
The current PR backlog is beyond what we can process at this time.
Only issues that have an [`E-Help-wanted`](https://github.com/rust-lang/mdBook/labels/E-Help-wanted) or [`Feature accepted`](https://github.com/rust-lang/mdBook/labels/Feature%20accepted) label will likely receive reviews.
If there isn't already an open issue for what you want to work on, please open one first to see if it is something we would be available to review.
### Issues to work on ### Issues to work on
Any issue is up for the grabbing, but if you are starting out, you might be interested in the 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. 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 These issues can be a good launching pad for more involved issues.
include documentation improvements, new tests, examples, updating dependencies, etc. 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 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-JavaScript](https://github.com/rust-lang/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-Style](https://github.com/rust-lang/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-HTML](https://github.com/rust-lang/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-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. When you decide you want to work on a specific issue, and it isn't already assigned to someone else, assign the issue to yourself by leaving a comment with the text `@rustbot claim`.
Again, do not hesitate to ask questions. We will gladly mentor anyone that want to tackle an issue. Again, do not hesitate to ask questions. We will gladly mentor anyone that want to tackle an issue.
Issues on the issue tracker are categorized with the following labels: Issues on the issue tracker are categorized with the following labels:
- **A**-prefixed labels state which area of the project an issue relates to. - **A**-prefixed labels state which area of the project an issue relates to.
- **E**-prefixed labels show an estimate of the experience necessary to fix the issue. - **E**-prefixed labels show an estimate of the experience necessary to fix the issue.
- **M**-prefixed labels are meta-issues used for questions, discussions, or tracking issues - **M**-prefixed labels are meta-issues regarding the management of the mdBook project itself
- **S**-prefixed labels show the status of the issue - **S**-prefixed labels show the status of the issue
- **T**-prefixed labels show the type of issue - **C**-prefixed labels show the category of issue
### Building mdBook ### Building mdBook
@ -41,20 +49,127 @@ mdBook builds on stable Rust, if you want to build mdBook from source, here are
0. Clone this repository with git. 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. Navigate into the newly created `mdBook` directory
0. Run `cargo build` 0. Run `cargo build`
The resulting binary can be found in `mdBook/target/debug/` under the name `mdBook` or `mdBook.exe`. The resulting binary can be found in `mdBook/target/debug/` under the name `mdbook` or `mdbook.exe`.
### Code Quality
We love code quality and Rust has some excellent tools to assist you with contributions.
#### Formatting Code with rustfmt
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.
[rustfmt](https://github.com/rust-lang/rustfmt) has a lot more information on the project.
The quick guide is
1. Install it (`rustfmt` is usually installed by default via [rustup](https://rustup.rs/)):
```
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 package.
For more information, such as running it from your favourite editor, please see the `rustfmt` project. [rustfmt](https://github.com/rust-lang/rustfmt)
#### Finding Issues with Clippy
[Clippy](https://doc.rust-lang.org/clippy/) is a code analyser/linter detecting mistakes, and therefore helps to improve your code.
Like formatting your code with `rustfmt`, running clippy regularly and before your Pull Request will help us maintain awesome code.
1. To install
```
rustup component add clippy
```
2. Running clippy
```
cargo clippy
```
### Change requirements
Please consider the following when making a change:
* Almost all changes that modify the Rust code must be accompanied with a test.
* Almost all features and changes must update the documentation.
mdBook has the [mdBook Guide](https://rust-lang.github.io/mdBook/) whose source is at <https://github.com/rust-lang/mdBook/tree/master/guide>.
* Almost all Rust items should be documented with doc comments.
See the [Rustdoc Book](https://doc.rust-lang.org/rustdoc/) for more information on writing doc comments.
* Breaking the API can only be done in major SemVer releases.
These are done very infrequently, so it is preferred to avoid these when possible.
See [SemVer Compatibility](https://doc.rust-lang.org/cargo/reference/semver.html) for more information on what a SemVer breaking change is.
(Note: At this time, some SemVer breaking changes are inevitable due to the current code structure.
An example is adding new fields to the config structures.
These are intended to be fixed in the next major release.)
* Similarly, the CLI interface is considered to be stable.
Care should be taken to avoid breaking existing workflows.
* Check out the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/) for guidelines on designing the API.
### Making a pull-request ### Making a pull-request
When you feel comfortable that your changes could be integrated into mdBook, you can create a pull-request on GitHub. When you feel comfortable that your changes could be integrated into mdBook, you can create a pull-request on GitHub.
One of the core maintainers will then approve the changes or request some changes before it gets merged. 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.
This is not a requirement though and will never block a pull-request from being merged.
That's it, happy contributions! :tada: :tada: :tada: That's it, happy contributions! :tada: :tada: :tada:
## Browser compatibility and testing
Currently we don't have a strict browser compatibility matrix due to our limited resources.
We generally strive to keep mdBook compatible with a relatively recent browser on all of the most major platforms.
That is, supporting Chrome, Safari, Firefox, Edge on Windows, macOS, Linux, iOS, and Android.
If possible, do your best to avoid breaking older browser releases.
Any change to the HTML or styling is encouraged to manually check on as many browsers and platforms that you can.
Unfortunately at this time we don't have any automated UI or browser testing, so your assistance in testing is appreciated.
## Updating highlight.js
The following are instructions for updating [highlight.js](https://highlightjs.org/).
1. Clone the repository at <https://github.com/highlightjs/highlight.js>
1. Check out a tagged release (like `10.1.1`).
1. Run `npm install`
1. Run `node tools/build.js :common apache armasm coffeescript d handlebars haskell http julia nginx nim nix properties r scala x86asm yaml`
1. Compare the language list that it spits out to the one in [`syntax-highlighting.md`](https://github.com/camelid/mdBook/blob/master/guide/src/format/theme/syntax-highlighting.md). If any are missing, add them to the list and rebuild (and update these docs). If any are added to the common set, add them to `syntax-highlighting.md`.
1. Copy `build/highlight.min.js` to mdbook's directory [`highlight.js`](https://github.com/rust-lang/mdBook/blob/master/src/theme/highlight.js).
1. Be sure to check the highlight.js [CHANGES](https://github.com/highlightjs/highlight.js/blob/main/CHANGES.md) for any breaking changes. Breaking changes that would affect users will need to wait until the next major release.
1. Build mdbook with the new file and build some books with the new version and compare the output with a variety of languages to see if anything changes. The [test_book](https://github.com/rust-lang/mdBook/tree/master/test_book) contains a chapter with many languages to examine.
## Publishing new releases
Instructions for mdBook maintainers to publish a new release:
1. Create a PR to update the version and update the CHANGELOG:
1. Update the version in `Cargo.toml`
2. Run `cargo test` to verify that everything is passing, and to update `Cargo.lock`.
3. Double-check for any SemVer breaking changes.
Try [`cargo-semver-checks`](https://crates.io/crates/cargo-semver-checks), though beware that the current version of mdBook isn't properly adhering to SemVer due to the lack of `#[non_exhaustive]` and other issues. See https://github.com/rust-lang/mdBook/issues/1835.
4. Update `CHANGELOG.md` with any changes that users may be interested in.
5. Update `continuous-integration.md` to update the version number for the installation instructions.
6. Commit the changes, and open a PR.
2. After the PR has been merged, create a release in GitHub. This can either be done in the GitHub web UI, or on the command-line:
```bash
MDBOOK_VERS="`cargo read-manifest | jq -r .version`" ; \
gh release create -R rust-lang/mdbook v$MDBOOK_VERS \
--title v$MDBOOK_VERS \
--notes "See https://github.com/rust-lang/mdBook/blob/master/CHANGELOG.md#mdbook-${MDBOOK_VERS//.} for a complete list of changes."
```

2555
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,66 +1,74 @@
[package] [package]
name = "mdbook" name = "mdbook"
version = "0.2.2-alpha.0" version = "0.4.37"
authors = [ authors = [
"Mathieu David <mathieudavid@mathieudavid.org>", "Mathieu David <mathieudavid@mathieudavid.org>",
"Michael-F-Bryan <michaelfbryan@gmail.com>", "Michael-F-Bryan <michaelfbryan@gmail.com>",
"Matt Ickstadt <mattico8@gmail.com>" "Matt Ickstadt <mattico8@gmail.com>"
] ]
description = "Create books from markdown files" documentation = "https://rust-lang.github.io/mdBook/index.html"
documentation = "http://rust-lang-nursery.github.io/mdBook/index.html" edition = "2021"
repository = "https://github.com/rust-lang-nursery/mdBook" exclude = ["/guide/*"]
keywords = ["book", "gitbook", "rustbook", "markdown"] keywords = ["book", "gitbook", "rustbook", "markdown"]
license = "MPL-2.0" license = "MPL-2.0"
readme = "README.md" readme = "README.md"
exclude = ["book-example/*"] repository = "https://github.com/rust-lang/mdBook"
description = "Creates a book from markdown files"
rust-version = "1.71"
[dependencies] [dependencies]
clap = "2.24" anyhow = "1.0.71"
chrono = "0.4" chrono = { version = "0.4.24", default-features = false, features = ["clock"] }
handlebars = "1.0" clap = { version = "4.3.12", features = ["cargo", "wrap_help"] }
serde = "1.0" clap_complete = "4.3.2"
serde_derive = "1.0" once_cell = "1.17.1"
error-chain = "0.12" env_logger = "0.11.1"
serde_json = "1.0" handlebars = "5.0"
pulldown-cmark = "0.1.2" log = "0.4.17"
lazy_static = "1.0" memchr = "2.5.0"
log = "0.4" opener = "0.6.1"
env_logger = "0.5" pulldown-cmark = { version = "0.10.0", default-features = false, features = ["html"] }
toml = "0.4" regex = "1.8.1"
memchr = "2.0" serde = { version = "1.0.163", features = ["derive"] }
open = "1.1" serde_json = "1.0.96"
regex = "1.0.0" shlex = "1.3.0"
tempfile = "3.0" tempfile = "3.4.0"
itertools = "0.7" toml = "0.5.11" # Do not update, see https://github.com/rust-lang/mdBook/issues/2037
shlex = "0.1" topological-sort = "0.2.2"
toml-query = "0.7"
# Watch feature # Watch feature
notify = { version = "4.0", optional = true } notify = { version = "6.1.1", optional = true }
notify-debouncer-mini = { version = "0.4.1", optional = true }
ignore = { version = "0.4.20", optional = true }
pathdiff = { version = "0.2.1", optional = true }
# Serve feature # Serve feature
iron = { version = "0.6", optional = true } futures-util = { version = "0.3.28", optional = true }
staticfile = { version = "0.5", optional = true } tokio = { version = "1.28.1", features = ["macros", "rt-multi-thread"], optional = true }
ws = { version = "0.7", optional = true} warp = { version = "0.3.6", default-features = false, features = ["websocket"], optional = true }
# Search feature # Search feature
elasticlunr-rs = { version = "2.3", optional = true, default-features = false } elasticlunr-rs = { version = "3.0.2", optional = true }
ammonia = { version = "1.1", optional = true } ammonia = { version = "3.3.0", optional = true }
[dev-dependencies] [dev-dependencies]
select = "0.4" assert_cmd = "2.0.11"
pretty_assertions = "0.5" predicates = "3.0.3"
walkdir = "2.0" select = "0.6.0"
pulldown-cmark-to-cmark = "1.1.0" semver = "1.0.17"
pretty_assertions = "1.3.0"
walkdir = "2.3.3"
[features] [features]
default = ["output", "watch", "serve", "search"] default = ["watch", "serve", "search"]
debug = [] watch = ["dep:notify", "dep:notify-debouncer-mini", "dep:ignore", "dep:pathdiff"]
output = [] serve = ["dep:futures-util", "dep:tokio", "dep:warp"]
watch = ["notify"] search = ["dep:elasticlunr-rs", "dep:ammonia"]
serve = ["iron", "staticfile", "ws"]
search = ["elasticlunr-rs", "ammonia"]
[[bin]] [[bin]]
doc = false doc = false
name = "mdbook" name = "mdbook"
[[example]]
name = "nop-preprocessor"
test = true

189
README.md
View File

@ -1,191 +1,20 @@
# mdBook # mdBook
<table> [![Build Status](https://github.com/rust-lang/mdBook/workflows/CI/badge.svg?event=push)](https://github.com/rust-lang/mdBook/actions?workflow=CI)
<tr> [![crates.io](https://img.shields.io/crates/v/mdbook.svg)](https://crates.io/crates/mdbook)
<td><strong>Linux / OS X</strong></td> [![LICENSE](https://img.shields.io/github/license/rust-lang/mdBook.svg)](LICENSE)
<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>
mdBook is a utility to create modern online books from Markdown files. mdBook is a utility to create modern online books from Markdown files.
Check out the **[User Guide]** for a list of features and installation and usage information.
The User Guide also serves as a demonstration to showcase what a book looks like.
## What does it look like? If you are interested in contributing to the development of mdBook, check out the [Contribution Guide].
The [User Guide] for mdBook has been written in Markdown and is using mdBook to
generate the online book-like website you can read. The documentation uses the
latest version on GitHub and showcases the available features.
## Installation
There are multiple ways to install mdBook.
1. **Binaries**
Binaries are available for download [here][releases]. Make sure to put the
path to the binary into your `PATH`.
2. **From Crates.io**
This requires at least [Rust] 1.20 and Cargo to be installed. Once you have installed
Rust, type the following in the terminal:
```
cargo install mdbook
```
This will download and compile mdBook for you, the only thing left to do is
to add the Cargo bin directory to your `PATH`.
**Note for automatic deployment**
If you are using a script to do automatic deployments using Travis or
another CI server, we recommend that you specify a semver version range for
mdBook when you install it through your script!
This will constrain the server to install the latests **non-breaking**
version of mdBook and will prevent your books from failing to build because
we released a new version. For example:
```
cargo install mdbook --vers "^0.1.0"
```
3. **From Git**
The version published to crates.io will ever so slightly be behind the
version hosted here on GitHub. If you need the latest version you can build
the git version of mdBook yourself. Cargo makes this ***super easy***!
```
cargo install --git https://github.com/rust-lang-nursery/mdBook.git mdbook
```
Again, make sure to add the Cargo bin directory to your `PATH`.
4. **For Contributions**
If you want to contribute to mdBook you will have to clone the repository on
your local machine:
```
git clone https://github.com/rust-lang-nursery/mdBook.git
```
`cd` into `mdBook/` and run
```
cargo build
```
The resulting binary can be found in `mdBook/target/debug/` under the name
`mdBook` or `mdBook.exe`.
## Usage
mdBook will primarily be used as a command line tool, even though it exposes
all its functionality as a Rust crate for integration in other projects.
Here are the main commands you will want to run. For a more exhaustive
explanation, check out the [User Guide].
- `mdbook init`
The init command will create a directory with the minimal boilerplate to
start with.
```
book-test/
├── book
└── src
├── chapter_1.md
└── SUMMARY.md
```
`book` and `src` are both directories. `src` contains the markdown files
that will be used to render the output to the `book` directory.
Please, take a look at the [CLI docs] for more information and some neat tricks.
- `mdbook build`
This is the command you will run to render your book, it reads the
`SUMMARY.md` file to understand the structure of your book, takes the
markdown files in the source directory as input and outputs static html
pages that you can upload to a server.
- `mdbook watch`
When you run this command, mdbook will watch your markdown files to rebuild
the book on every change. This avoids having to come back to the terminal
to type `mdbook build` over and over again.
- `mdbook serve`
Does the same thing as `mdbook watch` but additionally serves the book at
`http://localhost:3000` (port is changeable) and reloads the browser when a
change occurs.
- `mdbook clean`
Delete directory in which generated book is located.
### As a library
Aside from the command line interface, this crate can also be used as a
library. This means that you could integrate it in an existing project, like a
web-app for example. Since the command line interface is just a wrapper around
the library functionality, when you use this crate as a library you have full
access to all the functionality of the command line interface with an easy to
use API and more!
See the [User Guide] and the [API docs] for more information.
## Contributions
Contributions are highly appreciated and encouraged! Don't hesitate to
participate to discussions in the issues, propose new features and ask for
help.
If you are just starting out with Rust, there are a series of issus 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
you go through the build and contribution process!
There is also a [rendered version][master-docs] of the latest API docs
available, for those hacking on `master`.
## License ## License
All the code in this repository is released under the ***Mozilla Public License v2.0***, for more information take a look at the [LICENSE] file. All the code in this repository is released under the ***Mozilla Public License v2.0***, for more information take a look at the [LICENSE] file.
[User Guide]: https://rust-lang.github.io/mdBook/
[User Guide]: https://rust-lang-nursery.github.io/mdBook/ [contribution guide]: https://github.com/rust-lang/mdBook/blob/master/CONTRIBUTING.md
[API docs]: https://docs.rs/mdbook/*/mdbook/ [LICENSE]: https://github.com/rust-lang/mdBook/blob/master/LICENSE
[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
[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/

View File

@ -1,64 +0,0 @@
environment:
global:
PROJECT_NAME: mdBook
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
build: false
# Equivalent to Travis' `script` phase
test_script:
- cargo test --all
- cargo test --all --no-default-features
before_deploy:
# Generate artifacts for release
- cargo rustc --bin mdbook --release -- -C lto
- 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
- /^v\d+\.\d+\.\d+.*$/

View File

@ -1,19 +0,0 @@
[book]
title = "mdBook Documentation"
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
authors = ["Mathieu David", "Michael-F-Bryan"]
[output.html]
mathjax-support = true
[output.html.playpen]
editable = true
[output.html.search]
limit-results = 20
use-boolean-and = true
boost-title = 2
boost-hierarchy = 2
boost-paragraph = 1
expand = true
heading-split-level = 2

View File

@ -1,25 +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,55 +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 From Binaries
Precompiled binaries are provided for major platforms on a best-effort basis.
Visit [the releases page](https://github.com/rust-lang-nursery/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/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 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-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,49 +0,0 @@
# 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.
#### 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 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. If not specified it will default to the value of the `build.build-dir` key
in `book.toml`, or to `./book` relative to the book's root directory.
-----
***Note:*** *The `serve` command is for testing, and is not intended to be a
complete HTTP server for a website.*

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
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. If not specified it will default to the value of the `build.build-dir` key
in `book.toml`, or to `./book` relative to the book's root directory.

View File

@ -1,56 +0,0 @@
# 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.1" mdbook)
- cargo install-update -a
script:
- cd path/to/mybook && mdbook build && mdbook test
```
## 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!

View File

@ -1,109 +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: Book) -> Result<Book>;
fn supports_renderer(&self, _renderer: &str) -> bool {
true
}
}
```
Where the `PreprocessorContext` is defined as
```rust
pub struct PreprocessorContext {
pub root: PathBuf,
pub config: Config,
/// The `Renderer` this preprocessor is being used with.
pub renderer: String,
}
```
The `renderer` value allows you react accordingly, for example, PDF or HTML.
## 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 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-nursery/mdBook/blob/master/examples/de-emphasize.rs

View File

@ -1,259 +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
[preprocess.index]
[preprocess.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 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.
- **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 `{{ #playpen }}` and `{{ #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
[preprocess.links]
[preprocess.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
```
[preprocess.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.
```
[preprocessor.mathjax]
renderers = ["html"] # mathjax only makes sense with the HTML renderer
```
## 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.
- **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:
- **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 `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."
[build]
build-dir = "book"
create-missing = true
preprocess = ["links", "index"]
[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
copy-js = true
```
## 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,6 +0,0 @@
fn main() {
println!("Hello World!");
#
# // You can even hide lines! :D
# println!("I am hidden! Expand the code snippet to see me");
}

View File

@ -1,74 +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,38 +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,34 +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,70 +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 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-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,3 +0,0 @@
# Introduction
A frontmatter chapter.

View File

@ -1,32 +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
# This will slow down the build, but is necessary to not run out of disk space
cargo clean
cargo 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

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

@ -0,0 +1,30 @@
#!/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
if [ -n "$2" ]
then
TARGET="$2"
HOST=$(rustc -Vv | grep ^host: | sed -e "s/host: //g")
if [ "$HOST" != "$TARGET" ]
then
rustup component add llvm-tools-preview --toolchain=$TOOLCHAIN
rustup component add rust-std-$TARGET --toolchain=$TOOLCHAIN
fi
fi
rustup default $TOOLCHAIN
rustup -V
rustc -Vv
cargo -V

53
ci/make-release-asset.sh Executable file
View File

@ -0,0 +1,53 @@
#!/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")
target=$2
if [ "$host" != "$target" ]
then
export "CARGO_TARGET_$(echo $target | tr a-z- A-Z_)_LINKER"=rust-lld
fi
export CARGO_PROFILE_RELEASE_LTO=true
cargo build --locked --bin mdbook --release --target $target
cd target/$target/release
case $1 in
ubuntu*)
asset="mdbook-$TAG-$target.tar.gz"
tar czf ../../$asset mdbook
;;
macos*)
asset="mdbook-$TAG-$target.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-$target.zip"
7z a ../../$asset mdbook.exe
;;
*)
echo "OS should be first parameter, was: $1"
;;
esac
cd ../..
if [[ -z "$GITHUB_ENV" ]]
then
echo "GITHUB_ENV not set, run: gh release upload $TAG target/$asset"
else
echo "MDBOOK_TAG=$TAG" >> $GITHUB_ENV
echo "MDBOOK_ASSET=target/$asset" >> $GITHUB_ENV
fi

View File

@ -1,98 +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::book::{Book, BookItem, Chapter};
use mdbook::errors::{Error, Result};
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
use mdbook::MDBook;
use pulldown_cmark::{Event, Parser, Tag};
use pulldown_cmark_to_cmark::fmt::cmark;
use std::env::{args, args_os};
use std::ffi::OsString;
use std::process;
const NAME: &str = "md-links-to-html-links";
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);
}
}
struct Deemphasize;
impl Preprocessor for Deemphasize {
fn name(&self) -> &str {
NAME
}
fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result<Book> {
eprintln!("Running '{}' preprocessor", self.name());
let mut num_removed_items = 0;
process(&mut book.sections, &mut num_removed_items)?;
eprintln!(
"{}: removed {} events from markdown stream.",
self.name(),
num_removed_items
);
Ok(book)
}
}
fn process<'a, I>(items: I, num_removed_items: &mut usize) -> Result<()>
where
I: IntoIterator<Item = &'a mut BookItem> + 'a,
{
for item in items {
if let BookItem::Chapter(ref mut chapter) = *item {
eprintln!("{}: processing chapter '{}'", NAME, chapter.name);
let md = remove_emphasis(num_removed_items, chapter)?;
chapter.content = md;
}
}
Ok(())
}
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))
})
}

View File

@ -0,0 +1,160 @@
use crate::nop_lib::Nop;
use clap::{Arg, ArgMatches, Command};
use mdbook::book::Book;
use mdbook::errors::Error;
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
use semver::{Version, VersionReq};
use std::io;
use std::process;
pub fn make_app() -> Command {
Command::new("nop-preprocessor")
.about("A mdbook preprocessor which does precisely nothing")
.subcommand(
Command::new("supports")
.arg(Arg::new("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())?;
let book_version = Version::parse(&ctx.mdbook_version)?;
let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?;
if !version_req.matches(&book_version) {
eprintln!(
"Warning: The {} plugin was built against version {} of mdbook, \
but we're being called from version {}",
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
.get_one::<String>("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"
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn nop_preprocessor_run() {
let input_json = r##"[
{
"root": "/path/to/book",
"config": {
"book": {
"authors": ["AUTHOR"],
"language": "en",
"multilingual": false,
"src": "src",
"title": "TITLE"
},
"preprocessor": {
"nop": {}
}
},
"renderer": "html",
"mdbook_version": "0.4.21"
},
{
"sections": [
{
"Chapter": {
"name": "Chapter 1",
"content": "# Chapter 1\n",
"number": [1],
"sub_items": [],
"path": "chapter_1.md",
"source_path": "chapter_1.md",
"parent_names": []
}
}
],
"__non_exhaustive": null
}
]"##;
let input_json = input_json.as_bytes();
let (ctx, book) = mdbook::preprocess::CmdPreprocessor::parse_input(input_json).unwrap();
let expected_book = book.clone();
let result = Nop::new().run(&ctx, book);
assert!(result.is_ok());
// The nop-preprocessor should not have made any changes to the book content.
let actual_book = result.unwrap();
assert_eq!(actual_book, expected_book);
}
}
}

33
guide/book.toml Normal file
View File

@ -0,0 +1,33 @@
[book]
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/"
git-repository-url = "https://github.com/rust-lang/mdBook/tree/master/guide"
edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
[output.html.playground]
editable = true
line-numbers = true
[output.html.code.hidelines]
python = "~"
[output.html.search]
limit-results = 20
use-boolean-and = true
boost-title = 2
boost-hierarchy = 2
boost-paragraph = 1
expand = true
heading-split-level = 2
[output.html.redirect]
"/format/config.html" = "configuration/index.html"

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!

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

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

View File

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

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

@ -0,0 +1,14 @@
# Command Line Tool
The `mdbook` command-line tool is used to create and build books.
After you have [installed](../guide/installation.md) `mdbook`, you can run the `mdbook help` command in your terminal to view the available commands.
This following sections provide in-depth information on the different commands available.
* [`mdbook init <directory>`](init.md) — Creates a new book with minimal boilerplate to start with.
* [`mdbook build`](build.md) — Renders the book.
* [`mdbook watch`](watch.md) — Rebuilds the book any time a source file changes.
* [`mdbook serve`](serve.md) — Runs a web server to view the book, and rebuilds on changes.
* [`mdbook test`](test.md) — Tests Rust code samples.
* [`mdbook clean`](clean.md) — Deletes the rendered output.
* [`mdbook completions`](completions.md) — Support for shell auto-completion.

View File

@ -7,7 +7,8 @@ mdbook build
``` ```
It will try to parse your `SUMMARY.md` file to understand the structure of your It will try to parse your `SUMMARY.md` file to understand the structure of your
book and fetch the corresponding files. book and fetch the corresponding files. Note that this will also create files
mentioned in `SUMMARY.md` which are not yet present.
The rendered output will maintain the same directory structure as the source for The rendered output will maintain the same directory structure as the source for
convenience. Large books will therefore remain structured when rendered. convenience. Large books will therefore remain structured when rendered.
@ -29,10 +30,11 @@ your default web browser after building it.
#### --dest-dir #### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for the The `--dest-dir` (`-d`) option allows you to change the output directory for the
book. If not specified it will default to the value of the `build.build-dir` key book. Relative paths are interpreted relative to the book's root directory. If
in `book.toml`, or to `./book` relative to the book's root directory. not specified it will default to the value of the `build.build-dir` key in
`book.toml`, or to `./book`.
------------------- -------------------
***Note:*** *Make sure to run the build command in the root directory and not in ***Note:*** *The build command copies all files (excluding files with `.md` extension) from the source directory
the source directory* into the build directory.*

View File

@ -19,9 +19,9 @@ mdbook clean path/to/book
#### --dest-dir #### --dest-dir
The `--dest-dir` (`-d`) option allows you to override the book's output The `--dest-dir` (`-d`) option allows you to override the book's output
directory, which will be deleted by this command. If not specified it will directory, which will be deleted by this command. Relative paths are interpreted
default to the value of the `build.build-dir` key in `book.toml`, or to `./book` relative to the book's root directory. If not specified it will default to the
relative to the book's root directory. value of the `build.build-dir` key in `book.toml`, or to `./book`.
```bash ```bash
mdbook clean --dest-dir=path/to/book mdbook clean --dest-dir=path/to/book

View File

@ -0,0 +1,20 @@
# The completions command
The completions command is used to generate auto-completions for some common shells.
This means when you type `mdbook` in your shell, you can then press your shell's auto-complete key (usually the Tab key) and it may display what the valid options are, or finish partial input.
The completions first need to be installed for your shell:
```bash
# bash
mdbook completions bash > ~/.local/share/bash-completion/completions/mdbook
# oh-my-zsh
mdbook completions zsh > ~/.oh-my-zsh/completions/_mdbook
autoload -U compinit && compinit
```
The command prints a completion script for the given shell.
Run `mdbook completions --help` for a list of supported shells.
Where to place the completions depend on which shell you are using and your operating system.
Consult your shell's documentation for more information one where to place the script.

View File

@ -19,15 +19,15 @@ book-test/
└── SUMMARY.md └── SUMMARY.md
``` ```
- The `src` directory is were you write your book in markdown. It contains all - The `src` directory is where you write your book in markdown. It contains all
the source files, configuration files, etc. the source files, configuration files, etc.
- The `book` directory is where your book is rendered. All the output is ready - 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. 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 - The `SUMMARY.md` is the skeleton of your
book and is discussed in more detail [in another book, and is discussed in more detail [in another
chapter](../format/summary.md) chapter](../format/summary.md).
#### Tip: Generate chapters from SUMMARY.md #### Tip: Generate chapters from SUMMARY.md
@ -52,3 +52,31 @@ 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 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. overwrite a specific file, just delete it and the default file will be used.
#### --title
Specify a title for the book. If not supplied, an interactive prompt will ask for
a title.
```bash
mdbook init --title="my amazing book"
```
#### --ignore
Create a `.gitignore` file configured to ignore the `book` directory created when [building] a book.
If not supplied, an interactive prompt will ask whether it should be created.
```bash
mdbook init --ignore=none
```
```bash
mdbook init --ignore=git
```
[building]: build.md
#### --force
Skip the prompts to create a `.gitignore` and for the title for the book.

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

@ -0,0 +1,56 @@
# The serve command
The serve command is used to preview a book by serving it via HTTP at
`localhost:3000` by default:
```bash
mdbook serve
```
The `serve` command watches the book's `src` directory for
changes, rebuilding the book and refreshing clients for each change; this includes
re-creating deleted files still mentioned in `SUMMARY.md`! A websocket
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
The `serve` hostname defaults to `localhost`, and the port defaults to `3000`. Either option can be specified on the command line:
```bash
mdbook serve path/to/book -p 8000 -n 127.0.0.1
```
#### --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 the `.gitignore` from the book root directory is used. Global
`$HOME/.gitignore` or `.gitignore` files in parent directories are not used.*

View File

@ -6,8 +6,7 @@ of code examples that could get outdated. Therefore it is very important for
them to be able to automatically test these code examples. 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 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 moment, only Rust tests are supported.
the future.
#### Disable tests on a code block #### Disable tests on a code block
@ -43,10 +42,26 @@ mdbook test path/to/book
The `--library-path` (`-L`) option allows you to add directories to the library 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 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 directories can be specified with multiple options (`-L foo -L bar`) or with a
comma-delimited list (`-L foo,bar`). comma-delimited list (`-L foo,bar`). The path should point to the Cargo
[build cache](https://doc.rust-lang.org/cargo/guide/build-cache.html) `deps` directory that
contains the build output of your project. For example, if your Rust project's book is in a directory
named `my-book`, the following command would include the crate's dependencies when running `test`:
```shell
mdbook test my-book -L target/debug/deps/
```
See the `rustdoc` command-line [documentation](https://doc.rust-lang.org/rustdoc/command-line-arguments.html#-l--library-path-where-to-look-for-dependencies)
for more information.
#### --dest-dir #### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for the The `--dest-dir` (`-d`) option allows you to change the output directory for the
book. If not specified it will default to the value of the `build.build-dir` key book. Relative paths are interpreted relative to the book's root directory. If
in `book.toml`, or to `./book` relative to the book's root directory. not specified it will default to the value of the `build.build-dir` key in
`book.toml`, or to `./book`.
#### --chapter
The `--chapter` (`-c`) option allows you to test a specific chapter of the
book using the chapter name or the relative path to the chapter.

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

@ -0,0 +1,40 @@
# 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; this includes re-creating
deleted files still mentioned in `SUMMARY.md`!
#### 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,121 @@
# Running `mdbook` in Continuous Integration
There are a variety of services such as [GitHub Actions] or [GitLab CI/CD] which can be used to test and deploy your book automatically.
The following provides some general guidelines on how to configure your service to run mdBook.
Specific recipes can be found at the [Automated Deployment] wiki page.
[GitHub Actions]: https://docs.github.com/en/actions
[GitLab CI/CD]: https://docs.gitlab.com/ee/ci/
[Automated Deployment]: https://github.com/rust-lang/mdBook/wiki/Automated-Deployment
## Installing mdBook
There are several different strategies for installing mdBook.
The particular method depends on your needs and preferences.
### Pre-compiled binaries
Perhaps the easiest method is to use the pre-compiled binaries found on the [GitHub Releases page][releases].
A simple approach would be to use the popular `curl` CLI tool to download the executable:
```sh
mkdir bin
curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.37/mdbook-v0.4.37-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
bin/mdbook build
```
Some considerations for this approach:
* This is relatively fast, and does not necessarily require dealing with caching.
* This does not require installing Rust.
* Specifying a specific URL means you have to manually update your script to get a new version.
This may be a benefit if you want to lock to a specific version.
However, some users prefer to automatically get a newer version when they are published.
* You are reliant on the GitHub CDN being available.
[releases]: https://github.com/rust-lang/mdBook/releases
### Building from source
Building from source will require having Rust installed.
Some services have Rust pre-installed, but if your service does not, you will need to add a step to install it.
After Rust is installed, `cargo install` can be used to build and install mdBook.
We recommend using a SemVer version specifier so that you get the latest **non-breaking** version of mdBook.
For example:
```sh
cargo install mdbook --no-default-features --features search --vers "^0.4" --locked
```
This includes several recommended options:
* `--no-default-features` — Disables features like the HTTP server used by `mdbook serve` that is likely not needed on CI.
This will speed up the build time significantly.
* `--features search` — Disabling default features means you should then manually enable features that you want, such as the built-in [search] capability.
* `--vers "^0.4"` — This will install the most recent version of the `0.4` series.
However, versions after like `0.5.0` won't be installed, as they may break your build.
Cargo will automatically upgrade mdBook if you have an older version already installed.
* `--locked` — This will use the dependencies that were used when mdBook was released.
Without `--locked`, it will use the latest version of all dependencies, which may include some fixes since the last release, but may also (rarely) cause build problems.
You will likely want to investigate caching options, as building mdBook can be somewhat slow.
[search]: guide/reading.md#search
## Running tests
You may want to run tests using [`mdbook test`] every time you push a change or create a pull request.
This can be used to validate Rust code examples in the book.
This will require having Rust installed.
Some services have Rust pre-installed, but if your service does not, you will need to add a step to install it.
Other than making sure the appropriate version of Rust is installed, there's not much more than just running `mdbook test` from the book directory.
You may also want to consider running other kinds of tests, like [mdbook-linkcheck] which will check for broken links.
Or if you have your own style checks, spell checker, or any other tests it might be good to run them in CI.
[`mdbook test`]: cli/test.md
[mdbook-linkcheck]: https://github.com/Michael-F-Bryan/mdbook-linkcheck#continuous-integration
## Deploying
You may want to automatically deploy your book.
Some may want to do this every time a change is pushed, and others may want to only deploy when a specific release is tagged.
You'll also need to understand the specifics on how to push a change to your web service.
For example, [GitHub Pages] just requires committing the output onto a specific git branch.
Other services may require using something like SSH to connect to a remote server.
The basic outline is that you need to run `mdbook build` to generate the output, and then transfer the files (which are in the `book` directory) to the correct location.
You may then want to consider if you need to invalidate any caches on your web service.
See the [Automated Deployment] wiki page for examples of various different services.
[GitHub Pages]: https://docs.github.com/en/pages
### 404 handling
mdBook automatically generates a 404 page to be used for broken links.
The default output is a file named `404.html` at the root of the book.
Some services like [GitHub Pages] will automatically use this page for broken links.
For other services, you may want to consider configuring the web server to use this page as it will provide the reader navigation to get back to the book.
If your book is not deployed at the root of the domain, then you should set the [`output.html.site-url`] setting so that the 404 page works correctly.
It needs to know where the book is deployed in order to load the static files (like CSS) correctly.
For example, this guide is deployed at <https://rust-lang.github.io/mdBook/>, and the `site-url` setting is configured like this:
```toml
# book.toml
[output.html]
site-url = "/mdBook/"
```
You can customize the look of the 404 page by creating a file named `src/404.md` in your book.
If you want to use a different filename, you can set [`output.html.input-404`] to a different filename.
[`output.html.site-url`]: format/configuration/renderers.md#html-renderer-options
[`output.html.input-404`]: format/configuration/renderers.md#html-renderer-options

View File

@ -12,7 +12,7 @@ The *For Developers* chapters are here to show you the more advanced usage of
The two main ways a developer can hook into the book's build process is via, The two main ways a developer can hook into the book's build process is via,
- [Preprocessors](preprocessors.md) - [Preprocessors](preprocessors.md)
- [Alternate Backends](backends.md) - [Alternative Backends](backends.md)
## The Build Process ## The Build Process
@ -24,8 +24,9 @@ The process of rendering a book project goes through several steps.
exist exist
- Load the book chapters into memory - Load the book chapters into memory
- Discover which preprocessors/backends should be used - Discover which preprocessors/backends should be used
2. Run the preprocessors 2. For each backend:
3. Call each backend in turn 1. Run all the preprocessors.
2. Call the backend to render the processed result.
## Using `mdbook` as a Library ## Using `mdbook` as a Library
@ -41,6 +42,6 @@ The easiest way to find out how to use the `mdbook` crate is by looking at the
explanation on the configuration system. explanation on the configuration system.
[`MDBook`]: http://rust-lang-nursery.github.io/mdBook/mdbook/book/struct.MDBook.html [`MDBook`]: https://docs.rs/mdbook/*/mdbook/book/struct.MDBook.html
[API Docs]: http://rust-lang-nursery.github.io/mdBook/mdbook/ [API Docs]: https://docs.rs/mdbook/*/mdbook/
[config]: file:///home/michael/Documents/forks/mdBook/target/doc/mdbook/config/index.html [config]: https://docs.rs/mdbook/*/mdbook/config/index.html

View File

@ -1,30 +1,25 @@
# 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 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. information it is free to do whatever it wants.
There are already several alternate backends on GitHub which can be used as a See [Configuring Renderers](../format/configuration/renderers.md) for more information about using backends.
rough example of how this is accomplished in practice.
- [mdbook-linkcheck] - a simple program for verifying the book doesn't contain
any broken links
- [mdbook-epub] - an EPUB renderer
- [mdbook-test] - a program to run the book's contents through [rust-skeptic] to
verify everything compiles and runs correctly (similar to `rustdoc --test`)
This page will step you through creating your own alternate 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.
The community has developed several backends.
See the [Third Party Plugins] wiki page for a list of available backends.
## Setting Up ## Setting Up
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.
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. dependency.
``` ```shell
$ cargo new --bin mdbook-wordcount $ cargo new --bin mdbook-wordcount
$ cd mdbook-wordcount $ cd mdbook-wordcount
$ cargo add mdbook $ cargo add mdbook
@ -92,8 +87,8 @@ fn count_words(ch: &Chapter) -> usize {
Now we've got the basics running, we want to actually use it. First, install the Now we've got the basics running, we want to actually use it. First, install the
program. program.
``` ```shell
$ cargo install $ cargo install --path .
``` ```
Then `cd` to the particular book you'd like to count the words of and update its Then `cd` to the particular book you'd like to count the words of and update its
@ -120,7 +115,7 @@ 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*. Work*.
``` ```shell
$ mdbook build $ mdbook build
... ...
2018-01-16 07:31:15 [INFO] (mdbook::renderer): Invoking the "mdbook-wordcount" renderer 2018-01-16 07:31:15 [INFO] (mdbook::renderer): Invoking the "mdbook-wordcount" renderer
@ -140,7 +135,7 @@ Syntax highlighting: 314
MathJax Support: 153 MathJax Support: 153
Rust code specific features: 148 Rust code specific features: 148
For Developers: 788 For Developers: 788
Alternate Backends: 710 Alternative Backends: 710
Contributors: 85 Contributors: 85
``` ```
@ -261,6 +256,10 @@ in [`RenderContext`].
> **Note:** There is no guarantee that the destination directory exists or is > **Note:** There is no guarantee that the destination directory exists or is
> empty (`mdbook` may leave the previous contents to let backends do caching), > 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()`. > 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 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
@ -288,7 +287,7 @@ like this:
+ if cfg.deny_odds && num_words % 2 == 1 { + if cfg.deny_odds && num_words % 2 == 1 {
+ eprintln!("{} has an odd number of words!", ch.name); + eprintln!("{} has an odd number of words!", ch.name);
+ process::exit(1); + process::exit(1);
} + }
} }
} }
} }
@ -303,8 +302,8 @@ like this:
Now, if we reinstall the backend and build a book, Now, if we reinstall the backend and build a book,
``` ```shell
$ cargo install --force $ cargo install --path . --force
$ mdbook build /path/to/book $ mdbook build /path/to/book
... ...
2018-01-16 21:21:39 [INFO] (mdbook::renderer): Invoking the "wordcount" renderer 2018-01-16 21:21:39 [INFO] (mdbook::renderer): Invoking the "wordcount" renderer
@ -325,11 +324,10 @@ generation or a warning).
All environment variables are passed through to the backend, allowing you to use All environment variables are passed through to the backend, allowing you to use
the usual `RUST_LOG` to control logging verbosity. the usual `RUST_LOG` to control logging verbosity.
## Wrapping Up ## Wrapping Up
Although contrived, hopefully this example was enough to show how you'd create 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 hesitate to create an issue in the [issue tracker] so we can improve the user
guide. guide.
@ -338,14 +336,11 @@ as a good example of how it's done in real life, so feel free to skim through
the source code or ask questions. the source code or ask questions.
[mdbook-linkcheck]: https://github.com/Michael-F-Bryan/mdbook-linkcheck [Third Party Plugins]: https://github.com/rust-lang/mdBook/wiki/Third-party-plugins
[mdbook-epub]: https://github.com/Michael-F-Bryan/mdbook-epub [`RenderContext`]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html
[mdbook-test]: https://github.com/Michael-F-Bryan/mdbook-test [`RenderContext::from_json()`]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html#method.from_json
[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
[`semver`]: https://crates.io/crates/semver [`semver`]: https://crates.io/crates/semver
[`Book`]: http://rust-lang-nursery.github.io/mdBook/mdbook/book/struct.Book.html [`Book`]: https://docs.rs/mdbook/*/mdbook/book/struct.Book.html
[`Book::iter()`]: http://rust-lang-nursery.github.io/mdBook/mdbook/book/struct.Book.html#method.iter [`Book::iter()`]: https://docs.rs/mdbook/*/mdbook/book/struct.Book.html#method.iter
[`Config`]: http://rust-lang-nursery.github.io/mdBook/mdbook/config/struct.Config.html [`Config`]: https://docs.rs/mdbook/*/mdbook/config/struct.Config.html
[issue tracker]: https://github.com/rust-lang-nursery/mdBook/issues [issue tracker]: https://github.com/rust-lang/mdBook/issues

View File

@ -0,0 +1,134 @@
# 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}}`
- Substituting in latex-style expressions (`$$ \frac{1}{3} $$`) with their
mathjax equivalents
See [Configuring Preprocessors](../format/configuration/preprocessors.md) for more information about using preprocessors.
## Hooking Into MDBook
MDBook uses a fairly simple mechanism for discovering third party plugins.
A new table is added to `book.toml` (e.g. `[preprocessor.foo]` for the `foo`
preprocessor) and then `mdbook` will try to invoke the `mdbook-foo` program as
part of the build process.
Once the preprocessor has been defined and the build process starts, mdBook executes the command defined in the `preprocessor.foo.command` key twice.
The first time it runs the preprocessor to determine if it supports the given renderer.
mdBook passes two arguments to the process: the first argument is the string `supports` and the second argument is the renderer name.
The preprocessor should exit with a status code 0 if it supports the given renderer, or return a non-zero exit code if it does not.
If the preprocessor supports the renderer, then mdbook runs it a second time, passing JSON data into stdin.
The JSON consists of an array of `[context, book]` where `context` is the serialized object [`PreprocessorContext`] and `book` is a [`Book`] object containing the content of the book.
The preprocessor should return the JSON format of the [`Book`] object to stdout, with any modifications it wishes to perform.
The easiest way to get started is by creating your own implementation of the
`Preprocessor` trait (e.g. in `lib.rs`) and then creating a shell binary which
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] crate 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].
## Implementing a preprocessor with a different language
The fact that mdBook utilizes stdin and stdout to communicate with the preprocessors makes it easy to implement them in a language other than Rust.
The following code shows how to implement a simple preprocessor in Python, which will modify the content of the first chapter.
The example below follows the configuration shown above with `preprocessor.foo.command` actually pointing to a Python script.
```python
import json
import sys
if __name__ == '__main__':
if len(sys.argv) > 1: # we check if we received any argument
if sys.argv[1] == "supports":
# then we are good to return an exit status code of 0, since the other argument will just be the renderer's name
sys.exit(0)
# load both the context and the book representations from stdin
context, book = json.load(sys.stdin)
# and now, we can just modify the content of the first chapter
book['sections'][0]['Chapter']['content'] = '# Hello'
# we are done with the book's modification, we can just print it to stdout,
print(json.dumps(book))
```
[preprocessor-docs]: https://docs.rs/mdbook/latest/mdbook/preprocess/trait.Preprocessor.html
[pc]: https://crates.io/crates/pulldown-cmark
[pctc]: https://crates.io/crates/pulldown-cmark-to-cmark
[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
[`PreprocessorContext`]: https://docs.rs/mdbook/latest/mdbook/preprocess/struct.PreprocessorContext.html
[`Book`]: https://docs.rs/mdbook/latest/mdbook/book/struct.Book.html

View File

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

View File

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

View File

@ -0,0 +1,118 @@
# General Configuration
You can configure the parameters for your book in the ***book.toml*** file.
Here is an example of what a ***book.toml*** file might look like:
```toml
[book]
title = "Example book"
authors = ["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.
This is also used to derive the direction of text (RTL, LTR) within the book.
- **text-direction**: The direction of text in the book: Left-to-right (LTR) or Right-to-left (RTL). Possible values: `ltr`, `rtl`.
When not specified, the text direction is derived from the book's `language` attribute.
**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"
text-direction = "ltr"
```
### Rust options
Options for the Rust language, relevant to running tests and playground
integration.
```toml
[rust]
edition = "2015" # the default edition for code blocks
```
- **edition**: Rust edition to use by default for the code snippets. Default
is "2015". Individual code blocks can be controlled with the `edition2015`,
`edition2018` or `edition2021` annotations, such as:
~~~text
```rust,edition2015
// This only works in 2015.
let try = true;
```
~~~
### Build options
This controls the build process of your book.
```toml
[build]
build-dir = "book" # the directory where the output is placed
create-missing = true # whether or not to create missing pages
use-default-preprocessors = true # use the default preprocessors
extra-watch-dirs = [] # directories to watch for triggering builds
```
- **build-dir:** The directory to put the rendered book in. By default this is
`book/` in the book's root directory.
This can overridden with the `--dest-dir` CLI option.
- **create-missing:** By default, any missing files specified in `SUMMARY.md`
will be created when the book is built (i.e. `create-missing = true`). If this
is `false` then the build process will instead exit with an error if any files
do not exist.
- **use-default-preprocessors:** Disable the default preprocessors (of `links` &
`index`) by setting this option to `false`.
If you have the same, and/or other preprocessors declared via their table
of configuration, they will run instead.
- For clarity, with no preprocessor configuration, the default `links` and
`index` will run.
- Setting `use-default-preprocessors = false` will disable these
default preprocessors from running.
- Adding `[preprocessor.links]`, for example, will ensure, regardless of
`use-default-preprocessors` that `links` it will run.
- **extra-watch-dirs**: A list of paths to directories that will be watched in
the `watch` and `serve` commands. Changes to files under these directories will
trigger rebuilds. Useful if your book depends on files outside its `src` directory.

View File

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

View File

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

View File

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

View File

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

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

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

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

@ -0,0 +1,364 @@
# mdBook-specific features
## Hiding code lines
There is a feature in mdBook that lets you hide code lines by prepending them with a specific prefix.
For the Rust language, you can use the `#` character as a prefix which will hide lines [like you would with Rustdoc][rustdoc-hide].
[rustdoc-hide]: https://doc.rust-lang.org/stable/rustdoc/write-documentation/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 = 6;
println!("{}", x + y);
# }
```
When you tap or hover the mouse over the code block, there will be an eyeball icon (<i class="fa fa-eye"></i>) which will toggle the visibility of the hidden lines.
By default, this only works for code examples that are annotated with `rust`.
However, you can define custom prefixes for other languages by adding a new line-hiding prefix in your `book.toml` with the language name and prefix character(s):
```toml
[output.html.code.hidelines]
python = "~"
```
The prefix will hide any lines that begin with the given prefix. With the python prefix shown above, this:
```bash
~hidden()
nothidden():
~ hidden()
~hidden()
nothidden()
```
will render as
```python
~hidden()
nothidden():
~ hidden()
~hidden()
nothidden()
```
This behavior can be overridden locally with a different prefix. This has the same effect as above:
~~~markdown
```python,hidelines=!!!
!!!hidden()
nothidden():
!!! hidden()
!!!hidden()
nothidden()
```
~~~
## Rust Playground
Rust language code blocks will automatically get a play button (<i class="fa fa-play"></i>) which will execute the code and display the output just below the code block.
This works by sending the code to the [Rust Playground].
```rust
println!("Hello, World!");
```
If there is no `main` function, then the code is automatically wrapped inside one.
If you wish to disable the play button for a code block, you can include the `noplayground` option on the code block like this:
~~~markdown
```rust,noplayground
let mut name = String::new();
std::io::stdin().read_line(&mut name).expect("failed to read line");
println!("Hello {}!", name);
```
~~~
Or, if you wish to disable the play button for all code blocks in your book, you can write the config to the `book.toml` like this.
```toml
[output.html.playground]
runnable = false
```
## Rust code block attributes
Additional attributes can be included in Rust code blocks with comma, space, or tab-separated terms just after the language term. For example:
~~~markdown
```rust,ignore
# This example won't be tested.
panic!("oops!");
```
~~~
These are particularly important when using [`mdbook test`] to test Rust examples.
These use the same attributes as [rustdoc attributes], with a few additions:
* `editable` — Enables the [editor].
* `noplayground` — Removes the play button, but will still be tested.
* `mdbook-runnable` — Forces the play button to be displayed.
This is intended to be combined with the `ignore` attribute for examples that should not be tested, but you want to allow the reader to run.
* `ignore` — Will not be tested and no play button is shown, but it is still highlighted as Rust syntax.
* `should_panic` — When executed, it should produce a panic.
* `no_run` — The code is compiled when tested, but it is not run.
The play button is also not shown.
* `compile_fail` — The code should fail to compile.
* `edition2015`, `edition2018`, `edition2021` — Forces the use of a specific Rust edition.
See [`rust.edition`] to set this globally.
[`mdbook test`]: ../cli/test.md
[rustdoc attributes]: https://doc.rust-lang.org/rustdoc/documentation-tests.html#attributes
[editor]: theme/editor.md
[`rust.edition`]: configuration/general.md#rust-options
## Including files
With the following syntax, you can include files into your book:
```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
interpreting 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}}
Any additional values passed after the filename will be included as attributes of the code block.
For example `\{{#playground example.rs editable}}` will create the code block like the following:
~~~markdown
```rust,editable
# Contents of example.rs here.
```
~~~
And the `editable` attribute will enable the [editor] as described at [Rust code block attributes](#rust-code-block-attributes).
[Rust Playground]: https://play.rust-lang.org/
## Controlling page \<title\>
A chapter can set a \<title\> that is different from its entry in the table of
contents (sidebar) by including a `\{{#title ...}}` near the top of the page.
```hbs
\{{#title My Title}}
```
## HTML classes provided by mdBook
<img class="right" src="images/rust-logo-blk.svg" alt="The Rust logo">
### `class="left"` and `"right"`
These classes are provided by default, for inline HTML to float images.
```html
<img class="right" src="images/rust-logo-blk.svg" alt="The Rust logo">
```
### `class="hidden"`
HTML tags with class `hidden` will not be shown.
```html
<div class="hidden">This will not be seen.</div>
```
<div class="hidden">This will not be seen.</div>
### `class="warning"`
To make a warning or similar note stand out, wrap it in a warning div.
```html
<div class="warning">
This is a bad thing that you should pay attention to.
Warning blocks should be used sparingly in documentation, to avoid "warning
fatigue," where people are trained to ignore them because they usually don't
matter for what they're doing.
</div>
```
<div class="warning">
This is a bad thing that you should pay attention to.
Warning blocks should be used sparingly in documentation, to avoid "warning
fatigue," where people are trained to ignore them because they usually don't
matter for what they're doing.
</div>

100
guide/src/format/summary.md Normal file
View File

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

View File

@ -0,0 +1,52 @@
# Theme
The default renderer uses a [handlebars](https://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.
- **_css/_** contains the CSS files for styling the book.
- **_css/chrome.css_** is for UI elements.
- **_css/general.css_** is the base styles.
- **_css/print.css_** is the style for printer output.
- **_css/variables.css_** contains variables used in other CSS files.
- **_book.js_** is mostly used to add client side functionality, like hiding /
un-hiding the sidebar, changing the theme, ...
- **_highlight.js_** is the JavaScript that is used to highlight code snippets,
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].
- **fonts/fonts.css** contains the definition of which fonts to load.
Custom fonts can be included in the `fonts` directory.
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` and just
remove the files you don't want to override.
`mdbook init --theme` will not create every file listed above.
Some files, such as `head.hbs`, do not have built-in equivalents.
Just create the file if you need it.
If you completely replace all built-in themes, be sure to also set
[`output.html.preferred-dark-theme`] in the config, which defaults to the
built-in `navy` theme.
[`output.html.preferred-dark-theme`]: ../configuration/renderers.md#html-renderer-options
[newer browsers]: https://caniuse.com/#feat=link-icon-svg

View File

@ -1,25 +1,27 @@
# Editor # Editor
In addition to providing runnable code playpens, mdBook optionally allows them 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 to be editable. In order to enable editable code blocks, the following needs to
be added to the ***book.toml***: be added to the ***book.toml***:
```toml ```toml
[output.html.playpen] [output.html.playground]
editable = true editable = true
``` ```
To make a specific block available for editing, the attribute `editable` needs To make a specific block available for editing, the attribute `editable` needs
to be added to it: to be added to it:
<pre><code class="language-markdown">```rust,editable ~~~markdown
```rust,editable
fn main() { fn main() {
let number = 5; let number = 5;
print!("{}", number); print!("{}", number);
} }
```</code></pre> ```
~~~
The above will result in this editable playpen: The above will result in this editable playground:
```rust,editable ```rust,editable
fn main() { fn main() {
@ -28,19 +30,19 @@ fn main() {
} }
``` ```
Note the new `Undo Changes` button in the editable playpens. Note the new `Undo Changes` button in the editable playgrounds.
## Customizing the Editor ## Customizing the Editor
By default, the editor is the [Ace](https://ace.c9.io/) editor, but, if desired, By default, the editor is the [Ace](https://ace.c9.io/) editor, but, if desired,
the functionality may be overriden by providing a different folder: the functionality may be overridden by providing a different folder:
```toml ```toml
[output.html.playpen] [output.html.playground]
editable = true editable = true
editor = "/path/to/editor" editor = "/path/to/editor"
``` ```
Note that for the editor changes to function correctly, the `book.js` inside of Note that for the editor changes to function correctly, the `book.js` inside of
the `theme` folder will need to be overriden as it has some couplings with the the `theme` folder will need to be overridden as it has some couplings with the
default Ace editor. default Ace editor.

View File

@ -17,10 +17,10 @@ handlebars template you can access this information by using
Here is a list of the properties that are exposed: Here is a list of the properties that are exposed:
- ***language*** Language of the book in the form `en`. To use in <code - ***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. At the class="language-html">\<html lang="{{ language }}"></code> for example.
moment it is hardcoded. - ***title*** Title used for the current page. This is identical to `{{ chapter_title }} - {{ book_title }}` unless `book_title` is not set in which case it just defaults to the `chapter_title`.
- ***title*** Title of the book, as specified in `book.toml` - ***book_title*** Title of the book, as specified in `book.toml`
- ***chapter_title*** Title of the current chapter, as listed in `SUMMARY.md` - ***chapter_title*** Title of the current chapter, as listed in `SUMMARY.md`
- ***path*** Relative path to the original markdown file from the source - ***path*** Relative path to the original markdown file from the source
@ -51,7 +51,8 @@ at your disposal.
{{#toc}}{{/toc}} {{#toc}}{{/toc}}
``` ```
and outputs something that looks like this, depending on the structure of your book and outputs something that looks like this, depending on the structure of your
book
```html ```html
<ul class="chapter"> <ul class="chapter">
@ -64,8 +65,10 @@ at your disposal.
</ul> </ul>
``` ```
If you would like to make a toc with another structure, you have access to the chapters property containing all the data. If you would like to make a toc with another structure, you have access to the
The only limitation at the moment is that you would have to do it with JavaScript instead of with a handlebars helper. 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 ```html
<script> <script>
@ -76,14 +79,15 @@ at your disposal.
### 2. previous / next ### 2. previous / next
The previous and next helpers expose a `link` and `name` property to the previous and next chapters. The previous and next helpers expose a `link` and `title` property to the
previous and next chapters.
They are used like this They are used like this
```handlebars ```handlebars
{{#previous}} {{#previous}}
<a href="{{link}}" class="nav-chapters previous"> <a href="{{link}}" class="nav-chapters previous">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i> {{title}}
</a> </a>
{{/previous}} {{/previous}}
``` ```
@ -94,4 +98,4 @@ at your disposal.
------ ------
*If you would like other properties or helpers exposed, please [create a new *If you would like other properties or helpers exposed, please [create a new
issue](https://github.com/rust-lang-nursery/mdBook/issues)* issue](https://github.com/rust-lang/mdBook/issues)*

View File

@ -0,0 +1,91 @@
# 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
- nim
- nix
- 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.
## 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 too many flashy colors.

View File

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

109
guide/src/guide/creating.md Normal file
View File

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

View File

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

View File

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

View File

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

View File

@ -5,8 +5,11 @@ use std::io::{Read, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem}; use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
use config::BuildConfig; use crate::config::BuildConfig;
use errors::*; use crate::errors::*;
use crate::utils::bracket_escape;
use log::debug;
use serde::{Deserialize, Serialize};
/// Load a book into memory from its `src/` directory. /// Load a book into memory from its `src/` directory.
pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book> { pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book> {
@ -14,14 +17,15 @@ pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book>
let summary_md = src_dir.join("SUMMARY.md"); let summary_md = src_dir.join("SUMMARY.md");
let mut summary_content = String::new(); let mut summary_content = String::new();
File::open(summary_md) File::open(&summary_md)
.chain_err(|| "Couldn't open SUMMARY.md")? .with_context(|| format!("Couldn't open SUMMARY.md in {:?} directory", src_dir))?
.read_to_string(&mut summary_content)?; .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(|| format!("Summary parsing failed for file={:?}", summary_md))?;
if cfg.create_missing { 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) load_book_from_disk(&summary, src_dir)
@ -35,11 +39,10 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
.chain(summary.suffix_chapters.iter()) .chain(summary.suffix_chapters.iter())
.collect(); .collect();
while !items.is_empty() { while let Some(next) = items.pop() {
let next = items.pop().expect("already checked");
if let SummaryItem::Link(ref link) = *next { if let SummaryItem::Link(ref link) = *next {
let filename = src_dir.join(&link.location); if let Some(ref location) = link.location {
let filename = src_dir.join(location);
if !filename.exists() { if !filename.exists() {
if let Some(parent) = filename.parent() { if let Some(parent) = filename.parent() {
if !parent.exists() { if !parent.exists() {
@ -48,8 +51,11 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
} }
debug!("Creating missing file {}", filename.display()); debug!("Creating missing file {}", filename.display());
let mut f = File::create(&filename)?; let mut f = File::create(&filename).with_context(|| {
writeln!(f, "# {}", link.name)?; format!("Unable to create missing file: {}", filename.display())
})?;
writeln!(f, "# {}", bracket_escape(&link.name))?;
}
} }
items.extend(&link.nested_items); items.extend(&link.nested_items);
@ -61,7 +67,7 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
/// A dumb tree structure representing a book. /// A dumb tree structure representing a book.
/// ///
/// For the moment a book is just a collection of `BookItems` which are /// For the moment a book is just a collection of [`BookItems`] which are
/// accessible by either iterating (immutably) over the book with [`iter()`], or /// accessible by either iterating (immutably) over the book with [`iter()`], or
/// recursively applying a closure to each section to mutate the chapters, using /// recursively applying a closure to each section to mutate the chapters, using
/// [`for_each_mut()`]. /// [`for_each_mut()`].
@ -82,7 +88,7 @@ impl Book {
} }
/// Get a depth-first iterator over the items in the book. /// Get a depth-first iterator over the items in the book.
pub fn iter(&self) -> BookItems { pub fn iter(&self) -> BookItems<'_> {
BookItems { BookItems {
items: self.sections.iter().collect(), items: self.sections.iter().collect(),
} }
@ -116,7 +122,7 @@ where
I: IntoIterator<Item = &'a mut BookItem>, I: IntoIterator<Item = &'a mut BookItem>,
{ {
for item in items { 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); for_each_mut(func, &mut ch.sub_items);
} }
@ -131,6 +137,8 @@ pub enum BookItem {
Chapter(Chapter), Chapter(Chapter),
/// A section separator. /// A section separator.
Separator, Separator,
/// A part title.
PartTitle(String),
} }
impl From<Chapter> for BookItem { impl From<Chapter> for BookItem {
@ -152,8 +160,22 @@ pub struct Chapter {
/// Nested items. /// Nested items.
pub sub_items: Vec<BookItem>, pub sub_items: Vec<BookItem>,
/// The chapter's location, relative to the `SUMMARY.md` file. /// The chapter's location, relative to the `SUMMARY.md` file.
pub path: PathBuf, ///
/// An ordered list of the names of each chapter above this one, in the hierarchy. /// **Note**: After the index preprocessor runs, any README files will be
/// modified to be `index.md`. If you need access to the actual filename
/// on disk, use [`Chapter::source_path`] instead.
///
/// This is `None` for a draft chapter.
pub path: Option<PathBuf>,
/// The chapter's source file, relative to the `SUMMARY.md` file.
///
/// **Note**: Beware that README files will internally be treated as
/// `index.md` via the [`Chapter::path`] field. The `source_path` field
/// exists if you need access to the true file path.
///
/// This is `None` for a draft chapter.
pub source_path: Option<PathBuf>,
/// An ordered list of the names of each chapter above this one in the hierarchy.
pub parent_names: Vec<String>, pub parent_names: Vec<String>,
} }
@ -162,24 +184,44 @@ impl Chapter {
pub fn new<P: Into<PathBuf>>( pub fn new<P: Into<PathBuf>>(
name: &str, name: &str,
content: String, content: String,
path: P, p: P,
parent_names: Vec<String>, parent_names: Vec<String>,
) -> Chapter { ) -> Chapter {
let path: PathBuf = p.into();
Chapter { Chapter {
name: name.to_string(), name: name.to_string(),
content: content, content,
path: path.into(), path: Some(path.clone()),
parent_names: parent_names, source_path: Some(path),
parent_names,
..Default::default() ..Default::default()
} }
} }
/// Create a new draft chapter that is not attached to a source markdown file (and thus
/// has no content).
pub fn new_draft(name: &str, parent_names: Vec<String>) -> Self {
Chapter {
name: name.to_string(),
content: String::new(),
path: None,
source_path: None,
parent_names,
..Default::default()
}
}
/// Check if the chapter is a draft chapter, meaning it has no path to a source markdown file.
pub fn is_draft_chapter(&self) -> bool {
self.path.is_none()
}
} }
/// Use the provided `Summary` to load a `Book` from disk. /// 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 /// You need to pass in the book's source directory because all the links in
/// `SUMMARY.md` give the chapter locations relative to it. /// `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"); debug!("Loading the book from disk");
let src_dir = src_dir.as_ref(); let src_dir = src_dir.as_ref();
@ -202,16 +244,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, item: &SummaryItem,
src_dir: P, src_dir: P,
parent_names: Vec<String>, parent_names: Vec<String>,
) -> Result<BookItem> { ) -> Result<BookItem> {
match *item { match item {
SummaryItem::Separator => Ok(BookItem::Separator), SummaryItem::Separator => Ok(BookItem::Separator),
SummaryItem::Link(ref link) => { 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,28 +263,40 @@ fn load_chapter<P: AsRef<Path>>(
src_dir: P, src_dir: P,
parent_names: Vec<String>, parent_names: Vec<String>,
) -> Result<Chapter> { ) -> Result<Chapter> {
debug!("Loading {} ({})", link.name, link.location.display());
let src_dir = src_dir.as_ref(); let src_dir = src_dir.as_ref();
let location = if link.location.is_absolute() { let mut ch = if let Some(ref link_location) = link.location {
link.location.clone() debug!("Loading {} ({})", link.name, link_location.display());
let location = if link_location.is_absolute() {
link_location.clone()
} else { } else {
src_dir.join(&link.location) src_dir.join(link_location)
}; };
let mut f = File::open(&location) let mut f = File::open(&location)
.chain_err(|| format!("Chapter file not found, {}", link.location.display()))?; .with_context(|| format!("Chapter file not found, {}", link_location.display()))?;
let mut content = String::new(); let mut content = String::new();
f.read_to_string(&mut content) f.read_to_string(&mut content).with_context(|| {
.chain_err(|| format!("Unable to read \"{}\" ({})", link.name, location.display()))?; format!("Unable to read \"{}\" ({})", link.name, location.display())
})?;
if content.as_bytes().starts_with(b"\xef\xbb\xbf") {
content.replace_range(..3, "");
}
let stripped = location let stripped = location
.strip_prefix(&src_dir) .strip_prefix(src_dir)
.expect("Chapters are always inside a book"); .expect("Chapters are always inside a book");
let mut sub_item_parents = parent_names.clone(); Chapter::new(&link.name, content, stripped, parent_names.clone())
let mut ch = Chapter::new(&link.name, content, stripped, parent_names); } else {
Chapter::new_draft(&link.name, parent_names.clone())
};
let mut sub_item_parents = parent_names;
ch.number = link.number.clone(); ch.number = link.number.clone();
sub_item_parents.push(link.name.clone()); sub_item_parents.push(link.name.clone());
@ -262,8 +317,6 @@ fn load_chapter<P: AsRef<Path>>(
/// ///
/// This struct shouldn't be created directly, instead prefer the /// This struct shouldn't be created directly, instead prefer the
/// [`Book::iter()`] method. /// [`Book::iter()`] method.
///
/// [`Book::iter()`]: struct.Book.html#method.iter
pub struct BookItems<'a> { pub struct BookItems<'a> {
items: VecDeque<&'a BookItem>, items: VecDeque<&'a BookItem>,
} }
@ -274,7 +327,7 @@ impl<'a> Iterator for BookItems<'a> {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let item = self.items.pop_front(); let item = self.items.pop_front();
if let Some(&BookItem::Chapter(ref ch)) = item { if let Some(BookItem::Chapter(ch)) = item {
// if we wanted a breadth-first iterator we'd `extend()` here // if we wanted a breadth-first iterator we'd `extend()` here
for sub_item in ch.sub_items.iter().rev() { for sub_item in ch.sub_items.iter().rev() {
self.items.push_front(sub_item); self.items.push_front(sub_item);
@ -286,7 +339,7 @@ impl<'a> Iterator for BookItems<'a> {
} }
impl Display for Chapter { 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 { if let Some(ref section_number) = self.number {
write!(f, "{} ", section_number)?; write!(f, "{} ", section_number)?;
} }
@ -298,10 +351,9 @@ impl Display for Chapter {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::io::Write;
use tempfile::{Builder as TempFileBuilder, TempDir}; use tempfile::{Builder as TempFileBuilder, TempDir};
const DUMMY_SRC: &'static str = " const DUMMY_SRC: &str = "
# Dummy Chapter # Dummy Chapter
this is some dummy text. this is some dummy text.
@ -317,7 +369,7 @@ And here is some \
let chapter_path = temp.path().join("chapter_1.md"); let chapter_path = temp.path().join("chapter_1.md");
File::create(&chapter_path) File::create(&chapter_path)
.unwrap() .unwrap()
.write(DUMMY_SRC.as_bytes()) .write_all(DUMMY_SRC.as_bytes())
.unwrap(); .unwrap();
let link = Link::new("Chapter 1", chapter_path); let link = Link::new("Chapter 1", chapter_path);
@ -333,7 +385,7 @@ And here is some \
File::create(&second_path) File::create(&second_path)
.unwrap() .unwrap()
.write_all("Hello World!".as_bytes()) .write_all(b"Hello World!")
.unwrap(); .unwrap();
let mut second = Link::new("Nested Chapter 1", &second_path); let mut second = Link::new("Nested Chapter 1", &second_path);
@ -341,7 +393,7 @@ And here is some \
root.nested_items.push(second.clone().into()); root.nested_items.push(second.clone().into());
root.nested_items.push(SummaryItem::Separator); root.nested_items.push(SummaryItem::Separator);
root.nested_items.push(second.clone().into()); root.nested_items.push(second.into());
(root, temp_dir) (root, temp_dir)
} }
@ -360,6 +412,29 @@ And here is some \
assert_eq!(got, should_be); assert_eq!(got, should_be);
} }
#[test]
fn load_a_single_chapter_with_utf8_bom_from_disk() {
let temp_dir = TempFileBuilder::new().prefix("book").tempdir().unwrap();
let chapter_path = temp_dir.path().join("chapter_1.md");
File::create(&chapter_path)
.unwrap()
.write_all(("\u{feff}".to_owned() + DUMMY_SRC).as_bytes())
.unwrap();
let link = Link::new("Chapter 1", chapter_path);
let should_be = Chapter::new(
"Chapter 1",
DUMMY_SRC.to_string(),
"chapter_1.md",
Vec::new(),
);
let got = load_chapter(&link, temp_dir.path(), Vec::new()).unwrap();
assert_eq!(got, should_be);
}
#[test] #[test]
fn cant_load_a_nonexistent_chapter() { fn cant_load_a_nonexistent_chapter() {
let link = Link::new("Chapter 1", "/foo/bar/baz.md"); let link = Link::new("Chapter 1", "/foo/bar/baz.md");
@ -376,7 +451,8 @@ And here is some \
name: String::from("Nested Chapter 1"), name: String::from("Nested Chapter 1"),
content: String::from("Hello World!"), content: String::from("Hello World!"),
number: Some(SectionNumber(vec![1, 2])), number: Some(SectionNumber(vec![1, 2])),
path: PathBuf::from("second.md"), path: Some(PathBuf::from("second.md")),
source_path: Some(PathBuf::from("second.md")),
parent_names: vec![String::from("Chapter 1")], parent_names: vec![String::from("Chapter 1")],
sub_items: Vec::new(), sub_items: Vec::new(),
}; };
@ -384,12 +460,13 @@ And here is some \
name: String::from("Chapter 1"), name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC), content: String::from(DUMMY_SRC),
number: None, number: None,
path: PathBuf::from("chapter_1.md"), path: Some(PathBuf::from("chapter_1.md")),
source_path: Some(PathBuf::from("chapter_1.md")),
parent_names: Vec::new(), parent_names: Vec::new(),
sub_items: vec![ sub_items: vec![
BookItem::Chapter(nested.clone()), BookItem::Chapter(nested.clone()),
BookItem::Separator, BookItem::Separator,
BookItem::Chapter(nested.clone()), BookItem::Chapter(nested),
], ],
}); });
@ -408,7 +485,8 @@ And here is some \
sections: vec![BookItem::Chapter(Chapter { sections: vec![BookItem::Chapter(Chapter {
name: String::from("Chapter 1"), name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC), content: String::from(DUMMY_SRC),
path: PathBuf::from("chapter_1.md"), path: Some(PathBuf::from("chapter_1.md")),
source_path: Some(PathBuf::from("chapter_1.md")),
..Default::default() ..Default::default()
})], })],
..Default::default() ..Default::default()
@ -448,7 +526,8 @@ And here is some \
name: String::from("Chapter 1"), name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC), content: String::from(DUMMY_SRC),
number: None, number: None,
path: PathBuf::from("Chapter_1/index.md"), path: Some(PathBuf::from("Chapter_1/index.md")),
source_path: Some(PathBuf::from("Chapter_1/index.md")),
parent_names: Vec::new(), parent_names: Vec::new(),
sub_items: vec![ sub_items: vec![
BookItem::Chapter(Chapter::new( BookItem::Chapter(Chapter::new(
@ -481,7 +560,8 @@ And here is some \
.filter_map(|i| match *i { .filter_map(|i| match *i {
BookItem::Chapter(ref ch) => Some(ch.name.clone()), BookItem::Chapter(ref ch) => Some(ch.name.clone()),
_ => None, _ => None,
}).collect(); })
.collect();
let should_be: Vec<_> = vec![ let should_be: Vec<_> = vec![
String::from("Chapter 1"), String::from("Chapter 1"),
String::from("Hello World"), String::from("Hello World"),
@ -499,7 +579,8 @@ And here is some \
name: String::from("Chapter 1"), name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC), content: String::from(DUMMY_SRC),
number: None, number: None,
path: PathBuf::from("Chapter_1/index.md"), path: Some(PathBuf::from("Chapter_1/index.md")),
source_path: Some(PathBuf::from("Chapter_1/index.md")),
parent_names: Vec::new(), parent_names: Vec::new(),
sub_items: vec![ sub_items: vec![
BookItem::Chapter(Chapter::new( BookItem::Chapter(Chapter::new(
@ -536,9 +617,10 @@ And here is some \
let summary = Summary { let summary = Summary {
numbered_chapters: vec![SummaryItem::Link(Link { numbered_chapters: vec![SummaryItem::Link(Link {
name: String::from("Empty"), name: String::from("Empty"),
location: PathBuf::from(""), location: Some(PathBuf::from("")),
..Default::default() ..Default::default()
})], })],
..Default::default() ..Default::default()
}; };
@ -555,7 +637,7 @@ And here is some \
let summary = Summary { let summary = Summary {
numbered_chapters: vec![SummaryItem::Link(Link { numbered_chapters: vec![SummaryItem::Link(Link {
name: String::from("nested"), name: String::from("nested"),
location: dir, location: Some(dir),
..Default::default() ..Default::default()
})], })],
..Default::default() ..Default::default()

View File

@ -1,12 +1,13 @@
use std::fs::{self, File}; use std::fs::{self, File};
use std::io::Write; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use toml;
use super::MDBook; use super::MDBook;
use config::Config; use crate::config::Config;
use errors::*; use crate::errors::*;
use theme; use crate::theme;
use crate::utils::fs::write_file;
use log::{debug, error, info, trace};
/// A helper for setting up a new book and its directory structure. /// A helper for setting up a new book and its directory structure.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -29,7 +30,7 @@ impl BookBuilder {
} }
} }
/// Set the `Config` to be used. /// Set the [`Config`] to be used.
pub fn with_config(&mut self, cfg: Config) -> &mut BookBuilder { pub fn with_config(&mut self, cfg: Config) -> &mut BookBuilder {
self.config = cfg; self.config = cfg;
self self
@ -65,19 +66,19 @@ impl BookBuilder {
info!("Creating a new book with stub content"); info!("Creating a new book with stub content");
self.create_directory_structure() self.create_directory_structure()
.chain_err(|| "Unable to create directory structure")?; .with_context(|| "Unable to create directory structure")?;
self.create_stub_files() self.create_stub_files()
.chain_err(|| "Unable to create stub files")?; .with_context(|| "Unable to create stub files")?;
if self.create_gitignore { if self.create_gitignore {
self.build_gitignore() self.build_gitignore()
.chain_err(|| "Unable to create .gitignore")?; .with_context(|| "Unable to create .gitignore")?;
} }
if self.copy_theme { if self.copy_theme {
self.copy_across_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()?; self.write_book_toml()?;
@ -98,24 +99,20 @@ impl BookBuilder {
fn write_book_toml(&self) -> Result<()> { fn write_book_toml(&self) -> Result<()> {
debug!("Writing book.toml"); debug!("Writing book.toml");
let book_toml = self.root.join("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) File::create(book_toml)
.chain_err(|| "Couldn't create book.toml")? .with_context(|| "Couldn't create book.toml")?
.write_all(&cfg) .write_all(&cfg)
.chain_err(|| "Unable to write config to book.toml")?; .with_context(|| "Unable to write config to book.toml")?;
Ok(()) Ok(())
} }
fn copy_across_theme(&self) -> Result<()> { fn copy_across_theme(&self) -> Result<()> {
debug!("Copying theme"); debug!("Copying theme");
let themedir = self let html_config = self.config.html_config().unwrap_or_default();
.config let themedir = html_config.theme_dir(&self.root);
.html_config()
.and_then(|html| html.theme)
.unwrap_or_else(|| self.config.book.src.join("theme"));
let themedir = self.root.join(themedir);
if !themedir.exists() { if !themedir.exists() {
debug!( debug!(
@ -129,7 +126,9 @@ impl BookBuilder {
index.write_all(theme::INDEX)?; index.write_all(theme::INDEX)?;
let cssdir = themedir.join("css"); let cssdir = themedir.join("css");
if !cssdir.exists() {
fs::create_dir(&cssdir)?; fs::create_dir(&cssdir)?;
}
let mut general_css = File::create(cssdir.join("general.css"))?; let mut general_css = File::create(cssdir.join("general.css"))?;
general_css.write_all(theme::GENERAL_CSS)?; general_css.write_all(theme::GENERAL_CSS)?;
@ -137,14 +136,19 @@ impl BookBuilder {
let mut chrome_css = File::create(cssdir.join("chrome.css"))?; let mut chrome_css = File::create(cssdir.join("chrome.css"))?;
chrome_css.write_all(theme::CHROME_CSS)?; chrome_css.write_all(theme::CHROME_CSS)?;
if html_config.print.enable {
let mut print_css = File::create(cssdir.join("print.css"))?; let mut print_css = File::create(cssdir.join("print.css"))?;
print_css.write_all(theme::PRINT_CSS)?; print_css.write_all(theme::PRINT_CSS)?;
}
let mut variables_css = File::create(cssdir.join("variables.css"))?; let mut variables_css = File::create(cssdir.join("variables.css"))?;
variables_css.write_all(theme::VARIABLES_CSS)?; variables_css.write_all(theme::VARIABLES_CSS)?;
let mut favicon = File::create(themedir.join("favicon.png"))?; 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"))?; let mut js = File::create(themedir.join("book.js"))?;
js.write_all(theme::JS)?; js.write_all(theme::JS)?;
@ -155,6 +159,19 @@ impl BookBuilder {
let mut highlight_js = File::create(themedir.join("highlight.js"))?; let mut highlight_js = File::create(themedir.join("highlight.js"))?;
highlight_js.write_all(theme::HIGHLIGHT_JS)?; highlight_js.write_all(theme::HIGHLIGHT_JS)?;
write_file(&themedir.join("fonts"), "fonts.css", theme::fonts::CSS)?;
for (file_name, contents) in theme::fonts::LICENSES {
write_file(&themedir, file_name, contents)?;
}
for (file_name, contents) in theme::fonts::OPEN_SANS.iter() {
write_file(&themedir, file_name, contents)?;
}
write_file(
&themedir,
theme::fonts::SOURCE_CODE_PRO.0,
theme::fonts::SOURCE_CODE_PRO.1,
)?;
Ok(()) Ok(())
} }
@ -173,15 +190,19 @@ impl BookBuilder {
let src_dir = self.root.join(&self.config.book.src); let src_dir = self.root.join(&self.config.book.src);
let summary = src_dir.join("SUMMARY.md"); let summary = src_dir.join("SUMMARY.md");
let mut f = File::create(&summary).chain_err(|| "Unable to create SUMMARY.md")?; 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, "# Summary")?;
writeln!(f, "")?; writeln!(f)?;
writeln!(f, "- [Chapter 1](./chapter_1.md)")?; writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
let chapter_1 = src_dir.join("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")?; let mut f = File::create(chapter_1).with_context(|| "Unable to create chapter_1.md")?;
writeln!(f, "# Chapter 1")?; writeln!(f, "# Chapter 1")?;
} else {
trace!("Existing summary found, no need to create stub files.");
}
Ok(()) Ok(())
} }
@ -190,10 +211,10 @@ impl BookBuilder {
fs::create_dir_all(&self.root)?; fs::create_dir_all(&self.root)?;
let src = self.root.join(&self.config.book.src); let src = self.root.join(&self.config.book.src);
fs::create_dir_all(&src)?; fs::create_dir_all(src)?;
let build = self.root.join(&self.config.build.build_dir); let build = self.root.join(&self.config.build.build_dir);
fs::create_dir_all(&build)?; fs::create_dir_all(build)?;
Ok(()) Ok(())
} }

View File

@ -5,6 +5,7 @@
//! //!
//! [1]: ../index.html //! [1]: ../index.html
#[allow(clippy::module_inception)]
mod book; mod book;
mod init; mod init;
mod summary; mod summary;
@ -13,18 +14,23 @@ pub use self::book::{load_book, Book, BookItem, BookItems, Chapter};
pub use self::init::BookBuilder; pub use self::init::BookBuilder;
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem}; pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
use std::io::Write; use log::{debug, error, info, log_enabled, trace, warn};
use std::path::PathBuf; use std::ffi::OsString;
use std::io::{IsTerminal, Write};
use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use tempfile::Builder as TempFileBuilder; use tempfile::Builder as TempFileBuilder;
use toml::Value; use toml::Value;
use topological_sort::TopologicalSort;
use errors::*; use crate::errors::*;
use preprocess::{IndexPreprocessor, LinkPreprocessor, Preprocessor, PreprocessorContext}; use crate::preprocess::{
use renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer}; CmdPreprocessor, IndexPreprocessor, LinkPreprocessor, Preprocessor, PreprocessorContext,
use utils; };
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. /// The object used to manage and build a book.
pub struct MDBook { pub struct MDBook {
@ -34,10 +40,10 @@ pub struct MDBook {
pub config: Config, pub config: Config,
/// A representation of the book's contents in memory. /// A representation of the book's contents in memory.
pub book: Book, pub book: Book,
renderers: Vec<Box<Renderer>>, renderers: Vec<Box<dyn Renderer>>,
/// List of pre-processors to be run on the book /// List of pre-processors to be run on the book.
preprocessors: Vec<Box<Preprocessor>>, preprocessors: Vec<Box<dyn Preprocessor>>,
} }
impl MDBook { impl MDBook {
@ -53,7 +59,7 @@ impl MDBook {
warn!("This format is no longer used, so you should migrate to the"); warn!("This format is no longer used, so you should migrate to the");
warn!("book.toml format."); warn!("book.toml format.");
warn!("Check the user guide for migration information:"); 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() { let mut config = if config_location.exists() {
@ -65,7 +71,27 @@ impl MDBook {
config.update_from_env(); config.update_from_env();
if log_enabled!(::log::Level::Trace) { if let Some(html_config) = config.html_config() {
if html_config.google_analytics.is_some() {
warn!(
"The output.html.google-analytics field has been deprecated; \
it will be removed in a future release.\n\
Consider placing the appropriate site tag code into the \
theme/head.hbs file instead.\n\
The tracking code may be found in the Google Analytics Admin page.\n\
"
);
}
if html_config.curly_quotes {
warn!(
"The output.html.curly-quotes field has been renamed to \
output.html.smart-punctuation.\n\
Use the new name in book.toml to remove this warning."
);
}
}
if log_enabled!(log::Level::Trace) {
for line in format!("Config: {:#?}", config).lines() { for line in format!("Config: {:#?}", config).lines() {
trace!("{}", line); trace!("{}", line);
} }
@ -74,12 +100,35 @@ impl MDBook {
MDBook::load_with_config(book_root, config) MDBook::load_with_config(book_root, config)
} }
/// Load a book from its root directory using a custom config. /// Load a book from its root directory using a custom `Config`.
pub fn load_with_config<P: Into<PathBuf>>(book_root: P, config: Config) -> Result<MDBook> { pub fn load_with_config<P: Into<PathBuf>>(book_root: P, config: Config) -> Result<MDBook> {
let root = book_root.into(); let root = book_root.into();
let src_dir = root.join(&config.book.src); let src_dir = root.join(&config.book.src);
let book = book::load_book(&src_dir, &config.build)?; let book = book::load_book(src_dir, &config.build)?;
let renderers = determine_renderers(&config);
let preprocessors = determine_preprocessors(&config)?;
Ok(MDBook {
root,
config,
book,
renderers,
preprocessors,
})
}
/// 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 renderers = determine_renderers(&config);
let preprocessors = determine_preprocessors(&config)?; let preprocessors = determine_preprocessors(&config)?;
@ -94,20 +143,18 @@ impl MDBook {
} }
/// Returns a flat depth-first iterator over the elements of the book, /// Returns a flat depth-first iterator over the elements of the book,
/// it returns an [BookItem enum](bookitem.html): /// it returns a [`BookItem`] enum:
/// `(section: String, bookitem: &BookItem)` /// `(section: String, bookitem: &BookItem)`
/// ///
/// ```no_run /// ```no_run
/// # extern crate mdbook;
/// # use mdbook::MDBook; /// # use mdbook::MDBook;
/// # use mdbook::book::BookItem; /// # use mdbook::book::BookItem;
/// # #[allow(unused_variables)]
/// # fn main() {
/// # let book = MDBook::load("mybook").unwrap(); /// # let book = MDBook::load("mybook").unwrap();
/// for item in book.iter() { /// for item in book.iter() {
/// match *item { /// match *item {
/// BookItem::Chapter(ref chapter) => {}, /// BookItem::Chapter(ref chapter) => {},
/// BookItem::Separator => {}, /// BookItem::Separator => {},
/// BookItem::PartTitle(ref title) => {}
/// } /// }
/// } /// }
/// ///
@ -118,9 +165,8 @@ impl MDBook {
/// // 2. Chapter 2 /// // 2. Chapter 2
/// // /// //
/// // etc. /// // etc.
/// # }
/// ``` /// ```
pub fn iter(&self) -> BookItems { pub fn iter(&self) -> BookItems<'_> {
self.book.iter() self.book.iter()
} }
@ -156,116 +202,177 @@ impl MDBook {
Ok(()) Ok(())
} }
/// Run the entire build process for a particular `Renderer`. /// Run preprocessors and return the final book.
fn execute_build_process(&self, renderer: &Renderer) -> Result<()> { pub fn preprocess_book(&self, renderer: &dyn Renderer) -> Result<(Book, PreprocessorContext)> {
let mut preprocessed_book = self.book.clone(); let preprocess_ctx = PreprocessorContext::new(
let preprocess_ctx = PreprocessorContext::new(self.root.clone(), self.root.clone(),
self.config.clone(), self.config.clone(),
renderer.name().to_string()); renderer.name().to_string(),
);
let mut preprocessed_book = self.book.clone();
for preprocessor in &self.preprocessors { for preprocessor in &self.preprocessors {
if preprocessor_should_run(&**preprocessor, renderer, &self.config) { if preprocessor_should_run(&**preprocessor, renderer, &self.config) {
debug!("Running the {} preprocessor.", preprocessor.name()); debug!("Running the {} preprocessor.", preprocessor.name());
preprocessed_book = preprocessed_book = preprocessor.run(&preprocess_ctx, preprocessed_book)?;
preprocessor.run(&preprocess_ctx, preprocessed_book)?;
} }
} }
Ok((preprocessed_book, preprocess_ctx))
info!("Running the {} backend", renderer.name());
self.render(&preprocessed_book, renderer)?;
Ok(())
} }
fn render( /// Run the entire build process for a particular [`Renderer`].
&self, pub fn execute_build_process(&self, renderer: &dyn Renderer) -> Result<()> {
preprocessed_book: &Book, let (preprocessed_book, preprocess_ctx) = self.preprocess_book(renderer)?;
renderer: &Renderer,
) -> Result<()> {
let name = renderer.name(); let name = renderer.name();
let build_dir = self.build_dir_for(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) let mut render_context = RenderContext::new(
.chain_err(|| "Unable to clear output directory")?;
}
let render_context = RenderContext::new(
self.root.clone(), self.root.clone(),
preprocessed_book.clone(), preprocessed_book,
self.config.clone(), self.config.clone(),
build_dir, build_dir,
); );
render_context
.chapter_titles
.extend(preprocess_ctx.chapter_titles.borrow_mut().drain());
info!("Running the {} backend", renderer.name());
renderer renderer
.render(&render_context) .render(&render_context)
.chain_err(|| "Rendering failed") .with_context(|| "Rendering failed")
} }
/// You can change the default renderer to another one by using this method. /// You can change the default renderer to another one by using this method.
/// The only requirement is for your renderer to implement the [`Renderer` /// The only requirement is that your renderer implement the [`Renderer`]
/// trait](../renderer/trait.Renderer.html) /// trait.
pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self { pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self {
self.renderers.push(Box::new(renderer)); self.renderers.push(Box::new(renderer));
self self
} }
/// Register a [`Preprocessor`](../preprocess/trait.Preprocessor.html) to be used when rendering the book. /// Register a [`Preprocessor`] to be used when rendering the book.
pub fn with_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.preprocessors.push(Box::new(preprocessor));
self self
} }
/// Run `rustdoc` tests on the book, linking against the provided libraries. /// Run `rustdoc` tests on the book, linking against the provided libraries.
pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> { pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
let library_args: Vec<&str> = (0..library_paths.len()) // test_chapter with chapter:None will run all tests.
.map(|_| "-L") self.test_chapter(library_paths, None)
.zip(library_paths.into_iter()) }
.flat_map(|x| vec![x.0, x.1])
/// Run `rustdoc` tests on a specific chapter of the book, linking against the provided libraries.
/// If `chapter` is `None`, all tests will be run.
pub fn test_chapter(&mut self, library_paths: Vec<&str>, chapter: Option<&str>) -> Result<()> {
let cwd = std::env::current_dir()?;
let library_args: Vec<OsString> = library_paths
.into_iter()
.flat_map(|path| {
let path = Path::new(path);
let path = if path.is_relative() {
cwd.join(path).into_os_string()
} else {
path.to_path_buf().into_os_string()
};
[OsString::from("-L"), path]
})
.collect(); .collect();
let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?; let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
let mut chapter_found = false;
struct TestRenderer;
impl Renderer for TestRenderer {
// FIXME: Is "test" the proper renderer name to use here? // FIXME: Is "test" the proper renderer name to use here?
let preprocess_context = PreprocessorContext::new(self.root.clone(), fn name(&self) -> &str {
self.config.clone(), "test"
"test".to_string()); }
let book = LinkPreprocessor::new().run(&preprocess_context, self.book.clone())?; fn render(&self, _: &RenderContext) -> Result<()> {
// Index Preprocessor is disabled so that chapter paths continue to point to the Ok(())
// actual markdown files. }
}
// Index Preprocessor is disabled so that chapter paths
// continue to point to the actual markdown files.
self.preprocessors = determine_preprocessors(&self.config)?
.into_iter()
.filter(|pre| pre.name() != IndexPreprocessor::NAME)
.collect();
let (book, _) = self.preprocess_book(&TestRenderer)?;
let color_output = std::io::stderr().is_terminal();
let mut failed = false;
for item in book.iter() { for item in book.iter() {
if let BookItem::Chapter(ref ch) = *item { if let BookItem::Chapter(ref ch) = *item {
if !ch.path.as_os_str().is_empty() { let chapter_path = match ch.path {
let path = self.source_dir().join(&ch.path); Some(ref path) if !path.as_os_str().is_empty() => path,
let content = utils::fs::file_to_string(&path)?; _ => continue,
info!("Testing file: {:?}", path); };
if let Some(chapter) = chapter {
if ch.name != chapter && chapter_path.to_str() != Some(chapter) {
if chapter == "?" {
info!("Skipping chapter '{}'...", ch.name);
}
continue;
}
}
chapter_found = true;
info!("Testing chapter '{}': {:?}", ch.name, chapter_path);
// write preprocessed file to tempdir // write preprocessed file to tempdir
let path = temp_dir.path().join(&ch.path); let path = temp_dir.path().join(chapter_path);
let mut tmpf = utils::fs::create_file(&path)?; let mut tmpf = utils::fs::create_file(&path)?;
tmpf.write_all(content.as_bytes())?; tmpf.write_all(ch.content.as_bytes())?;
let output = Command::new("rustdoc") let mut cmd = Command::new("rustdoc");
.arg(&path) cmd.current_dir(temp_dir.path())
.arg(&chapter_path)
.arg("--test") .arg("--test")
.args(&library_args) .args(&library_args);
.output()?;
if let Some(edition) = self.config.rust.edition {
match edition {
RustEdition::E2015 => {
cmd.args(["--edition", "2015"]);
}
RustEdition::E2018 => {
cmd.args(["--edition", "2018"]);
}
RustEdition::E2021 => {
cmd.args(["--edition", "2021"]);
}
}
}
if color_output {
cmd.args(&["--color", "always"]);
}
debug!("running {:?}", cmd);
let output = cmd.output()?;
if !output.status.success() { if !output.status.success() {
bail!(ErrorKind::Subprocess( failed = true;
"Rustdoc returned an error".to_string(), error!(
output "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");
}
if let Some(chapter) = chapter {
if !chapter_found {
bail!("Chapter not found: {}", chapter);
}
} }
Ok(()) Ok(())
} }
@ -274,7 +381,7 @@ impl MDBook {
/// artefacts. /// artefacts.
/// ///
/// If there is only 1 renderer, put it in the directory pointed to by the /// If there is only 1 renderer, put it in the directory pointed to by the
/// `build.build_dir` key in `Config`. If there is more than one then the /// `build.build_dir` key in [`Config`]. If there is more than one then the
/// renderer gets its own directory within the main build dir. /// renderer gets its own directory within the main build dir.
/// ///
/// i.e. If there were only one renderer (in this case, the HTML renderer): /// i.e. If there were only one renderer (in this case, the HTML renderer):
@ -319,19 +426,19 @@ impl MDBook {
} }
/// Look at the `Config` and try to figure out what renderers to use. /// Look at the `Config` and try to figure out what renderers to use.
fn determine_renderers(config: &Config) -> Vec<Box<Renderer>> { fn determine_renderers(config: &Config) -> Vec<Box<dyn Renderer>> {
let mut renderers: Vec<Box<Renderer>> = Vec::new(); let mut renderers = Vec::new();
if let Some(output_table) = config.get("output").and_then(|o| o.as_table()) { if let Some(output_table) = config.get("output").and_then(Value::as_table) {
for (key, table) in output_table.iter() { renderers.extend(output_table.iter().map(|(key, table)| {
// the "html" backend has its own Renderer
if key == "html" { 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 { } else {
let renderer = interpret_custom_renderer(key, table); interpret_custom_renderer(key, table)
renderers.push(renderer);
}
} }
}));
} }
// if we couldn't find anything, add the HTML renderer as a default // if we couldn't find anything, add the HTML renderer as a default
@ -342,60 +449,149 @@ fn determine_renderers(config: &Config) -> Vec<Box<Renderer>> {
renderers renderers
} }
fn default_preprocessors() -> Vec<Box<Preprocessor>> { const DEFAULT_PREPROCESSORS: &[&str] = &["links", "index"];
vec![
Box::new(LinkPreprocessor::new()),
Box::new(IndexPreprocessor::new()),
]
}
fn is_default_preprocessor(pre: &Preprocessor) -> bool { fn is_default_preprocessor(pre: &dyn Preprocessor) -> bool {
let name = pre.name(); let name = pre.name();
name == LinkPreprocessor::NAME || name == IndexPreprocessor::NAME name == LinkPreprocessor::NAME || name == IndexPreprocessor::NAME
} }
/// Look at the `MDBook` and try to figure out what preprocessors to run. /// Look at the `MDBook` and try to figure out what preprocessors to run.
fn determine_preprocessors(config: &Config) -> Result<Vec<Box<Preprocessor>>> { fn determine_preprocessors(config: &Config) -> Result<Vec<Box<dyn Preprocessor>>> {
let preprocessor_keys = config.get("preprocessor") // Collect the names of all preprocessors intended to be run, and the order
.and_then(|value| value.as_table()) // in which they should be run.
.map(|table| table.keys()); let mut preprocessor_names = TopologicalSort::<String>::new();
let mut preprocessors = if config.build.use_default_preprocessors { if config.build.use_default_preprocessors {
default_preprocessors() for name in DEFAULT_PREPROCESSORS {
preprocessor_names.insert(name.to_string());
}
}
if let Some(preprocessor_table) = config.get("preprocessor").and_then(Value::as_table) {
for (name, table) in preprocessor_table.iter() {
preprocessor_names.insert(name.to_string());
let exists = |name| {
(config.build.use_default_preprocessors && DEFAULT_PREPROCESSORS.contains(&name))
|| preprocessor_table.contains_key(name)
};
if let Some(before) = table.get("before") {
let before = before.as_array().ok_or_else(|| {
Error::msg(format!(
"Expected preprocessor.{}.before to be an array",
name
))
})?;
for after in before {
let after = after.as_str().ok_or_else(|| {
Error::msg(format!(
"Expected preprocessor.{}.before to contain strings",
name
))
})?;
if !exists(after) {
// Only warn so that preprocessors can be toggled on and off (e.g. for
// troubleshooting) without having to worry about order too much.
warn!(
"preprocessor.{}.after contains \"{}\", which was not found",
name, after
);
} else { } else {
Vec::new() preprocessor_names.add_dependency(name, after);
}; }
let preprocessor_keys = match preprocessor_keys {
Some(keys) => keys,
// If no preprocessor field is set, default to the LinkPreprocessor and
// IndexPreprocessor. This allows you to disable default preprocessors
// by setting "preprocess" to an empty list.
None => return Ok(preprocessors),
};
for key in preprocessor_keys {
match key.as_ref() {
"links" => preprocessors.push(Box::new(LinkPreprocessor::new())),
"index" => preprocessors.push(Box::new(IndexPreprocessor::new())),
_ => bail!("{:?} is not a recognised preprocessor", key),
} }
} }
if let Some(after) = table.get("after") {
let after = after.as_array().ok_or_else(|| {
Error::msg(format!(
"Expected preprocessor.{}.after to be an array",
name
))
})?;
for before in after {
let before = before.as_str().ok_or_else(|| {
Error::msg(format!(
"Expected preprocessor.{}.after to contain strings",
name
))
})?;
if !exists(before) {
// See equivalent warning above for rationale
warn!(
"preprocessor.{}.before contains \"{}\", which was not found",
name, before
);
} else {
preprocessor_names.add_dependency(before, name);
}
}
}
}
}
// Now that all links have been established, queue preprocessors in a suitable order
let mut preprocessors = Vec::with_capacity(preprocessor_names.len());
// `pop_all()` returns an empty vector when no more items are not being depended upon
for mut names in std::iter::repeat_with(|| preprocessor_names.pop_all())
.take_while(|names| !names.is_empty())
{
// The `topological_sort` crate does not guarantee a stable order for ties, even across
// runs of the same program. Thus, we break ties manually by sorting.
// Careful: `str`'s default sorting, which we are implicitly invoking here, uses code point
// values ([1]), which may not be an alphabetical sort.
// As mentioned in [1], doing so depends on locale, which is not desirable for deciding
// preprocessor execution order.
// [1]: https://doc.rust-lang.org/stable/std/cmp/trait.Ord.html#impl-Ord-14
names.sort();
for name in names {
let preprocessor: Box<dyn Preprocessor> = match name.as_str() {
"links" => Box::new(LinkPreprocessor::new()),
"index" => Box::new(IndexPreprocessor::new()),
_ => {
// The only way to request a custom preprocessor is through the `preprocessor`
// table, so it must exist, be a table, and contain the key.
let table = &config.get("preprocessor").unwrap().as_table().unwrap()[&name];
let command = get_custom_preprocessor_cmd(&name, table);
Box::new(CmdPreprocessor::new(name, command))
}
};
preprocessors.push(preprocessor);
}
}
// "If `pop_all` returns an empty vector and `len` is not 0, there are cyclic dependencies."
// Normally, `len() == 0` is equivalent to `is_empty()`, so we'll use that.
if preprocessor_names.is_empty() {
Ok(preprocessors) Ok(preprocessors)
} else {
Err(Error::msg("Cyclic dependency detected in preprocessors"))
}
} }
fn interpret_custom_renderer(key: &str, table: &Value) -> Box<Renderer> { fn get_custom_preprocessor_cmd(key: &str, table: &Value) -> String {
table
.get("command")
.and_then(Value::as_str)
.map(ToString::to_string)
.unwrap_or_else(|| format!("mdbook-{}", key))
}
fn interpret_custom_renderer(key: &str, table: &Value) -> Box<CmdRenderer> {
// look for the `command` field, falling back to using the key // look for the `command` field, falling back to using the key
// prepended by "mdbook-" // prepended by "mdbook-"
let table_dot_command = table let table_dot_command = table
.get("command") .get("command")
.and_then(|c| c.as_str()) .and_then(Value::as_str)
.map(|s| s.to_string()); .map(ToString::to_string);
let command = table_dot_command.unwrap_or_else(|| format!("mdbook-{}", key)); 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 /// Check whether we should run a particular `Preprocessor` in combination
@ -404,7 +600,11 @@ fn interpret_custom_renderer(key: &str, table: &Value) -> Box<Renderer> {
/// ///
/// The `build.use-default-preprocessors` config option can be used to ensure /// The `build.use-default-preprocessors` config option can be used to ensure
/// default preprocessors always run if they support the renderer. /// default preprocessors always run if they support the renderer.
fn preprocessor_should_run(preprocessor: &Preprocessor, renderer: &Renderer, cfg: &Config) -> bool { fn preprocessor_should_run(
preprocessor: &dyn Preprocessor,
renderer: &dyn Renderer,
cfg: &Config,
) -> bool {
// default preprocessors should be run by default (if supported) // default preprocessors should be run by default (if supported)
if cfg.build.use_default_preprocessors && is_default_preprocessor(preprocessor) { if cfg.build.use_default_preprocessors && is_default_preprocessor(preprocessor) {
return preprocessor.supports_renderer(renderer.name()); return preprocessor.supports_renderer(renderer.name());
@ -414,19 +614,20 @@ fn preprocessor_should_run(preprocessor: &Preprocessor, renderer: &Renderer, cfg
let renderer_name = renderer.name(); let renderer_name = renderer.name();
if let Some(Value::Array(ref explicit_renderers)) = cfg.get(&key) { if let Some(Value::Array(ref explicit_renderers)) = cfg.get(&key) {
return explicit_renderers.into_iter() return explicit_renderers
.filter_map(|val| val.as_str()) .iter()
.filter_map(Value::as_str)
.any(|name| name == renderer_name); .any(|name| name == renderer_name);
} }
preprocessor.supports_renderer(renderer_name) preprocessor.supports_renderer(renderer_name)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use toml::value::{Table, Value}; use std::str::FromStr;
use toml::value::Table;
#[test] #[test]
fn config_defaults_to_html_renderer_if_empty() { fn config_defaults_to_html_renderer_if_empty() {
@ -477,8 +678,8 @@ mod tests {
assert!(got.is_ok()); assert!(got.is_ok());
assert_eq!(got.as_ref().unwrap().len(), 2); assert_eq!(got.as_ref().unwrap().len(), 2);
assert_eq!(got.as_ref().unwrap()[0].name(), "links"); assert_eq!(got.as_ref().unwrap()[0].name(), "index");
assert_eq!(got.as_ref().unwrap()[1].name(), "index"); assert_eq!(got.as_ref().unwrap()[1].name(), "links");
} }
#[test] #[test]
@ -492,8 +693,8 @@ mod tests {
} }
#[test] #[test]
fn config_complains_if_unimplemented_preprocessor() { fn can_determine_third_party_preprocessors() {
let cfg_str: &'static str = r#" let cfg_str = r#"
[book] [book]
title = "Some Book" title = "Some Book"
@ -509,14 +710,142 @@ mod tests {
// make sure the `preprocessor.random` table exists // make sure the `preprocessor.random` table exists
assert!(cfg.get_preprocessor("random").is_some()); assert!(cfg.get_preprocessor("random").is_some());
let got = determine_preprocessors(&cfg); let got = determine_preprocessors(&cfg).unwrap();
assert!(got.is_err()); assert!(got.into_iter().any(|p| p.name() == "random"));
}
#[test]
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 the `preprocessor.random` table exists
let random = cfg.get_preprocessor("random").unwrap();
let random = get_custom_preprocessor_cmd("random", &Value::Table(random.clone()));
assert_eq!(random, "python random.py");
}
#[test]
fn preprocessor_before_must_be_array() {
let cfg_str = r#"
[preprocessor.random]
before = 0
"#;
let cfg = Config::from_str(cfg_str).unwrap();
assert!(determine_preprocessors(&cfg).is_err());
}
#[test]
fn preprocessor_after_must_be_array() {
let cfg_str = r#"
[preprocessor.random]
after = 0
"#;
let cfg = Config::from_str(cfg_str).unwrap();
assert!(determine_preprocessors(&cfg).is_err());
}
#[test]
fn preprocessor_order_is_honored() {
let cfg_str = r#"
[preprocessor.random]
before = [ "last" ]
after = [ "index" ]
[preprocessor.last]
after = [ "links", "index" ]
"#;
let cfg = Config::from_str(cfg_str).unwrap();
let preprocessors = determine_preprocessors(&cfg).unwrap();
let index = |name| {
preprocessors
.iter()
.enumerate()
.find(|(_, preprocessor)| preprocessor.name() == name)
.unwrap()
.0
};
let assert_before = |before, after| {
if index(before) >= index(after) {
eprintln!("Preprocessor order:");
for preprocessor in &preprocessors {
eprintln!(" {}", preprocessor.name());
}
panic!("{} should come before {}", before, after);
}
};
assert_before("index", "random");
assert_before("index", "last");
assert_before("random", "last");
assert_before("links", "last");
}
#[test]
fn cyclic_dependencies_are_detected() {
let cfg_str = r#"
[preprocessor.links]
before = [ "index" ]
[preprocessor.index]
before = [ "links" ]
"#;
let cfg = Config::from_str(cfg_str).unwrap();
assert!(determine_preprocessors(&cfg).is_err());
}
#[test]
fn dependencies_dont_register_undefined_preprocessors() {
let cfg_str = r#"
[preprocessor.links]
before = [ "random" ]
"#;
let cfg = Config::from_str(cfg_str).unwrap();
let preprocessors = determine_preprocessors(&cfg).unwrap();
assert!(!preprocessors
.iter()
.any(|preprocessor| preprocessor.name() == "random"));
}
#[test]
fn dependencies_dont_register_builtin_preprocessors_if_disabled() {
let cfg_str = r#"
[preprocessor.random]
before = [ "links" ]
[build]
use-default-preprocessors = false
"#;
let cfg = Config::from_str(cfg_str).unwrap();
let preprocessors = determine_preprocessors(&cfg).unwrap();
assert!(!preprocessors
.iter()
.any(|preprocessor| preprocessor.name() == "links"));
} }
#[test] #[test]
fn config_respects_preprocessor_selection() { fn config_respects_preprocessor_selection() {
let cfg_str: &'static str = r#" let cfg_str = r#"
[preprocessor.links] [preprocessor.links]
renderers = ["html"] renderers = ["html"]
"#; "#;
@ -524,11 +853,12 @@ mod tests {
let cfg = Config::from_str(cfg_str).unwrap(); let cfg = Config::from_str(cfg_str).unwrap();
// double-check that we can access preprocessor.links.renderers[0] // double-check that we can access preprocessor.links.renderers[0]
let html = cfg.get_preprocessor("links") let html = cfg
.get_preprocessor("links")
.and_then(|links| links.get("renderers")) .and_then(|links| links.get("renderers"))
.and_then(|renderers| renderers.as_array()) .and_then(Value::as_array)
.and_then(|renderers| renderers.get(0)) .and_then(|renderers| renderers.get(0))
.and_then(|renderer| renderer.as_str()) .and_then(Value::as_str)
.unwrap(); .unwrap();
assert_eq!(html, "html"); assert_eq!(html, "html");
let html_renderer = HtmlHandlebars::default(); let html_renderer = HtmlHandlebars::default();

View File

@ -1,8 +1,9 @@
use errors::*; use crate::errors::*;
use memchr::{self, Memchr}; use log::{debug, trace, warn};
use pulldown_cmark::{self, Event, Tag}; use memchr::Memchr;
use pulldown_cmark::{DefaultBrokenLinkCallback, Event, HeadingLevel, Tag, TagEnd};
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::iter::FromIterator;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -25,12 +26,17 @@ use std::path::{Path, PathBuf};
/// [Title of prefix element](relative/path/to/markdown.md) /// [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, /// **Numbered Chapter:** Numbered chapters are the main content of the book,
/// they /// they
/// will be numbered and can be nested, resulting in a nice hierarchy (chapters, /// will be numbered and can be nested, resulting in a nice hierarchy (chapters,
/// sub-chapters, etc.) /// sub-chapters, etc.)
/// ///
/// ```markdown /// ```markdown
/// # Title of Part
///
/// - [Title of the Chapter](relative/path/to/markdown.md) /// - [Title of the Chapter](relative/path/to/markdown.md)
/// ``` /// ```
/// ///
@ -55,7 +61,7 @@ pub struct Summary {
pub title: Option<String>, pub title: Option<String>,
/// Chapters before the main text (e.g. an introduction). /// Chapters before the main text (e.g. an introduction).
pub prefix_chapters: Vec<SummaryItem>, 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>, pub numbered_chapters: Vec<SummaryItem>,
/// Items which come after the main document (e.g. a conclusion). /// Items which come after the main document (e.g. a conclusion).
pub suffix_chapters: Vec<SummaryItem>, pub suffix_chapters: Vec<SummaryItem>,
@ -71,7 +77,7 @@ pub struct Link {
pub name: String, pub name: String,
/// The location of the chapter's source file, taking the book's `src` /// The location of the chapter's source file, taking the book's `src`
/// directory as the root. /// directory as the root.
pub location: PathBuf, pub location: Option<PathBuf>,
/// The section number, if this chapter is in the numbered section. /// The section number, if this chapter is in the numbered section.
pub number: Option<SectionNumber>, pub number: Option<SectionNumber>,
/// Any nested items this chapter may contain. /// Any nested items this chapter may contain.
@ -83,7 +89,7 @@ impl Link {
pub fn new<S: Into<String>, P: AsRef<Path>>(name: S, location: P) -> Link { pub fn new<S: Into<String>, P: AsRef<Path>>(name: S, location: P) -> Link {
Link { Link {
name: name.into(), name: name.into(),
location: location.as_ref().to_path_buf(), location: Some(location.as_ref().to_path_buf()),
number: None, number: None,
nested_items: Vec::new(), nested_items: Vec::new(),
} }
@ -94,7 +100,7 @@ impl Default for Link {
fn default() -> Self { fn default() -> Self {
Link { Link {
name: String::new(), name: String::new(),
location: PathBuf::new(), location: Some(PathBuf::new()),
number: None, number: None,
nested_items: Vec::new(), nested_items: Vec::new(),
} }
@ -108,6 +114,8 @@ pub enum SummaryItem {
Link(Link), Link(Link),
/// A separator (`---`). /// A separator (`---`).
Separator, Separator,
/// A part title.
PartTitle(String),
} }
impl SummaryItem { impl SummaryItem {
@ -139,7 +147,8 @@ impl From<Link> for SummaryItem {
/// | EPSILON /// | EPSILON
/// prefix_chapters ::= item* /// prefix_chapters ::= item*
/// suffix_chapters ::= item* /// suffix_chapters ::= item*
/// numbered_chapters ::= dotted_item+ /// numbered_chapters ::= part+
/// part ::= title dotted_item+
/// dotted_item ::= INDENT* DOT_POINT item /// dotted_item ::= INDENT* DOT_POINT item
/// item ::= link /// item ::= link
/// | separator /// | separator
@ -153,14 +162,19 @@ impl From<Link> for SummaryItem {
/// > match the following regex: "[^<>\n[]]+". /// > match the following regex: "[^<>\n[]]+".
struct SummaryParser<'a> { struct SummaryParser<'a> {
src: &'a str, src: &'a str,
stream: pulldown_cmark::Parser<'a>, stream: pulldown_cmark::OffsetIter<'a, DefaultBrokenLinkCallback>,
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 /// Reads `Events` from the provided stream until the corresponding
/// `Event::End` is encountered which matches the `$delimiter` pattern. /// `Event::End` is encountered which matches the `$delimiter` pattern.
/// ///
/// This is the equivalent of doing /// This is the equivalent of doing
/// `$stream.take_while(|e| e != $delimeter).collect()` but it allows you to /// `$stream.take_while(|e| e != $delimiter).collect()` but it allows you to
/// use pattern matching and you won't get errors because `take_while()` /// use pattern matching and you won't get errors because `take_while()`
/// moves `$stream` out of self. /// moves `$stream` out of self.
macro_rules! collect_events { macro_rules! collect_events {
@ -174,7 +188,7 @@ macro_rules! collect_events {
let mut events = Vec::new(); let mut events = Vec::new();
loop { loop {
let event = $stream.next(); let event = $stream.next().map(|(ev, _range)| ev);
trace!("Next event: {:?}", event); trace!("Next event: {:?}", event);
match event { match event {
@ -195,24 +209,24 @@ macro_rules! collect_events {
} }
impl<'a> SummaryParser<'a> { impl<'a> SummaryParser<'a> {
fn new(text: &str) -> SummaryParser { fn new(text: &'a str) -> SummaryParser<'a> {
let pulldown_parser = pulldown_cmark::Parser::new(text); let pulldown_parser = pulldown_cmark::Parser::new(text).into_offset_iter();
SummaryParser { SummaryParser {
src: text, src: text,
stream: pulldown_parser, stream: pulldown_parser,
offset: 0,
back: None,
} }
} }
/// Get the current line and column to give the user more useful error /// Get the current line and column to give the user more useful error
/// messages. /// messages.
fn current_location(&self) -> (usize, usize) { fn current_location(&self) -> (usize, usize) {
let byte_offset = self.stream.get_offset(); let previous_text = self.src[..self.offset].as_bytes();
let previous_text = self.src[..byte_offset].as_bytes();
let line = Memchr::new(b'\n', previous_text).count() + 1; let line = Memchr::new(b'\n', previous_text).count() + 1;
let start_of_line = memchr::memrchr(b'\n', previous_text).unwrap_or(0); 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) (line, col)
} }
@ -223,13 +237,13 @@ impl<'a> SummaryParser<'a> {
let prefix_chapters = self let prefix_chapters = self
.parse_affix(true) .parse_affix(true)
.chain_err(|| "There was an error parsing the prefix chapters")?; .with_context(|| "There was an error parsing the prefix chapters")?;
let numbered_chapters = self let numbered_chapters = self
.parse_numbered() .parse_parts()
.chain_err(|| "There was an error parsing the numbered chapters")?; .with_context(|| "There was an error parsing the numbered chapters")?;
let suffix_chapters = self let suffix_chapters = self
.parse_affix(false) .parse_affix(false)
.chain_err(|| "There was an error parsing the suffix chapters")?; .with_context(|| "There was an error parsing the suffix chapters")?;
Ok(Summary { Ok(Summary {
title, title,
@ -239,8 +253,7 @@ impl<'a> SummaryParser<'a> {
}) })
} }
/// Parse the affix chapters. This expects the first event (start of /// Parse the affix chapters.
/// paragraph) to have already been consumed by the previous parser.
fn parse_affix(&mut self, is_prefix: bool) -> Result<Vec<SummaryItem>> { fn parse_affix(&mut self, is_prefix: bool) -> Result<Vec<SummaryItem>> {
let mut items = Vec::new(); let mut items = Vec::new();
debug!( debug!(
@ -250,20 +263,27 @@ impl<'a> SummaryParser<'a> {
loop { loop {
match self.next_event() { match self.next_event() {
Some(Event::Start(Tag::List(..))) => { Some(ev @ Event::Start(Tag::List(..)))
| Some(
ev @ Event::Start(Tag::Heading {
level: HeadingLevel::H1,
..
}),
) => {
if is_prefix { if is_prefix {
// we've finished prefix chapters and are at the start // we've finished prefix chapters and are at the start
// of the numbered section. // of the numbered section.
self.back(ev);
break; break;
} else { } else {
bail!(self.parse_error("Suffix chapters cannot be followed by a list")); bail!(self.parse_error("Suffix chapters cannot be followed by a list"));
} }
} }
Some(Event::Start(Tag::Link(href, _))) => { Some(Event::Start(Tag::Link { dest_url, .. })) => {
let link = self.parse_link(href.to_string())?; let link = self.parse_link(dest_url.to_string());
items.push(SummaryItem::Link(link)); items.push(SummaryItem::Link(link));
} }
Some(Event::Start(Tag::Rule)) => items.push(SummaryItem::Separator), Some(Event::Rule) => items.push(SummaryItem::Separator),
Some(_) => {} Some(_) => {}
None => break, None => break,
} }
@ -272,85 +292,164 @@ impl<'a> SummaryParser<'a> {
Ok(items) Ok(items)
} }
fn parse_link(&mut self, href: String) -> Result<Link> { fn parse_parts(&mut self) -> Result<Vec<SummaryItem>> {
let link_content = collect_events!(self.stream, end Tag::Link(..)); let mut parts = vec![];
let name = stringify_events(link_content);
if href.is_empty() { // We want the section numbers to be continues through all parts.
Err(self.parse_error("You can't have an empty link.")) let mut root_number = SectionNumber::default();
} else { let mut root_items = 0;
Ok(Link {
name: name,
location: PathBuf::from(href.to_string()),
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>> {
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 :)
loop { loop {
let mut bunch_of_items = self.parse_nested_numbered(&root_number)?; // 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 {
level: HeadingLevel::H1,
..
})) => {
debug!("Found a h1 in the SUMMARY");
let tags = collect_events!(self.stream, end TagEnd::Heading(HeadingLevel::H1));
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 TagEnd::Link);
let name = stringify_events(link_content);
let path = if href.is_empty() {
None
} else {
Some(PathBuf::from(href))
};
Link {
name,
location: path,
number: None,
nested_items: Vec::new(),
}
}
/// 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();
// 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 {
match self.next_event() {
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 {
level: HeadingLevel::H1,
..
}),
) => {
// 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 // 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 // will be numbered from 1. We need to manually go back and update
// them // them
update_section_numbers(&mut bunch_of_items, 0, items.len() as u32); update_section_numbers(&mut bunch_of_items, 0, *root_items);
*root_items += bunch_of_items.len() as u32;
items.extend(bunch_of_items); items.extend(bunch_of_items);
match self.next_event() {
Some(Event::Start(Tag::Paragraph)) => {
// we're starting the suffix chapters
break;
} }
Some(Event::Start(other_tag)) => { Some(Event::Start(other_tag)) => {
if other_tag == Tag::Rule {
items.push(SummaryItem::Separator);
}
trace!("Skipping contents of {:?}", other_tag); trace!("Skipping contents of {:?}", other_tag);
// Skip over the contents of this tag // Skip over the contents of this tag
while let Some(event) = self.next_event() { while let Some(event) = self.next_event() {
if event == Event::End(other_tag.clone()) { if event == Event::End(other_tag.clone().into()) {
break;
}
}
}
Some(Event::Rule) => {
items.push(SummaryItem::Separator);
}
// something else... ignore
Some(_) => {}
// EOF, bail...
None => {
break; break;
} }
} }
if let Some(Event::Start(Tag::List(..))) = self.next_event() { // From now on, we cannot accept any new paragraph opening tags.
continue; first = false;
} else {
break;
}
}
Some(_) => {
// something else... ignore
continue;
}
None => {
// EOF, bail...
break;
}
}
} }
Ok(items) 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>> { 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); trace!("Next event: {:?}", next);
next next
@ -367,6 +466,10 @@ impl<'a> SummaryParser<'a> {
items.push(item); items.push(item);
} }
Some(Event::Start(Tag::List(..))) => { Some(Event::Start(Tag::List(..))) => {
// Skip this tag after comment because it is not nested.
if items.is_empty() {
continue;
}
// recurse to parse the nested list // recurse to parse the nested list
let (_, last_item) = get_last_link(&mut items)?; let (_, last_item) = get_last_link(&mut items)?;
let last_item_number = last_item let last_item_number = last_item
@ -378,7 +481,7 @@ impl<'a> SummaryParser<'a> {
last_item.nested_items = sub_items; last_item.nested_items = sub_items;
} }
Some(Event::End(Tag::List(..))) => break, Some(Event::End(TagEnd::List(..))) => break,
Some(_) => {} Some(_) => {}
None => break, None => break,
} }
@ -395,8 +498,8 @@ impl<'a> SummaryParser<'a> {
loop { loop {
match self.next_event() { match self.next_event() {
Some(Event::Start(Tag::Paragraph)) => continue, Some(Event::Start(Tag::Paragraph)) => continue,
Some(Event::Start(Tag::Link(href, _))) => { Some(Event::Start(Tag::Link { dest_url, .. })) => {
let mut link = self.parse_link(href.to_string())?; let mut link = self.parse_link(dest_url.to_string());
let mut number = parent.clone(); let mut number = parent.clone();
number.0.push(num_existing_items as u32 + 1); number.0.push(num_existing_items as u32 + 1);
@ -404,7 +507,10 @@ impl<'a> SummaryParser<'a> {
"Found chapter: {} {} ({})", "Found chapter: {} {} ({})",
number, number,
link.name, link.name,
link.location.display() link.location
.as_ref()
.map(|p| p.to_str().unwrap_or(""))
.unwrap_or("[draft]")
); );
link.number = Some(number); link.number = Some(number);
@ -423,19 +529,37 @@ impl<'a> SummaryParser<'a> {
fn parse_error<D: Display>(&self, msg: D) -> Error { fn parse_error<D: Display>(&self, msg: D) -> Error {
let (line, col) = self.current_location(); let (line, col) = self.current_location();
anyhow::anyhow!(
ErrorKind::ParseError(line, col, msg.to_string()).into() "failed to parse SUMMARY.md line {}, column {}: {}",
line,
col,
msg
)
} }
/// Try to parse the title line. /// Try to parse the title line.
fn parse_title(&mut self) -> Option<String> { fn parse_title(&mut self) -> Option<String> {
if let Some(Event::Start(Tag::Header(1))) = self.next_event() { loop {
match self.next_event() {
Some(Event::Start(Tag::Heading {
level: HeadingLevel::H1,
..
})) => {
debug!("Found a h1 in the SUMMARY"); debug!("Found a h1 in the SUMMARY");
let tags = collect_events!(self.stream, end Tag::Header(1)); let tags = collect_events!(self.stream, end TagEnd::Heading(HeadingLevel::H1));
Some(stringify_events(tags)) return Some(stringify_events(tags));
} else { }
None // Skip a HTML element such as a comment line.
Some(Event::Html(_) | Event::InlineHtml(_))
| Some(Event::Start(Tag::HtmlBlock) | Event::End(TagEnd::HtmlBlock)) => {}
// Otherwise, no title.
Some(ev) => {
self.back(ev);
return None;
}
_ => return None,
}
} }
} }
} }
@ -461,21 +585,22 @@ fn get_last_link(links: &mut [SummaryItem]) -> Result<(usize, &mut Link)> {
.filter_map(|(i, item)| item.maybe_link_mut().map(|l| (i, l))) .filter_map(|(i, item)| item.maybe_link_mut().map(|l| (i, l)))
.rev() .rev()
.next() .next()
.ok_or_else(|| { .ok_or_else(||
"Unable to get last link because the list of SummaryItems doesn't contain any Links" anyhow::anyhow!("Unable to get last link because the list of SummaryItems doesn't contain any Links")
.into() )
})
} }
/// Removes the styling from a list of Markdown events and returns just the /// Removes the styling from a list of Markdown events and returns just the
/// plain text. /// plain text.
fn stringify_events(events: Vec<Event>) -> String { fn stringify_events(events: Vec<Event<'_>>) -> String {
events events
.into_iter() .into_iter()
.filter_map(|t| match t { .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, _ => None,
}).collect() })
.collect()
} }
/// A section number like "1.2.3", basically just a newtype'd `Vec<u32>` with /// A section number like "1.2.3", basically just a newtype'd `Vec<u32>` with
@ -484,7 +609,7 @@ fn stringify_events(events: Vec<Event>) -> String {
pub struct SectionNumber(pub Vec<u32>); pub struct SectionNumber(pub Vec<u32>);
impl Display for SectionNumber { 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() { if self.0.is_empty() {
write!(f, "0") write!(f, "0")
} else { } else {
@ -544,6 +669,18 @@ mod tests {
assert_eq!(got, should_be); assert_eq!(got, should_be);
} }
#[test]
fn no_initial_title() {
let src = "[Link]()";
let mut parser = SummaryParser::new(src);
assert!(parser.parse_title().is_none());
assert!(matches!(
parser.next_event(),
Some(Event::Start(Tag::Paragraph))
));
}
#[test] #[test]
fn parse_title_with_styling() { fn parse_title_with_styling() {
let src = "# My **Awesome** Summary"; let src = "# My **Awesome** Summary";
@ -574,17 +711,16 @@ mod tests {
let should_be = vec![ let should_be = vec![
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("First"), name: String::from("First"),
location: PathBuf::from("./first.md"), location: Some(PathBuf::from("./first.md")),
..Default::default() ..Default::default()
}), }),
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("Second"), name: String::from("Second"),
location: PathBuf::from("./second.md"), location: Some(PathBuf::from("./second.md")),
..Default::default() ..Default::default()
}), }),
]; ];
let _ = parser.stream.next(); // step past first event
let got = parser.parse_affix(true).unwrap(); let got = parser.parse_affix(true).unwrap();
assert_eq!(got, should_be); assert_eq!(got, should_be);
@ -595,7 +731,6 @@ mod tests {
let src = "[First](./first.md)\n\n---\n\n[Second](./second.md)\n"; let src = "[First](./first.md)\n\n---\n\n[Second](./second.md)\n";
let mut parser = SummaryParser::new(src); let mut parser = SummaryParser::new(src);
let _ = parser.stream.next(); // step past first event
let got = parser.parse_affix(true).unwrap(); let got = parser.parse_affix(true).unwrap();
assert_eq!(got.len(), 3); assert_eq!(got.len(), 3);
@ -607,7 +742,6 @@ mod tests {
let src = "[First](./first.md)\n- [Second](./second.md)\n"; let src = "[First](./first.md)\n- [Second](./second.md)\n";
let mut parser = SummaryParser::new(src); let mut parser = SummaryParser::new(src);
let _ = parser.stream.next(); // step past first event
let got = parser.parse_affix(false); let got = parser.parse_affix(false);
assert!(got.is_err()); assert!(got.is_err());
@ -618,19 +752,19 @@ mod tests {
let src = "[First](./first.md)"; let src = "[First](./first.md)";
let should_be = Link { let should_be = Link {
name: String::from("First"), name: String::from("First"),
location: PathBuf::from("./first.md"), location: Some(PathBuf::from("./first.md")),
..Default::default() ..Default::default()
}; };
let mut parser = SummaryParser::new(src); 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() { let href = match parser.stream.next() {
Some(Event::Start(Tag::Link(href, _))) => href.to_string(), Some((Event::Start(Tag::Link { dest_url, .. }), _range)) => dest_url.to_string(),
other => panic!("Unreachable, {:?}", other), other => panic!("Unreachable, {:?}", other),
}; };
let got = parser.parse_link(href).unwrap(); let got = parser.parse_link(href);
assert_eq!(got, should_be); assert_eq!(got, should_be);
} }
@ -639,16 +773,16 @@ mod tests {
let src = "- [First](./first.md)\n"; let src = "- [First](./first.md)\n";
let link = Link { let link = Link {
name: String::from("First"), name: String::from("First"),
location: PathBuf::from("./first.md"), location: Some(PathBuf::from("./first.md")),
number: Some(SectionNumber(vec![1])), number: Some(SectionNumber(vec![1])),
..Default::default() ..Default::default()
}; };
let should_be = vec![SummaryItem::Link(link)]; let should_be = vec![SummaryItem::Link(link)];
let mut parser = SummaryParser::new(src); let mut parser = SummaryParser::new(src);
let _ = parser.stream.next(); let got = parser
.parse_numbered(&mut 0, &mut SectionNumber::default())
let got = parser.parse_numbered().unwrap(); .unwrap();
assert_eq!(got, should_be); assert_eq!(got, should_be);
} }
@ -660,27 +794,92 @@ mod tests {
let should_be = vec![ let should_be = vec![
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("First"), name: String::from("First"),
location: PathBuf::from("./first.md"), location: Some(PathBuf::from("./first.md")),
number: Some(SectionNumber(vec![1])), number: Some(SectionNumber(vec![1])),
nested_items: vec![SummaryItem::Link(Link { nested_items: vec![SummaryItem::Link(Link {
name: String::from("Nested"), name: String::from("Nested"),
location: PathBuf::from("./nested.md"), location: Some(PathBuf::from("./nested.md")),
number: Some(SectionNumber(vec![1, 1])), number: Some(SectionNumber(vec![1, 1])),
nested_items: Vec::new(), nested_items: Vec::new(),
})], })],
}), }),
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("Second"), name: String::from("Second"),
location: PathBuf::from("./second.md"), location: Some(PathBuf::from("./second.md")),
number: Some(SectionNumber(vec![2])), number: Some(SectionNumber(vec![2])),
nested_items: Vec::new(), nested_items: Vec::new(),
}), }),
]; ];
let mut parser = SummaryParser::new(src); 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); assert_eq!(got, should_be);
} }
@ -695,33 +894,221 @@ mod tests {
let should_be = vec![ let should_be = vec![
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("First"), name: String::from("First"),
location: PathBuf::from("./first.md"), location: Some(PathBuf::from("./first.md")),
number: Some(SectionNumber(vec![1])), number: Some(SectionNumber(vec![1])),
nested_items: Vec::new(), nested_items: Vec::new(),
}), }),
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("Second"), name: String::from("Second"),
location: PathBuf::from("./second.md"), location: Some(PathBuf::from("./second.md")),
number: Some(SectionNumber(vec![2])), number: Some(SectionNumber(vec![2])),
nested_items: Vec::new(), nested_items: Vec::new(),
}), }),
]; ];
let mut parser = SummaryParser::new(src); let mut parser = SummaryParser::new(src);
let _ = parser.stream.next(); let got = parser
.parse_numbered(&mut 0, &mut SectionNumber::default())
let got = parser.parse_numbered().unwrap(); .unwrap();
assert_eq!(got, should_be); assert_eq!(got, should_be);
} }
#[test] #[test]
fn an_empty_link_location_is_an_error() { fn an_empty_link_location_is_a_draft_chapter() {
let src = "- [Empty]()\n"; let src = "- [Empty]()\n";
let mut parser = SummaryParser::new(src); let mut parser = SummaryParser::new(src);
parser.stream.next();
let got = parser.parse_numbered(); let got = parser.parse_numbered(&mut 0, &mut SectionNumber::default());
assert!(got.is_err()); 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);
}
#[test]
fn skip_html_comments() {
let src = r#"<!--
# Title - En
-->
# Title - Local
<!--
[Prefix 00-01 - En](ch00-01.md)
[Prefix 00-02 - En](ch00-02.md)
-->
[Prefix 00-01 - Local](ch00-01.md)
[Prefix 00-02 - Local](ch00-02.md)
<!--
## Section Title - En
-->
## Section Title - Localized
<!--
- [Ch 01-00 - En](ch01-00.md)
- [Ch 01-01 - En](ch01-01.md)
- [Ch 01-02 - En](ch01-02.md)
-->
- [Ch 01-00 - Local](ch01-00.md)
- [Ch 01-01 - Local](ch01-01.md)
- [Ch 01-02 - Local](ch01-02.md)
<!--
- [Ch 02-00 - En](ch02-00.md)
-->
- [Ch 02-00 - Local](ch02-00.md)
<!--
[Appendix A - En](appendix-01.md)
[Appendix B - En](appendix-02.md)
-->`
[Appendix A - Local](appendix-01.md)
[Appendix B - Local](appendix-02.md)
"#;
let mut parser = SummaryParser::new(src);
// ---- Title ----
let title = parser.parse_title();
assert_eq!(title, Some(String::from("Title - Local")));
// ---- Prefix Chapters ----
let new_affix_item = |name, location| {
SummaryItem::Link(Link {
name: String::from(name),
location: Some(PathBuf::from(location)),
..Default::default()
})
};
let should_be = vec![
new_affix_item("Prefix 00-01 - Local", "ch00-01.md"),
new_affix_item("Prefix 00-02 - Local", "ch00-02.md"),
];
let got = parser.parse_affix(true).unwrap();
assert_eq!(got, should_be);
// ---- Numbered Chapters ----
let new_numbered_item = |name, location, numbers: &[u32], nested_items| {
SummaryItem::Link(Link {
name: String::from(name),
location: Some(PathBuf::from(location)),
number: Some(SectionNumber(numbers.to_vec())),
nested_items,
})
};
let ch01_nested = vec![
new_numbered_item("Ch 01-01 - Local", "ch01-01.md", &[1, 1], vec![]),
new_numbered_item("Ch 01-02 - Local", "ch01-02.md", &[1, 2], vec![]),
];
let should_be = vec![
new_numbered_item("Ch 01-00 - Local", "ch01-00.md", &[1], ch01_nested),
new_numbered_item("Ch 02-00 - Local", "ch02-00.md", &[2], vec![]),
];
let got = parser.parse_parts().unwrap();
assert_eq!(got, should_be);
// ---- Suffix Chapters ----
let should_be = vec![
new_affix_item("Appendix A - Local", "appendix-01.md"),
new_affix_item("Appendix B - Local", "appendix-02.md"),
];
let got = parser.parse_affix(false).unwrap();
assert_eq!(got, should_be);
} }
} }

View File

@ -1,35 +1,37 @@
use clap::{App, ArgMatches, SubCommand}; use super::command_prelude::*;
use crate::{get_book_dir, open};
use mdbook::errors::Result; use mdbook::errors::Result;
use mdbook::MDBook; use mdbook::MDBook;
use {get_book_dir, open}; use std::path::PathBuf;
// Create clap subcommand arguments // Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { pub fn make_subcommand() -> Command {
SubCommand::with_name("build") Command::new("build")
.about("Builds a book from its markdown files") .about("Builds a book from its markdown files")
.arg_from_usage( .arg_dest_dir()
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\ .arg_root_dir()
(If omitted, uses build.build-dir from book.toml or defaults to ./book)'", .arg_open()
).arg_from_usage(
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",
).arg_from_usage("-o, --open 'Opens the compiled book in a web browser'")
} }
// Build command implementation // Build command implementation
pub fn execute(args: &ArgMatches) -> Result<()> { pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args); let book_dir = get_book_dir(args);
let mut book = MDBook::load(&book_dir)?; let mut book = MDBook::load(book_dir)?;
if let Some(dest_dir) = args.value_of("dest-dir") { if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") {
book.config.build.build_dir = dest_dir.into(); book.config.build.build_dir = dest_dir.into();
} }
book.build()?; book.build()?;
if args.is_present("open") { if args.get_flag("open") {
// FIXME: What's the right behaviour if we don't use the HTML renderer? // FIXME: What's the right behaviour if we don't use the HTML renderer?
open(book.build_dir_for("html").join("index.html")); let path = book.build_dir_for("html").join("index.html");
if !path.exists() {
error!("No chapter available to open");
std::process::exit(1)
}
open(path);
} }
Ok(()) Ok(())

View File

@ -1,32 +1,32 @@
use clap::{App, ArgMatches, SubCommand}; use super::command_prelude::*;
use get_book_dir; use crate::get_book_dir;
use mdbook::errors::*; use anyhow::Context;
use mdbook::MDBook; use mdbook::MDBook;
use std::fs; use std::fs;
use std::path::PathBuf;
// Create clap subcommand arguments // Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { pub fn make_subcommand() -> Command {
SubCommand::with_name("clean") Command::new("clean")
.about("Deletes a built book") .about("Deletes a built book")
.arg_from_usage( .arg_dest_dir()
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\ .arg_root_dir()
(If omitted, 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 // Clean command implementation
pub fn execute(args: &ArgMatches) -> ::mdbook::errors::Result<()> { pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> {
let book_dir = get_book_dir(args); let book_dir = get_book_dir(args);
let book = MDBook::load(&book_dir)?; let book = MDBook::load(book_dir)?;
let dir_to_remove = match args.value_of("dest-dir") { let dir_to_remove = match args.get_one::<PathBuf>("dest-dir") {
Some(dest_dir) => dest_dir.into(), Some(dest_dir) => dest_dir.into(),
None => book.root.join(&book.config.build.build_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")?;
if dir_to_remove.exists() {
fs::remove_dir_all(&dir_to_remove)
.with_context(|| "Unable to remove the build directory")?;
}
Ok(()) Ok(())
} }

View File

@ -0,0 +1,45 @@
//! Helpers for building the command-line arguments for commands.
pub use clap::{arg, Arg, ArgMatches, Command};
use std::path::PathBuf;
pub trait CommandExt: Sized {
fn _arg(self, arg: Arg) -> Self;
fn arg_dest_dir(self) -> Self {
self._arg(
Arg::new("dest-dir")
.short('d')
.long("dest-dir")
.value_name("dest-dir")
.value_parser(clap::value_parser!(PathBuf))
.help(
"Output directory for the book\n\
Relative paths are interpreted relative to the book's root directory.\n\
If omitted, mdBook uses build.build-dir from book.toml \
or defaults to `./book`.",
),
)
}
fn arg_root_dir(self) -> Self {
self._arg(
Arg::new("dir")
.help(
"Root directory for the book\n\
(Defaults to the current directory when omitted)",
)
.value_parser(clap::value_parser!(PathBuf)),
)
}
fn arg_open(self) -> Self {
self._arg(arg!(-o --open "Opens the compiled book in a web browser"))
}
}
impl CommandExt for Command {
fn _arg(self, arg: Arg) -> Self {
self.arg(arg)
}
}

View File

@ -1,5 +1,5 @@
use clap::{App, ArgMatches, SubCommand}; use crate::get_book_dir;
use get_book_dir; use clap::{arg, ArgMatches, Command as ClapCommand};
use mdbook::config; use mdbook::config;
use mdbook::errors::Result; use mdbook::errors::Result;
use mdbook::MDBook; use mdbook::MDBook;
@ -8,14 +8,23 @@ use std::io::Write;
use std::process::Command; use std::process::Command;
// Create clap subcommand arguments // Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { pub fn make_subcommand() -> ClapCommand {
SubCommand::with_name("init") ClapCommand::new("init")
.about("Creates the boilerplate structure and files for a new book") .about("Creates the boilerplate structure and files for a new book")
// the {n} denotes a newline which will properly aligned in all help messages .arg(
.arg_from_usage("[dir] 'Directory to create the book in{n}\ arg!([dir]
(Defaults to the Current Directory when omitted)'") "Directory to create the book in\n\
.arg_from_usage("--theme 'Copies the default theme into your source folder'") (Defaults to the current directory when omitted)"
.arg_from_usage("--force 'Skips confirmation prompts'") )
.value_parser(clap::value_parser!(std::path::PathBuf)),
)
.arg(arg!(--theme "Copies the default theme into your source folder"))
.arg(arg!(--force "Skips confirmation prompts"))
.arg(arg!(--title <title> "Sets the book title"))
.arg(
arg!(--ignore <ignore> "Creates a VCS ignore file (i.e. .gitignore)")
.value_parser(["none", "git"]),
)
} }
// Init command implementation // Init command implementation
@ -23,18 +32,13 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args); let book_dir = get_book_dir(args);
let mut builder = MDBook::init(&book_dir); let mut builder = MDBook::init(&book_dir);
let mut config = config::Config::default(); let mut config = config::Config::default();
// If flag `--theme` is present, copy theme to src // If flag `--theme` is present, copy theme to src
if args.is_present("theme") { if args.get_flag("theme") {
config.set("output.html.theme", "src/theme")?; let theme_dir = book_dir.join("theme");
// Skip this if `--force` is present
if !args.is_present("force") {
// Print warning
println!(); println!();
println!( println!("Copying the default theme to {}", theme_dir.display());
"Copying the default theme to {}", // Skip this if `--force` is present
builder.config().book.src.display() if !args.get_flag("force") && theme_dir.exists() {
);
println!("This could potentially overwrite files already present in that directory."); println!("This could potentially overwrite files already present in that directory.");
print!("\nAre you sure you want to continue? (y/n) "); print!("\nAre you sure you want to continue? (y/n) ");
@ -47,13 +51,25 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
} }
} }
if let Some(ignore) = args.get_one::<String>("ignore").map(|s| s.as_str()) {
match ignore {
"git" => builder.create_gitignore(true),
_ => builder.create_gitignore(false),
};
} else if !args.get_flag("force") {
println!("\nDo you want a .gitignore to be created? (y/n)"); println!("\nDo you want a .gitignore to be created? (y/n)");
if confirm() { if confirm() {
builder.create_gitignore(true); builder.create_gitignore(true);
} }
}
config.book.title = request_book_title(); config.book.title = if args.contains_id("title") {
args.get_one::<String>("title").map(String::from)
} else if args.get_flag("force") {
None
} else {
request_book_title()
};
if let Some(author) = get_author_name() { if let Some(author) = get_author_name() {
debug!("Obtained user name from gitconfig: {:?}", author); debug!("Obtained user name from gitconfig: {:?}", author);
@ -70,7 +86,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
/// Obtains author name from git config file by running the `git config` command. /// Obtains author name from git config file by running the `git config` command.
fn get_author_name() -> Option<String> { fn get_author_name() -> Option<String> {
let output = Command::new("git") let output = Command::new("git")
.args(&["config", "--get", "user.name"]) .args(["config", "--get", "user.name"])
.output() .output()
.ok()?; .ok()?;
@ -100,8 +116,5 @@ fn confirm() -> bool {
io::stdout().flush().unwrap(); io::stdout().flush().unwrap();
let mut s = String::new(); let mut s = String::new();
io::stdin().read_line(&mut s).ok(); io::stdin().read_line(&mut s).ok();
match &*s.trim() { matches!(s.trim(), "Y" | "y" | "yes" | "Yes")
"Y" | "y" | "yes" | "Yes" => true,
_ => false,
}
} }

View File

@ -2,6 +2,7 @@
pub mod build; pub mod build;
pub mod clean; pub mod clean;
pub mod command_prelude;
pub mod init; pub mod init;
#[cfg(feature = "serve")] #[cfg(feature = "serve")]
pub mod serve; pub mod serve;

View File

@ -1,109 +1,92 @@
extern crate iron; use super::command_prelude::*;
extern crate staticfile;
extern crate ws;
use self::iron::{
status, AfterMiddleware, Chain, Iron, IronError, IronResult, Request, Response, Set,
};
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
use super::watch; use super::watch;
use clap::{App, Arg, ArgMatches, SubCommand}; use crate::{get_book_dir, open};
use clap::builder::NonEmptyStringValueParser;
use futures_util::sink::SinkExt;
use futures_util::StreamExt;
use mdbook::errors::*; use mdbook::errors::*;
use mdbook::utils; use mdbook::utils;
use mdbook::utils::fs::get_404_output_file;
use mdbook::MDBook; use mdbook::MDBook;
use std; use std::net::{SocketAddr, ToSocketAddrs};
use {get_book_dir, open}; use std::path::PathBuf;
use tokio::sync::broadcast;
use warp::ws::Message;
use warp::Filter;
struct ErrorRecover; /// The HTTP endpoint for the websocket used to trigger reloads when a file changes.
const LIVE_RELOAD_ENDPOINT: &str = "__livereload";
// Create clap subcommand arguments // Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { pub fn make_subcommand() -> Command {
SubCommand::with_name("serve") Command::new("serve")
.about("Serves a book at http://localhost:3000, and rebuilds it on changes") .about("Serves a book at http://localhost:3000, and rebuilds it on changes")
.arg_from_usage( .arg_dest_dir()
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\ .arg_root_dir()
(If omitted, 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(
Arg::with_name("hostname") Arg::new("hostname")
.short("n") .short('n')
.long("hostname") .long("hostname")
.takes_value(true) .num_args(1)
.default_value("localhost") .default_value("localhost")
.empty_values(false) .value_parser(NonEmptyStringValueParser::new())
.help("Hostname to listen on for HTTP connections"), .help("Hostname to listen on for HTTP connections"),
) )
.arg( .arg(
Arg::with_name("port") Arg::new("port")
.short("p") .short('p')
.long("port") .long("port")
.takes_value(true) .num_args(1)
.default_value("3000") .default_value("3000")
.empty_values(false) .value_parser(NonEmptyStringValueParser::new())
.help("Port to use for HTTP connections"), .help("Port to use for HTTP connections"),
) )
.arg( .arg_open()
Arg::with_name("websocket-hostname")
.long("websocket-hostname")
.takes_value(true)
.empty_values(false)
.help(
"Hostname to connect to for WebSockets connections (Defaults to the HTTP hostname)",
),
)
.arg(
Arg::with_name("websocket-port")
.short("w")
.long("websocket-port")
.takes_value(true)
.default_value("3001")
.empty_values(false)
.help("Port to use for WebSockets livereload connections"),
)
.arg_from_usage("-o, --open 'Opens the book server in a web browser'")
} }
// Watch command implementation // Serve command implementation
pub fn execute(args: &ArgMatches) -> Result<()> { pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args); let book_dir = get_book_dir(args);
let mut book = MDBook::load(&book_dir)?; let mut book = MDBook::load(book_dir)?;
let port = args.value_of("port").unwrap(); let port = args.get_one::<String>("port").unwrap();
let ws_port = args.value_of("websocket-port").unwrap(); let hostname = args.get_one::<String>("hostname").unwrap();
let hostname = args.value_of("hostname").unwrap(); let open_browser = args.get_flag("open");
let public_address = args.value_of("websocket-address").unwrap_or(hostname);
let open_browser = args.is_present("open");
let address = format!("{}:{}", hostname, port); let address = format!("{}:{}", hostname, port);
let ws_address = format!("{}:{}", hostname, ws_port);
let livereload_url = format!("ws://{}:{}", public_address, ws_port); let update_config = |book: &mut MDBook| {
book.config book.config
.set("output.html.livereload-url", &livereload_url)?; .set("output.html.live-reload-endpoint", LIVE_RELOAD_ENDPOINT)
.expect("live-reload-endpoint update failed");
if let Some(dest_dir) = args.value_of("dest-dir") { if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") {
book.config.build.build_dir = dest_dir.into(); 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()?; book.build()?;
let mut chain = Chain::new(staticfile::Static::new(book.build_dir_for("html"))); let sockaddr: SocketAddr = address
chain.link_after(ErrorRecover); .to_socket_addrs()?
let _iron = Iron::new(chain) .next()
.http(&*address) .ok_or_else(|| anyhow::anyhow!("no address found for {}", address))?;
.chain_err(|| "Unable to launch the server")?; let build_dir = book.build_dir_for("html");
let input_404 = book
.config
.get("output.html.input-404")
.and_then(toml::Value::as_str)
.map(ToString::to_string);
let file_404 = get_404_output_file(&input_404);
let ws_server = // A channel used to broadcast to any websockets to reload when a file changes.
ws::WebSocket::new(|_| |_| Ok(())).chain_err(|| "Unable to start the websocket")?; let (tx, _rx) = tokio::sync::broadcast::channel::<Message>(100);
let broadcaster = ws_server.broadcaster(); let reload_tx = tx.clone();
let thread_handle = std::thread::spawn(move || {
std::thread::spawn(move || { serve(build_dir, sockaddr, reload_tx, &file_404);
ws_server.listen(&*ws_address).unwrap();
}); });
let serving_url = format!("http://{}", address); let serving_url = format!("http://{}", address);
@ -114,36 +97,68 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
} }
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
watch::trigger_on_change(&mut book, move |path, book_dir| { watch::trigger_on_change(&book, move |paths, book_dir| {
info!("File changed: {:?}", path); info!("Files changed: {:?}", paths);
info!("Building book..."); info!("Building book...");
// FIXME: This area is really ugly because we need to re-set livereload :( // FIXME: This area is really ugly because we need to re-set livereload :(
let result = MDBook::load(book_dir).and_then(|mut b| {
let result = MDBook::load(&book_dir) update_config(&mut b);
.and_then(|mut b| { b.build()
b.config });
.set("output.html.livereload-url", &livereload_url)?;
Ok(b)
}).and_then(|b| b.build());
if let Err(e) = result { if let Err(e) = result {
error!("Unable to load the book"); error!("Unable to load the book");
utils::log_backtrace(&e); utils::log_backtrace(&e);
} else { } else {
let _ = broadcaster.send("reload"); let _ = tx.send(Message::text("reload"));
} }
}); });
let _ = thread_handle.join();
Ok(()) Ok(())
} }
impl AfterMiddleware for ErrorRecover { #[tokio::main]
fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> { async fn serve(
match err.response.status { build_dir: PathBuf,
// each error will result in 404 response address: SocketAddr,
Some(_) => Ok(err.response.set(status::NotFound)), reload_tx: broadcast::Sender<Message>,
_ => Err(err), 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);
std::panic::set_hook(Box::new(move |panic_info| {
// exit if serve panics
error!("Unable to serve: {}", panic_info);
std::process::exit(1);
}));
warp::serve(routes).run(address).await;
} }

View File

@ -1,45 +1,58 @@
use clap::{App, Arg, ArgMatches, SubCommand}; use super::command_prelude::*;
use get_book_dir; use crate::get_book_dir;
use clap::builder::NonEmptyStringValueParser;
use clap::ArgAction;
use mdbook::errors::Result; use mdbook::errors::Result;
use mdbook::MDBook; use mdbook::MDBook;
use std::path::PathBuf;
// Create clap subcommand arguments // Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { pub fn make_subcommand() -> Command {
SubCommand::with_name("test") Command::new("test")
.about("Tests that a book's Rust code samples compile") .about("Tests that a book's Rust code samples compile")
.arg_from_usage( // FIXME: --dest-dir is unused by the test command, it should be removed
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\ .arg_dest_dir()
(If omitted, uses build.build-dir from book.toml or defaults to ./book)'", .arg_root_dir()
.arg(
Arg::new("chapter")
.short('c')
.long("chapter")
.value_name("chapter"),
) )
.arg_from_usage( .arg(
"[dir] 'Root directory for the book{n}\ Arg::new("library-path")
(Defaults to the Current Directory when omitted)'", .short('L')
)
.arg(Arg::with_name("library-path")
.short("L")
.long("library-path") .long("library-path")
.value_name("dir") .value_name("dir")
.takes_value(true) .value_delimiter(',')
.require_delimiter(true) .value_parser(NonEmptyStringValueParser::new())
.multiple(true) .action(ArgAction::Append)
.empty_values(false) .help(
.help("A comma-separated list of directories to add to {n}the crate search path when building tests")) "A comma-separated list of directories to add to the crate \
search path when building tests",
),
)
} }
// test command implementation // test command implementation
pub fn execute(args: &ArgMatches) -> Result<()> { pub fn execute(args: &ArgMatches) -> Result<()> {
let library_paths: Vec<&str> = args let library_paths: Vec<&str> = args
.values_of("library-path") .get_many("library-path")
.map(|v| v.collect()) .map(|it| it.map(String::as_str).collect())
.unwrap_or_default(); .unwrap_or_default();
let chapter: Option<&str> = args.get_one::<String>("chapter").map(|s| s.as_str());
let book_dir = get_book_dir(args); let book_dir = get_book_dir(args);
let mut book = MDBook::load(&book_dir)?; let mut book = MDBook::load(book_dir)?;
if let Some(dest_dir) = args.value_of("dest-dir") { if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") {
book.config.build.build_dir = dest_dir.into(); book.config.build.build_dir = dest_dir.to_path_buf();
} }
match chapter {
book.test(library_paths)?; Some(_) => book.test_chapter(library_paths, chapter),
None => book.test(library_paths),
}?;
Ok(()) Ok(())
} }

View File

@ -1,41 +1,52 @@
extern crate notify; use super::command_prelude::*;
use crate::{get_book_dir, open};
use self::notify::Watcher; use ignore::gitignore::Gitignore;
use clap::{App, ArgMatches, SubCommand};
use mdbook::errors::Result; use mdbook::errors::Result;
use mdbook::utils; use mdbook::utils;
use mdbook::MDBook; use mdbook::MDBook;
use std::path::Path; use pathdiff::diff_paths;
use std::path::{Path, PathBuf};
use std::sync::mpsc::channel; use std::sync::mpsc::channel;
use std::thread::sleep;
use std::time::Duration; use std::time::Duration;
use {get_book_dir, open};
// Create clap subcommand arguments // Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { pub fn make_subcommand() -> Command {
SubCommand::with_name("watch") Command::new("watch")
.about("Watches a book's files and rebuilds it on changes") .about("Watches a book's files and rebuilds it on changes")
.arg_from_usage( .arg_dest_dir()
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\ .arg_root_dir()
(If omitted, uses build.build-dir from book.toml or defaults to ./book)'", .arg_open()
).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 // Watch command implementation
pub fn execute(args: &ArgMatches) -> Result<()> { pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args); let book_dir = get_book_dir(args);
let book = MDBook::load(&book_dir)?; let mut book = MDBook::load(book_dir)?;
if args.is_present("open") { let update_config = |book: &mut MDBook| {
if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") {
book.config.build.build_dir = dest_dir.into();
}
};
update_config(&mut book);
if args.get_flag("open") {
book.build()?; book.build()?;
open(book.build_dir_for("html").join("index.html")); let path = book.build_dir_for("html").join("index.html");
if !path.exists() {
error!("No chapter available to open");
std::process::exit(1)
}
open(path);
} }
trigger_on_change(&book, |path, book_dir| { trigger_on_change(&book, |paths, book_dir| {
info!("File changed: {:?}\nBuilding book...\n", path); info!("Files changed: {:?}\nBuilding book...\n", paths);
let result = MDBook::load(&book_dir).and_then(|b| b.build()); let result = MDBook::load(book_dir).and_then(|mut b| {
update_config(&mut b);
b.build()
});
if let Err(e) = result { if let Err(e) = result {
error!("Unable to build the book"); error!("Unable to build the book");
@ -46,45 +57,173 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
Ok(()) Ok(())
} }
fn remove_ignored_files(book_root: &Path, paths: &[PathBuf]) -> Vec<PathBuf> {
if paths.is_empty() {
return vec![];
}
match find_gitignore(book_root) {
Some(gitignore_path) => {
let (ignore, err) = Gitignore::new(&gitignore_path);
if let Some(err) = err {
warn!(
"error reading gitignore `{}`: {err}",
gitignore_path.display()
);
}
filter_ignored_files(ignore, paths)
}
None => {
// There is no .gitignore file.
paths.iter().map(|path| path.to_path_buf()).collect()
}
}
}
fn find_gitignore(book_root: &Path) -> Option<PathBuf> {
book_root
.ancestors()
.map(|p| p.join(".gitignore"))
.find(|p| p.exists())
}
// Note: The usage of `canonicalize` may encounter occasional failures on the Windows platform, presenting a potential risk.
// For more details, refer to [Pull Request #2229](https://github.com/rust-lang/mdBook/pull/2229#discussion_r1408665981).
fn filter_ignored_files(ignore: Gitignore, paths: &[PathBuf]) -> Vec<PathBuf> {
let ignore_root = ignore
.path()
.canonicalize()
.expect("ignore root canonicalize error");
paths
.iter()
.filter(|path| {
let relative_path =
diff_paths(&path, &ignore_root).expect("One of the paths should be an absolute");
!ignore
.matched_path_or_any_parents(&relative_path, relative_path.is_dir())
.is_ignore()
})
.map(|path| path.to_path_buf())
.collect()
}
/// Calls the closure when a book source file is changed, blocking indefinitely. /// Calls the closure when a book source file is changed, blocking indefinitely.
pub fn trigger_on_change<F>(book: &MDBook, closure: F) pub fn trigger_on_change<F>(book: &MDBook, closure: F)
where where
F: Fn(&Path, &Path), F: Fn(Vec<PathBuf>, &Path),
{ {
use self::notify::DebouncedEvent::*; use notify::RecursiveMode::*;
use self::notify::RecursiveMode::*;
// Create a channel to receive the events. // Create a channel to receive the events.
let (tx, rx) = channel(); let (tx, rx) = channel();
let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) { let mut debouncer = match notify_debouncer_mini::new_debouncer(Duration::from_secs(1), tx) {
Ok(w) => w, Ok(d) => d,
Err(e) => { Err(e) => {
error!("Error while trying to watch the files:\n\n\t{:?}", e); error!("Error while trying to watch the files:\n\n\t{:?}", e);
::std::process::exit(1) std::process::exit(1)
} }
}; };
let watcher = debouncer.watcher();
// Add the source directory to the watcher // Add the source directory to the watcher
if let Err(e) = watcher.watch(book.source_dir(), Recursive) { if let Err(e) = watcher.watch(&book.source_dir(), Recursive) {
error!("Error while watching {:?}:\n {:?}", book.source_dir(), e); error!("Error while watching {:?}:\n {:?}", book.source_dir(), e);
::std::process::exit(1); std::process::exit(1);
}; };
let _ = watcher.watch(book.theme_dir(), Recursive); let _ = watcher.watch(&book.theme_dir(), Recursive);
// Add the book.toml file to the watcher if it exists // Add the book.toml file to the watcher if it exists
let _ = watcher.watch(book.root.join("book.toml"), NonRecursive); let _ = watcher.watch(&book.root.join("book.toml"), NonRecursive);
for dir in &book.config.build.extra_watch_dirs {
let path = book.root.join(dir);
let canonical_path = path.canonicalize().unwrap_or_else(|e| {
error!("Error while watching extra directory {path:?}:\n {e}");
std::process::exit(1);
});
if let Err(e) = watcher.watch(&canonical_path, Recursive) {
error!(
"Error while watching extra directory {:?}:\n {:?}",
canonical_path, e
);
std::process::exit(1);
}
}
info!("Listening for changes..."); info!("Listening for changes...");
for event in rx.iter() { loop {
debug!("Received filesystem event: {:?}", event); let first_event = rx.recv().unwrap();
match event { sleep(Duration::from_millis(50));
Create(path) | Write(path) | Remove(path) | Rename(_, path) => { let other_events = rx.try_iter();
closure(&path, &book.root);
let all_events = std::iter::once(first_event).chain(other_events);
let paths: Vec<_> = all_events
.filter_map(|event| match event {
Ok(events) => Some(events),
Err(error) => {
log::warn!("error while watching for changes: {error}");
None
} }
_ => {} })
.flatten()
.map(|event| event.path)
.collect();
// If we are watching files outside the current repository (via extra-watch-dirs), then they are definitionally
// ignored by gitignore. So we handle this case by including such files into the watched paths list.
let any_external_paths = paths.iter().filter(|p| !p.starts_with(&book.root)).cloned();
let mut paths = remove_ignored_files(&book.root, &paths[..]);
paths.extend(any_external_paths);
if !paths.is_empty() {
closure(paths, &book.root);
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use ignore::gitignore::GitignoreBuilder;
use std::env;
#[test]
fn test_filter_ignored_files() {
let current_dir = env::current_dir().unwrap();
let ignore = GitignoreBuilder::new(&current_dir)
.add_line(None, "*.html")
.unwrap()
.build()
.unwrap();
let should_remain = current_dir.join("record.text");
let should_filter = current_dir.join("index.html");
let remain = filter_ignored_files(ignore, &[should_remain.clone(), should_filter]);
assert_eq!(remain, vec![should_remain])
}
#[test]
fn filter_ignored_files_should_handle_parent_dir() {
let current_dir = env::current_dir().unwrap();
let ignore = GitignoreBuilder::new(&current_dir)
.add_line(None, "*.html")
.unwrap()
.build()
.unwrap();
let parent_dir = current_dir.join("..");
let should_remain = parent_dir.join("record.text");
let should_filter = parent_dir.join("index.html");
let remain = filter_ignored_files(ignore, &[should_remain.clone(), should_filter]);
assert_eq!(remain, vec![should_remain])
}
}

File diff suppressed because it is too large Load Diff

View File

@ -75,37 +75,13 @@
//! directly, making deserializing the `RenderContext` easy and giving you //! directly, making deserializing the `RenderContext` easy and giving you
//! access to the various methods for working with the [`Config`]. //! 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 //! [`RenderContext`]: renderer::RenderContext
//! [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 //! [`Config`]: config::Config
#![deny(missing_docs)] #![deny(missing_docs)]
#![deny(rust_2018_idioms)]
#[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 tempfile;
extern crate toml;
extern crate toml_query;
#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;
pub mod book; pub mod book;
pub mod config; pub mod config;
@ -114,53 +90,19 @@ pub mod renderer;
pub mod theme; pub mod theme;
pub mod utils; pub mod utils;
pub use book::BookItem; /// The current version of `mdbook`.
pub use book::MDBook; ///
pub use config::Config; /// This is provided as a way for custom preprocessors and renderers to do
pub use renderer::Renderer; /// 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. /// The error types used through out this crate.
pub mod errors { pub mod errors {
use std::path::PathBuf; pub(crate) use anyhow::{bail, ensure, Context};
pub use anyhow::{Error, Result};
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))
}
}
} }

View File

@ -1,70 +1,97 @@
extern crate chrono;
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;
extern crate env_logger;
extern crate error_chain;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
extern crate mdbook;
extern crate open;
use anyhow::anyhow;
use chrono::Local; use chrono::Local;
use clap::{App, AppSettings, ArgMatches}; use clap::{Arg, ArgMatches, Command};
use clap_complete::Shell;
use env_logger::Builder; use env_logger::Builder;
use log::LevelFilter; use log::LevelFilter;
use mdbook::utils; use mdbook::utils;
use std::env; use std::env;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::PathBuf;
mod cmd; mod cmd;
const NAME: &'static str = "mdBook"; const VERSION: &str = concat!("v", crate_version!());
const VERSION: &'static str = concat!("v", crate_version!());
fn main() { fn main() {
init_logger(); init_logger();
// Create a list of valid arguments and sub-commands let command = create_clap_command();
let app = App::new(NAME)
.about("Creates a book from markdown files") // Check which subcommand the user ran...
let res = match command.get_matches().subcommand() {
Some(("init", sub_matches)) => cmd::init::execute(sub_matches),
Some(("build", sub_matches)) => cmd::build::execute(sub_matches),
Some(("clean", sub_matches)) => cmd::clean::execute(sub_matches),
#[cfg(feature = "watch")]
Some(("watch", sub_matches)) => cmd::watch::execute(sub_matches),
#[cfg(feature = "serve")]
Some(("serve", sub_matches)) => cmd::serve::execute(sub_matches),
Some(("test", sub_matches)) => cmd::test::execute(sub_matches),
Some(("completions", sub_matches)) => (|| {
let shell = sub_matches
.get_one::<Shell>("shell")
.ok_or_else(|| anyhow!("Shell name missing."))?;
let mut complete_app = create_clap_command();
clap_complete::generate(
*shell,
&mut complete_app,
"mdbook",
&mut std::io::stdout().lock(),
);
Ok(())
})(),
_ => unreachable!(),
};
if let Err(e) = res {
utils::log_backtrace(&e);
std::process::exit(101);
}
}
/// Create a list of valid arguments and sub-commands
fn create_clap_command() -> Command {
let app = Command::new(crate_name!())
.about(crate_description!())
.author("Mathieu David <mathieudavid@mathieudavid.org>") .author("Mathieu David <mathieudavid@mathieudavid.org>")
.version(VERSION) .version(VERSION)
.setting(AppSettings::GlobalVersion) .propagate_version(true)
.setting(AppSettings::ArgRequiredElseHelp) .arg_required_else_help(true)
.after_help( .after_help(
"For more information about a specific command, try `mdbook <command> --help`\n\ "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-nursery/mdBook", The source code for mdBook is available at: https://github.com/rust-lang/mdBook",
) )
.subcommand(cmd::init::make_subcommand()) .subcommand(cmd::init::make_subcommand())
.subcommand(cmd::build::make_subcommand()) .subcommand(cmd::build::make_subcommand())
.subcommand(cmd::test::make_subcommand()) .subcommand(cmd::test::make_subcommand())
.subcommand(cmd::clean::make_subcommand()); .subcommand(cmd::clean::make_subcommand())
.subcommand(
Command::new("completions")
.about("Generate shell completions for your shell to stdout")
.arg(
Arg::new("shell")
.value_parser(clap::value_parser!(Shell))
.help("the shell to generate completions for")
.value_name("SHELL")
.required(true),
),
);
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
let app = app.subcommand(cmd::watch::make_subcommand()); let app = app.subcommand(cmd::watch::make_subcommand());
#[cfg(feature = "serve")] #[cfg(feature = "serve")]
let app = app.subcommand(cmd::serve::make_subcommand()); let app = app.subcommand(cmd::serve::make_subcommand());
// Check which subcomamnd the user ran... app
let res = match app.get_matches().subcommand() {
("init", Some(sub_matches)) => cmd::init::execute(sub_matches),
("build", Some(sub_matches)) => cmd::build::execute(sub_matches),
("clean", Some(sub_matches)) => cmd::clean::execute(sub_matches),
#[cfg(feature = "watch")]
("watch", Some(sub_matches)) => cmd::watch::execute(sub_matches),
#[cfg(feature = "serve")]
("serve", Some(sub_matches)) => cmd::serve::execute(sub_matches),
("test", Some(sub_matches)) => cmd::test::execute(sub_matches),
(_, _) => unreachable!(),
};
if let Err(e) = res {
utils::log_backtrace(&e);
::std::process::exit(101);
}
} }
fn init_logger() { fn init_logger() {
@ -82,7 +109,7 @@ fn init_logger() {
}); });
if let Ok(var) = env::var("RUST_LOG") { if let Ok(var) = env::var("RUST_LOG") {
builder.parse(&var); builder.parse_filters(&var);
} else { } else {
// if no RUST_LOG provided, default to logging at the Info level // if no RUST_LOG provided, default to logging at the Info level
builder.filter(None, LevelFilter::Info); builder.filter(None, LevelFilter::Info);
@ -94,11 +121,10 @@ fn init_logger() {
} }
fn get_book_dir(args: &ArgMatches) -> PathBuf { fn get_book_dir(args: &ArgMatches) -> PathBuf {
if let Some(dir) = args.value_of("dir") { if let Some(p) = args.get_one::<PathBuf>("dir") {
// Check if path is relative from current dir, or absolute... // Check if path is relative from current dir, or absolute...
let p = Path::new(dir);
if p.is_relative() { if p.is_relative() {
env::current_dir().unwrap().join(dir) env::current_dir().unwrap().join(p)
} else { } else {
p.to_path_buf() p.to_path_buf()
} }
@ -108,7 +134,13 @@ fn get_book_dir(args: &ArgMatches) -> PathBuf {
} }
fn open<P: AsRef<OsStr>>(path: P) { fn open<P: AsRef<OsStr>>(path: P) {
if let Err(e) = open::that(path) { info!("Opening web browser");
if let Err(e) = opener::open(path) {
error!("Error opening web browser: {}", e); error!("Error opening web browser: {}", e);
} }
} }
#[test]
fn verify_app() {
create_clap_command().debug_assert();
}

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

@ -0,0 +1,208 @@
use super::{Preprocessor, PreprocessorContext};
use crate::book::Book;
use crate::errors::*;
use log::{debug, trace, warn};
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(|| {
format!(
"Error waiting for the \"{}\" preprocessor to complete",
self.name
)
})?;
trace!("{} exited with output: {:?}", self.cmd, output);
ensure!(
output.status.success(),
format!(
"The \"{}\" preprocessor exited unsuccessfully with {} status",
self.name, output.status
)
);
serde_json::from_slice(&output.stdout).with_context(|| {
format!(
"Unable to parse the preprocessed book from \"{}\" processor",
self.name
)
})
}
fn supports_renderer(&self, renderer: &str) -> bool {
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);
}
}

View File

@ -1,13 +1,15 @@
use regex::Regex; use regex::Regex;
use std::path::Path; use std::path::Path;
use errors::*;
use super::{Preprocessor, PreprocessorContext}; use super::{Preprocessor, PreprocessorContext};
use book::{Book, BookItem}; use crate::book::{Book, BookItem};
use crate::errors::*;
use log::warn;
use once_cell::sync::Lazy;
/// A preprocessor for converting file name `README.md` to `index.md` since /// A preprocessor for converting file name `README.md` to `index.md` since
/// `README.md` is the de facto index file in a markdown-based documentation. /// `README.md` is the de facto index file in markdown-based documentation.
#[derive(Default)]
pub struct IndexPreprocessor; pub struct IndexPreprocessor;
impl IndexPreprocessor { impl IndexPreprocessor {
@ -28,13 +30,15 @@ impl Preprocessor for IndexPreprocessor {
let source_dir = ctx.root.join(&ctx.config.book.src); let source_dir = ctx.root.join(&ctx.config.book.src);
book.for_each_mut(|section: &mut BookItem| { book.for_each_mut(|section: &mut BookItem| {
if let BookItem::Chapter(ref mut ch) = *section { if let BookItem::Chapter(ref mut ch) = *section {
if is_readme_file(&ch.path) { if let Some(ref mut path) = ch.path {
let index_md = source_dir.join(ch.path.with_file_name("index.md")); if is_readme_file(&path) {
let mut index_md = source_dir.join(path.with_file_name("index.md"));
if index_md.exists() { if index_md.exists() {
warn_readme_name_conflict(&ch.path, &index_md); warn_readme_name_conflict(&path, &&mut index_md);
} }
ch.path.set_file_name("index.md"); path.set_file_name("index.md");
}
} }
} }
}); });
@ -45,7 +49,10 @@ impl Preprocessor for IndexPreprocessor {
fn warn_readme_name_conflict<P: AsRef<Path>>(readme_path: P, index_path: P) { 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 file_name = readme_path.as_ref().file_name().unwrap_or_default();
let parent_dir = index_path.as_ref().parent().unwrap_or(index_path.as_ref()); let parent_dir = index_path
.as_ref()
.parent()
.unwrap_or_else(|| index_path.as_ref());
warn!( warn!(
"It seems that there are both {:?} and index.md under \"{}\".", "It seems that there are both {:?} and index.md under \"{}\".",
file_name, file_name,
@ -61,13 +68,12 @@ fn warn_readme_name_conflict<P: AsRef<Path>>(readme_path: P, index_path: P) {
} }
fn is_readme_file<P: AsRef<Path>>(path: P) -> bool { fn is_readme_file<P: AsRef<Path>>(path: P) -> bool {
lazy_static! { static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)^readme$").unwrap());
static ref RE: Regex = Regex::new(r"(?i)^readme$").unwrap();
}
RE.is_match( RE.is_match(
path.as_ref() path.as_ref()
.file_stem() .file_stem()
.and_then(|s| s.to_str()) .and_then(std::ffi::OsStr::to_str)
.unwrap_or_default(), .unwrap_or_default(),
) )
} }

View File

@ -1,18 +1,32 @@
use errors::*; 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 regex::{CaptureMatches, Captures, Regex};
use std::ops::{Range, RangeFrom, RangeFull, RangeTo}; use std::fs;
use std::ops::{Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeTo};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use utils::fs::file_to_string;
use utils::take_lines;
use super::{Preprocessor, PreprocessorContext}; use super::{Preprocessor, PreprocessorContext};
use book::{Book, BookItem}; use crate::book::{Book, BookItem};
use log::{error, warn};
use once_cell::sync::Lazy;
const ESCAPE_CHAR: char = '\\'; const ESCAPE_CHAR: char = '\\';
const MAX_LINK_NESTED_DEPTH: usize = 10; const MAX_LINK_NESTED_DEPTH: usize = 10;
/// A preprocessor for expanding the `{{# playpen}}` and `{{# include}}` /// A preprocessor for expanding helpers in a chapter. Supported helpers are:
/// helpers in a chapter. ///
/// - `{{# 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
/// - `{{# title}}` - Override \<title\> of a webpage.
#[derive(Default)]
pub struct LinkPreprocessor; pub struct LinkPreprocessor;
impl LinkPreprocessor { impl LinkPreprocessor {
@ -34,14 +48,22 @@ impl Preprocessor for LinkPreprocessor {
book.for_each_mut(|section: &mut BookItem| { book.for_each_mut(|section: &mut BookItem| {
if let BookItem::Chapter(ref mut ch) = *section { if let BookItem::Chapter(ref mut ch) = *section {
let base = ch if let Some(ref chapter_path) = ch.path {
.path let base = chapter_path
.parent() .parent()
.map(|dir| src_dir.join(dir)) .map(|dir| src_dir.join(dir))
.expect("All book items have a parent"); .expect("All book items have a parent");
let content = replace_all(&ch.content, base, &ch.path, 0); let mut chapter_title = ch.name.clone();
let content =
replace_all(&ch.content, base, chapter_path, 0, &mut chapter_title);
ch.content = content; ch.content = content;
if chapter_title != ch.name {
ctx.chapter_titles
.borrow_mut()
.insert(chapter_path.clone(), chapter_title);
}
}
} }
}); });
@ -49,7 +71,13 @@ impl Preprocessor for LinkPreprocessor {
} }
} }
fn replace_all<P1, P2>(s: &str, path: P1, source: P2, depth: usize) -> String fn replace_all<P1, P2>(
s: &str,
path: P1,
source: P2,
depth: usize,
chapter_title: &mut String,
) -> String
where where
P1: AsRef<Path>, P1: AsRef<Path>,
P2: AsRef<Path>, P2: AsRef<Path>,
@ -62,14 +90,20 @@ where
let mut previous_end_index = 0; let mut previous_end_index = 0;
let mut replaced = String::new(); let mut replaced = String::new();
for playpen in find_links(s) { for link in find_links(s) {
replaced.push_str(&s[previous_end_index..playpen.start_index]); replaced.push_str(&s[previous_end_index..link.start_index]);
match playpen.render_with_path(&path) { match link.render_with_path(path, chapter_title) {
Ok(new_content) => { Ok(new_content) => {
if depth < MAX_LINK_NESTED_DEPTH { if depth < MAX_LINK_NESTED_DEPTH {
if let Some(rel_path) = playpen.link.relative_path(path) { if let Some(rel_path) = link.link_type.relative_path(path) {
replaced.push_str(&replace_all(&new_content, rel_path, source, depth + 1)); replaced.push_str(&replace_all(
&new_content,
rel_path,
source,
depth + 1,
chapter_title,
));
} else { } else {
replaced.push_str(&new_content); replaced.push_str(&new_content);
} }
@ -79,13 +113,17 @@ where
source.display() source.display()
); );
} }
previous_end_index = playpen.end_index; previous_end_index = link.end_index;
} }
Err(e) => { 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 // This should make sure we include the raw `{{# ... }}` snippet
// in the page content if there are any errors. // in the page content if there are any errors.
previous_end_index = playpen.start_index; previous_end_index = link.start_index;
} }
} }
} }
@ -97,11 +135,70 @@ where
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
enum LinkType<'a> { enum LinkType<'a> {
Escaped, Escaped,
IncludeRange(PathBuf, Range<usize>), Include(PathBuf, RangeOrAnchor),
IncludeRangeFrom(PathBuf, RangeFrom<usize>), Playground(PathBuf, Vec<&'a str>),
IncludeRangeTo(PathBuf, RangeTo<usize>), RustdocInclude(PathBuf, RangeOrAnchor),
IncludeRangeFull(PathBuf, RangeFull), Title(&'a str),
Playpen(PathBuf, Vec<&'a str>), }
#[derive(PartialEq, Debug, Clone)]
enum RangeOrAnchor {
Range(LineRange),
Anchor(String),
}
// A range of lines specified with some include directive.
#[allow(clippy::enum_variant_names)] // The prefix can't be removed, and is meant to mirror the contained type
#[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> { impl<'a> LinkType<'a> {
@ -109,11 +206,10 @@ impl<'a> LinkType<'a> {
let base = base.as_ref(); let base = base.as_ref();
match self { match self {
LinkType::Escaped => None, LinkType::Escaped => None,
LinkType::IncludeRange(p, _) => Some(return_relative_path(base, &p)), LinkType::Include(p, _) => Some(return_relative_path(base, &p)),
LinkType::IncludeRangeFrom(p, _) => Some(return_relative_path(base, &p)), LinkType::Playground(p, _) => Some(return_relative_path(base, &p)),
LinkType::IncludeRangeTo(p, _) => Some(return_relative_path(base, &p)), LinkType::RustdocInclude(p, _) => Some(return_relative_path(base, &p)),
LinkType::IncludeRangeFull(p, _) => Some(return_relative_path(base, &p)), LinkType::Title(_) => None,
LinkType::Playpen(p, _) => Some(return_relative_path(base, &p)),
} }
} }
} }
@ -125,56 +221,68 @@ fn return_relative_path<P: AsRef<Path>>(base: P, relative: P) -> PathBuf {
.to_path_buf() .to_path_buf()
} }
fn parse_include_path(path: &str) -> LinkType<'static> { fn parse_range_or_anchor(parts: Option<&str>) -> RangeOrAnchor {
let mut parts = path.split(':'); let mut parts = parts.unwrap_or("").splitn(3, ':').fuse();
let path = parts.next().unwrap().into();
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 // subtract 1 since line numbers usually begin with 1
let start = parts Some(value.saturating_sub(1))
.next() } else if let Some("") = next_element {
.and_then(|s| s.parse::<usize>().ok()) None
.map(|val| val.saturating_sub(1)); } else if let Some(anchor) = next_element {
let end = parts.next(); return RangeOrAnchor::Anchor(String::from(anchor));
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 { } else {
LinkType::IncludeRange( None
path, };
Range {
start: start, let end = parts.next();
end: start + 1, // 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>());
},
None => match end { match (start, end) {
Some(end) => LinkType::IncludeRangeTo(path, RangeTo { end: end }), (Some(start), Some(Ok(end))) => RangeOrAnchor::Range(LineRange::from(start..end)),
None => LinkType::IncludeRangeFull(path, RangeFull), (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.splitn(2, ':');
let path = parts.next().unwrap().into();
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)] #[derive(PartialEq, Debug, Clone)]
struct Link<'a> { struct Link<'a> {
start_index: usize, start_index: usize,
end_index: usize, end_index: usize,
link: LinkType<'a>, link_type: LinkType<'a>,
link_text: &'a str, link_text: &'a str,
} }
impl<'a> Link<'a> { impl<'a> Link<'a> {
fn from_capture(cap: Captures<'a>) -> Option<Link<'a>> { fn from_capture(cap: Captures<'a>) -> Option<Link<'a>> {
let link_type = match (cap.get(0), cap.get(1), cap.get(2)) { let link_type = match (cap.get(0), cap.get(1), cap.get(2)) {
(_, Some(typ), Some(title)) if typ.as_str() == "title" => {
Some(LinkType::Title(title.as_str()))
}
(_, Some(typ), Some(rest)) => { (_, Some(typ), Some(rest)) => {
let mut path_props = rest.as_str().split_whitespace(); let mut path_props = rest.as_str().split_whitespace();
let file_arg = path_props.next(); let file_arg = path_props.next();
@ -182,7 +290,16 @@ impl<'a> Link<'a> {
match (typ.as_str(), file_arg) { match (typ.as_str(), file_arg) {
("include", Some(pth)) => Some(parse_include_path(pth)), ("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, _ => None,
} }
} }
@ -192,43 +309,86 @@ impl<'a> Link<'a> {
_ => None, _ => None,
}; };
link_type.and_then(|lnk| { link_type.and_then(|lnk_type| {
cap.get(0).map(|mat| Link { cap.get(0).map(|mat| Link {
start_index: mat.start(), start_index: mat.start(),
end_index: mat.end(), end_index: mat.end(),
link: lnk, link_type: lnk_type,
link_text: mat.as_str(), link_text: mat.as_str(),
}) })
}) })
} }
fn render_with_path<P: AsRef<Path>>(&self, base: P) -> Result<String> { fn render_with_path<P: AsRef<Path>>(
&self,
base: P,
chapter_title: &mut String,
) -> Result<String> {
let base = base.as_ref(); let base = base.as_ref();
match self.link { match self.link_type {
// omit the escape char // omit the escape char
LinkType::Escaped => Ok((&self.link_text[1..]).to_owned()), LinkType::Escaped => Ok(self.link_text[1..].to_owned()),
LinkType::IncludeRange(ref pat, ref range) => file_to_string(base.join(pat)) LinkType::Include(ref pat, ref range_or_anchor) => {
.map(|s| take_lines(&s, range.clone())) let target = base.join(pat);
.chain_err(|| format!("Could not read file for link {}", self.link_text)),
LinkType::IncludeRangeFrom(ref pat, ref range) => file_to_string(base.join(pat)) fs::read_to_string(&target)
.map(|s| take_lines(&s, range.clone())) .map(|s| match range_or_anchor {
.chain_err(|| format!("Could not read file for link {}", self.link_text)), RangeOrAnchor::Range(range) => take_lines(&s, range.clone()),
LinkType::IncludeRangeTo(ref pat, ref range) => file_to_string(base.join(pat)) RangeOrAnchor::Anchor(anchor) => take_anchored_lines(&s, anchor),
.map(|s| take_lines(&s, range.clone())) })
.chain_err(|| format!("Could not read file for link {}", self.link_text)), .with_context(|| {
LinkType::IncludeRangeFull(ref pat, _) => file_to_string(base.join(pat)) format!(
.chain_err(|| format!("Could not read file for link {}", self.link_text)), "Could not read file for link {} ({})",
LinkType::Playpen(ref pat, ref attrs) => { self.link_text,
let contents = file_to_string(base.join(pat)) target.display(),
.chain_err(|| format!("Could not read file for link {}", self.link_text))?; )
})
}
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 mut 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" }; let ftype = if !attrs.is_empty() { "rust," } else { "rust" };
if !contents.ends_with('\n') {
contents.push('\n');
}
Ok(format!( Ok(format!(
"```{}{}\n{}\n```\n", "```{}{}\n{}```\n",
ftype, ftype,
attrs.join(","), attrs.join(","),
contents contents
)) ))
} }
LinkType::Title(title) => {
*chapter_title = title.to_owned();
Ok(String::new())
}
} }
} }
} }
@ -247,21 +407,23 @@ impl<'a> Iterator for LinkIter<'a> {
} }
} }
fn find_links(contents: &str) -> LinkIter { fn find_links(contents: &str) -> LinkIter<'_> {
// lazily compute following regex // lazily compute following regex
// r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([a-zA-Z0-9_.\-:/\\\s]+)\}\}")?; // r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([^}]+)\}\}")?;
lazy_static! { static RE: Lazy<Regex> = Lazy::new(|| {
static ref RE: Regex = Regex::new( Regex::new(
r"(?x) # insignificant whitespace mode r"(?x) # insignificant whitespace mode
\\\{\{\#.*\}\} # match escaped link \\\{\{\#.*\}\} # match escaped link
| # or | # or
\{\{\s* # link opening parens and whitespace \{\{\s* # link opening parens and whitespace
\#([a-zA-Z0-9]+) # link type \#([a-zA-Z0-9_]+) # link type
\s+ # separating whitespace \s+ # separating whitespace
([a-zA-Z0-9\s_.\-:/\\]+) # link target path and space separated properties ([^}]+) # link target path and space separated properties
\s*\}\} # whitespace and link closing parens" \}\} # link closing parens",
).unwrap(); )
} .unwrap()
});
LinkIter(RE.captures_iter(contents)) LinkIter(RE.captures_iter(contents))
} }
@ -281,7 +443,21 @@ mod tests {
```hbs ```hbs
{{#include file.rs}} << an escaped link! {{#include file.rs}} << an escaped link!
```"; ```";
assert_eq!(replace_all(start, "", "", 0), end); let mut chapter_title = "test_replace_all_escaped".to_owned();
assert_eq!(replace_all(start, "", "", 0, &mut chapter_title), end);
}
#[test]
fn test_set_chapter_title() {
let start = r"{{#title My Title}}
# My Chapter
";
let end = r"
# My Chapter
";
let mut chapter_title = "test_set_chapter_title".to_owned();
assert_eq!(replace_all(start, "", "", 0, &mut chapter_title), end);
assert_eq!(chapter_title, "My Title");
} }
#[test] #[test]
@ -292,7 +468,7 @@ mod tests {
#[test] #[test]
fn test_find_links_partial_link() { 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![]); assert!(find_links(s).collect::<Vec<_>>() == vec![]);
let s = "Some random text with {{#include..."; let s = "Some random text with {{#include...";
assert!(find_links(s).collect::<Vec<_>>() == vec![]); assert!(find_links(s).collect::<Vec<_>>() == vec![]);
@ -302,19 +478,19 @@ mod tests {
#[test] #[test]
fn test_find_links_empty_link() { 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![]); assert!(find_links(s).collect::<Vec<_>>() == vec![]);
} }
#[test] #[test]
fn test_find_links_unknown_link_type() { 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![]); assert!(find_links(s).collect::<Vec<_>>() == vec![]);
} }
#[test] #[test]
fn test_find_links_simple_link() { 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<_>>(); let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res); println!("\nOUTPUT: {:?}\n", res);
@ -324,20 +500,38 @@ mod tests {
vec![ vec![
Link { Link {
start_index: 22, start_index: 22,
end_index: 42, end_index: 45,
link: LinkType::Playpen(PathBuf::from("file.rs"), vec![]), link_type: LinkType::Playground(PathBuf::from("file.rs"), vec![]),
link_text: "{{#playpen file.rs}}", link_text: "{{#playground file.rs}}",
}, },
Link { Link {
start_index: 47, start_index: 50,
end_index: 68, end_index: 74,
link: LinkType::Playpen(PathBuf::from("test.rs"), vec![]), link_type: LinkType::Playground(PathBuf::from("test.rs"), vec![]),
link_text: "{{#playpen test.rs }}", 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] #[test]
fn test_find_links_with_range() { fn test_find_links_with_range() {
let s = "Some random text with {{#include file.rs:10:20}}..."; let s = "Some random text with {{#include file.rs:10:20}}...";
@ -348,7 +542,10 @@ mod tests {
vec![Link { vec![Link {
start_index: 22, start_index: 22,
end_index: 48, end_index: 48,
link: LinkType::IncludeRange(PathBuf::from("file.rs"), 9..20), link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(9..20))
),
link_text: "{{#include file.rs:10:20}}", link_text: "{{#include file.rs:10:20}}",
}] }]
); );
@ -364,7 +561,10 @@ mod tests {
vec![Link { vec![Link {
start_index: 22, start_index: 22,
end_index: 45, end_index: 45,
link: LinkType::IncludeRange(PathBuf::from("file.rs"), 9..10), link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(9..10))
),
link_text: "{{#include file.rs:10}}", link_text: "{{#include file.rs:10}}",
}] }]
); );
@ -380,7 +580,10 @@ mod tests {
vec![Link { vec![Link {
start_index: 22, start_index: 22,
end_index: 46, end_index: 46,
link: LinkType::IncludeRangeFrom(PathBuf::from("file.rs"), 9..), link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(9..))
),
link_text: "{{#include file.rs:10:}}", link_text: "{{#include file.rs:10:}}",
}] }]
); );
@ -396,7 +599,10 @@ mod tests {
vec![Link { vec![Link {
start_index: 22, start_index: 22,
end_index: 46, end_index: 46,
link: LinkType::IncludeRangeTo(PathBuf::from("file.rs"), ..20), link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..20))
),
link_text: "{{#include file.rs::20}}", link_text: "{{#include file.rs::20}}",
}] }]
); );
@ -412,7 +618,10 @@ mod tests {
vec![Link { vec![Link {
start_index: 22, start_index: 22,
end_index: 44, end_index: 44,
link: LinkType::IncludeRangeFull(PathBuf::from("file.rs"), ..), link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..))
),
link_text: "{{#include file.rs::}}", link_text: "{{#include file.rs::}}",
}] }]
); );
@ -428,15 +637,37 @@ mod tests {
vec![Link { vec![Link {
start_index: 22, start_index: 22,
end_index: 42, end_index: 42,
link: LinkType::IncludeRangeFull(PathBuf::from("file.rs"), ..), link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..))
),
link_text: "{{#include file.rs}}", 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] #[test]
fn test_find_links_escaped_link() { 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<_>>(); let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res); println!("\nOUTPUT: {:?}\n", res);
@ -444,18 +675,19 @@ mod tests {
assert_eq!( assert_eq!(
res, res,
vec![Link { vec![Link {
start_index: 38, start_index: 41,
end_index: 68, end_index: 74,
link: LinkType::Escaped, link_type: LinkType::Escaped,
link_text: "\\{{#playpen file.rs editable}}", link_text: "\\{{#playground file.rs editable}}",
}] }]
); );
} }
#[test] #[test]
fn test_find_playpens_with_properties() { fn test_find_playgrounds_with_properties() {
let s = "Some random text with escaped playpen {{#playpen file.rs editable }} and some \ let s =
more\n text {{#playpen my.rs editable no_run should_panic}} ..."; "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<_>>(); let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res); println!("\nOUTPUT: {:?}\n", res);
@ -463,19 +695,19 @@ mod tests {
res, res,
vec![ vec![
Link { Link {
start_index: 38, start_index: 41,
end_index: 68, end_index: 74,
link: LinkType::Playpen(PathBuf::from("file.rs"), vec!["editable"]), link_type: LinkType::Playground(PathBuf::from("file.rs"), vec!["editable"]),
link_text: "{{#playpen file.rs editable }}", link_text: "{{#playground file.rs editable }}",
}, },
Link { Link {
start_index: 89, start_index: 95,
end_index: 136, end_index: 145,
link: LinkType::Playpen( link_type: LinkType::Playground(
PathBuf::from("my.rs"), PathBuf::from("my.rs"),
vec!["editable", "no_run", "should_panic"], 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}}",
}, },
] ]
); );
@ -483,8 +715,9 @@ mod tests {
#[test] #[test]
fn test_find_all_link_types() { fn test_find_all_link_types() {
let s = "Some random text with escaped playpen {{#include file.rs}} and \\{{#contents are \ let s =
insignifficant in escaped link}} some more\n text {{#playpen my.rs editable \ "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}} ..."; no_run should_panic}} ...";
let res = find_links(s).collect::<Vec<_>>(); let res = find_links(s).collect::<Vec<_>>();
@ -493,33 +726,215 @@ mod tests {
assert_eq!( assert_eq!(
res[0], res[0],
Link { Link {
start_index: 38, start_index: 41,
end_index: 58, end_index: 61,
link: LinkType::IncludeRangeFull(PathBuf::from("file.rs"), ..), link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..))
),
link_text: "{{#include file.rs}}", link_text: "{{#include file.rs}}",
} }
); );
assert_eq!( assert_eq!(
res[1], res[1],
Link { Link {
start_index: 63, start_index: 66,
end_index: 112, end_index: 115,
link: LinkType::Escaped, link_type: LinkType::Escaped,
link_text: "\\{{#contents are insignifficant in escaped link}}", link_text: "\\{{#contents are insignifficant in escaped link}}",
} }
); );
assert_eq!( assert_eq!(
res[2], res[2],
Link { Link {
start_index: 130, start_index: 133,
end_index: 177, end_index: 183,
link: LinkType::Playpen( link_type: LinkType::Playground(
PathBuf::from("my.rs"), PathBuf::from("my.rs"),
vec!["editable", "no_run", "should_panic"] 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,19 +1,25 @@
//! Book preprocessing. //! Book preprocessing.
pub use self::cmd::CmdPreprocessor;
pub use self::index::IndexPreprocessor; pub use self::index::IndexPreprocessor;
pub use self::links::LinkPreprocessor; pub use self::links::LinkPreprocessor;
mod cmd;
mod index; mod index;
mod links; mod links;
use book::Book; use crate::book::Book;
use config::Config; use crate::config::Config;
use errors::*; use crate::errors::*;
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::PathBuf; 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. /// processing a book.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PreprocessorContext { pub struct PreprocessorContext {
/// The location of the book directory on disk. /// The location of the book directory on disk.
pub root: PathBuf, pub root: PathBuf,
@ -21,12 +27,25 @@ pub struct PreprocessorContext {
pub config: Config, pub config: Config,
/// The `Renderer` this preprocessor is being used with. /// The `Renderer` this preprocessor is being used with.
pub renderer: String, pub renderer: String,
/// The calling `mdbook` version.
pub mdbook_version: String,
#[serde(skip)]
pub(crate) chapter_titles: RefCell<HashMap<PathBuf, String>>,
#[serde(skip)]
__non_exhaustive: (),
} }
impl PreprocessorContext { impl PreprocessorContext {
/// Create a new `PreprocessorContext`. /// Create a new `PreprocessorContext`.
pub(crate) fn new(root: PathBuf, config: Config, renderer: String) -> Self { pub(crate) fn new(root: PathBuf, config: Config, renderer: String) -> Self {
PreprocessorContext { root, config, renderer } PreprocessorContext {
root,
config,
renderer,
mdbook_version: crate::MDBOOK_VERSION.to_string(),
chapter_titles: RefCell::new(HashMap::new()),
__non_exhaustive: (),
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,10 +1,13 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::path::Path; use std::path::Path;
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError, Renderable}; use handlebars::{
use serde_json; Context, Handlebars, Helper, Output, RenderContext, RenderError, RenderErrorReason, Renderable,
};
use utils; use crate::utils;
use log::{debug, trace};
use serde_json::json;
type StringMap = BTreeMap<String, String>; type StringMap = BTreeMap<String, String>;
@ -18,23 +21,23 @@ impl Target {
/// Returns target if found. /// Returns target if found.
fn find( fn find(
&self, &self,
base_path: &String, base_path: &str,
current_path: &String, current_path: &str,
current_item: &StringMap, current_item: &StringMap,
previous_item: &StringMap, previous_item: &StringMap,
) -> Result<Option<StringMap>, RenderError> { ) -> Result<Option<StringMap>, RenderError> {
match self { match *self {
&Target::Next => { Target::Next => {
let previous_path = previous_item let previous_path = previous_item.get("path").ok_or_else(|| {
.get("path") RenderErrorReason::Other("No path found for chapter in JSON data".to_owned())
.ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))?; })?;
if previous_path == base_path { if previous_path == base_path {
return Ok(Some(current_item.clone())); return Ok(Some(current_item.clone()));
} }
} }
&Target::Previous => { Target::Previous => {
if current_path == base_path { if current_path == base_path {
return Ok(Some(previous_item.clone())); return Ok(Some(previous_item.clone()));
} }
@ -47,21 +50,45 @@ impl Target {
fn find_chapter( fn find_chapter(
ctx: &Context, ctx: &Context,
rc: &mut RenderContext, rc: &mut RenderContext<'_, '_>,
target: Target, target: Target,
) -> Result<Option<StringMap>, RenderError> { ) -> Result<Option<StringMap>, RenderError> {
debug!("Get data from context"); debug!("Get data from context");
let chapters = rc.evaluate_absolute(ctx, "chapters", true).and_then(|c| { let chapters = rc.evaluate(ctx, "@root/chapters").and_then(|c| {
serde_json::value::from_value::<Vec<StringMap>>(c.clone()) serde_json::value::from_value::<Vec<StringMap>>(c.as_json().clone()).map_err(|_| {
.map_err(|_| RenderError::new("Could not decode the JSON data")) RenderErrorReason::Other("Could not decode the JSON data".to_owned()).into()
})
})?; })?;
let base_path = rc let base_path = rc
.evaluate_absolute(ctx, "path", true)? .evaluate(ctx, "@root/path")?
.as_json()
.as_str() .as_str()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))? .ok_or_else(|| {
.replace("\"", ""); RenderErrorReason::Other("Type error for `path`, string expected".to_owned())
})?
.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; let mut previous: Option<StringMap> = None;
@ -71,12 +98,12 @@ fn find_chapter(
match item.get("path") { match item.get("path") {
Some(path) if !path.is_empty() => { Some(path) if !path.is_empty() => {
if let Some(previous) = previous { if let Some(previous) = previous {
if let Some(item) = target.find(&base_path, &path, &item, &previous)? { if let Some(item) = target.find(&base_path, path, &item, &previous)? {
return Ok(Some(item)); return Ok(Some(item));
} }
} }
previous = Some(item.clone()); previous = Some(item);
} }
_ => continue, _ => continue,
} }
@ -86,62 +113,68 @@ fn find_chapter(
} }
fn render( fn render(
_h: &Helper, _h: &Helper<'_>,
r: &Handlebars, r: &Handlebars<'_>,
ctx: &Context, ctx: &Context,
rc: &mut RenderContext, rc: &mut RenderContext<'_, '_>,
out: &mut Output, out: &mut dyn Output,
chapter: &StringMap, chapter: &StringMap,
) -> Result<(), RenderError> { ) -> Result<(), RenderError> {
trace!("Creating BTreeMap to inject in context"); trace!("Creating BTreeMap to inject in context");
let mut context = BTreeMap::new(); let mut context = BTreeMap::new();
let base_path = rc let base_path = rc
.evaluate_absolute(ctx, "path", false)? .evaluate(ctx, "@root/path")?
.as_json()
.as_str() .as_str()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))? .ok_or_else(|| {
.replace("\"", ""); RenderErrorReason::Other("Type error for `path`, string expected".to_owned())
})?
.replace('\"', "");
context.insert( context.insert(
"path_to_root".to_owned(), "path_to_root".to_owned(),
json!(utils::fs::path_to_root(&base_path)), json!(utils::fs::path_to_root(base_path)),
); );
chapter chapter
.get("name") .get("name")
.ok_or_else(|| RenderError::new("No title found for chapter in JSON data")) .ok_or_else(|| {
RenderErrorReason::Other("No title found for chapter in JSON data".to_owned())
})
.map(|name| context.insert("title".to_owned(), json!(name)))?; .map(|name| context.insert("title".to_owned(), json!(name)))?;
chapter chapter
.get("path") .get("path")
.ok_or_else(|| RenderError::new("No path found for chapter in JSON data")) .ok_or_else(|| {
RenderErrorReason::Other("No path found for chapter in JSON data".to_owned())
})
.and_then(|p| { .and_then(|p| {
Path::new(p) Path::new(p)
.with_extension("html") .with_extension("html")
.to_str() .to_str()
.ok_or_else(|| RenderError::new("Link could not be converted to str")) .ok_or_else(|| {
.map(|p| context.insert("link".to_owned(), json!(p.replace("\\", "/")))) RenderErrorReason::Other("Link could not be converted to str".to_owned())
})
.map(|p| context.insert("link".to_owned(), json!(p.replace('\\', "/"))))
})?; })?;
trace!("Render template"); trace!("Render template");
_h.template() let t = _h
.ok_or_else(|| RenderError::new("Error with the handlebars template")) .template()
.and_then(|t| { .ok_or_else(|| RenderErrorReason::Other("Error with the handlebars template".to_owned()))?;
let mut local_rc = rc.new_for_block();
let local_ctx = Context::wraps(&context)?; let local_ctx = Context::wraps(&context)?;
let mut local_rc = rc.clone();
t.render(r, &local_ctx, &mut local_rc, out) t.render(r, &local_ctx, &mut local_rc, out)
})?;
Ok(())
} }
pub fn previous( pub fn previous(
_h: &Helper, _h: &Helper<'_>,
r: &Handlebars, r: &Handlebars<'_>,
ctx: &Context, ctx: &Context,
rc: &mut RenderContext, rc: &mut RenderContext<'_, '_>,
out: &mut Output, out: &mut dyn Output,
) -> Result<(), RenderError> { ) -> Result<(), RenderError> {
trace!("previous (handlebars helper)"); trace!("previous (handlebars helper)");
@ -153,11 +186,11 @@ pub fn previous(
} }
pub fn next( pub fn next(
_h: &Helper, _h: &Helper<'_>,
r: &Handlebars, r: &Handlebars<'_>,
ctx: &Context, ctx: &Context,
rc: &mut RenderContext, rc: &mut RenderContext<'_, '_>,
out: &mut Output, out: &mut dyn Output,
) -> Result<(), RenderError> { ) -> Result<(), RenderError> {
trace!("next (handlebars helper)"); trace!("next (handlebars helper)");
@ -172,7 +205,7 @@ pub fn next(
mod tests { mod tests {
use super::*; use super::*;
static TEMPLATE: &'static str = static TEMPLATE: &str =
"{{#previous}}{{title}}: {{link}}{{/previous}}|{{#next}}{{title}}: {{link}}{{/next}}"; "{{#previous}}{{title}}: {{link}}{{/previous}}|{{#next}}{{title}}: {{link}}{{/next}}";
#[test] #[test]

View File

@ -0,0 +1,38 @@
use handlebars::{
Context, Handlebars, Helper, Output, RenderContext, RenderError, RenderErrorReason,
};
use log::trace;
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(|| {
RenderErrorReason::ParamTypeMismatchForName(
"theme_option",
"0".to_owned(),
"string".to_owned(),
)
})?;
let default_theme = rc.evaluate(ctx, "@root/default_theme")?;
let default_theme_name = default_theme.as_json().as_str().ok_or_else(|| {
RenderErrorReason::ParamTypeMismatchForName(
"theme_option",
"default_theme".to_owned(),
"string".to_owned(),
)
})?;
out.write(param)?;
if param.to_lowercase() == default_theme_name.to_lowercase() {
out.write(" (default)")?;
}
Ok(())
}

View File

@ -1,11 +1,12 @@
use std::collections::BTreeMap;
use std::path::Path; use std::path::Path;
use std::{cmp::Ordering, collections::BTreeMap};
use utils; use crate::utils;
use crate::utils::bracket_escape;
use handlebars::{Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError}; use handlebars::{
use pulldown_cmark::{html, Event, Parser, Tag}; Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError, RenderErrorReason,
use serde_json; };
// Handlebars helper to construct TOC // Handlebars helper to construct TOC
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -16,28 +17,61 @@ pub struct RenderToc {
impl HelperDef for RenderToc { impl HelperDef for RenderToc {
fn call<'reg: 'rc, 'rc>( fn call<'reg: 'rc, 'rc>(
&self, &self,
_h: &Helper, _h: &Helper<'rc>,
_: &Handlebars, _r: &'reg Handlebars<'_>,
ctx: &Context, ctx: &'rc Context,
rc: &mut RenderContext, rc: &mut RenderContext<'reg, 'rc>,
out: &mut Output, out: &mut dyn Output,
) -> Result<(), RenderError> { ) -> Result<(), RenderError> {
// get value from context data // get value from context data
// rc.get_path() is current json parent path, you should always use it like this // 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 // param is the key of value you want to display
let chapters = rc.evaluate_absolute(ctx, "chapters", true).and_then(|c| { let chapters = rc.evaluate(ctx, "@root/chapters").and_then(|c| {
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone()) serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.as_json().clone())
.map_err(|_| RenderError::new("Could not decode the JSON data")) .map_err(|_| {
RenderErrorReason::Other("Could not decode the JSON data".to_owned()).into()
})
})?; })?;
let current = rc let current_path = rc
.evaluate_absolute(ctx, "path", true)? .evaluate(ctx, "@root/path")?
.as_json()
.as_str() .as_str()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))? .ok_or_else(|| {
.replace("\"", ""); RenderErrorReason::Other("Type error for `path`, string expected".to_owned())
})?
.replace('\"', "");
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(|| {
RenderErrorReason::Other("Type error for `fold_enable`, bool expected".to_owned())
})?;
let fold_level = rc
.evaluate(ctx, "@root/fold_level")?
.as_json()
.as_u64()
.ok_or_else(|| {
RenderErrorReason::Other("Type error for `fold_level`, u64 expected".to_owned())
})?;
out.write("<ol class=\"chapter\">")?; out.write("<ol class=\"chapter\">")?;
let mut current_level = 1; let mut current_level = 1;
// The "index" page, which has this attribute set, is supposed to alias the first chapter in
// the book, i.e. the first link. There seems to be no easy way to determine which chapter
// the "index" is aliasing from within the renderer, so this is used instead to force the
// first link to be active. See further below.
let mut is_first_chapter = ctx.data().get("is_index").is_some();
for item in chapters { for item in chapters {
// Spacer // Spacer
@ -46,97 +80,109 @@ impl HelperDef for RenderToc {
continue; continue;
} }
let level = if let Some(s) = item.get("section") { let (section, level) = if let Some(s) = item.get("section") {
s.matches('.').count() (s.as_str(), s.matches('.').count())
} else { } else {
1 ("", 1)
}; };
if level > current_level { 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
};
match level.cmp(&current_level) {
Ordering::Greater => {
while level > current_level { while level > current_level {
out.write("<li>")?; out.write("<li>")?;
out.write("<ol class=\"section\">")?; out.write("<ol class=\"section\">")?;
current_level += 1; current_level += 1;
} }
out.write("<li>")?; write_li_open_tag(out, is_expanded, false)?;
} else if level < current_level { }
Ordering::Less => {
while level < current_level { while level < current_level {
out.write("</ol>")?; out.write("</ol>")?;
out.write("</li>")?; out.write("</li>")?;
current_level -= 1; current_level -= 1;
} }
out.write("<li>")?; write_li_open_tag(out, is_expanded, false)?;
} else {
out.write("<li")?;
if item.get("section").is_none() {
out.write(" class=\"affix\"")?;
} }
out.write(">")?; Ordering::Equal => {
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(&bracket_escape(title))?;
out.write("</li>")?;
continue;
} }
// Link // Link
let path_exists = if let Some(path) = item.get("path") { let path_exists: bool;
if !path.is_empty() { match item.get("path") {
Some(path) if !path.is_empty() => {
out.write("<a href=\"")?; out.write("<a href=\"")?;
let tmp = Path::new(path)
let tmp = Path::new(item.get("path").expect("Error: path should be Some(_)"))
.with_extension("html") .with_extension("html")
.to_str() .to_str()
.unwrap() .unwrap()
// Hack for windows who tends to use `\` as separator instead of `/` // Hack for windows who tends to use `\` as separator instead of `/`
.replace("\\", "/"); .replace('\\', "/");
// Add link // Add link
out.write(&utils::fs::path_to_root(&current))?; out.write(&utils::fs::path_to_root(&current_path))?;
out.write(&tmp)?; out.write(&tmp)?;
out.write("\"")?; out.write("\"")?;
if path == &current { if path == &current_path || is_first_chapter {
is_first_chapter = false;
out.write(" class=\"active\"")?; out.write(" class=\"active\"")?;
} }
out.write(">")?; out.write(">")?;
true path_exists = true;
} else { }
false _ => {
out.write("<div>")?;
path_exists = false;
}
} }
} else {
false
};
if !self.no_section_label { if !self.no_section_label {
// Section does not necessarily exist // Section does not necessarily exist
if let Some(section) = item.get("section") { if let Some(section) = item.get("section") {
out.write("<strong aria-hidden=\"true\">")?; out.write("<strong aria-hidden=\"true\">")?;
out.write(&section)?; out.write(section)?;
out.write("</strong> ")?; out.write("</strong> ")?;
} }
} }
if let Some(name) = item.get("name") { if let Some(name) = item.get("name") {
// Render only inline code blocks out.write(&bracket_escape(name))?
// 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,
_ => false,
});
// render markdown to html
let mut markdown_parsed_name = String::with_capacity(name.len() * 3 / 2);
html::push_html(&mut markdown_parsed_name, parser);
// write to the handlebars template
out.write(&markdown_parsed_name)?;
} }
if path_exists { if path_exists {
out.write("</a>")?; out.write("</a>")?;
} else {
out.write("</div>")?;
} }
// 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>")?; out.write("</li>")?;
} }
while current_level > 1 { while current_level > 1 {
@ -149,3 +195,19 @@ impl HelperDef for RenderToc {
Ok(()) 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,30 +1,45 @@
extern crate ammonia;
extern crate elasticlunr;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::path::Path; use std::path::Path;
use self::elasticlunr::Index; use elasticlunr::{Index, IndexBuilder};
use once_cell::sync::Lazy;
use pulldown_cmark::*; use pulldown_cmark::*;
use serde_json;
use book::{Book, BookItem}; use crate::book::{Book, BookItem};
use config::Search; use crate::config::Search;
use errors::*; use crate::errors::*;
use theme::searcher; use crate::theme::searcher;
use utils; use crate::utils;
use log::{debug, warn};
use serde::Serialize;
const MAX_WORD_LENGTH_TO_INDEX: usize = 80;
/// Tokenizes in the same way as elasticlunr-rs (for English), but also drops long tokens.
fn tokenize(text: &str) -> Vec<String> {
text.split(|c: char| c.is_whitespace() || c == '-')
.filter(|s| !s.is_empty())
.map(|s| s.trim().to_lowercase())
.filter(|s| s.len() <= MAX_WORD_LENGTH_TO_INDEX)
.collect()
}
/// Creates all files required for search. /// Creates all files required for search.
pub fn create_files(search_config: &Search, destination: &Path, book: &Book) -> Result<()> { pub fn create_files(search_config: &Search, destination: &Path, book: &Book) -> Result<()> {
let mut index = Index::new(&["title", "body", "breadcrumbs"]); let mut index = IndexBuilder::new()
.add_field_with_tokenizer("title", Box::new(&tokenize))
.add_field_with_tokenizer("body", Box::new(&tokenize))
.add_field_with_tokenizer("breadcrumbs", Box::new(&tokenize))
.build();
let mut doc_urls = Vec::with_capacity(book.sections.len()); let mut doc_urls = Vec::with_capacity(book.sections.len());
for item in book.iter() { for item in book.iter() {
render_item(&mut index, &search_config, &mut doc_urls, item)?; render_item(&mut index, search_config, &mut doc_urls, item)?;
} }
let index = write_to_json(index, &search_config, doc_urls)?; let index = write_to_json(index, search_config, doc_urls)?;
debug!("Writing search index ✓"); debug!("Writing search index ✓");
if index.len() > 10_000_000 { if index.len() > 10_000_000 {
warn!("searchindex.json is very large ({} bytes)", index.len()); warn!("searchindex.json is very large ({} bytes)", index.len());
@ -35,7 +50,7 @@ pub fn create_files(search_config: &Search, destination: &Path, book: &Book) ->
utils::fs::write_file( utils::fs::write_file(
destination, destination,
"searchindex.js", "searchindex.js",
format!("window.search = {};", index).as_bytes(), format!("Object.assign(window.search, {});", index).as_bytes(),
)?; )?;
utils::fs::write_file(destination, "searcher.js", searcher::JS)?; 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, "mark.min.js", searcher::MARK_JS)?;
@ -51,10 +66,23 @@ fn add_doc(
index: &mut Index, index: &mut Index,
doc_urls: &mut Vec<String>, doc_urls: &mut Vec<String>,
anchor_base: &str, anchor_base: &str,
section_id: &Option<String>, heading: &str,
id_counter: &mut HashMap<String, usize>,
section_id: &Option<CowStr<'_>>,
items: &[&str], items: &[&str],
) { ) {
let url = if let &Some(ref id) = section_id { // Either use the explicit section id the user specified, or generate one
// from the heading content.
let section_id = section_id.as_ref().map(|id| id.to_string()).or_else(|| {
if heading.is_empty() {
// In the case where a chapter has no heading, don't set a section id.
None
} else {
Some(utils::unique_id_from_content(heading, id_counter))
}
});
let url = if let Some(id) = section_id {
Cow::Owned(format!("{}#{}", anchor_base, id)) Cow::Owned(format!("{}#{}", anchor_base, id))
} else { } else {
Cow::Borrowed(anchor_base) Cow::Borrowed(anchor_base)
@ -74,95 +102,132 @@ fn render_item(
doc_urls: &mut Vec<String>, doc_urls: &mut Vec<String>,
item: &BookItem, item: &BookItem,
) -> Result<()> { ) -> Result<()> {
let chapter = match item { let chapter = match *item {
&BookItem::Chapter(ref ch) => ch, BookItem::Chapter(ref ch) if !ch.is_draft_chapter() => ch,
_ => return Ok(()), _ => 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 let filepath = filepath
.to_str() .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 anchor_base = utils::fs::normalize_path(filepath);
let mut opts = Options::empty(); let mut p = utils::new_cmark_parser(&chapter.content, false).peekable();
opts.insert(OPTION_ENABLE_TABLES);
opts.insert(OPTION_ENABLE_FOOTNOTES);
let p = Parser::new_ext(&chapter.content, opts);
let mut in_header = false; let mut in_heading = false;
let max_section_depth = search_config.heading_split_level as i32; let max_section_depth = u32::from(search_config.heading_split_level);
let mut section_id = None; let mut section_id = None;
let mut heading = String::new(); let mut heading = String::new();
let mut body = String::new(); let mut body = String::new();
let mut breadcrumbs = chapter.parent_names.clone(); let mut breadcrumbs = chapter.parent_names.clone();
let mut footnote_numbers = HashMap::new(); let mut footnote_numbers = HashMap::new();
for event in p { breadcrumbs.push(chapter.name.clone());
let mut id_counter = HashMap::new();
while let Some(event) = p.next() {
match event { match event {
Event::Start(Tag::Header(i)) if i <= max_section_depth => { Event::Start(Tag::Heading { level, id, .. }) if level as u32 <= max_section_depth => {
if heading.len() > 0 { if !heading.is_empty() {
// Section finished, the next header is following now // Section finished, the next heading is following now
// Write the data to the index, and clear it for the next section // Write the data to the index, and clear it for the next section
add_doc( add_doc(
index, index,
doc_urls, doc_urls,
&anchor_base, &anchor_base,
&heading,
&mut id_counter,
&section_id, &section_id,
&[&heading, &body, &breadcrumbs.join(" » ")], &[&heading, &body, &breadcrumbs.join(" » ")],
); );
section_id = None;
heading.clear(); heading.clear();
body.clear(); body.clear();
breadcrumbs.pop(); breadcrumbs.pop();
} }
in_header = true; section_id = id;
in_heading = true;
} }
Event::End(Tag::Header(i)) if i <= max_section_depth => { Event::End(TagEnd::Heading(level)) if level as u32 <= max_section_depth => {
in_header = false; in_heading = false;
section_id = Some(utils::id_from_content(&heading));
breadcrumbs.push(heading.clone()); breadcrumbs.push(heading.clone());
} }
Event::Start(Tag::FootnoteDefinition(name)) => { Event::Start(Tag::FootnoteDefinition(name)) => {
let number = footnote_numbers.len() + 1; let number = footnote_numbers.len() + 1;
footnote_numbers.entry(name).or_insert(number); footnote_numbers.entry(name).or_insert(number);
} }
Event::Start(_) | Event::End(_) | Event::SoftBreak | Event::HardBreak => { Event::Html(html) => {
// Insert spaces where HTML output would usually seperate text 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::InlineHtml(html) => {
// This is not capable of cleaning inline tags like
// `foo <script>…</script>`. The `<script>` tags show up as
// individual InlineHtml events, and the content inside is
// just a regular Text event. There isn't a very good way to
// know how to collect all the content in-between. I'm not
// sure if this is easily fixable. It should be extremely
// rare, since script and style tags should almost always be
// blocks, and worse case you have some noise in the index.
body.push_str(&clean_html(&html));
}
Event::Start(_) | Event::End(_) | Event::Rule | Event::SoftBreak | Event::HardBreak => {
// Insert spaces where HTML output would usually separate text
// to ensure words don't get merged together // to ensure words don't get merged together
if in_header { if in_heading {
heading.push(' '); heading.push(' ');
} else { } else {
body.push(' '); body.push(' ');
} }
} }
Event::Text(text) => { Event::Text(text) | Event::Code(text) => {
if in_header { if in_heading {
heading.push_str(&text); heading.push_str(&text);
} else { } else {
body.push_str(&text); body.push_str(&text);
} }
} }
Event::Html(html) | Event::InlineHtml(html) => {
body.push_str(&clean_html(&html));
}
Event::FootnoteReference(name) => { Event::FootnoteReference(name) => {
let len = footnote_numbers.len() + 1; let len = footnote_numbers.len() + 1;
let number = footnote_numbers.entry(name).or_insert(len); let number = footnote_numbers.entry(name).or_insert(len);
body.push_str(&format!(" [{}] ", number)); body.push_str(&format!(" [{}] ", number));
} }
Event::TaskListMarker(_checked) => {}
} }
} }
if heading.len() > 0 { if !body.is_empty() || !heading.is_empty() {
let title = if heading.is_empty() {
if let Some(chapter) = breadcrumbs.first() {
chapter
} else {
""
}
} else {
&heading
};
// Make sure the last section is added to the index // Make sure the last section is added to the index
add_doc( add_doc(
index, index,
doc_urls, doc_urls,
&anchor_base, &anchor_base,
&heading,
&mut id_counter,
&section_id, &section_id,
&[&heading, &body, &breadcrumbs.join(" » ")], &[title, &body, &breadcrumbs.join(" » ")],
); );
} }
@ -170,7 +235,7 @@ fn render_item(
} }
fn write_to_json(index: Index, search_config: &Search, doc_urls: Vec<String>) -> Result<String> { fn write_to_json(index: Index, search_config: &Search, doc_urls: Vec<String>) -> Result<String> {
use self::elasticlunr::config::{SearchBool, SearchOptions, SearchOptionsField}; use elasticlunr::config::{SearchBool, SearchOptions, SearchOptionsField};
use std::collections::BTreeMap; use std::collections::BTreeMap;
#[derive(Serialize)] #[derive(Serialize)]
@ -193,12 +258,13 @@ fn write_to_json(index: Index, search_config: &Search, doc_urls: Vec<String>) ->
let mut fields = BTreeMap::new(); let mut fields = BTreeMap::new();
let mut opt = SearchOptionsField::default(); let mut opt = SearchOptionsField::default();
opt.boost = Some(search_config.boost_title); let mut insert_boost = |key: &str, boost| {
fields.insert("title".into(), opt); opt.boost = Some(boost);
opt.boost = Some(search_config.boost_paragraph); fields.insert(key.into(), opt);
fields.insert("body".into(), opt); };
opt.boost = Some(search_config.boost_hierarchy); insert_boost("title", search_config.boost_title);
fields.insert("breadcrumbs".into(), opt); insert_boost("body", search_config.boost_paragraph);
insert_boost("breadcrumbs", search_config.boost_hierarchy);
let search_options = SearchOptions { let search_options = SearchOptions {
bool: if search_config.use_boolean_and { bool: if search_config.use_boolean_and {
@ -231,8 +297,7 @@ fn write_to_json(index: Index, search_config: &Search, doc_urls: Vec<String>) ->
} }
fn clean_html(html: &str) -> String { fn clean_html(html: &str) -> String {
lazy_static! { static AMMONIA: Lazy<ammonia::Builder<'static>> = Lazy::new(|| {
static ref AMMONIA: ammonia::Builder<'static> = {
let mut clean_content = HashSet::new(); let mut clean_content = HashSet::new();
clean_content.insert("script"); clean_content.insert("script");
clean_content.insert("style"); clean_content.insert("style");
@ -245,7 +310,6 @@ fn clean_html(html: &str) -> String {
.allowed_classes(HashMap::new()) .allowed_classes(HashMap::new())
.clean_content_tags(clean_content); .clean_content_tags(clean_content);
builder builder
}; });
}
AMMONIA.clean(html).to_string() AMMONIA.clean(html).to_string()
} }

View File

@ -0,0 +1,52 @@
use crate::book::BookItem;
use crate::errors::*;
use crate::renderer::{RenderContext, Renderer};
use crate::utils;
use log::trace;
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,29 @@
//! //!
//! The definition for [RenderContext] may be useful though. //! 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 //! [RenderContext]: struct.RenderContext.html
pub use self::html_handlebars::HtmlHandlebars; pub use self::html_handlebars::HtmlHandlebars;
pub use self::markdown_renderer::MarkdownRenderer;
mod html_handlebars; mod html_handlebars;
mod markdown_renderer;
use serde_json;
use shlex::Shlex; use shlex::Shlex;
use std::collections::HashMap;
use std::fs; use std::fs;
use std::io::{self, Read}; use std::io::{self, ErrorKind, Read};
use std::path::PathBuf; use std::path::{Path, PathBuf};
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use book::Book; use crate::book::Book;
use config::Config; use crate::config::Config;
use errors::*; use crate::errors::*;
use log::{error, info, trace, warn};
use toml::Value;
const MDBOOK_VERSION: &str = env!("CARGO_PKG_VERSION"); use serde::{Deserialize, Serialize};
/// An arbitrary `mdbook` backend. /// An arbitrary `mdbook` backend.
/// ///
@ -34,12 +38,9 @@ const MDBOOK_VERSION: &str = env!("CARGO_PKG_VERSION");
/// provide your own renderer, there are two main renderer implementations that /// provide your own renderer, there are two main renderer implementations that
/// 99% of users will ever use: /// 99% of users will ever use:
/// ///
/// - [HtmlHandlebars] - the built-in HTML renderer /// - [`HtmlHandlebars`] - the built-in HTML renderer
/// - [CmdRenderer] - a generic renderer which shells out to a program to do the /// - [`CmdRenderer`] - a generic renderer which shells out to a program to do the
/// actual rendering /// actual rendering
///
/// [HtmlHandlebars]: struct.HtmlHandlebars.html
/// [CmdRenderer]: struct.CmdRenderer.html
pub trait Renderer { pub trait Renderer {
/// The `Renderer`'s name. /// The `Renderer`'s name.
fn name(&self) -> &str; fn name(&self) -> &str;
@ -66,6 +67,10 @@ pub struct RenderContext {
/// renderers to cache intermediate results, this directory is not /// renderers to cache intermediate results, this directory is not
/// guaranteed to be empty or even exist. /// guaranteed to be empty or even exist.
pub destination: PathBuf, pub destination: PathBuf,
#[serde(skip)]
pub(crate) chapter_titles: HashMap<PathBuf, String>,
#[serde(skip)]
__non_exhaustive: (),
} }
impl RenderContext { impl RenderContext {
@ -76,11 +81,13 @@ impl RenderContext {
Q: Into<PathBuf>, Q: Into<PathBuf>,
{ {
RenderContext { RenderContext {
book: book, book,
config: config, config,
version: MDBOOK_VERSION.to_string(), version: crate::MDBOOK_VERSION.to_string(),
root: root.into(), root: root.into(),
destination: destination.into(), destination: destination.into(),
chapter_titles: HashMap::new(),
__non_exhaustive: (),
} }
} }
@ -91,7 +98,7 @@ impl RenderContext {
/// Load a `RenderContext` from its JSON representation. /// Load a `RenderContext` from its JSON representation.
pub fn from_json<R: Read>(reader: R) -> Result<RenderContext> { 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`")
} }
} }
@ -130,14 +137,44 @@ impl CmdRenderer {
CmdRenderer { name, cmd } CmdRenderer { name, cmd }
} }
fn compose_command(&self) -> Result<Command> { fn compose_command(&self, root: &Path, destination: &Path) -> Result<Command> {
let mut words = Shlex::new(&self.cmd); let mut words = Shlex::new(&self.cmd);
let executable = match words.next() { let exe = match words.next() {
Some(e) => e, Some(e) => PathBuf::from(e),
None => bail!("Command string was empty"), None => bail!("Command string was empty"),
}; };
let mut cmd = Command::new(executable); let exe = if exe.components().count() == 1 {
// Search PATH for the executable.
exe
} else {
// Relative paths are preferred to be relative to the book root.
let abs_exe = root.join(&exe);
if abs_exe.exists() {
abs_exe
} else {
// Historically paths were relative to the destination, but
// this is not the preferred way.
let legacy_path = destination.join(&exe);
if legacy_path.exists() {
warn!(
"Renderer command `{}` uses a path relative to the \
renderer output directory `{}`. This was previously \
accepted, but has been deprecated. Relative executable \
paths should be relative to the book root.",
exe.display(),
destination.display()
);
legacy_path
} else {
// Let this bubble through to later be handled by
// handle_render_command_error.
abs_exe
}
}
};
let mut cmd = Command::new(exe);
for arg in words { for arg in words {
cmd.arg(arg); cmd.arg(arg);
@ -147,6 +184,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 { impl Renderer for CmdRenderer {
fn name(&self) -> &str { fn name(&self) -> &str {
&self.name &self.name
@ -158,7 +229,7 @@ impl Renderer for CmdRenderer {
let _ = fs::create_dir_all(&ctx.destination); let _ = fs::create_dir_all(&ctx.destination);
let mut child = match self let mut child = match self
.compose_command()? .compose_command(&ctx.root, &ctx.destination)?
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::inherit()) .stdout(Stdio::inherit())
.stderr(Stdio::inherit()) .stderr(Stdio::inherit())
@ -166,20 +237,9 @@ impl Renderer for CmdRenderer {
.spawn() .spawn()
{ {
Ok(c) => c, Ok(c) => c,
Err(ref e) if e.kind() == io::ErrorKind::NotFound => { Err(e) => return self.handle_render_command_error(ctx, e),
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")?;
}
}; };
{
let mut stdin = child.stdin.take().expect("Child has stdin"); let mut stdin = child.stdin.take().expect("Child has stdin");
if let Err(e) = serde_json::to_writer(&mut stdin, &ctx) { if let Err(e) = serde_json::to_writer(&mut stdin, &ctx) {
// Looks like the backend hung up before we could finish // Looks like the backend hung up before we could finish
@ -189,11 +249,10 @@ impl Renderer for CmdRenderer {
// explicitly close the `stdin` file handle // explicitly close the `stdin` file handle
drop(stdin); drop(stdin);
}
let status = child let status = child
.wait() .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); trace!("{} exited with output: {:?}", self.cmd, status);

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