Compare commits

..

652 Commits

Author SHA1 Message Date
Eric Huss
4941acdb87 Merge pull request #2551 from ehuss/bump-version
Update to 0.4.45
2025-02-17 18:26:17 +00:00
Eric Huss
7e3d2f96ab Update to 0.4.45 2025-02-17 10:18:04 -08:00
Eric Huss
ddba36b24c Merge pull request #2524 from WaffleLapkin/first-last-of-type-footnote
nicer style rules for margin around footnote defs
2025-02-17 18:12:15 +00:00
Eric Huss
35cf96a064 Merge pull request #2550 from ehuss/fix-expected-source-path
Fix issue with None source_path
2025-02-17 17:52:50 +00:00
Eric Huss
5777a0edc4 Fix issue with None source_path
This fixes an issue where mdbook would panic if a non-draft chapter has
a None source_path when generating the search index. The code was
assuming that only draft chapters would have that behavior. However, API
users can inject synthetic chapters that have no path on disk.

This updates it to fall back to the path, or skip if neither is set.
2025-02-17 09:41:52 -08:00
Eric Huss
53c3a92285 Add test for a chapter with no source path 2025-02-17 08:20:16 -08:00
Eric Huss
132ca0dca3 Merge pull request #2548 from tamird/patch-1
README.md: update workflow status badge
2025-02-13 16:25:11 +00:00
Tamir Duberstein
56c2b9ba3a README.md: update workflow status badge
The previous badge was broken.

Link: https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/monitoring-workflows/adding-a-workflow-status-badge
2025-02-13 11:01:08 -05:00
Eric Huss
542b6feed1 Merge pull request #2545 from ehuss/rustdoc-missing-error
Add context when `rustdoc` command is not found
2025-02-03 19:10:48 +00:00
Eric Huss
2af44a396f Add context when rustdoc command is not found 2025-02-03 11:02:53 -08:00
Eric Huss
40d91fff29 Merge pull request #2540 from ehuss/bump-version
Update to 0.4.44
2025-01-28 17:58:16 +00:00
Eric Huss
59eab7cfc2 Update to 0.4.44 2025-01-28 09:50:04 -08:00
Eric Huss
1b524ff356 Merge pull request #2539 from ehuss/update-notify
Update notify to 8.0.0
2025-01-28 17:41:05 +00:00
Eric Huss
9b873e9d97 Bump rust-version to 1.77 2025-01-28 09:35:11 -08:00
Eric Huss
b6d6cb2711 Update notify to 8.0.0 2025-01-28 09:32:17 -08:00
Eric Huss
c8095160d0 Merge pull request #2538 from ehuss/update-dependencies
Update dependencies
2025-01-28 17:19:41 +00:00
Eric Huss
ae6db3a87e Update dependencies
Updating anstyle-wincon v3.0.6 -> v3.0.7
Updating anyhow v1.0.93 -> v1.0.95
Updating bitflags v2.6.0 -> v2.8.0
Updating bstr v1.10.0 -> v1.11.3
Updating bytes v1.8.0 -> v1.9.0
Updating cc v1.1.36 -> v1.2.10
Updating chrono v0.4.38 -> v0.4.39
Updating clap v4.5.20 -> v4.5.27
Updating clap_builder v4.5.20 -> v4.5.27
Updating clap_complete v4.5.37 -> v4.5.43
Updating clap_lex v0.7.2 -> v0.7.4
Updating cpufeatures v0.2.14 -> v0.2.17
Updating crossbeam-channel v0.5.13 -> v0.5.14
Updating crossbeam-deque v0.8.5 -> v0.8.6
Updating crossbeam-utils v0.8.20 -> v0.8.21
  Adding darling v0.20.10
  Adding darling_core v0.20.10
  Adding darling_macro v0.20.10
Updating data-encoding v2.6.0 -> v2.7.0
  Adding derive_builder v0.20.2
  Adding derive_builder_core v0.20.2
  Adding derive_builder_macro v0.20.2
Updating env_filter v0.1.2 -> v0.1.3
Updating env_logger v0.11.5 -> v0.11.6
Updating errno v0.3.9 -> v0.3.10
Updating fastrand v2.1.1 -> v2.3.0
Updating float-cmp v0.9.0 -> v0.10.0
Updating handlebars v6.2.0 -> v6.3.0
Updating hashbrown v0.15.1 -> v0.15.2
Removing hermit-abi v0.3.9
Updating http v1.1.0 -> v1.2.0
Updating httparse v1.9.5 -> v1.10.0
Updating hyper v0.14.31 -> v0.14.32
  Adding ident_case v1.0.1
Updating indexmap v2.6.0 -> v2.7.1
Updating itoa v1.0.11 -> v1.0.14
Updating js-sys v0.3.72 -> v0.3.77
Updating libc v0.2.161 -> v0.2.169
Updating linux-raw-sys v0.4.14 -> v0.4.15
Updating litemap v0.7.3 -> v0.7.4
Updating log v0.4.22 -> v0.4.25
Updating miniz_oxide v0.8.0 -> v0.8.3
Updating mio v1.0.2 -> v1.0.3
Updating object v0.36.5 -> v0.36.7
Updating pathdiff v0.2.2 -> v0.2.3
Updating pest v2.7.14 -> v2.7.15
Updating pest_derive v2.7.14 -> v2.7.15
Updating pest_generator v2.7.14 -> v2.7.15
Updating pest_meta v2.7.14 -> v2.7.15
Updating phf v0.11.2 -> v0.11.3
Updating phf_codegen v0.11.2 -> v0.11.3
Updating phf_generator v0.11.2 -> v0.11.3
Updating phf_shared v0.11.2 -> v0.11.3
Updating pin-project v1.1.7 -> v1.1.8
Updating pin-project-internal v1.1.7 -> v1.1.8
Updating pin-project-lite v0.2.15 -> v0.2.16
Updating predicates v3.1.2 -> v3.1.3
Updating predicates-core v1.0.8 -> v1.0.9
Updating predicates-tree v1.0.11 -> v1.0.12
Updating proc-macro2 v1.0.89 -> v1.0.93
Updating quote v1.0.37 -> v1.0.38
Updating redox_syscall v0.5.7 -> v0.5.8
Updating regex-automata v0.4.8 -> v0.4.9
Updating rustix v0.38.39 -> v0.38.44
  Adding rustversion v1.0.19
Updating ryu v1.0.18 -> v1.0.19
Updating semver v1.0.23 -> v1.0.25
Updating serde v1.0.214 -> v1.0.217
Updating serde_derive v1.0.214 -> v1.0.217
Updating serde_json v1.0.132 -> v1.0.137
  Adding siphasher v1.0.1
Updating socket2 v0.5.7 -> v0.5.8
Updating syn v2.0.87 -> v2.0.96
Updating tempfile v3.13.0 -> v3.15.0
Updating terminal_size v0.4.0 -> v0.4.1
Updating termtree v0.4.1 -> v0.5.1
Removing thiserror v1.0.68
  Adding thiserror v1.0.69
  Adding thiserror v2.0.11
Removing thiserror-impl v1.0.68
  Adding thiserror-impl v1.0.69
  Adding thiserror-impl v2.0.11
Updating tokio v1.41.0 -> v1.43.0
Updating tokio-macros v2.4.0 -> v2.5.0
Updating tokio-util v0.7.12 -> v0.7.13
Updating tracing v0.1.40 -> v0.1.41
Updating tracing-core v0.1.32 -> v0.1.33
Updating unicase v2.8.0 -> v2.8.1
Updating unicode-ident v1.0.13 -> v1.0.16
Updating url v2.5.3 -> v2.5.4
Updating wasm-bindgen v0.2.95 -> v0.2.100
Updating wasm-bindgen-backend v0.2.95 -> v0.2.100
Updating wasm-bindgen-macro v0.2.95 -> v0.2.100
Updating wasm-bindgen-macro-support v0.2.95 -> v0.2.100
Updating wasm-bindgen-shared v0.2.95 -> v0.2.100
Updating yoke v0.7.4 -> v0.7.5
Updating yoke-derive v0.7.4 -> v0.7.5
Updating zerofrom v0.1.4 -> v0.1.5
Updating zerofrom-derive v0.1.4 -> v0.1.5
2025-01-28 09:11:17 -08:00
Eric Huss
18f57f5bd9 Merge pull request #2533 from ehuss/search-chapter-settings
Add output.html.search.chapter
2025-01-28 14:43:02 +00:00
Eric Huss
09a37284b0 Add output.html.search.chapter
This config setting provides the ability to disable search indexing on a
per-chapter (or sub-path) basis.

This is structured to possibly add additional settings, such as perhaps
a score multiplier or other settings.
2025-01-27 19:45:50 -08:00
Eric Huss
dff5ac64e5 Merge pull request #2458 from dcampbell24/display-for-clean
Display what is removed from mdbook clean.
2025-01-25 21:54:30 +00:00
Eric Huss
0ee565a5ff Merge pull request #2530 from max-heller/rust-hidelines
fix: make line hiding in Rust code blocks consistent with `rustdoc`
2025-01-25 21:50:47 +00:00
Eric Huss
9e4854f349 Merge pull request #2532 from notriddle/sync-toggle
Prevent the real sidebar position from becoming unsynced from the JS
2025-01-25 21:17:31 +00:00
Michael Howell
74d48f5ad2 Prevent the real sidebar position from becoming unsynced from the JS
This way, whatever behavior the browser might use for checkboxes
will apply to the CSS class, localStorage, and the visible state.
2025-01-23 10:18:21 -07:00
Eric Huss
0b51a74c16 Merge pull request #2531 from GuillaumeGomez/regression-test-2529
Add GUI regression test for #2529
2025-01-23 14:33:22 +00:00
Guillaume Gomez
ce63cc31f4 Add GUI regression test for #2529 2025-01-23 14:01:38 +01:00
Guillaume Gomez
d6720fc671 Update browser-ui-test version to 0.19.0 2025-01-23 13:58:35 +01:00
Waffle Lapkin
64cca1399b nicer style rules for margin around footnote defs
previous implementation used `:not(.fd) + .fd` and `.fd + :not(.fd)`.
the latter selector caused many problems:
- it doesn't select footnote defs which are last children
  (this can be easily triggered in a blockquote)
- it changes the margin of the next sibling, rather than the footnote def
  itself, which can also *shrink* margin for elements with big margins
  (this happens to headings)
- because it applies to the next sibling it is also quite hard to
  override in user styles, since it may apply to any element
  
this commit replaces the latter selector with `:not(:has(+ .fd))`,
which fixes all of the mentioned problems.
2025-01-21 01:21:53 +01:00
Eric Huss
629c2ad2fd Merge pull request #2529 from GuillaumeGomez/fix-sidebar-display
Fix display of sidebar when JS is disabled
2025-01-20 17:42:49 +00:00
Max Heller
d325e821cd fix: make line hiding in Rust code blocks consistent with rustdoc
Requires a space following a `#` for a line to be hidden.
2025-01-20 11:43:39 -05:00
Guillaume Gomez
ac3a7faa54 Fix display of sidebar when JS is disabled 2025-01-20 17:29:07 +01:00
Eric Huss
35ed24cd18 Merge pull request #2523 from marcoieni/ubuntu-22
ci: move ubuntu-20 jobs to ubuntu-22
2025-01-15 14:37:44 +00:00
MarcoIeni
81d42f1c6e ci: move ubuntu-20 jobs to ubuntu-22 2025-01-15 10:21:10 +01:00
Eric Huss
618a2fa78b Merge pull request #2476 from GuillaumeGomez/gui-tests
Add base for GUI tests
2025-01-06 22:46:26 +00:00
Eric Huss
0bf6751eed Merge pull request #2517 from notriddle/master
Ignore fragment when figuring out sidebar items
2025-01-02 20:25:15 +00:00
Michael Howell
f92eac4acd Ignore fragment when figuring out sidebar items 2025-01-02 10:34:03 -07:00
Guillaume Gomez
69ef52fd13 Disable sandbox when running GUI tests 2024-12-19 20:01:25 +01:00
Guillaume Gomez
cc8ce35b4d Run GUI tests as a separate testsuite 2024-12-18 11:25:11 +01:00
Guillaume Gomez
2a13ca2fbf Add base for GUI tests 2024-12-16 17:45:36 +01:00
Eric Huss
59e6afcaad Merge pull request #2500 from rukai/release_for_aarch64_macos
Add aarch64-apple-darwin release target
2024-12-02 14:51:39 +00:00
Lucas Kent
4d9a455a27 Add aarch64-apple-darwin release target 2024-12-02 11:43:57 +11:00
Eric Huss
74b2c79d46 Merge pull request #2497 from ehuss/bump-version
Update to 0.4.43
2024-11-25 17:21:18 +00:00
Eric Huss
ed407b091c Update to 0.4.43 2024-11-25 09:14:43 -08:00
Eric Huss
6c8020a3b9 Merge pull request #2495 from ehuss/stabilize-2024
Stabilize 2024 flag
2024-11-25 16:05:27 +00:00
Eric Huss
42f18d1e51 Stabilize 2024 flag
The 2024 edition is now stable on nightly, so the `-Z` flag is no longer necessary.
2024-11-23 15:25:29 -08:00
David Campbell
abf3e4ab50 Display what is removed from mdbook clean.
This is based off of [cargo's][1] clean command. cargo is licensed
under MIT or Apache-2.0.

[1]: https://github.com/rust-lang/cargo
2024-11-22 15:10:51 -05:00
Eric Huss
d1078434af Merge pull request #2486 from eureka-cpu/eureka-cpu/2485
fix `init --title` option failure when git user is not configured
2024-11-18 19:25:00 +00:00
eureka-cpu
8f024dabc3 fix init --title option failure when git user is not configured 2024-11-18 11:10:11 -08:00
Eric Huss
0c580c32c4 Add regression test for mdbook init title with no git config
Regression test for https://github.com/rust-lang/mdBook/issues/2485
2024-11-18 11:08:26 -08:00
Eric Huss
90960126e8 Merge pull request #2478 from rust-lang/ehuss-patch-1
Add note about updating `index.hbs`
2024-11-09 13:47:04 +00:00
Eric Huss
aa37f24fc1 Add note about updating index.hbs 2024-11-09 05:40:12 -08:00
Eric Huss
3f4f287e6e Merge pull request #2474 from ehuss/bump-version
Update to 0.4.42
2024-11-07 14:49:21 +00:00
Eric Huss
55fe75c716 Update to 0.4.42 2024-11-07 06:42:05 -08:00
Eric Huss
c6236ead67 Merge pull request #2473 from notriddle/notriddle/folding
Fix inadvertently broken folding behavior
2024-11-07 14:39:31 +00:00
Michael Howell
68e3572278 Fix inadvertently broken folding behavior 2024-11-06 15:47:12 -07:00
Eric Huss
27ab7eb2f0 Merge pull request #2470 from ehuss/update-deps
Update dependencies
2024-11-06 17:42:16 +00:00
Eric Huss
6d183be0ec Merge pull request #2471 from ehuss/bump-version
Update to 0.4.41
2024-11-06 17:41:56 +00:00
Eric Huss
c83a34b473 Update to 0.4.41 2024-11-06 09:36:21 -08:00
Eric Huss
d3e0e597d2 Update dependencies 2024-11-06 09:34:07 -08:00
Eric Huss
271bbba7dd Merge pull request #2414 from notriddle/on2
Load the sidebar toc from a shared JS file or iframe
2024-11-02 23:56:19 +00:00
Eric Huss
86ff2e1e6b Merge pull request #2465 from ehuss/footnote-line-height
Set line-height of superscripts to 0
2024-11-02 23:19:27 +00:00
Eric Huss
6ef7cc0ccb Set line-height of superscripts to 0
This changes it so that superscript (and in particular footnote tags)
do not bump the line spacing of previous lines.
2024-11-02 16:12:07 -07:00
Eric Huss
f4cf32e768 Merge pull request #2464 from ehuss/remove-emphasis
Add a real example of remove-emphasis
2024-11-02 22:49:49 +00:00
Eric Huss
47384c1f18 Merge pull request #2463 from Pistonight/bug/theme_popup
fix: themes broken when localStorage has invalid theme id stored
2024-11-02 22:48:17 +00:00
Eric Huss
9e3d533acc Add a real example of remove-emphasis 2024-11-02 15:41:55 -07:00
Eric Huss
5ec4f65ac3 Merge pull request #2454 from GuillaumeGomez/theme-noscript
Improve theme support when JS is disabled
2024-11-02 21:33:57 +00:00
Pistonight
4a330ae36f fix: themes broken when localStorage has invalid theme id stored 2024-10-31 19:02:35 -07:00
Guillaume Gomez
d93fbc0f6b Improve theme support when JS is disabled 2024-10-29 16:20:41 +01:00
Eric Huss
684bb78897 Merge pull request #2448 from jackieh/enhance-syntax-highlighting
Enhance syntax highlighting
2024-10-22 15:49:01 +00:00
Jackie Harris
d0dd16c527 Enhance syntax highlighting
Add syntax highlighting for `hljs-attr` and `hljs-section` CSS classes,
consistent with the Ayu theme.
2024-10-17 12:25:15 -05:00
Eric Huss
f4805343f8 Merge pull request #2442 from hamirmahal/style/simplify-string-formatting-for-readability
style: simplify string formatting for readability
2024-09-25 20:49:56 +00:00
Hamir Mahal
f9add3e936 fix: formatting in src/ and tests/ directories 2024-09-21 15:56:13 -07:00
Hamir Mahal
1fd9656291 style: simplify string formatting for readability 2024-09-21 15:53:59 -07:00
Eric Huss
6f281a6401 Merge pull request #2416 from campeis/update_handlebars_to_v6
chore: update handlebars to v6
2024-09-07 16:11:07 +00:00
Eric Huss
5194d2b3cd Merge pull request #2421 from GuillaumeGomez/copy-code
Unify copy to clipboard icon with docs.rs, rustdoc and crates.io
2024-08-14 15:10:39 +00:00
Guillaume Gomez
b3c23c5f88 Add credits for clipboard image 2024-08-11 16:18:19 +02:00
Eric Huss
a15134cc2f Merge pull request #2423 from radeksvarz/patch-1
added update how to
2024-08-11 12:51:44 +00:00
Eric Huss
b51bb101f2 Tweak heading wording 2024-08-11 05:46:48 -07:00
Eric Huss
59d26dbbe7 Move "upgrade mdbook" description to the build-from-source section 2024-08-11 05:45:26 -07:00
Radek
94baf19e6a added update how to
Updating workflow is not clear for non rust users.
2024-08-07 12:01:19 +02:00
Guillaume Gomez
f1a446fb02 Unify copy to clipboard icon with docs.rs, rustdoc and crates.io 2024-08-02 11:55:17 +02:00
Alessandro Campeis
01d1242753 chore: update handlebars to v6 2024-07-23 10:32:47 +02:00
Michael Howell
203685e91c Make the sidebar work without JS
Uses an iframe instead. The downside of iframes comes from them
not necessarily being same-origin as the main page (particularly
with `file:///` URLs), which can cause themes to fall out of sync,
but that's not a problem here since themes don't work without JS
anyway.
2024-07-16 12:38:00 -07:00
Michael Howell
2cb5b85ab2 Load the sidebar toc from a shared JS file
Before this change, the Rust `unstable-book` is 88MiB.
With this change, it becomes 15MiB. Other pages might not be
as extreme, but it's expected to help any book like this.

This change is so drastic because, if every chapter has a link to
every other chapter, the result is *O*(n<sup>2</sup>) text output.
2024-07-15 18:51:32 -07:00
Eric Huss
ec996d3509 Merge pull request #2406 from ehuss/fix-smart-link
Fix broken link to "Smart Punctuation"
2024-06-24 21:40:46 +00:00
Eric Huss
5ed3223185 Fix broken link to "Smart Punctuation" 2024-06-24 14:32:55 -07:00
Eric Huss
3bdcc0a5a6 Merge pull request #2398 from ehuss/edition2024
Add support for Rust Edition 2024
2024-06-12 22:59:25 +00:00
Eric Huss
1e4d4887e1 Add support for Rust Edition 2024 2024-06-12 15:53:56 -07:00
Eric Huss
94b922d27a Merge pull request #2389 from ehuss/bump-version
Update to 0.4.40
2024-05-17 01:35:51 +00:00
Eric Huss
0a8f9a3f6b Update to 0.4.40 2024-05-16 18:29:30 -07:00
Eric Huss
a5d4d1e0ad Merge pull request #2386 from ehuss/revert-pulldown-cmark
Revert update to pulldown-cmark 0.11
2024-05-17 01:19:17 +00:00
Eric Huss
2bdb5866a7 Add comment not to update pulldown-cmark. 2024-05-16 18:10:56 -07:00
Eric Huss
65932289f7 Revert "Merge pull request #2381 from ehuss/update-pulldown-cmark"
This reverts commit 8884008b4d,
(https://github.com/rust-lang/mdBook/pull/2381) reversing
changes made to 3d6caa504f.

The `pulldown_cmark` types are a public API, which I did not realize.
2024-05-16 18:08:09 -07:00
Eric Huss
e34f9c6408 Merge pull request #2385 from ehuss/bump-version
Update to 0.4.39
2024-05-17 00:50:51 +00:00
Eric Huss
e0e13e375e Merge pull request #2384 from ehuss/arg_watcher-dead-code
Fix dead_code warning for arg_watcher
2024-05-17 00:44:51 +00:00
Eric Huss
4c333bee95 Update to 0.4.39 2024-05-16 17:44:03 -07:00
Eric Huss
965f7bde0d Fix dead_code warning for arg_watcher 2024-05-16 17:38:52 -07:00
Eric Huss
cfcba01d03 Merge pull request #2383 from ehuss/musl-fix
CI: Test more targets.
2024-05-17 00:36:14 +00:00
Eric Huss
3dc40f1742 Update actions/checkout to v4 2024-05-16 17:29:23 -07:00
Eric Huss
5a366f5707 Test more targets. 2024-05-16 17:27:28 -07:00
Eric Huss
b960c697dc Merge pull request #2382 from ehuss/bump-version
Update to 0.4.38
2024-05-16 22:09:16 +00:00
Eric Huss
0383b26f46 Update to 0.4.38 2024-05-16 15:02:56 -07:00
Eric Huss
8884008b4d Merge pull request #2381 from ehuss/update-pulldown-cmark
Update pulldown-cmark to 0.11
2024-05-16 21:40:10 +00:00
Eric Huss
af3012b0f2 Update pulldown-cmark to 0.11 2024-05-16 14:17:19 -07:00
Eric Huss
3d6caa504f Merge pull request #2378 from ehuss/update-deps
Update all dependencies
2024-05-14 18:42:30 +00:00
Eric Huss
87213edf39 Raise msrv to 1.74 2024-05-14 11:38:13 -07:00
Eric Huss
4f081bb5ce Update all semver-compatible dependencies
Updating aho-corasick v1.1.2 -> v1.1.3
Updating anstream v0.6.11 -> v0.6.14
Updating anstyle v1.0.6 -> v1.0.7
Updating anstyle-parse v0.2.3 -> v0.2.4
Updating anstyle-query v1.0.2 -> v1.0.3
Updating anstyle-wincon v3.0.2 -> v3.0.3
Updating anyhow v1.0.79 -> v1.0.83
Updating assert_cmd v2.0.13 -> v2.0.14
Updating autocfg v1.1.0 -> v1.3.0
Updating backtrace v0.3.69 -> v0.3.71
Updating bitflags v2.4.2 -> v2.5.0
Updating bstr v1.9.0 -> v1.9.1
Updating bumpalo v3.14.0 -> v3.16.0
Updating bytes v1.5.0 -> v1.6.0
Updating cc v1.0.83 -> v1.0.97
Updating chrono v0.4.33 -> v0.4.38
Updating clap v4.4.18 -> v4.5.4
Updating clap_builder v4.4.18 -> v4.5.2
Updating clap_complete v4.4.10 -> v4.5.2
Updating clap_lex v0.6.0 -> v0.7.0
Updating colorchoice v1.0.0 -> v1.0.1
Updating crossbeam-channel v0.5.11 -> v0.5.12
Updating data-encoding v2.5.0 -> v2.6.0
Updating env_logger v0.11.1 -> v0.11.3
Updating errno v0.3.8 -> v0.3.9
Updating fastrand v2.0.1 -> v2.1.0
Updating getrandom v0.2.12 -> v0.2.15
Updating handlebars v5.1.0 -> v5.1.2
Updating hashbrown v0.14.3 -> v0.14.5
Updating hermit-abi v0.3.5 -> v0.3.9
Removing http v0.2.11
  Adding http v0.2.12 (latest: v1.1.0)
  Adding http v1.1.0
Updating indexmap v2.2.2 -> v2.2.6
  Adding is_terminal_polyfill v1.70.0
Updating itoa v1.0.10 -> v1.0.11
Updating js-sys v0.3.67 -> v0.3.69
Updating libc v0.2.153 -> v0.2.154
Updating lock_api v0.4.11 -> v0.4.12
Updating log v0.4.20 -> v0.4.21
Updating memchr v2.7.1 -> v2.7.2
Updating new_debug_unreachable v1.0.4 -> v1.0.6
Updating normpath v1.1.1 -> v1.2.0
Updating num-traits v0.2.17 -> v0.2.19
Updating parking_lot v0.12.1 -> v0.12.2
Updating parking_lot_core v0.9.9 -> v0.9.10
Updating pest v2.7.7 -> v2.7.10
Updating pest_derive v2.7.7 -> v2.7.10
Updating pest_generator v2.7.7 -> v2.7.10
Updating pest_meta v2.7.7 -> v2.7.10
Updating pin-project v1.1.4 -> v1.1.5
Updating pin-project-internal v1.1.4 -> v1.1.5
Updating pin-project-lite v0.2.13 -> v0.2.14
Updating proc-macro2 v1.0.78 -> v1.0.82
Updating pulldown-cmark v0.10.0 -> v0.10.3
Updating pulldown-cmark-escape v0.10.0 -> v0.10.1
Updating quote v1.0.35 -> v1.0.36
  Adding redox_syscall v0.5.1
Updating regex v1.10.3 -> v1.10.4
Updating regex-automata v0.4.5 -> v0.4.6
Updating regex-syntax v0.8.2 -> v0.8.3
Updating rustc-demangle v0.1.23 -> v0.1.24
Updating rustix v0.38.31 -> v0.38.34
Removing rustls-pemfile v1.0.4
Updating ryu v1.0.16 -> v1.0.18
Updating semver v1.0.21 -> v1.0.23
Updating serde v1.0.196 -> v1.0.201
Updating serde_derive v1.0.196 -> v1.0.201
Updating serde_json v1.0.113 -> v1.0.117
Updating smallvec v1.13.1 -> v1.13.2
Updating socket2 v0.5.5 -> v0.5.7
Updating strsim v0.10.0 -> v0.11.1
Updating syn v2.0.48 -> v2.0.63
Updating tempfile v3.10.0 -> v3.10.1
Updating thiserror v1.0.56 -> v1.0.60
Updating thiserror-impl v1.0.56 -> v1.0.60
Updating tokio v1.36.0 -> v1.37.0
Removing tokio-stream v0.1.14
Updating tokio-tungstenite v0.20.1 -> v0.21.0
Updating tokio-util v0.7.10 -> v0.7.11
Updating tungstenite v0.20.1 -> v0.21.0
Updating unicode-normalization v0.1.22 -> v0.1.23
Updating walkdir v2.4.0 -> v2.5.0
Updating warp v0.3.6 -> v0.3.7
Updating wasm-bindgen v0.2.90 -> v0.2.92
Updating wasm-bindgen-backend v0.2.90 -> v0.2.92
Updating wasm-bindgen-macro v0.2.90 -> v0.2.92
Updating wasm-bindgen-macro-support v0.2.90 -> v0.2.92
Updating wasm-bindgen-shared v0.2.90 -> v0.2.92
Updating winapi-util v0.1.6 -> v0.1.8
Updating windows-targets v0.52.0 -> v0.52.5
Updating windows_aarch64_gnullvm v0.52.0 -> v0.52.5
Updating windows_aarch64_msvc v0.52.0 -> v0.52.5
Updating windows_i686_gnu v0.52.0 -> v0.52.5
  Adding windows_i686_gnullvm v0.52.5
Updating windows_i686_msvc v0.52.0 -> v0.52.5
Updating windows_x86_64_gnu v0.52.0 -> v0.52.5
Updating windows_x86_64_gnullvm v0.52.0 -> v0.52.5
Updating windows_x86_64_msvc v0.52.0 -> v0.52.5
2024-05-14 11:24:04 -07:00
Eric Huss
78d356d2ed Update opener from 0.6.1 to 0.7.0 2024-05-14 11:22:41 -07:00
Eric Huss
64dc7f41d8 Update ammonia from 3.3.0 to 4.0.0 2024-05-14 11:21:52 -07:00
Eric Huss
cb0a992d8d Merge pull request #2364 from expikr/patch-3
Fix dividers being folded by sections
2024-05-14 16:04:42 +00:00
expikr
c2eb375f69 Fix spacers in summary with folding.
The spacer was incorrectly being included in the previous fold.
2024-05-13 14:10:42 -07:00
Eric Huss
a555c6b6b2 Merge pull request #2325 from ehuss/poll-watcher
Add a poll-based file watcher.
2024-05-13 20:54:36 +00:00
Eric Huss
0752fa4e43 Fix --watcher markdown header. 2024-05-13 13:51:04 -07:00
Eric Huss
f14fc61b4b Merge pull request #2377 from ehuss/guide-smart-punctuation
docs: Switch the guide to use smart punctuation.
2024-05-13 20:49:33 +00:00
Eric Huss
89878519b4 docs: Switch the guide to use smart punctuation. 2024-05-13 13:43:04 -07:00
Eric Huss
46d57bcf3c Add some more context on why --watcher=native may not be desirable. 2024-05-13 13:18:50 -07:00
Eric Huss
f3e85da9a7 Add a poll-based file watcher. 2024-05-13 13:14:22 -07:00
Eric Huss
83444650a3 Merge pull request #2376 from ehuss/clippy-fixes
Apply a few minor clippy fixes
2024-05-13 19:18:52 +00:00
Eric Huss
dae7490739 Merge pull request #2375 from ehuss/clippy-well-known
Remove cargo-clippy unknown feature
2024-05-13 19:14:01 +00:00
Eric Huss
5bc87d5c17 Apply a few minor clippy fixes 2024-05-13 12:13:50 -07:00
Eric Huss
7a58c415de Remove cargo-clippy unknown feature 2024-05-13 12:07:49 -07:00
Eric Huss
09576d7d57 Merge pull request #2260 from KFearsoff/remove-css-double-import
fix: remove double imports of css
2024-05-13 18:41:09 +00:00
Eric Huss
0bfcd3c9ce Merge pull request #2374 from rust-lang/revert-2373-patch-1
Revert "Maybe a typo in preprocessors.md"
2024-05-13 15:42:33 +00:00
Dylan DPC
bf3de7a80d Revert "Maybe a typo in preprocessors.md" 2024-05-13 20:31:18 +05:30
Dylan DPC
7269c372d6 Merge pull request #2373 from TGITS/patch-1
Maybe a typo in preprocessors.md
2024-05-13 13:07:00 +00:00
TheGeekInTheShell
8308f15e93 Maybe a typo in preprocessors.md
It seems to me there is a small typo in the file preprocessors.md
2024-05-12 14:35:21 +02:00
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
KFears
4b6813ecee fix: remove double imports of css 2023-12-06 22:15:24 +04: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
6be81214b1 Stabilize 2021 flag. 2021-09-03 11:00:27 -07:00
148 changed files with 10157 additions and 3246 deletions

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

View File

@@ -3,31 +3,47 @@ on:
release:
types: [created]
defaults:
run:
shell: bash
permissions:
contents: write
jobs:
release:
name: Deploy Release
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
include:
- target: aarch64-unknown-linux-musl
os: ubuntu-22.04
- target: x86_64-unknown-linux-gnu
os: ubuntu-22.04
- target: x86_64-unknown-linux-musl
os: ubuntu-22.04
- target: x86_64-apple-darwin
os: macos-latest
- target: aarch64-apple-darwin
os: macos-latest
- target: x86_64-pc-windows-msvc
os: windows-latest
name: Deploy ${{ matrix.target }}
steps:
- uses: actions/checkout@master
- name: Install hub
run: ci/install-hub.sh ${{ matrix.os }}
shell: bash
- uses: actions/checkout@v4
- name: Install Rust
run: ci/install-rust.sh stable
shell: bash
- name: Build and deploy artifacts
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: ci/make-release.sh ${{ matrix.os }}
shell: bash
run: gh release upload $MDBOOK_TAG $MDBOOK_ASSET
pages:
name: GitHub Pages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v4
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable
- name: Build book
@@ -40,3 +56,14 @@ jobs:
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@v4
- 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

View File

@@ -1,51 +1,107 @@
name: CI
on:
# Only run when merging to master, or open/synchronize/reopen a PR.
push:
branches:
- master
pull_request:
merge_group:
env:
BROWSER_UI_TEST_VERSION: '0.19.0'
jobs:
test:
name: Test
runs-on: ${{ matrix.os }}
strategy:
matrix:
build: [stable, beta, nightly, macos, windows, msrv]
include:
- build: stable
- name: stable linux
os: ubuntu-latest
rust: stable
- build: beta
target: x86_64-unknown-linux-gnu
- name: beta linux
os: ubuntu-latest
rust: beta
- build: nightly
target: x86_64-unknown-linux-gnu
- name: nightly linux
os: ubuntu-latest
rust: nightly
- build: macos
target: x86_64-unknown-linux-gnu
- name: stable x86_64-unknown-linux-musl
os: ubuntu-22.04
rust: stable
target: x86_64-unknown-linux-musl
- name: stable x86_64 macos
os: macos-latest
rust: stable
- build: windows
target: x86_64-apple-darwin
- name: stable aarch64 macos
os: macos-latest
rust: stable
target: aarch64-apple-darwin
- name: stable windows-msvc
os: windows-latest
rust: stable
- build: msrv
os: ubuntu-latest
rust: 1.46.0
target: x86_64-pc-windows-msvc
- name: msrv
os: ubuntu-22.04
# sync MSRV with docs: guide/src/guide/installation.md and Cargo.toml
rust: 1.77.0
target: x86_64-unknown-linux-gnu
name: ${{ matrix.name }}
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v4
- name: Install Rust
run: bash ci/install-rust.sh ${{ matrix.rust }}
run: bash ci/install-rust.sh ${{ matrix.rust }} ${{ matrix.target }}
- name: Build and run tests
run: cargo test
run: cargo test --locked --target ${{ matrix.target }}
- name: Test no default
run: cargo test --no-default-features
run: cargo test --no-default-features --target ${{ matrix.target }}
aarch64-cross-builds:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: bash ci/install-rust.sh stable aarch64-unknown-linux-musl
- name: Build
run: cargo build --locked --target aarch64-unknown-linux-musl
rustfmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v4
- name: Install Rust
run: rustup update stable && rustup default stable && rustup component add rustfmt
- run: cargo fmt -- --check
- run: cargo fmt --check
gui:
name: GUI tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: bash ci/install-rust.sh stable x86_64-unknown-linux-gnu
- name: Install npm
uses: actions/setup-node@v3
with:
node-version: 20
- name: Install browser-ui-test
run: npm install browser-ui-test@"${BROWSER_UI_TEST_VERSION}"
- name: Build and run tests (+ GUI)
run: cargo test --locked --target x86_64-unknown-linux-gnu --test gui
# 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

6
.gitignore vendored
View File

@@ -8,6 +8,7 @@ guide/book
.vscode
tests/dummy_book/book/
test_book/book/
# Ignore Jetbrains specific files.
.idea/
@@ -15,3 +16,8 @@ tests/dummy_book/book/
# Ignore Vim temporary and swap files.
*.sw?
*~
# GUI tests
node_modules
package-lock.json
package.json

View File

@@ -1,5 +1,522 @@
# Changelog
## mdBook 0.4.45
[v0.4.44...v0.4.45](https://github.com/rust-lang/mdBook/compare/v0.4.44...v0.4.45)
### Changed
- Added context to error message when rustdoc is not found.
[#2545](https://github.com/rust-lang/mdBook/pull/2545)
- Slightly changed the styling rules around margins of footnotes.
[#2524](https://github.com/rust-lang/mdBook/pull/2524)
### Fixed
- Fixed an issue where it would panic if a source_path is not set.
[#2550](https://github.com/rust-lang/mdBook/pull/2550)
## mdBook 0.4.44
[v0.4.43...v0.4.44](https://github.com/rust-lang/mdBook/compare/v0.4.43...v0.4.44)
### Added
- Added pre-built aarch64-apple-darwin binaries to the releases.
[#2500](https://github.com/rust-lang/mdBook/pull/2500)
- `mdbook clean` now shows a summary of what it did.
[#2458](https://github.com/rust-lang/mdBook/pull/2458)
- Added the `output.html.search.chapter` config setting to disable search indexing of individual chapters.
[#2533](https://github.com/rust-lang/mdBook/pull/2533)
### Fixed
- Fixed auto-scrolling the side-bar when loading a page with a `#` fragment URL.
[#2517](https://github.com/rust-lang/mdBook/pull/2517)
- Fixed display of sidebar when javascript is disabled.
[#2529](https://github.com/rust-lang/mdBook/pull/2529)
- Fixed the sidebar visibility getting out of sync with the button.
[#2532](https://github.com/rust-lang/mdBook/pull/2532)
### Changed
- ❗ Rust code block hidden lines now follow the same logic as rustdoc. This requires a space after the `#` symbol.
[#2530](https://github.com/rust-lang/mdBook/pull/2530)
- ❗ Updated the Linux pre-built binaries which requires a newer version of glibc (2.34).
[#2523](https://github.com/rust-lang/mdBook/pull/2523)
- Updated dependencies
[#2538](https://github.com/rust-lang/mdBook/pull/2538)
[#2539](https://github.com/rust-lang/mdBook/pull/2539)
## mdBook 0.4.43
[v0.4.42...v0.4.43](https://github.com/rust-lang/mdBook/compare/v0.4.42...v0.4.43)
### Fixed
- Fixed setting the title in `mdbook init` when no git user is configured.
[#2486](https://github.com/rust-lang/mdBook/pull/2486)
### Changed
- The Rust 2024 edition no longer needs `-Zunstable-options`.
[#2495](https://github.com/rust-lang/mdBook/pull/2495)
## mdBook 0.4.42
[v0.4.41...v0.4.42](https://github.com/rust-lang/mdBook/compare/v0.4.41...v0.4.42)
### Fixed
- Fixed chapter list folding.
[#2473](https://github.com/rust-lang/mdBook/pull/2473)
## mdBook 0.4.41
[v0.4.40...v0.4.41](https://github.com/rust-lang/mdBook/compare/v0.4.40...v0.4.41)
**Note:** If you have a custom `index.hbs` theme file, you will need to update it to the latest version.
### Added
- Added preliminary support for Rust 2024 edition.
[#2398](https://github.com/rust-lang/mdBook/pull/2398)
- Added a full example of the remove-emphasis preprocessor.
[#2464](https://github.com/rust-lang/mdBook/pull/2464)
### Changed
- Adjusted styling of clipboard/play icons.
[#2421](https://github.com/rust-lang/mdBook/pull/2421)
- Updated to handlebars v6.
[#2416](https://github.com/rust-lang/mdBook/pull/2416)
- Attr and section rules now have specific code highlighting.
[#2448](https://github.com/rust-lang/mdBook/pull/2448)
- The sidebar is now loaded from a common file, significantly reducing the book size when there are many chapters.
[#2414](https://github.com/rust-lang/mdBook/pull/2414)
- Updated dependencies.
[#2470](https://github.com/rust-lang/mdBook/pull/2470)
### Fixed
- Improved theme support when JavaScript is disabled.
[#2454](https://github.com/rust-lang/mdBook/pull/2454)
- Fixed broken themes when localStorage has an invalid theme id.
[#2463](https://github.com/rust-lang/mdBook/pull/2463)
- Adjusted the line-height of superscripts (and footnotes) to avoid adding extra space between lines.
[#2465](https://github.com/rust-lang/mdBook/pull/2465)
## mdBook 0.4.40
[v0.4.39...v0.4.40](https://github.com/rust-lang/mdBook/compare/v0.4.39...v0.4.40)
### Fixed
- Reverted the update to pulldown-cmark which broke the semver API.
[#2388](https://github.com/rust-lang/mdBook/pull/2388)
## mdBook 0.4.39
[v0.4.38...v0.4.39](https://github.com/rust-lang/mdBook/compare/v0.4.38...v0.4.39)
### Fixed
- Fixed the automatic deploy broken in the previous release.
[#2383](https://github.com/rust-lang/mdBook/pull/2383)
## mdBook 0.4.38
[v0.4.37...v0.4.38](https://github.com/rust-lang/mdBook/compare/v0.4.37...v0.4.38)
### Added
- Added `nix` to the default set of languages supported for syntax highlighting.
[#2262](https://github.com/rust-lang/mdBook/pull/2262)
### Changed
- The `output.html.curly-quotes` option has been renamed to `output.html.smart-punctuation` to better reflect what it does. The old option `curly-quotes` is kept for compatibility, but may be removed in the future.
[#2327](https://github.com/rust-lang/mdBook/pull/2327)
- The file-watcher used in `mdbook serve` and `mdbook watch` now uses a poll-based watcher instead of the native operating system notifications. This should fix issues on various systems and environments, and more accurately detect when files change. The native watcher can still be used with the `--watcher native` CLI option.
[#2325](https://github.com/rust-lang/mdBook/pull/2325)
- `mdbook test` output now includes color, and shows relative paths to the source.
[#2259](https://github.com/rust-lang/mdBook/pull/2259)
- Updated dependencies, MSRV raised to 1.74
[#2350](https://github.com/rust-lang/mdBook/pull/2350)
[#2351](https://github.com/rust-lang/mdBook/pull/2351)
[#2378](https://github.com/rust-lang/mdBook/pull/2378)
[#2381](https://github.com/rust-lang/mdBook/pull/2381)
### Fixed
- Reduced memory allocation when copying files.
[#2355](https://github.com/rust-lang/mdBook/pull/2355)
- Fixed the horizontal divider in `SUMMARY.md` from being indented into the previous nested section.
[#2364](https://github.com/rust-lang/mdBook/pull/2364)
- Removed unnecessary `@import` in the CSS.
[#2260](https://github.com/rust-lang/mdBook/pull/2260)
## mdBook 0.4.37
[v0.4.36...v0.4.37](https://github.com/rust-lang/mdBook/compare/v0.4.36...v0.4.37)
### Changed
- ❗️ Updated the markdown parser. This brings in many changes to more closely follow the CommonMark spec. This may cause some small rendering changes. It is recommended to compare the output of the old and new version to check for changes. See <https://github.com/raphlinus/pulldown-cmark/releases/tag/v0.10.0> for more information.
[#2308](https://github.com/rust-lang/mdBook/pull/2308)
- The warning about the legacy `src/theme` directory has been removed.
[#2263](https://github.com/rust-lang/mdBook/pull/2263)
- Updated dependencies. MSRV raised to 1.71.0.
[#2283](https://github.com/rust-lang/mdBook/pull/2283)
[#2293](https://github.com/rust-lang/mdBook/pull/2293)
[#2297](https://github.com/rust-lang/mdBook/pull/2297)
[#2310](https://github.com/rust-lang/mdBook/pull/2310)
[#2309](https://github.com/rust-lang/mdBook/pull/2309)
- Some internal performance/memory improvements.
[#2273](https://github.com/rust-lang/mdBook/pull/2273)
[#2290](https://github.com/rust-lang/mdBook/pull/2290)
- Made the `pathdiff` dependency optional based on the `watch` feature.
[#2291](https://github.com/rust-lang/mdBook/pull/2291)
### Fixed
- The `s` shortcut key handler should not trigger when focus is in an HTML form.
[#2311](https://github.com/rust-lang/mdBook/pull/2311)
## mdBook 0.4.36
[v0.4.35...v0.4.36](https://github.com/rust-lang/mdBook/compare/v0.4.35...v0.4.36)
### Added
- Added Nim to the default highlighted languages.
[#2232](https://github.com/rust-lang/mdBook/pull/2232)
- Added a small indicator for the sidebar resize handle.
[#2209](https://github.com/rust-lang/mdBook/pull/2209)
### Changed
- Updated dependencies. MSRV raised to 1.70.0.
[#2173](https://github.com/rust-lang/mdBook/pull/2173)
[#2250](https://github.com/rust-lang/mdBook/pull/2250)
[#2252](https://github.com/rust-lang/mdBook/pull/2252)
### Fixed
- Fixed blank column in print page when the sidebar was visible.
[#2235](https://github.com/rust-lang/mdBook/pull/2235)
- Fixed indentation of code blocks when Javascript is disabled.
[#2162](https://github.com/rust-lang/mdBook/pull/2162)
- Fixed a panic when `mdbook serve` or `mdbook watch` were given certain kinds of paths.
[#2229](https://github.com/rust-lang/mdBook/pull/2229)
## mdBook 0.4.35
[v0.4.34...v0.4.35](https://github.com/rust-lang/mdBook/compare/v0.4.34...v0.4.35)
### Added
- Added the `book.text-direction` setting for explicit support for right-to-left languages.
[#1641](https://github.com/rust-lang/mdBook/pull/1641)
- Added `rel=prefetch` to the "next" links to potentially improve browser performance.
[#2168](https://github.com/rust-lang/mdBook/pull/2168)
- Added a `.warning` CSS class which is styled for displaying warning blocks.
[#2187](https://github.com/rust-lang/mdBook/pull/2187)
### Changed
- Better support of the sidebar when JavaScript is disabled.
[#2175](https://github.com/rust-lang/mdBook/pull/2175)
## mdBook 0.4.34
[v0.4.33...v0.4.34](https://github.com/rust-lang/mdBook/compare/v0.4.33...v0.4.34)
### Fixed
- Fixed file change watcher failing on macOS with a large number of files.
[#2157](https://github.com/rust-lang/mdBook/pull/2157)
## mdBook 0.4.33
[v0.4.32...v0.4.33](https://github.com/rust-lang/mdBook/compare/v0.4.32...v0.4.33)
### Added
- The `color-scheme` CSS property is now set based on the light/dark theme, which applies some slight color differences in browser elements like scroll bars on some browsers.
[#2134](https://github.com/rust-lang/mdBook/pull/2134)
### Fixed
- Fixed watching of extra-watch-dirs when not running in the book root directory.
[#2146](https://github.com/rust-lang/mdBook/pull/2146)
- Reverted the dependency update to the `toml` crate (again!). This was an unintentional breaking change in 0.4.32.
[#2021](https://github.com/rust-lang/mdBook/pull/2021)
- Changed macOS change notifications to use the kqueue implementation which should fix some issues with repeated rebuilds when a file changed.
[#2152](https://github.com/rust-lang/mdBook/pull/2152)
- Don't set a background color in the print page for code blocks in a header.
[#2150](https://github.com/rust-lang/mdBook/pull/2150)
## mdBook 0.4.32
[v0.4.31...v0.4.32](https://github.com/rust-lang/mdBook/compare/v0.4.31...v0.4.32)
### Fixed
- Fixed theme-color meta tag not syncing with the theme.
[#2118](https://github.com/rust-lang/mdBook/pull/2118)
### Changed
- Updated all dependencies.
[#2121](https://github.com/rust-lang/mdBook/pull/2121)
[#2122](https://github.com/rust-lang/mdBook/pull/2122)
[#2123](https://github.com/rust-lang/mdBook/pull/2123)
[#2124](https://github.com/rust-lang/mdBook/pull/2124)
[#2125](https://github.com/rust-lang/mdBook/pull/2125)
[#2126](https://github.com/rust-lang/mdBook/pull/2126)
## mdBook 0.4.31
[v0.4.30...v0.4.31](https://github.com/rust-lang/mdBook/compare/v0.4.30...v0.4.31)
### Fixed
- Fixed menu border render flash during page navigation.
[#2101](https://github.com/rust-lang/mdBook/pull/2101)
- Fixed flicker setting sidebar scroll position.
[#2104](https://github.com/rust-lang/mdBook/pull/2104)
- Fixed compile error with proc-macro2 on latest Rust nightly.
[#2109](https://github.com/rust-lang/mdBook/pull/2109)
## mdBook 0.4.30
[v0.4.29...v0.4.30](https://github.com/rust-lang/mdBook/compare/v0.4.29...v0.4.30)
### Added
- Added support for heading attributes.
Attributes are specified in curly braces just after the heading text.
An HTML ID can be specified with `#` and classes with `.`.
For example: `## My heading {#custom-id .class1 .class2}`
[#2013](https://github.com/rust-lang/mdBook/pull/2013)
- Added support for hidden code lines for languages other than Rust.
The `output.html.code.hidelines` table allows you to define the prefix character that will be used to hide code lines based on the language.
[#2093](https://github.com/rust-lang/mdBook/pull/2093)
### Fixed
- Fixed a few minor markdown rendering issues.
[#2092](https://github.com/rust-lang/mdBook/pull/2092)
## mdBook 0.4.29
[v0.4.28...v0.4.29](https://github.com/rust-lang/mdBook/compare/v0.4.28...v0.4.29)
### Changed
- Built-in fonts are no longer copied when `fonts/fonts.css` is overridden in the theme directory.
Additionally, the warning about `copy-fonts` has been removed if `fonts/fonts.css` is specified.
[#2080](https://github.com/rust-lang/mdBook/pull/2080)
- `mdbook init --force` now skips all interactive prompts as intended.
[#2057](https://github.com/rust-lang/mdBook/pull/2057)
- Updated dependencies
[#2063](https://github.com/rust-lang/mdBook/pull/2063)
[#2086](https://github.com/rust-lang/mdBook/pull/2086)
[#2082](https://github.com/rust-lang/mdBook/pull/2082)
[#2084](https://github.com/rust-lang/mdBook/pull/2084)
[#2085](https://github.com/rust-lang/mdBook/pull/2085)
### Fixed
- Switched from the `gitignore` library to `ignore`. This should bring some improvements with gitignore handling.
[#2076](https://github.com/rust-lang/mdBook/pull/2076)
## mdBook 0.4.28
[v0.4.27...v0.4.28](https://github.com/rust-lang/mdBook/compare/v0.4.27...v0.4.28)
### Changed
- The sidebar is now shown on wide screens when localstorage is disabled.
[#2017](https://github.com/rust-lang/mdBook/pull/2017)
- Preprocessors are now run with `mdbook test`.
[#1986](https://github.com/rust-lang/mdBook/pull/1986)
### Fixed
- Fixed regression in 0.4.26 that prevented the title bar from scrolling properly on smaller screens.
[#2039](https://github.com/rust-lang/mdBook/pull/2039)
## mdBook 0.4.27
[v0.4.26...v0.4.27](https://github.com/rust-lang/mdBook/compare/v0.4.26...v0.4.27)
### Changed
- Reverted the dependency update to the `toml` crate. This was an unintentional breaking change in 0.4.26.
[#2021](https://github.com/rust-lang/mdBook/pull/2021)
## mdBook 0.4.26
[v0.4.25...v0.4.26](https://github.com/rust-lang/mdBook/compare/v0.4.25...v0.4.26)
**The 0.4.26 release has been yanked due to an unintentional breaking change.**
### Changed
- Removed custom scrollbars for webkit browsers
[#1961](https://github.com/rust-lang/mdBook/pull/1961)
- Updated some dependencies
[#1998](https://github.com/rust-lang/mdBook/pull/1998)
[#2009](https://github.com/rust-lang/mdBook/pull/2009)
[#2011](https://github.com/rust-lang/mdBook/pull/2011)
- Fonts are now part of the theme.
The `output.html.copy-fonts` option has been deprecated.
To define custom fonts, be sure to define `theme/fonts.css`.
[#1987](https://github.com/rust-lang/mdBook/pull/1987)
### Fixed
- Fixed overflow viewport issue with mobile Safari
[#1994](https://github.com/rust-lang/mdBook/pull/1994)
## mdBook 0.4.25
[e14d381...1ba74a3](https://github.com/rust-lang/mdBook/compare/e14d381...1ba74a3)
### Fixed
- Fixed a regression where `mdbook test -L deps path-to-book` would not work.
[#1959](https://github.com/rust-lang/mdBook/pull/1959)
## mdBook 0.4.24
[eb77083...8767ebf](https://github.com/rust-lang/mdBook/compare/eb77083...8767ebf)
### Fixed
- The precompiled linux-gnu mdbook binary available on [GitHub Releases](https://github.com/rust-lang/mdBook/releases) inadvertently switched to a newer version of glibc. This release goes back to an older version that should be more compatible on older versions of Linux.
[#1955](https://github.com/rust-lang/mdBook/pull/1955)
## mdBook 0.4.23
[678b469...68a75da](https://github.com/rust-lang/mdBook/compare/678b469...68a75da)
### Changed
- Updated all dependencies
[#1951](https://github.com/rust-lang/mdBook/pull/1951)
[#1952](https://github.com/rust-lang/mdBook/pull/1952)
[#1844](https://github.com/rust-lang/mdBook/pull/1844)
- Updated minimum Rust version to 1.60.
[#1951](https://github.com/rust-lang/mdBook/pull/1951)
### Fixed
- Fixed a regression where playground code was missing hidden lines, preventing it from compiling correctly.
[#1950](https://github.com/rust-lang/mdBook/pull/1950)
## mdBook 0.4.22
[40c06f5...4844f72](https://github.com/rust-lang/mdBook/compare/40c06f5...4844f72)
### Added
- Added a `--chapter` option to `mdbook test` to specify a specific chapter to test.
[#1741](https://github.com/rust-lang/mdBook/pull/1741)
- Added CSS styling for `<kbd>` tags.
[#1906](https://github.com/rust-lang/mdBook/pull/1906)
- Added pre-compiled binaries for `x86_64-unknown-linux-musl` and `aarch64-unknown-linux-musl` (see [Releases](https://github.com/rust-lang/mdBook/releases)).
[#1862](https://github.com/rust-lang/mdBook/pull/1862)
- Added `build.extra-watch-dirs` which is an array of additional directories to watch for changes when running `mdbook serve`.
[#1884](https://github.com/rust-lang/mdBook/pull/1884)
### Changed
- Removed the `type="text/javascript"` attribute from `<script>` tags.
[#1881](https://github.com/rust-lang/mdBook/pull/1881)
- Switched to building with Rust Edition 2021.
This raises the minimum supported Rust version to 1.56.
[#1887](https://github.com/rust-lang/mdBook/pull/1887)
- When hidden code is hidden, the hidden parts are no longer copied to the clipboard via the copy button.
[#1911](https://github.com/rust-lang/mdBook/pull/1911)
- Various HTML changes and fixes to be more compliant with HTML5.
[#1924](https://github.com/rust-lang/mdBook/pull/1924)
- The theme picker now shows which theme is currently selected.
[#1935](https://github.com/rust-lang/mdBook/pull/1935)
### Fixed
- Avoid blank line at the end of an ACE code block
[#1836](https://github.com/rust-lang/mdBook/pull/1836)
## mdBook 0.4.21
[92afe9b...8f01d02](https://github.com/rust-lang/mdBook/compare/92afe9b...8f01d02)
### Fixed
- Fixed an issue where mdBook would fail to compile with Rust nightly-2022-07-22.
[#1861](https://github.com/rust-lang/mdBook/pull/1861)
## mdBook 0.4.20
[53055e0...da166e0](https://github.com/rust-lang/mdBook/compare/53055e0...da166e0)
### Fixed
- Fixed a regression in 0.4.19 where inline code would have excessive padding
in some situations such as headings.
[#1855](https://github.com/rust-lang/mdBook/pull/1855)
## mdBook 0.4.19
[ae275ad...53055e0](https://github.com/rust-lang/mdBook/compare/ae275ad...53055e0)
### Added
- The `serve` command now supports HEAD requests.
[#1825](https://github.com/rust-lang/mdBook/pull/1825)
### Changed
- An error is now generated when a custom theme directory does not exist.
[#1791](https://github.com/rust-lang/mdBook/pull/1791)
- Very wide tables now have independent horizontal scrolling so that scrolling
to see the rest of the table will not scroll the entire page.
[#1617](https://github.com/rust-lang/mdBook/pull/1617)
- The buttons on code blocks are now only shown when the mouse cursor hovers
over them (or tapped on mobile). There is also some extra spacing to reduce
the overlap with the code.
[#1806](https://github.com/rust-lang/mdBook/pull/1806)
- The first chapter always generates an `index.html` file. Previously it would
only generate the index file for prefix chapters.
[#1829](https://github.com/rust-lang/mdBook/pull/1829)
### Fixed
- `mdbook serve --open` now properly handles the case if the first chapter is a draft.
[#1714](https://github.com/rust-lang/mdBook/pull/1714)
[#1830](https://github.com/rust-lang/mdBook/pull/1830)
- Very long words (over 80 characters) are no longer indexed to avoid a stack overflow.
[#1833](https://github.com/rust-lang/mdBook/pull/1833)
## mdBook 0.4.18
[981b79b...ae275ad](https://github.com/rust-lang/mdBook/compare/981b79b...ae275ad)
### Fixed
- Fixed rendering of SUMMARY links that contain markdown escapes or other
markdown elements.
[#1785](https://github.com/rust-lang/mdBook/pull/1785)
## mdBook 0.4.17
[a5fddfa...981b79b](https://github.com/rust-lang/mdBook/compare/a5fddfa...981b79b)
### Fixed
- Fixed parsing of `output.html.print` configuration table.
[#1775](https://github.com/rust-lang/mdBook/pull/1775)
## mdBook 0.4.16
[68a5c09...a5fddfa](https://github.com/rust-lang/mdBook/compare/68a5c09...a5fddfa)
### Added
- Added `output.html.print.page-break` config option to control whether or not
there is a page break between chapters in the print output.
[#1728](https://github.com/rust-lang/mdBook/pull/1728)
- Added `output.html.playground.runnable` config option to globally disable
the run button in code blocks.
[#1546](https://github.com/rust-lang/mdBook/pull/1546)
### Changed
- The `mdbook serve` live reload websocket now uses the protocol, host, and
port of the current page, allowing access through a proxy.
[#1771](https://github.com/rust-lang/mdBook/pull/1771)
- The 404 not-found page now includes the books title in the HTML title tag.
[#1693](https://github.com/rust-lang/mdBook/pull/1693)
- Migrated to clap 3.0 which handles CLI option parsing.
[#1731](https://github.com/rust-lang/mdBook/pull/1731)
### Fixed
- Minor fixes to the markdown parser.
[#1729](https://github.com/rust-lang/mdBook/pull/1729)
- Fixed incorrect parsing in `SUMMARY.md` when it didn't start with a title.
[#1744](https://github.com/rust-lang/mdBook/pull/1744)
- Fixed duplicate anchor IDs for links in search results.
[#1749](https://github.com/rust-lang/mdBook/pull/1749)
## mdBook 0.4.15
[5eb7d46...68a5c09](https://github.com/rust-lang/mdBook/compare/5eb7d46...68a5c09)
### Changed
- Major update to expand the documentation located at <https://rust-lang.github.io/mdBook/>.
[#1709](https://github.com/rust-lang/mdBook/pull/1709)
[#1710](https://github.com/rust-lang/mdBook/pull/1710)
- Updated the markdown parser with various fixes for common-mark compliance.
[#1712](https://github.com/rust-lang/mdBook/pull/1712)
## mdBook 0.4.14
[ffa8284...c9b6be8](https://github.com/rust-lang/mdBook/compare/ffa8284...c9b6be8)
### Added
- The 2021 Rust edition option has been stabilized.
[#1642](https://github.com/rust-lang/mdBook/pull/1642)
### Changed
- Header anchors no longer include any HTML tags. Previously only a small
subset were excluded.
[#1683](https://github.com/rust-lang/mdBook/pull/1683)
- Deprecated the google-analytics option. Books using this option should place
the appropriate code in the `theme/head.hbs` file instead.
[#1675](https://github.com/rust-lang/mdBook/pull/1675)
### Fixed
- Updated the markdown parser which brings in a few small fixes and removes
the custom smart quote handling.
[#1668](https://github.com/rust-lang/mdBook/pull/1668)
- Fixed iOS Safari enlarging text when going into landscape mode.
[#1685](https://github.com/rust-lang/mdBook/pull/1685)
## mdBook 0.4.13
[e6629cd...f55028b](https://github.com/rust-lang/mdBook/compare/e6629cd...f55028b)

View File

@@ -7,13 +7,22 @@ If you have come here to learn how to contribute to mdBook, we have some tips fo
First of all, don't hesitate to ask questions!
Use the [issue tracker](https://github.com/rust-lang/mdBook/issues), no question is too simple.
### 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
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/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AE-Easy).
Those are issues that are considered more straightforward for beginners to Rust or the codebase itself.
These issues can be a good launching pad for more involved issues. Easy tasks for a first time contribution
include documentation improvements, new tests, examples, updating dependencies, etc.
These issues can be a good launching pad for more involved issues.
Easy tasks for a first time contribution include documentation improvements, new tests, examples, updating dependencies, etc.
If you come from a web development background, you might be interested in issues related to web technologies tagged
[A-JavaScript](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-JavaScript),
@@ -21,16 +30,16 @@ If you come from a web development background, you might be interested in issues
[A-HTML](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-HTML) or
[A-Mobile](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-Mobile).
When you decide you want to work on a specific issue, ping us on that issue so that we can assign it to you.
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.
Issues on the issue tracker are categorized with the following labels:
- **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.
- **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
- **T**-prefixed labels show the type of issue
- **C**-prefixed labels show the category of issue
### Building mdBook
@@ -59,7 +68,7 @@ This will ensure we have good quality source code that is better for us all to m
[rustfmt](https://github.com/rust-lang/rustfmt) has a lot more information on the project.
The quick guide is
1. Install it
1. Install it (`rustfmt` is usually installed by default via [rustup](https://rustup.rs/)):
```
rustup component add rustfmt
```
@@ -71,18 +80,15 @@ The quick guide is
```
cargo fmt
```
When run through `cargo` it will format all bin and lib files in the current crate.
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 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.
The best documentation can be found over at [rust-clippy](https://github.com/rust-lang/rust-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
```
@@ -93,17 +99,36 @@ The best documentation can be found over at [rust-clippy](https://github.com/rus
cargo clippy
```
Clippy has an ever growing list of checks, that are managed in [lint files](https://rust-lang.github.io/rust-clippy/master/index.html).
### 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
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.
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/rustfmt) on the code first.
This is not a requirement though and will never block a pull-request from being merged.
That's it, happy contributions! :tada: :tada: :tada:
## Browser compatibility and testing
@@ -113,18 +138,53 @@ We generally strive to keep mdBook compatible with a relatively recent browser o
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.
GUI tests are checked with the GUI testsuite. To run it, you need to install `npm` first. Then run:
## Updating higlight.js
```
cargo test --test gui
```
The first time, it'll fail and ask you to install the `browser-ui-test` package. Install it then re-run the tests.
If you want to disable the headless mode, use the `DISABLE_HEADLESS_TEST=1` environment variable:
```
cargo test --test gui -- --disable-headless-test
```
The GUI tests are in the directory `tests/gui` in text files with the `.goml` extension. These tests are run
using a `node.js` framework called `browser-ui-test`. You can find documentation for this language on its
[repository](https://github.com/GuillaumeGomez/browser-UI-test/blob/master/goml-script.md).
## 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 properties r scala x86asm yaml`
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. (TODO: It would be nice to have a demo file in the repo to help with this.)
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."
```

2394
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,67 +1,91 @@
[workspace]
members = [".", "examples/remove-emphasis/mdbook-remove-emphasis"]
[package]
name = "mdbook"
version = "0.4.13"
version = "0.4.45"
authors = [
"Mathieu David <mathieudavid@mathieudavid.org>",
"Michael-F-Bryan <michaelfbryan@gmail.com>",
"Matt Ickstadt <mattico8@gmail.com>"
]
documentation = "http://rust-lang.github.io/mdBook/index.html"
edition = "2018"
documentation = "https://rust-lang.github.io/mdBook/index.html"
edition = "2021"
exclude = ["/guide/*"]
keywords = ["book", "gitbook", "rustbook", "markdown"]
license = "MPL-2.0"
readme = "README.md"
repository = "https://github.com/rust-lang/mdBook"
description = "Creates a book from markdown files"
rust-version = "1.77" # Keep in sync with installation.md and .github/workflows/main.yml
[dependencies]
anyhow = "1.0.28"
chrono = "0.4"
clap = "2.24"
env_logger = "0.7.1"
handlebars = "4.0"
lazy_static = "1.0"
log = "0.4"
memchr = "2.0"
opener = "0.5"
pulldown-cmark = "0.7.0"
regex = "1.0.0"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
shlex = "1"
tempfile = "3.0"
toml = "0.5.1"
topological-sort = "0.1.0"
anyhow = "1.0.71"
chrono = { version = "0.4.24", default-features = false, features = ["clock"] }
clap = { version = "4.3.12", features = ["cargo", "wrap_help"] }
clap_complete = "4.3.2"
once_cell = "1.17.1"
env_logger = "0.11.1"
handlebars = "6.0"
log = "0.4.17"
memchr = "2.5.0"
opener = "0.7.0"
pulldown-cmark = { version = "0.10.0", default-features = false, features = ["html"] } # Do not update, part of the public api.
regex = "1.8.1"
serde = { version = "1.0.163", features = ["derive"] }
serde_json = "1.0.96"
shlex = "1.3.0"
tempfile = "3.4.0"
toml = "0.5.11" # Do not update, see https://github.com/rust-lang/mdBook/issues/2037
topological-sort = "0.2.2"
# Watch feature
notify = { version = "4.0", optional = true }
gitignore = { version = "1.0", optional = true }
notify = { version = "8.0.0", optional = true }
notify-debouncer-mini = { version = "0.6.0", optional = true }
ignore = { version = "0.4.20", optional = true }
pathdiff = { version = "0.2.1", optional = true }
walkdir = { version = "2.3.3", optional = true }
# Serve feature
futures-util = { version = "0.3.4", optional = true }
tokio = { version = "1", features = ["macros", "rt-multi-thread"], optional = true }
warp = { version = "0.3.1", default-features = false, features = ["websocket"], optional = true }
futures-util = { version = "0.3.28", optional = true }
tokio = { version = "1.28.1", features = ["macros", "rt-multi-thread"], optional = true }
warp = { version = "0.3.6", default-features = false, features = ["websocket"], optional = true }
# Search feature
elasticlunr-rs = { version = "2.3", optional = true, default-features = false }
ammonia = { version = "3", optional = true }
elasticlunr-rs = { version = "3.0.2", optional = true }
ammonia = { version = "4.0.0", optional = true }
[dev-dependencies]
assert_cmd = "1"
predicates = "2"
select = "0.5"
semver = "0.11.0"
pretty_assertions = "0.6"
walkdir = "2.0"
assert_cmd = "2.0.11"
predicates = "3.0.3"
select = "0.6.0"
semver = "1.0.17"
pretty_assertions = "1.3.0"
walkdir = "2.3.3"
[features]
default = ["watch", "serve", "search"]
watch = ["notify", "gitignore"]
serve = ["futures-util", "tokio", "warp"]
search = ["elasticlunr-rs", "ammonia"]
watch = ["dep:notify", "dep:notify-debouncer-mini", "dep:ignore", "dep:pathdiff", "dep:walkdir"]
serve = ["dep:futures-util", "dep:tokio", "dep:warp"]
search = ["dep:elasticlunr-rs", "dep:ammonia"]
[[bin]]
doc = false
name = "mdbook"
[[example]]
name = "nop-preprocessor"
test = true
[[example]]
name = "remove-emphasis"
path = "examples/remove-emphasis/test.rs"
crate-type = ["lib"]
test = true
[[test]]
harness = false
test = false
name = "gui"
path = "tests/gui/runner.rs"
crate-type = ["bin"]

226
README.md
View File

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

View File

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

View File

@@ -13,6 +13,30 @@ 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
if [[ $TARGET == *"musl" ]]
then
# This is needed by libdbus-sys.
sudo apt update -y && sudo apt install musl-dev musl-tools -y
fi
if [[ $TARGET == "aarch64-unknown-linux-musl" ]]
then
echo CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=rust-lld >> $GITHUB_ENV
# This `CC` is some nonsense needed for libdbus-sys (via opener).
# I don't know if this is really the right thing to do, but it seems to work.
sudo apt install gcc-aarch64-linux-gnu -y
echo CC=aarch64-linux-gnu-gcc >> $GITHUB_ENV
fi
fi
rustup default $TOOLCHAIN
rustup -V
rustc -Vv

View File

@@ -11,16 +11,17 @@ fi
TAG=${GITHUB_REF#*/tags/}
host=$(rustc -Vv | grep ^host: | sed -e "s/host: //g")
target=$2
export CARGO_PROFILE_RELEASE_LTO=true
cargo build --bin mdbook --release
cd target/release
cargo build --locked --bin mdbook --release --target $target
cd target/$target/release
case $1 in
ubuntu*)
asset="mdbook-$TAG-$host.tar.gz"
asset="mdbook-$TAG-$target.tar.gz"
tar czf ../../$asset mdbook
;;
macos*)
asset="mdbook-$TAG-$host.tar.gz"
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
@@ -30,7 +31,7 @@ case $1 in
tar czf ../../$asset mdbook
;;
windows*)
asset="mdbook-$TAG-$host.zip"
asset="mdbook-$TAG-$target.zip"
7z a ../../$asset mdbook.exe
;;
*)
@@ -39,9 +40,10 @@ case $1 in
esac
cd ../..
if [[ -z "$GITHUB_TOKEN" ]]
if [[ -z "$GITHUB_ENV" ]]
then
echo "$GITHUB_TOKEN not set, skipping deploy."
echo "GITHUB_ENV not set, run: gh release upload $TAG target/$asset"
else
hub release edit -m "" --attach $asset $TAG
echo "MDBOOK_TAG=$TAG" >> $GITHUB_ENV
echo "MDBOOK_ASSET=target/$asset" >> $GITHUB_ENV
fi

View File

@@ -1,5 +1,5 @@
use crate::nop_lib::Nop;
use clap::{App, Arg, ArgMatches, SubCommand};
use clap::{Arg, ArgMatches, Command};
use mdbook::book::Book;
use mdbook::errors::Error;
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
@@ -7,12 +7,12 @@ use semver::{Version, VersionReq};
use std::io;
use std::process;
pub fn make_app() -> App<'static, 'static> {
App::new("nop-preprocessor")
pub fn make_app() -> Command {
Command::new("nop-preprocessor")
.about("A mdbook preprocessor which does precisely nothing")
.subcommand(
SubCommand::with_name("supports")
.arg(Arg::with_name("renderer").required(true))
Command::new("supports")
.arg(Arg::new("renderer").required(true))
.about("Check whether a renderer is supported by this preprocessor"),
)
}
@@ -26,7 +26,7 @@ fn main() {
if let Some(sub_args) = matches.subcommand_matches("supports") {
handle_supports(&preprocessor, sub_args);
} else if let Err(e) = handle_preprocessing(&preprocessor) {
eprintln!("{}", e);
eprintln!("{e}");
process::exit(1);
}
}
@@ -54,7 +54,9 @@ fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
}
fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! {
let renderer = sub_args.value_of("renderer").expect("Required argument");
let 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.
@@ -101,4 +103,58 @@ mod nop_lib {
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);
}
}
}

1
examples/remove-emphasis/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
book

View File

@@ -0,0 +1,5 @@
[book]
title = "remove-emphasis"
[preprocessor.remove-emphasis]
command = "cargo run --manifest-path=mdbook-remove-emphasis/Cargo.toml --locked"

View File

@@ -0,0 +1,10 @@
[package]
name = "mdbook-remove-emphasis"
version = "0.1.0"
edition = "2021"
[dependencies]
mdbook = { version = "0.4.40", path = "../../.." }
pulldown-cmark = { version = "0.12.2", default-features = false }
pulldown-cmark-to-cmark = "18.0.0"
serde_json = "1.0.132"

View File

@@ -0,0 +1,82 @@
//! This is a demonstration of an mdBook preprocessor which parses markdown
//! and removes any instances of emphasis.
use mdbook::book::{Book, Chapter};
use mdbook::errors::Error;
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
use mdbook::BookItem;
use pulldown_cmark::{Event, Parser, Tag, TagEnd};
use std::io;
fn main() {
let mut args = std::env::args().skip(1);
match args.next().as_deref() {
Some("supports") => {
// Supports all renderers.
return;
}
Some(arg) => {
eprintln!("unknown argument: {arg}");
std::process::exit(1);
}
None => {}
}
if let Err(e) = handle_preprocessing() {
eprintln!("{}", e);
std::process::exit(1);
}
}
struct RemoveEmphasis;
impl Preprocessor for RemoveEmphasis {
fn name(&self) -> &str {
"remove-emphasis"
}
fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result<Book, Error> {
let mut total = 0;
book.for_each_mut(|item| {
let BookItem::Chapter(ch) = item else {
return;
};
if ch.is_draft_chapter() {
return;
}
match remove_emphasis(&mut total, ch) {
Ok(s) => ch.content = s,
Err(e) => eprintln!("failed to process chapter: {e:?}"),
}
});
eprintln!("removed {total} emphasis");
Ok(book)
}
}
// ANCHOR: remove_emphasis
fn remove_emphasis(num_removed_items: &mut usize, chapter: &mut Chapter) -> Result<String, Error> {
let mut buf = String::with_capacity(chapter.content.len());
let events = Parser::new(&chapter.content).filter(|e| match e {
Event::Start(Tag::Emphasis) | Event::Start(Tag::Strong) => {
*num_removed_items += 1;
false
}
Event::End(TagEnd::Emphasis) | Event::End(TagEnd::Strong) => false,
_ => true,
});
Ok(pulldown_cmark_to_cmark::cmark(events, &mut buf).map(|_| buf)?)
}
// ANCHOR_END: remove_emphasis
pub fn handle_preprocessing() -> Result<(), Error> {
let pre = RemoveEmphasis;
let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
let processed_book = pre.run(&ctx, book)?;
serde_json::to_writer(io::stdout(), &processed_book)?;
Ok(())
}

View File

@@ -0,0 +1,3 @@
# Summary
- [Chapter 1](./chapter_1.md)

View File

@@ -0,0 +1,3 @@
# Chapter 1
This has *light emphasis* and **bold emphasis**.

View File

@@ -0,0 +1,13 @@
use mdbook::MDBook;
#[test]
fn remove_emphasis_works() {
// Tests that the remove-emphasis example works as expected.
// Workaround for https://github.com/rust-lang/mdBook/issues/1424
std::env::set_current_dir("examples/remove-emphasis").unwrap();
let book = MDBook::load(".").unwrap();
book.build().unwrap();
let ch1 = std::fs::read_to_string("book/chapter_1.html").unwrap();
assert!(ch1.contains("This has light emphasis and bold emphasis."));
}

View File

@@ -8,6 +8,7 @@ language = "en"
edition = "2018"
[output.html]
smart-punctuation = true
mathjax-support = true
site-url = "/mdBook/"
git-repository-url = "https://github.com/rust-lang/mdBook/tree/master/guide"
@@ -17,6 +18,9 @@ edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path
editable = true
line-numbers = true
[output.html.code.hidelines]
python = "~"
[output.html.search]
limit-results = 20
use-boolean-and = true

View File

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

View File

@@ -1,6 +1,15 @@
# Summary
- [Introduction](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)
- [init](cli/init.md)
- [build](cli/build.md)
@@ -8,6 +17,7 @@
- [serve](cli/serve.md)
- [test](cli/test.md)
- [clean](cli/clean.md)
- [completions](cli/completions.md)
- [Format](format/README.md)
- [SUMMARY.md](format/summary.md)
- [Draft chapter]()
@@ -22,6 +32,7 @@
- [Editor](format/theme/editor.md)
- [MathJax Support](format/mathjax.md)
- [mdBook-specific features](format/mdbook.md)
- [Markdown](format/markdown.md)
- [Continuous Integration](continuous-integration.md)
- [For Developers](for_developers/README.md)
- [Preprocessors](for_developers/preprocessors.md)

View File

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

@@ -0,0 +1,7 @@
#### `--watcher`
There are different backends used to determine when a file has changed.
* `poll` (default) --- Checks for file modifications by scanning the filesystem every second.
* `native` --- Uses the native operating system facilities to receive notifications when files change.
This can have less constant overhead, but may not be as reliable as the `poll` based watcher. See these issues for more information: [#383](https://github.com/rust-lang/mdBook/issues/383) [#1441](https://github.com/rust-lang/mdBook/issues/1441) [#1707](https://github.com/rust-lang/mdBook/issues/1707) [#2035](https://github.com/rust-lang/mdBook/issues/2035) [#2102](https://github.com/rust-lang/mdBook/issues/2102)

View File

@@ -7,8 +7,8 @@ mdbook build
```
It will try to parse your `SUMMARY.md` file to understand the structure of your
book and fetch the corresponding files. Note that files mentioned in `SUMMARY.md`
but not present will be created.
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
convenience. Large books will therefore remain structured when rendered.
@@ -22,12 +22,12 @@ root instead of the current working directory.
mdbook build path/to/book
```
#### --open
#### `--open`
When you use the `--open` (`-o`) flag, mdbook will open the rendered book in
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
book. Relative paths are interpreted relative to the book's root directory. If

View File

@@ -16,7 +16,7 @@ root instead of the current working directory.
mdbook clean path/to/book
```
#### --dest-dir
#### `--dest-dir`
The `--dest-dir` (`-d`) option allows you to override the book's output
directory, which will be deleted by this command. Relative paths are interpreted
@@ -27,4 +27,4 @@ value of the `build.build-dir` key in `book.toml`, or to `./book`.
mdbook clean --dest-dir=path/to/book
```
`path/to/book` could be absolute or relative.
`path/to/book` could be absolute or relative.

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

@@ -45,7 +45,7 @@ instead of the current working directory.
mdbook init path/to/book
```
#### --theme
#### `--theme`
When you use the `--theme` flag, the default theme will be copied into a
directory called `theme` in your source directory so that you can modify it.
@@ -53,7 +53,7 @@ directory called `theme` in your source directory so that you can modify it.
The theme is selectively overwritten, this means that if you don't want to
overwrite a specific file, just delete it and the default file will be used.
#### --title
#### `--title`
Specify a title for the book. If not supplied, an interactive prompt will ask for
a title.
@@ -62,9 +62,21 @@ a title.
mdbook init --title="my amazing book"
```
#### --ignore
#### `--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.

View File

@@ -32,18 +32,20 @@ The `serve` hostname defaults to `localhost`, and the port defaults to `3000`. E
mdbook serve path/to/book -p 8000 -n 127.0.0.1
```
#### --open
#### `--open`
When you use the `--open` (`-o`) flag, mdbook will open the book in your
default web browser after starting the server.
#### --dest-dir
#### `--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`.
{{#include arg-watcher.md}}
#### Specify exclude patterns
The `serve` command will not automatically trigger a build for files listed in

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.
mdBook supports a `test` command that will run all available tests in a book. At
the moment, only rustdoc tests are supported, but this may be expanded upon in
the future.
the moment, only Rust tests are supported.
#### Disable tests on a code block
@@ -38,12 +37,12 @@ instead of the current working directory.
mdbook test path/to/book
```
#### --library-path
#### `--library-path`
The `--library-path` (`-L`) option allows you to add directories to the library
search path used by `rustdoc` when it builds and tests the examples. Multiple
directories can be specified with multiple options (`-L foo -L bar`) or with a
comma-delimited list (`-L foo,bar`). The path should point to the Cargo
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`:
@@ -55,9 +54,14 @@ 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
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`.
#### `--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.

View File

@@ -15,18 +15,19 @@ root instead of the current working directory.
mdbook watch path/to/book
```
#### --open
#### `--open`
When you use the `--open` (`-o`) option, mdbook will open the rendered book in
your default web browser.
#### --dest-dir
#### `--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`.
{{#include arg-watcher.md}}
#### Specify exclude patterns

View File

@@ -1,154 +1,121 @@
# Running `mdbook` in Continuous Integration
While the following examples use Travis CI, their principles should
straightforwardly transfer to other continuous integration providers as well.
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.
## Ensuring Your Book Builds and Tests Pass
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.
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.
[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
```yaml
language: rust
sudo: false
## Installing mdBook
cache:
- cargo
There are several different strategies for installing mdBook.
The particular method depends on your needs and preferences.
rust:
- stable
### Pre-compiled binaries
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.3" mdbook)
- cargo install-update -a
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:
script:
- mdbook build && mdbook test # In case of custom book path: mdbook build path/to/mybook && mdbook test path/to/mybook
```sh
mkdir bin
curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.45/mdbook-v0.4.45-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
bin/mdbook build
```
## Deploying Your Book to GitHub Pages
Some considerations for this approach:
Following these instructions will result in your book being published to GitHub
pages after a successful CI run on your repository's `master` branch.
* 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.
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.
[releases]: https://github.com/rust-lang/mdBook/releases
Whilst still in your repository's settings page, navigate to Options and change the
Source on GitHub pages to `gh-pages`.
### Building from source
Then, append this snippet to your `.travis.yml` and update the path to the
`book` directory:
Building from source will require having Rust installed.
Some services have Rust pre-installed, but if your service does not, you will need to add a step to install it.
```yaml
deploy:
provider: pages
skip-cleanup: true
github-token: $GITHUB_TOKEN
local-dir: book # In case of custom book path: path/to/mybook/book
keep-history: false
on:
branch: main
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
```
That's it!
This includes several recommended options:
Note: Travis has a new [dplv2](https://blog.travis-ci.com/2019-08-27-deployment-tooling-dpl-v2-preview-release) configuration that is currently in beta. To use this new format, update your `.travis.yml` file to:
* `--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.
```yaml
language: rust
os: linux
dist: xenial
You will likely want to investigate caching options, as building mdBook can be somewhat slow.
cache:
- cargo
[search]: guide/reading.md#search
rust:
- stable
## Running tests
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.3" mdbook)
- cargo install-update -a
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.
script:
- mdbook build && mdbook test # In case of custom book path: mdbook build path/to/mybook && mdbook test path/to/mybook
deploy:
provider: pages
strategy: git
edge: true
cleanup: false
github-token: $GITHUB_TOKEN
local-dir: book # In case of custom book path: path/to/mybook/book
keep-history: false
on:
branch: main
target_branch: gh-pages
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/"
```
### Deploying to GitHub Pages manually
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.
If your CI doesn't support GitHub pages, or you're deploying somewhere else
with integrations such as Github Pages:
*note: you may want to use different tmp dirs*:
```console
$> git worktree add /tmp/book gh-pages
$> mdbook build
$> rm -rf /tmp/book/* # this won't delete the .git directory
$> cp -rp book/* /tmp/book/
$> cd /tmp/book
$> git add -A
$> git commit 'new book message'
$> git push origin gh-pages
$> cd -
```
Or put this into a Makefile rule:
```makefile
.PHONY: deploy
deploy: book
@echo "====> deploying to github"
git worktree add /tmp/book gh-pages
rm -rf /tmp/book/*
cp -rp book/* /tmp/book/
cd /tmp/book && \
git add -A && \
git commit -m "deployed on $(shell date) by ${USER}" && \
git push origin gh-pages
```
## Deploying Your Book to GitLab Pages
Inside your repository's project root, create a file named `.gitlab-ci.yml` with the following contents:
```yml
stages:
- deploy
pages:
stage: deploy
image: rust
variables:
CARGO_HOME: $CI_PROJECT_DIR/cargo
before_script:
- export PATH="$PATH:$CARGO_HOME/bin"
- mdbook --version || cargo install mdbook
script:
- mdbook build -d public
rules:
- if: '$CI_COMMIT_REF_NAME == "master"'
artifacts:
paths:
- public
cache:
paths:
- $CARGO_HOME/bin
```
After you commit and push this new file, GitLab CI will run and your book will be available!
[`output.html.site-url`]: format/configuration/renderers.md#html-renderer-options
[`output.html.input-404`]: format/configuration/renderers.md#html-renderer-options

View File

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

View File

@@ -5,23 +5,17 @@ rendering process. This program is passed a JSON representation of the book and
configuration information via `stdin`. Once the backend receives this
information it is free to do whatever it wants.
There are already several alternative backends on GitHub which can be used as a
rough example of how this is accomplished in practice.
See [Configuring Renderers](../format/configuration/renderers.md) for more information about using backends.
- [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`)
- [mdbook-man] - generate manual pages from the book
The community has developed several backends.
See the [Third Party Plugins] wiki page for a list of available backends.
## 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.
## Setting Up
First you'll want to create a new binary program and add `mdbook` as a
dependency.
@@ -293,7 +287,7 @@ like this:
+ if cfg.deny_odds && num_words % 2 == 1 {
+ eprintln!("{} has an odd number of words!", ch.name);
+ process::exit(1);
}
+ }
}
}
}
@@ -330,39 +324,6 @@ generation or a warning).
All environment variables are passed through to the backend, allowing you to use
the usual `RUST_LOG` to control logging verbosity.
## Handling missing backends
If you enable a backend that isn't installed, the default behavior is to throw an error:
```text
The command `mdbook-wordcount` wasn't found, is the "wordcount" backend installed?
If you want to ignore this error when the "wordcount" backend is not installed,
set `optional = true` in the `[output.wordcount]` section of the book.toml configuration file.
```
This behavior can be changed by marking the backend as optional.
```diff
[book]
title = "mdBook Documentation"
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
authors = ["Mathieu David", "Michael-F-Bryan"]
[output.html]
[output.wordcount]
command = "python /path/to/wordcount.py"
+ optional = true
```
This demotes the error to a warning, and it will instead look like this:
```text
The command was not found, but was marked as optional.
Command: wordcount
```
## Wrapping Up
Although contrived, hopefully this example was enough to show how you'd create
@@ -375,11 +336,7 @@ as a good example of how it's done in real life, so feel free to skim through
the source code or ask questions.
[mdbook-linkcheck]: https://github.com/Michael-F-Bryan/mdbook-linkcheck
[mdbook-epub]: https://github.com/Michael-F-Bryan/mdbook-epub
[mdbook-test]: https://github.com/Michael-F-Bryan/mdbook-test
[mdbook-man]: https://github.com/vv9k/mdbook-man
[rust-skeptic]: https://github.com/budziq/rust-skeptic
[Third Party Plugins]: https://github.com/rust-lang/mdBook/wiki/Third-party-plugins
[`RenderContext`]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html
[`RenderContext::from_json()`]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html#method.from_json
[`semver`]: https://crates.io/crates/semver

View File

@@ -5,35 +5,18 @@ book is loaded and before it gets rendered, allowing you to update and mutate
the book. Possible use cases are:
- Creating custom helpers like `\{{#include /path/to/file.md}}`
- Updating links so `[some chapter](some_chapter.md)` is automatically changed
to `[some chapter](some_chapter.html)` for the HTML renderer
- Substituting in latex-style expressions (`$$ \frac{1}{3} $$`) with their
mathjax equivalents
See [Configuring Preprocessors](../format/configuration/preprocessors.md) for more information about using preprocessors.
## Hooking Into MDBook
MDBook uses a fairly simple mechanism for discovering third party plugins.
A new table is added to `book.toml` (e.g. `preprocessor.foo` for the `foo`
A new table is added to `book.toml` (e.g. `[preprocessor.foo]` for the `foo`
preprocessor) and then `mdbook` will try to invoke the `mdbook-foo` program as
part of the build process.
A preprocessor can be hard-coded to specify which backend(s) it should be run
for with the `preprocessor.foo.renderer` key. For example, it doesn't make sense for
[MathJax](../format/mathjax.md) to be used for non-HTML renderers.
```toml
[book]
title = "My Book"
authors = ["Michael-F-Bryan"]
[preprocessor.foo]
# The command can also be specified manually
command = "python3 /path/to/foo.py"
# Only run the `foo` preprocessor for the HTML and EPUB renderer
renderer = ["html", "epub"]
```
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.
@@ -78,40 +61,17 @@ The `chapter.content` is just a string which happens to be markdown. While it's
entirely possible to use regular expressions or do a manual find & replace,
you'll probably want to process the input into something more computer-friendly.
The [`pulldown-cmark`][pc] crate implements a production-quality event-based
Markdown parser, with the [`pulldown-cmark-to-cmark`][pctc] allowing you to
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))
})
}
{{#rustdoc_include ../../../examples/remove-emphasis/mdbook-remove-emphasis/src/main.rs:remove_emphasis}}
```
For everything else, have a look [at the complete example][example].
Take a look at the [full example source][emphasis-example] for more details.
## Implementing a preprocessor with a different language
@@ -139,11 +99,10 @@ if __name__ == '__main__':
```
[emphasis-example]: https://github.com/rust-lang/mdBook/tree/master/examples/remove-emphasis/
[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

View File

@@ -29,7 +29,7 @@ book's title without needing to touch your `book.toml`.
> building the book with something like
>
> ```shell
> $ export MDBOOK_BOOK="{'title': 'My Awesome Book', authors: ['Michael-F-Bryan']}"
> $ export MDBOOK_BOOK='{"title": "My Awesome Book", "authors": ["Michael-F-Bryan"]}'
> $ mdbook build
> ```

View File

@@ -7,7 +7,7 @@ Here is an example of what a ***book.toml*** file might look like:
```toml
[book]
title = "Example book"
author = "John Doe"
authors = ["John Doe"]
description = "The example book covers examples."
[rust]
@@ -46,6 +46,9 @@ This is general information about your book.
`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
@@ -55,6 +58,7 @@ 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
@@ -62,8 +66,13 @@ language = "en"
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`,
is `"2015"`. Individual code blocks can be controlled with the `edition2015`,
`edition2018` or `edition2021` annotations, such as:
~~~text
@@ -77,13 +86,22 @@ integration.
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` &
- **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
@@ -95,3 +113,6 @@ This controls the build process of your book.
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

@@ -1,51 +1,58 @@
# Configuring Preprocessors
The following preprocessors are available and included by default:
Preprocessors are extensions that can modify the raw Markdown source before it gets sent to the renderer.
- `links`: Expand the `{{ #playground }}`, `{{ #include }}`, and `{{ #rustdoc_include }}` handlebars
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.
**book.toml**
```toml
[build]
build-dir = "build"
create-missing = false
The community has developed several preprocessors.
See the [Third Party Plugins] wiki page for a list of available preprocessors.
[preprocessor.links]
For information on how to create a new preprocessor, see the [Preprocessors for Developers] chapter.
[preprocessor.index]
```
[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
## 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
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.links]
# set the renderers this preprocessor will run for
renderers = ["html"]
some_extra_feature = true
[preprocessor.example]
```
#### Locking a Preprocessor dependency to a renderer
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.mathjax]
renderers = ["html"] # mathjax only makes sense with the HTML renderer
[preprocessor.example]
renderers = ["html"] # example preprocessor only runs with the HTML renderer
```
### Provide Your Own Command
## 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
@@ -57,7 +64,7 @@ be overridden by adding a `command` field.
command = "python random.py"
```
### Require A Certain Order
## 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:

View File

@@ -1,9 +1,115 @@
# Configuring Renderers
### HTML renderer options
Renderers (also called "backends") are responsible for creating the output of the book.
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 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:
@@ -14,15 +120,20 @@ The following configuration options are available:
'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)
[`prefers-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)
CSS media query. Defaults to `navy`.
- **curly-quotes:** Convert straight quotes to curly quotes, except for those
that occur in code blocks and code spans. Defaults to `false`.
- **smart-punctuation:** Converts quotes to curly quotes, `...` to `…`, `--` to en-dash, and `---` to em-dash.
See [Smart Punctuation](../markdown.md#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:** Copies fonts.css and respective font files to the output directory and use them in the default theme. Defaults to `true`.
- **google-analytics:** If you use Google Analytics, this option lets you enable
it by simply specifying your ID in the configuration file.
- **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
@@ -30,40 +141,29 @@ The following configuration options are available:
- **additional-js:** If you need to add some behaviour to your book without
removing the current behaviour, you can specify a set of JavaScript files that
will be loaded alongside the default one.
- **print:** A subtable for configuration print settings. mdBook by default adds
support for printing out the book as a single page. This is accessed using the
print icon on the top right of the book.
- **no-section-label:** mdBook by defaults adds section label in table of
- **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`.
- **fold:** A subtable for configuring sidebar section-folding behavior.
- **playground:** A subtable for configuring various playground settings.
- **search:** A subtable for configuring the in-browser search functionality.
mdBook must be compiled with the `search` feature enabled (on by default).
- **git-repository-url:** A url to the git repository for the book. If provided
an icon link will be output in the menu bar of the book.
- **git-repository-icon:** The FontAwesome icon class to use for the git
repository link. Defaults to `fa-github`.
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 for directly jumping to editing the currently
"Suggest an edit" button (which looks like <i class="fa fa-edit"></i>) for directly jumping to editing the currently
viewed page. For e.g. GitHub projects set this to
`https://github.com/<owner>/<repo>/edit/master/{path}` or for
`https://github.com/<owner>/<repo>/edit/<branch>/{path}` or for
Bitbucket projects set it to
`https://bitbucket.org/<owner>/<repo>/src/master/{path}?mode=edit`
`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.
- **redirect:** A subtable used for generating redirects when a page is moved.
The table contains key-value pairs where the key is where the redirect file
needs to be created, as an absolute path from the build directory, (e.g.
`/appendices/bibliography.html`). The value can be any valid URI the
browser should navigate to (e.g. `https://rust-lang.org/`,
`/overview.html`, or `../bibliography.html`).
- **input-404:** The name of the markdown file used for 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 `/`.
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
@@ -71,29 +171,94 @@ The following configuration options are available:
[custom domain]: https://docs.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site
Available configuration options for the `[output.html.print]` table:
### `[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`.
Available configuration options for the `[output.html.fold]` table:
### `[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`.
Available configuration options for the `[output.html.playground]` table:
### `[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`.
- **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/
Available configuration options for the `[output.html.search]` table:
### `[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`.
@@ -116,62 +281,38 @@ Available configuration options for the `[output.html.search]` table:
- **copy-js:** Copy JavaScript files for the search implementation to the output
directory. Defaults to `true`.
This shows all available HTML output options in the **book.toml**:
#### `[output.html.search.chapter]`
The [`output.html.search.chapter`] table provides the ability to modify search settings per chapter or directory. Each key is the path to the chapter source file or directory, and the value is a table of settings to apply to that path. This will merge recursively, with more specific paths taking precedence.
```toml
[book]
title = "Example book"
authors = ["John Doe", "Jane Doe"]
description = "The example book covers examples."
[output.html.search.chapter]
# Disables search indexing for all chapters in the `appendix` directory.
"appendix" = { enable = false }
# Enables search indexing for just this one appendix chapter.
"appendix/glossary.md" = { enable = true }
```
[output.html]
theme = "my-theme"
default-theme = "light"
preferred-dark-theme = "navy"
curly-quotes = true
mathjax-support = false
copy-fonts = true
google-analytics = "UA-123456-7"
additional-css = ["custom.css", "custom2.css"]
additional-js = ["custom.js"]
no-section-label = false
git-repository-url = "https://github.com/rust-lang/mdBook"
git-repository-icon = "fa-github"
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"
- **enable:** Enables or disables search indexing for the given chapters. Defaults to `true`. This does not override the overall `output.html.search.enable` setting; that must be `true` for any search functionality to be enabled. Be cautious when disabling indexing for chapters because that can potentially lead to user confusion when they search for terms and expect them to be found. This should only be used in exceptional circumstances where keeping the chapter in the index will cause issues with the quality of the search results.
[output.html.print]
enable = true
### `[output.html.redirect]`
[output.html.fold]
enable = false
level = 0
[output.html.playground]
editable = false
copy-js = true
line-numbers = false
[output.html.search]
enable = true
limit-results = 30
teaser-word-count = 30
use-boolean-and = true
boost-title = 2
boost-hierarchy = 1
boost-paragraph = 1
expand = true
heading-split-level = 3
copy-js = true
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"
```
### Markdown Renderer
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
@@ -190,20 +331,3 @@ 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.
### Custom Renderers
A custom renderer can be enabled by adding a `[output.foo]` table to your
`book.toml`. Similar to [preprocessors](preprocessors.md) this will
instruct `mdbook` to pass a representation of the book to `mdbook-foo` for
rendering. See the [alternative backends] chapter for more detail.
The custom renderer has access to all the fields within its table (i.e.
anything under `[output.foo]`). mdBook checks for two common fields:
- **command:** The command to execute for this custom renderer. Defaults to
the name of the renderer with the `mdbook-` prefix (such as `mdbook-foo`).
- **optional:** If `true`, then the command will be ignored if it is not
installed, otherwise mdBook will fail with an error. Defaults to `false`.
[alternative backends]: ../../for_developers/backends.md

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).

View File

@@ -2,10 +2,12 @@
## Hiding code lines
There is a feature in mdBook that lets you hide code lines by prepending them
with a `#` [like you would with Rustdoc][rustdoc-hide].
There is a feature in mdBook that lets you hide code lines by prepending them with a specific prefix.
[rustdoc-hide]: https://doc.rust-lang.org/stable/rustdoc/documentation-tests.html#hiding-portions-of-the-example
For the Rust language, you can prefix lines with `# ` (`#` followed by a space) to hide them [like you would with Rustdoc][rustdoc-hide].
This prefix can be escaped with `##` to prevent the hiding of a line that should begin with the literal string `# ` (see [Rustdoc's docs][rustdoc-hide] for more details)
[rustdoc-hide]: https://doc.rust-lang.org/stable/rustdoc/write-documentation/documentation-tests.html#hiding-portions-of-the-example
```bash
# fn main() {
@@ -27,6 +29,107 @@ Will render as
# }
```
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:
@@ -191,6 +294,17 @@ 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\>
@@ -201,3 +315,51 @@ 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>

View File

@@ -21,7 +21,7 @@ to be ignored at best, or may cause an error when attempting to build the book.
1. ***Prefix Chapter*** - Before the main numbered chapters, prefix chapters can be added
that will not be numbered. This is useful for forewords,
introductions, etc. There are, however, some constraints. Prefix chapters cannot be
nested; they should all be on the root level. And you can not add
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)
@@ -29,13 +29,14 @@ to be ignored at best, or may cause an error when attempting to build the book.
- [First Chapter](relative/path/to/markdown2.md)
```
1. ***Part Title*** - Headers can be used as a title for the following numbered
chapters. This can be used to logically separate different sections
of the book. The title is rendered as unclickable text.
Titles are optional, and the numbered chapters can be broken into as many
parts as desired.
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 Tile
# My Part Title
- [First Chapter](relative/path/to/markdown.md)
```

View File

@@ -1,6 +1,6 @@
# Theme
The default renderer uses a [handlebars](http://handlebarsjs.com/) template to
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.
@@ -26,6 +26,8 @@ Here are the files you can override:
- **_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
@@ -35,9 +37,13 @@ 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
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.

View File

@@ -12,12 +12,14 @@ editable = true
To make a specific block available for editing, the attribute `editable` needs
to be added to it:
<pre><code class="language-markdown">```rust,editable
~~~markdown
```rust,editable
fn main() {
let number = 5;
print!("{}", number);
}
```</code></pre>
```
~~~
The above will result in this editable playground:

View File

@@ -18,7 +18,7 @@ handlebars template you can access this information by using
Here is a list of the properties that are exposed:
- ***language*** Language of the book in the form `en`, as specified in `book.toml` (if not specified, defaults to `en`). To use in <code
class="language-html">\<html lang="{{ language }}"></code> for example.
class="language-html">\<html lang=\"{{ language }}\"></code> for example.
- ***title*** Title used for the current page. This is identical to `{{ chapter_title }} - {{ book_title }}` unless `book_title` is not set in which case it just defaults to the `chapter_title`.
- ***book_title*** Title of the book, as specified in `book.toml`
- ***chapter_title*** Title of the current chapter, as listed in `SUMMARY.md`
@@ -79,7 +79,7 @@ var chapters = {{chapters}};
### 2. previous / next
The previous and next helpers expose a `link` and `name` property to the
The previous and next helpers expose a `link` and `title` property to the
previous and next chapters.
They are used like this
@@ -87,7 +87,7 @@ They are used like this
```handlebars
{{#previous}}
<a href="{{link}}" class="nav-chapters previous">
<i class="fa fa-angle-left"></i>
<i class="fa fa-angle-left"></i> {{title}}
</a>
{{/previous}}
```

View File

@@ -44,6 +44,8 @@ your own `highlight.js` file:
- makefile
- markdown
- nginx
- nim
- nix
- objectivec
- perl
- php
@@ -77,38 +79,6 @@ the `theme` folder of your book.
Now your theme will be used instead of the default theme.
## Hiding code lines
There is a feature in mdBook that lets you hide code lines by prepending them
with a `#`.
```bash
# fn main() {
let x = 5;
let y = 6;
println!("{}", x + y);
# }
```
Will render as
```rust
# fn main() {
let x = 5;
let y = 7;
println!("{}", x + y);
# }
```
**At the moment, this only works for code examples that are annotated with
`rust`. Because it would collide with semantics of some programming languages.
In the future, we want to make this configurable through the `book.toml` so that
everyone can benefit from it.**
## Improve default theme
If you think the default theme doesn't look quite right for a specific language,

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,57 @@
# 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.77.
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).
You can run `cargo install mdbook` again whenever you want to update to a new version.
That command will check if there is a newer version, and re-install mdBook if a newer version is found.
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`.
## Modifying and contributing
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

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

View File

@@ -7,6 +7,9 @@ use std::path::{Path, PathBuf};
use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
use crate::config::BuildConfig;
use crate::errors::*;
use crate::utils::bracket_escape;
use log::debug;
use serde::{Deserialize, Serialize};
/// Load a book into memory from its `src/` directory.
pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book> {
@@ -15,11 +18,11 @@ pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book>
let mut summary_content = String::new();
File::open(&summary_md)
.with_context(|| format!("Couldn't open SUMMARY.md in {:?} directory", src_dir))?
.with_context(|| format!("Couldn't open SUMMARY.md in {src_dir:?} directory"))?
.read_to_string(&mut summary_content)?;
let summary = parse_summary(&summary_content)
.with_context(|| format!("Summary parsing failed for file={:?}", summary_md))?;
.with_context(|| format!("Summary parsing failed for file={summary_md:?}"))?;
if cfg.create_missing {
create_missing(src_dir, &summary).with_context(|| "Unable to create missing chapters")?;
@@ -36,9 +39,7 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
.chain(summary.suffix_chapters.iter())
.collect();
while !items.is_empty() {
let next = items.pop().expect("already checked");
while let Some(next) = items.pop() {
if let SummaryItem::Link(ref link) = *next {
if let Some(ref location) = link.location {
let filename = src_dir.join(location);
@@ -53,7 +54,7 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
let mut f = File::create(&filename).with_context(|| {
format!("Unable to create missing file: {}", filename.display())
})?;
writeln!(f, "# {}", link.name)?;
writeln!(f, "# {}", bracket_escape(&link.name))?;
}
}
@@ -159,8 +160,21 @@ pub struct Chapter {
/// Nested items.
pub sub_items: Vec<BookItem>,
/// The chapter's location, relative to the `SUMMARY.md` file.
///
/// **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, or a synthetically generated
/// chapter that has no file on disk.
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>,
@@ -274,7 +288,7 @@ fn load_chapter<P: AsRef<Path>>(
}
let stripped = location
.strip_prefix(&src_dir)
.strip_prefix(src_dir)
.expect("Chapters are always inside a book");
Chapter::new(&link.name, content, stripped, parent_names.clone())
@@ -314,7 +328,7 @@ impl<'a> Iterator for BookItems<'a> {
fn next(&mut self) -> Option<Self::Item> {
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
for sub_item in ch.sub_items.iter().rev() {
self.items.push_front(sub_item);
@@ -328,7 +342,7 @@ impl<'a> Iterator for BookItems<'a> {
impl Display for Chapter {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if let Some(ref section_number) = self.number {
write!(f, "{} ", section_number)?;
write!(f, "{section_number} ")?;
}
write!(f, "{}", self.name)
@@ -338,7 +352,6 @@ impl Display for Chapter {
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::{Builder as TempFileBuilder, TempDir};
const DUMMY_SRC: &str = "

View File

@@ -6,6 +6,8 @@ use super::MDBook;
use crate::config::Config;
use crate::errors::*;
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.
#[derive(Debug, Clone, PartialEq)]
@@ -157,6 +159,19 @@ impl BookBuilder {
let mut highlight_js = File::create(themedir.join("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(())
}
@@ -183,8 +198,7 @@ impl BookBuilder {
writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
let chapter_1 = src_dir.join("chapter_1.md");
let mut f =
File::create(&chapter_1).with_context(|| "Unable to create chapter_1.md")?;
let mut f = File::create(chapter_1).with_context(|| "Unable to create chapter_1.md")?;
writeln!(f, "# Chapter 1")?;
} else {
trace!("Existing summary found, no need to create stub files.");
@@ -197,10 +211,10 @@ impl BookBuilder {
fs::create_dir_all(&self.root)?;
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);
fs::create_dir_all(&build)?;
fs::create_dir_all(build)?;
Ok(())
}

View File

@@ -14,10 +14,11 @@ pub use self::book::{load_book, Book, BookItem, BookItems, Chapter};
pub use self::init::BookBuilder;
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
use std::io::Write;
use std::path::PathBuf;
use log::{debug, error, info, log_enabled, trace, warn};
use std::ffi::OsString;
use std::io::{IsTerminal, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::string::ToString;
use tempfile::Builder as TempFileBuilder;
use toml::Value;
use topological_sort::TopologicalSort;
@@ -70,8 +71,28 @@ impl MDBook {
config.update_from_env();
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);
}
}
@@ -84,7 +105,7 @@ impl MDBook {
let root = book_root.into();
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)?;
@@ -107,7 +128,7 @@ impl 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 book = book::load_book_from_disk(&summary, src_dir)?;
let renderers = determine_renderers(&config);
let preprocessors = determine_preprocessors(&config)?;
@@ -181,21 +202,26 @@ impl MDBook {
Ok(())
}
/// Run the entire build process for a particular [`Renderer`].
pub fn execute_build_process(&self, renderer: &dyn Renderer) -> Result<()> {
let mut preprocessed_book = self.book.clone();
/// Run preprocessors and return the final book.
pub fn preprocess_book(&self, renderer: &dyn Renderer) -> Result<(Book, PreprocessorContext)> {
let preprocess_ctx = PreprocessorContext::new(
self.root.clone(),
self.config.clone(),
renderer.name().to_string(),
);
let mut preprocessed_book = self.book.clone();
for preprocessor in &self.preprocessors {
if preprocessor_should_run(&**preprocessor, renderer, &self.config) {
debug!("Running the {} preprocessor.", preprocessor.name());
preprocessed_book = preprocessor.run(&preprocess_ctx, preprocessed_book)?;
}
}
Ok((preprocessed_book, preprocess_ctx))
}
/// Run the entire build process for a particular [`Renderer`].
pub fn execute_build_process(&self, renderer: &dyn Renderer) -> Result<()> {
let (preprocessed_book, preprocess_ctx) = self.preprocess_book(renderer)?;
let name = renderer.name();
let build_dir = self.build_dir_for(name);
@@ -232,22 +258,52 @@ impl MDBook {
/// Run `rustdoc` tests on the book, linking against the provided libraries.
pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
let library_args: Vec<&str> = (0..library_paths.len())
.map(|_| "-L")
.zip(library_paths.into_iter())
.flat_map(|x| vec![x.0, x.1])
// test_chapter with chapter:None will run all tests.
self.test_chapter(library_paths, None)
}
/// 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();
let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
// FIXME: Is "test" the proper renderer name to use here?
let preprocess_context =
PreprocessorContext::new(self.root.clone(), self.config.clone(), "test".to_string());
let mut chapter_found = false;
let book = LinkPreprocessor::new().run(&preprocess_context, self.book.clone())?;
// Index Preprocessor is disabled so that chapter paths continue to point to the
// actual markdown files.
struct TestRenderer;
impl Renderer for TestRenderer {
// FIXME: Is "test" the proper renderer name to use here?
fn name(&self) -> &str {
"test"
}
fn render(&self, _: &RenderContext) -> Result<()> {
Ok(())
}
}
// 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() {
if let BookItem::Chapter(ref ch) = *item {
@@ -256,33 +312,53 @@ impl MDBook {
_ => continue,
};
let path = self.source_dir().join(&chapter_path);
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
let path = temp_dir.path().join(&chapter_path);
let path = temp_dir.path().join(chapter_path);
let mut tmpf = utils::fs::create_file(&path)?;
tmpf.write_all(ch.content.as_bytes())?;
let mut cmd = Command::new("rustdoc");
cmd.arg(&path).arg("--test").args(&library_args);
cmd.current_dir(temp_dir.path())
.arg(chapter_path)
.arg("--test")
.args(&library_args);
if let Some(edition) = self.config.rust.edition {
match edition {
RustEdition::E2015 => {
cmd.args(&["--edition", "2015"]);
cmd.args(["--edition", "2015"]);
}
RustEdition::E2018 => {
cmd.args(&["--edition", "2018"]);
cmd.args(["--edition", "2018"]);
}
RustEdition::E2021 => {
cmd.args(&["--edition", "2021"])
.args(&["-Z", "unstable-options"]);
cmd.args(["--edition", "2021"]);
}
RustEdition::E2024 => {
cmd.args(["--edition", "2024"]);
}
}
}
let output = cmd.output()?;
if color_output {
cmd.args(["--color", "always"]);
}
debug!("running {:?}", cmd);
let output = cmd
.output()
.with_context(|| "failed to execute `rustdoc`")?;
if !output.status.success() {
failed = true;
@@ -298,6 +374,11 @@ impl MDBook {
if failed {
bail!("One or more tests failed");
}
if let Some(chapter) = chapter {
if !chapter_found {
bail!("Chapter not found: {}", chapter);
}
}
Ok(())
}
@@ -373,7 +454,7 @@ fn determine_renderers(config: &Config) -> Vec<Box<dyn Renderer>> {
renderers
}
const DEFAULT_PREPROCESSORS: &[&'static str] = &["links", "index"];
const DEFAULT_PREPROCESSORS: &[&str] = &["links", "index"];
fn is_default_preprocessor(pre: &dyn Preprocessor) -> bool {
let name = pre.name();
@@ -404,15 +485,13 @@ fn determine_preprocessors(config: &Config) -> Result<Vec<Box<dyn Preprocessor>>
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
"Expected preprocessor.{name}.before to be an array"
))
})?;
for after in before {
let after = after.as_str().ok_or_else(|| {
Error::msg(format!(
"Expected preprocessor.{}.before to contain strings",
name
"Expected preprocessor.{name}.before to contain strings"
))
})?;
@@ -431,16 +510,12 @@ fn determine_preprocessors(config: &Config) -> Result<Vec<Box<dyn Preprocessor>>
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
))
Error::msg(format!("Expected preprocessor.{name}.after to be an array"))
})?;
for before in after {
let before = before.as_str().ok_or_else(|| {
Error::msg(format!(
"Expected preprocessor.{}.after to contain strings",
name
"Expected preprocessor.{name}.after to contain strings"
))
})?;
@@ -502,7 +577,7 @@ fn get_custom_preprocessor_cmd(key: &str, table: &Value) -> String {
.get("command")
.and_then(Value::as_str)
.map(ToString::to_string)
.unwrap_or_else(|| format!("mdbook-{}", key))
.unwrap_or_else(|| format!("mdbook-{key}"))
}
fn interpret_custom_renderer(key: &str, table: &Value) -> Box<CmdRenderer> {
@@ -513,7 +588,7 @@ fn interpret_custom_renderer(key: &str, table: &Value) -> Box<CmdRenderer> {
.and_then(Value::as_str)
.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))
}
@@ -551,7 +626,7 @@ fn preprocessor_should_run(
mod tests {
use super::*;
use std::str::FromStr;
use toml::value::{Table, Value};
use toml::value::Table;
#[test]
fn config_defaults_to_html_renderer_if_empty() {
@@ -707,7 +782,7 @@ mod tests {
for preprocessor in &preprocessors {
eprintln!(" {}", preprocessor.name());
}
panic!("{} should come before {}", before, after);
panic!("{before} should come before {after}");
}
};
@@ -743,10 +818,9 @@ mod tests {
let preprocessors = determine_preprocessors(&cfg).unwrap();
assert!(preprocessors
assert!(!preprocessors
.iter()
.find(|preprocessor| preprocessor.name() == "random")
.is_none());
.any(|preprocessor| preprocessor.name() == "random"));
}
#[test]
@@ -763,10 +837,9 @@ mod tests {
let preprocessors = determine_preprocessors(&cfg).unwrap();
assert!(preprocessors
assert!(!preprocessors
.iter()
.find(|preprocessor| preprocessor.name() == "links")
.is_none());
.any(|preprocessor| preprocessor.name() == "links"));
}
#[test]

View File

@@ -1,8 +1,9 @@
use crate::errors::*;
use memchr::{self, Memchr};
use pulldown_cmark::{self, Event, Tag};
use log::{debug, trace, warn};
use memchr::Memchr;
use pulldown_cmark::{DefaultBrokenLinkCallback, Event, HeadingLevel, Tag, TagEnd};
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
use std::iter::FromIterator;
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
@@ -161,7 +162,7 @@ impl From<Link> for SummaryItem {
/// > match the following regex: "[^<>\n[]]+".
struct SummaryParser<'a> {
src: &'a str,
stream: pulldown_cmark::OffsetIter<'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
@@ -208,7 +209,7 @@ macro_rules! collect_events {
}
impl<'a> SummaryParser<'a> {
fn new(text: &str) -> SummaryParser<'_> {
fn new(text: &'a str) -> SummaryParser<'a> {
let pulldown_parser = pulldown_cmark::Parser::new(text).into_offset_iter();
SummaryParser {
@@ -263,7 +264,12 @@ impl<'a> SummaryParser<'a> {
loop {
match self.next_event() {
Some(ev @ Event::Start(Tag::List(..)))
| Some(ev @ Event::Start(Tag::Heading(1))) => {
| Some(
ev @ Event::Start(Tag::Heading {
level: HeadingLevel::H1,
..
}),
) => {
if is_prefix {
// we've finished prefix chapters and are at the start
// of the numbered section.
@@ -273,8 +279,8 @@ impl<'a> SummaryParser<'a> {
bail!(self.parse_error("Suffix chapters cannot be followed by a list"));
}
}
Some(Event::Start(Tag::Link(_type, href, _title))) => {
let link = self.parse_link(href.to_string());
Some(Event::Start(Tag::Link { dest_url, .. })) => {
let link = self.parse_link(dest_url.to_string());
items.push(SummaryItem::Link(link));
}
Some(Event::Rule) => items.push(SummaryItem::Separator),
@@ -302,10 +308,13 @@ impl<'a> SummaryParser<'a> {
break;
}
Some(Event::Start(Tag::Heading(1))) => {
Some(Event::Start(Tag::Heading {
level: HeadingLevel::H1,
..
})) => {
debug!("Found a h1 in the SUMMARY");
let tags = collect_events!(self.stream, end Tag::Heading(1));
let tags = collect_events!(self.stream, end TagEnd::Heading(HeadingLevel::H1));
Some(stringify_events(tags))
}
@@ -334,7 +343,7 @@ impl<'a> SummaryParser<'a> {
/// Finishes parsing a link once the `Event::Start(Tag::Link(..))` has been opened.
fn parse_link(&mut self, href: String) -> Link {
let href = href.replace("%20", " ");
let link_content = collect_events!(self.stream, end Tag::Link(..));
let link_content = collect_events!(self.stream, end TagEnd::Link);
let name = stringify_events(link_content);
let path = if href.is_empty() {
@@ -375,7 +384,12 @@ impl<'a> SummaryParser<'a> {
}
// The expectation is that pulldown cmark will terminate a paragraph before a new
// heading, so we can always count on this to return without skipping headings.
Some(ev @ Event::Start(Tag::Heading(1))) => {
Some(
ev @ Event::Start(Tag::Heading {
level: HeadingLevel::H1,
..
}),
) => {
// we're starting a new part
self.back(ev);
break;
@@ -396,7 +410,7 @@ impl<'a> SummaryParser<'a> {
// Skip over the contents of this tag
while let Some(event) = self.next_event() {
if event == Event::End(other_tag.clone()) {
if event == Event::End(other_tag.clone().into()) {
break;
}
}
@@ -452,7 +466,7 @@ impl<'a> SummaryParser<'a> {
items.push(item);
}
Some(Event::Start(Tag::List(..))) => {
// Skip this tag after comment bacause it is not nested.
// Skip this tag after comment because it is not nested.
if items.is_empty() {
continue;
}
@@ -467,7 +481,7 @@ impl<'a> SummaryParser<'a> {
last_item.nested_items = sub_items;
}
Some(Event::End(Tag::List(..))) => break,
Some(Event::End(TagEnd::List(..))) => break,
Some(_) => {}
None => break,
}
@@ -484,8 +498,8 @@ impl<'a> SummaryParser<'a> {
loop {
match self.next_event() {
Some(Event::Start(Tag::Paragraph)) => continue,
Some(Event::Start(Tag::Link(_type, href, _title))) => {
let mut link = self.parse_link(href.to_string());
Some(Event::Start(Tag::Link { dest_url, .. })) => {
let mut link = self.parse_link(dest_url.to_string());
let mut number = parent.clone();
number.0.push(num_existing_items as u32 + 1);
@@ -527,15 +541,23 @@ impl<'a> SummaryParser<'a> {
fn parse_title(&mut self) -> Option<String> {
loop {
match self.next_event() {
Some(Event::Start(Tag::Heading(1))) => {
Some(Event::Start(Tag::Heading {
level: HeadingLevel::H1,
..
})) => {
debug!("Found a h1 in the SUMMARY");
let tags = collect_events!(self.stream, end Tag::Heading(1));
let tags = collect_events!(self.stream, end TagEnd::Heading(HeadingLevel::H1));
return Some(stringify_events(tags));
}
// Skip a HTML element such as a comment line.
Some(Event::Html(_)) => {}
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,
}
}
@@ -561,11 +583,13 @@ fn get_last_link(links: &mut [SummaryItem]) -> Result<(usize, &mut Link)> {
.iter_mut()
.enumerate()
.filter_map(|(i, item)| item.maybe_link_mut().map(|l| (i, l)))
.rev()
.next()
.ok_or_else(||
anyhow::anyhow!("Unable to get last link because the list of SummaryItems doesn't contain any Links")
.next_back()
.ok_or_else(|| {
anyhow::anyhow!(
"Unable to get last link because the list of SummaryItems \
doesn't contain any Links"
)
})
}
/// Removes the styling from a list of Markdown events and returns just the
@@ -592,7 +616,7 @@ impl Display for SectionNumber {
write!(f, "0")
} else {
for item in &self.0 {
write!(f, "{}.", item)?;
write!(f, "{item}.")?;
}
Ok(())
}
@@ -647,6 +671,18 @@ mod tests {
assert_eq!(got, should_be);
}
#[test]
fn no_initial_title() {
let src = "[Link]()";
let mut parser = SummaryParser::new(src);
assert!(parser.parse_title().is_none());
assert!(matches!(
parser.next_event(),
Some(Event::Start(Tag::Paragraph))
));
}
#[test]
fn parse_title_with_styling() {
let src = "# My **Awesome** Summary";
@@ -726,8 +762,8 @@ mod tests {
let _ = parser.stream.next(); // Discard opening paragraph
let href = match parser.stream.next() {
Some((Event::Start(Tag::Link(_type, href, _title)), _range)) => href.to_string(),
other => panic!("Unreachable, {:?}", other),
Some((Event::Start(Tag::Link { dest_url, .. }), _range)) => dest_url.to_string(),
other => panic!("Unreachable, {other:?}"),
};
let got = parser.parse_link(href);

View File

@@ -1,38 +1,37 @@
use super::command_prelude::*;
use crate::{get_book_dir, open};
use clap::{App, ArgMatches, SubCommand};
use mdbook::errors::Result;
use mdbook::MDBook;
use std::path::PathBuf;
// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("build")
pub fn make_subcommand() -> Command {
Command::new("build")
.about("Builds a book from its markdown files")
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
)
.arg_from_usage(
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",
)
.arg_from_usage("-o, --open 'Opens the compiled book in a web browser'")
.arg_dest_dir()
.arg_root_dir()
.arg_open()
}
// Build command implementation
pub fn execute(args: &ArgMatches) -> Result<()> {
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.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?
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(())

View File

@@ -1,39 +1,111 @@
use super::command_prelude::*;
use crate::get_book_dir;
use anyhow::Context;
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use std::fs;
use std::mem::take;
use std::path::PathBuf;
use std::{fmt, fs};
// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("clean")
pub fn make_subcommand() -> Command {
Command::new("clean")
.about("Deletes a built book")
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
Running this command deletes this directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
)
.arg_from_usage(
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",
)
.arg_dest_dir()
.arg_root_dir()
}
// Clean command implementation
pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> {
let book_dir = get_book_dir(args);
let book = MDBook::load(&book_dir)?;
let 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(),
None => book.root.join(&book.config.build.build_dir),
};
if dir_to_remove.exists() {
fs::remove_dir_all(&dir_to_remove)
.with_context(|| "Unable to remove the build directory")?;
}
let removed = Clean::new(&dir_to_remove)?;
println!("{removed}");
Ok(())
}
/// Formats a number of bytes into a human readable SI-prefixed size.
/// Returns a tuple of `(quantity, units)`.
pub fn human_readable_bytes(bytes: u64) -> (f32, &'static str) {
static UNITS: [&str; 7] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];
let bytes = bytes as f32;
let i = ((bytes.log2() / 10.0) as usize).min(UNITS.len() - 1);
(bytes / 1024_f32.powi(i as i32), UNITS[i])
}
#[derive(Debug)]
pub struct Clean {
num_files_removed: u64,
num_dirs_removed: u64,
total_bytes_removed: u64,
}
impl Clean {
fn new(dir: &PathBuf) -> mdbook::errors::Result<Clean> {
let mut files = vec![dir.clone()];
let mut children = Vec::new();
let mut num_files_removed = 0;
let mut num_dirs_removed = 0;
let mut total_bytes_removed = 0;
if dir.exists() {
while !files.is_empty() {
for file in files {
if let Ok(meta) = file.metadata() {
// Note: This can over-count bytes removed for hard-linked
// files. It also under-counts since it only counts the exact
// byte sizes and not the block sizes.
total_bytes_removed += meta.len();
}
if file.is_file() {
num_files_removed += 1;
} else if file.is_dir() {
num_dirs_removed += 1;
for entry in fs::read_dir(file)? {
children.push(entry?.path());
}
}
}
files = take(&mut children);
}
fs::remove_dir_all(&dir).with_context(|| "Unable to remove the build directory")?;
}
Ok(Clean {
num_files_removed,
num_dirs_removed,
total_bytes_removed,
})
}
}
impl fmt::Display for Clean {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Removed ")?;
match (self.num_files_removed, self.num_dirs_removed) {
(0, 0) => write!(f, "0 files")?,
(0, 1) => write!(f, "1 directory")?,
(0, 2..) => write!(f, "{} directories", self.num_dirs_removed)?,
(1, _) => write!(f, "1 file")?,
(2.., _) => write!(f, "{} files", self.num_files_removed)?,
}
if self.total_bytes_removed == 0 {
Ok(())
} else {
// Don't show a fractional number of bytes.
if self.total_bytes_removed < 1024 {
write!(f, ", {}B total", self.total_bytes_removed)
} else {
let (bytes, unit) = human_readable_bytes(self.total_bytes_removed);
write!(f, ", {bytes:.2}{unit} total")
}
}
}
}

View File

@@ -0,0 +1,59 @@
//! 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"))
}
#[cfg(any(feature = "watch", feature = "serve"))]
fn arg_watcher(self) -> Self {
#[cfg(feature = "watch")]
return self._arg(
Arg::new("watcher")
.long("watcher")
.value_parser(["poll", "native"])
.default_value("poll")
.help("The filesystem watching technique"),
);
#[cfg(not(feature = "watch"))]
return self;
}
}
impl CommandExt for Command {
fn _arg(self, arg: Arg) -> Self {
self.arg(arg)
}
}

View File

@@ -1,5 +1,5 @@
use crate::get_book_dir;
use clap::{App, Arg, ArgMatches, SubCommand};
use clap::{arg, ArgMatches, Command as ClapCommand};
use mdbook::config;
use mdbook::errors::Result;
use mdbook::MDBook;
@@ -8,30 +8,22 @@ use std::io::Write;
use std::process::Command;
// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("init")
pub fn make_subcommand() -> ClapCommand {
ClapCommand::new("init")
.about("Creates the boilerplate structure and files for a new book")
// the {n} denotes a newline which will properly aligned in all help messages
.arg_from_usage(
"[dir] 'Directory to create the book in{n}\
(Defaults to the Current Directory when omitted)'",
)
.arg_from_usage("--theme 'Copies the default theme into your source folder'")
.arg_from_usage("--force 'Skips confirmation prompts'")
.arg(
Arg::with_name("title")
.long("title")
.takes_value(true)
.help("Sets the book title")
.required(false),
arg!([dir]
"Directory to create the book in\n\
(Defaults to the current directory when omitted)"
)
.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::with_name("ignore")
.long("ignore")
.takes_value(true)
.possible_values(&["none", "git"])
.help("Creates a VCS ignore file (i.e. .gitignore)")
.required(false),
arg!(--ignore <ignore> "Creates a VCS ignore file (i.e. .gitignore)")
.value_parser(["none", "git"]),
)
}
@@ -41,12 +33,12 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
let mut builder = MDBook::init(&book_dir);
let mut config = config::Config::default();
// If flag `--theme` is present, copy theme to src
if args.is_present("theme") {
if args.get_flag("theme") {
let theme_dir = book_dir.join("theme");
println!();
println!("Copying the default theme to {}", theme_dir.display());
// Skip this if `--force` is present
if !args.is_present("force") && theme_dir.exists() {
if !args.get_flag("force") && theme_dir.exists() {
println!("This could potentially overwrite files already present in that directory.");
print!("\nAre you sure you want to continue? (y/n) ");
@@ -59,20 +51,22 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
}
}
if let Some(ignore) = args.value_of("ignore") {
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 {
} else if !args.get_flag("force") {
println!("\nDo you want a .gitignore to be created? (y/n)");
if confirm() {
builder.create_gitignore(true);
}
}
config.book.title = if args.is_present("title") {
args.value_of("title").map(String::from)
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()
};
@@ -80,9 +74,9 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
if let Some(author) = get_author_name() {
debug!("Obtained user name from gitconfig: {:?}", author);
config.book.authors.push(author);
builder.with_config(config);
}
builder.with_config(config);
builder.build()?;
println!("\nAll done, no errors...");
@@ -92,7 +86,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
/// Obtains author name from git config file by running the `git config` command.
fn get_author_name() -> Option<String> {
let output = Command::new("git")
.args(&["config", "--get", "user.name"])
.args(["config", "--get", "user.name"])
.output()
.ok()?;
@@ -122,8 +116,5 @@ fn confirm() -> bool {
io::stdout().flush().unwrap();
let mut s = String::new();
io::stdin().read_line(&mut s).ok();
match &*s.trim() {
"Y" | "y" | "yes" | "Yes" => true,
_ => false,
}
matches!(s.trim(), "Y" | "y" | "yes" | "Yes")
}

View File

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

View File

@@ -1,11 +1,11 @@
use super::command_prelude::*;
#[cfg(feature = "watch")]
use super::watch;
use crate::{get_book_dir, open};
use clap::{App, Arg, ArgMatches, SubCommand};
use clap::builder::NonEmptyStringValueParser;
use futures_util::sink::SinkExt;
use futures_util::StreamExt;
use mdbook::errors::*;
use mdbook::utils;
use mdbook::utils::fs::get_404_output_file;
use mdbook::MDBook;
use std::net::{SocketAddr, ToSocketAddrs};
@@ -18,37 +18,31 @@ use warp::Filter;
const LIVE_RELOAD_ENDPOINT: &str = "__livereload";
// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("serve")
pub fn make_subcommand() -> Command {
Command::new("serve")
.about("Serves a book at http://localhost:3000, and rebuilds it on changes")
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'",
)
.arg_from_usage(
"[dir] 'Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'",
)
.arg_dest_dir()
.arg_root_dir()
.arg(
Arg::with_name("hostname")
.short("n")
Arg::new("hostname")
.short('n')
.long("hostname")
.takes_value(true)
.num_args(1)
.default_value("localhost")
.empty_values(false)
.value_parser(NonEmptyStringValueParser::new())
.help("Hostname to listen on for HTTP connections"),
)
.arg(
Arg::with_name("port")
.short("p")
Arg::new("port")
.short('p')
.long("port")
.takes_value(true)
.num_args(1)
.default_value("3000")
.empty_values(false)
.value_parser(NonEmptyStringValueParser::new())
.help("Port to use for HTTP connections"),
)
.arg_from_usage("-o, --open 'Opens the book server in a web browser'")
.arg_open()
.arg_watcher()
}
// Serve command implementation
@@ -56,18 +50,17 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args);
let mut book = MDBook::load(&book_dir)?;
let port = args.value_of("port").unwrap();
let hostname = args.value_of("hostname").unwrap();
let open_browser = args.is_present("open");
let port = args.get_one::<String>("port").unwrap();
let hostname = args.get_one::<String>("hostname").unwrap();
let open_browser = args.get_flag("open");
let address = format!("{}:{}", hostname, port);
let address = format!("{hostname}:{port}");
let livereload_url = format!("ws://{}/{}", address, LIVE_RELOAD_ENDPOINT);
let update_config = |book: &mut MDBook| {
book.config
.set("output.html.livereload-url", &livereload_url)
.expect("livereload-url update failed");
if let Some(dest_dir) = args.value_of("dest-dir") {
.set("output.html.live-reload-endpoint", LIVE_RELOAD_ENDPOINT)
.expect("live-reload-endpoint update failed");
if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") {
book.config.build.build_dir = dest_dir.into();
}
// Override site-url for local serving of the 404 file
@@ -84,8 +77,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
let input_404 = book
.config
.get("output.html.input-404")
.map(toml::Value::as_str)
.and_then(std::convert::identity) // flatten
.and_then(toml::Value::as_str)
.map(ToString::to_string);
let file_404 = get_404_output_file(&input_404);
@@ -97,7 +89,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
serve(build_dir, sockaddr, reload_tx, &file_404);
});
let serving_url = format!("http://{}", address);
let serving_url = format!("http://{address}");
info!("Serving on: {}", serving_url);
if open_browser {
@@ -105,23 +97,12 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
}
#[cfg(feature = "watch")]
watch::trigger_on_change(&book, move |paths, book_dir| {
info!("Files changed: {:?}", paths);
info!("Building book...");
// FIXME: This area is really ugly because we need to re-set livereload :(
let result = MDBook::load(&book_dir).and_then(|mut b| {
update_config(&mut b);
b.build()
});
if let Err(e) = result {
error!("Unable to load the book");
utils::log_backtrace(&e);
} else {
{
let watcher = watch::WatcherKind::from_str(args.get_one::<String>("watcher").unwrap());
watch::rebuild_on_change(watcher, &book_dir, &update_config, &move || {
let _ = tx.send(Message::text("reload"));
}
});
});
}
let _ = thread_handle.join();

View File

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

View File

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

189
src/cmd/watch/native.rs Normal file
View File

@@ -0,0 +1,189 @@
//! A filesystem watcher using native operating system facilities.
use ignore::gitignore::Gitignore;
use mdbook::MDBook;
use std::path::{Path, PathBuf};
use std::sync::mpsc::channel;
use std::thread::sleep;
use std::time::Duration;
pub fn rebuild_on_change(
book_dir: &Path,
update_config: &dyn Fn(&mut MDBook),
post_build: &dyn Fn(),
) {
use notify::RecursiveMode::*;
let mut book = MDBook::load(book_dir).unwrap_or_else(|e| {
error!("failed to load book: {e}");
std::process::exit(1);
});
// Create a channel to receive the events.
let (tx, rx) = channel();
let mut debouncer = match notify_debouncer_mini::new_debouncer(Duration::from_secs(1), tx) {
Ok(d) => d,
Err(e) => {
error!("Error while trying to watch the files:\n\n\t{:?}", e);
std::process::exit(1)
}
};
let watcher = debouncer.watcher();
// Add the source directory to the watcher
if let Err(e) = watcher.watch(&book.source_dir(), Recursive) {
error!("Error while watching {:?}:\n {:?}", book.source_dir(), e);
std::process::exit(1);
};
let _ = watcher.watch(&book.theme_dir(), Recursive);
// Add the book.toml file to the watcher if it exists
let _ = watcher.watch(&book.root.join("book.toml"), NonRecursive);
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...");
loop {
let first_event = rx.recv().unwrap();
sleep(Duration::from_millis(50));
let other_events = rx.try_iter();
let all_events = std::iter::once(first_event).chain(other_events);
let paths: 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() {
info!("Files changed: {paths:?}");
match MDBook::load(book_dir) {
Ok(mut b) => {
update_config(&mut b);
if let Err(e) = b.build() {
error!("failed to build the book: {e:?}");
} else {
post_build();
}
book = b;
}
Err(e) => error!("failed to load book config: {e:?}"),
}
}
}
}
fn remove_ignored_files(book_root: &Path, paths: &[PathBuf]) -> Vec<PathBuf> {
if paths.is_empty() {
return vec![];
}
match super::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()
}
}
}
// 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 = pathdiff::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()
}
#[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])
}
}

386
src/cmd/watch/poller.rs Normal file
View File

@@ -0,0 +1,386 @@
//! A simple poll-based filesystem watcher.
//!
//! This exists because the native change notifications have historically had
//! lots of problems. Various operating systems and different filesystems have
//! had problems correctly reporting changes.
use ignore::gitignore::Gitignore;
use mdbook::MDBook;
use pathdiff::diff_paths;
use std::collections::HashMap;
use std::fs::FileType;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant, SystemTime};
use walkdir::WalkDir;
/// Calls the closure when a book source file is changed, blocking indefinitely.
pub fn rebuild_on_change(
book_dir: &Path,
update_config: &dyn Fn(&mut MDBook),
post_build: &dyn Fn(),
) {
let mut book = MDBook::load(book_dir).unwrap_or_else(|e| {
error!("failed to load book: {e}");
std::process::exit(1);
});
let mut watcher = Watcher::new(book_dir);
info!("Watching for changes...");
// Scan once to initialize the starting point.
watcher.set_roots(&book);
watcher.scan();
// Track average scan time, to help investigate if the poller is taking
// undesirably long. This is not a rigorous benchmark, just a rough
// estimate.
const AVG_SIZE: usize = 60;
let mut avgs = vec![0.0; AVG_SIZE];
let mut avg_i = 0;
loop {
std::thread::sleep(Duration::new(1, 0));
watcher.set_roots(&book);
let start = Instant::now();
let paths = watcher.scan();
let elapsed = start.elapsed().as_secs_f64();
avgs[avg_i] = elapsed;
avg_i += 1;
if avg_i >= AVG_SIZE {
avg_i = 0;
let avg = avgs.iter().sum::<f64>() / (avgs.len() as f64);
trace!(
"scan average time: {avg:.2}s, scan size is {}",
watcher.path_data.len()
);
}
if !paths.is_empty() {
info!("Files changed: {paths:?}");
match MDBook::load(book_dir) {
Ok(mut b) => {
update_config(&mut b);
if let Err(e) = b.build() {
error!("failed to build the book: {e:?}");
} else {
post_build();
}
book = b;
}
Err(e) => error!("failed to load book config: {e:?}"),
}
}
}
}
#[derive(PartialEq)]
struct PathData {
file_type: FileType,
mtime: SystemTime,
size: u64,
}
/// A very simple poll-watcher that scans for modified files.
#[derive(Default)]
struct Watcher {
/// The root paths where it will recursively scan for changes.
root_paths: Vec<PathBuf>,
/// Data about files on disk.
path_data: HashMap<PathBuf, PathData>,
/// Filters paths that will be watched.
ignore: Option<(PathBuf, Gitignore)>,
}
impl Watcher {
fn new(book_root: &Path) -> Watcher {
// FIXME: ignore should be reloaded when it changes.
let ignore = super::find_gitignore(book_root).map(|gitignore_path| {
let (ignore, err) = Gitignore::new(&gitignore_path);
if let Some(err) = err {
warn!(
"error reading gitignore `{}`: {err}",
gitignore_path.display()
);
}
// 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).
let ignore_path = ignore
.path()
.canonicalize()
.expect("ignore root canonicalize error");
(ignore_path, ignore)
});
Watcher {
ignore,
..Default::default()
}
}
/// Sets the root directories where scanning will start.
fn set_roots(&mut self, book: &MDBook) {
let mut root_paths = vec![
book.source_dir(),
book.theme_dir(),
book.root.join("book.toml"),
];
root_paths.extend(
book.config
.build
.extra_watch_dirs
.iter()
.map(|path| book.root.join(path)),
);
if let Some(html_config) = book.config.html_config() {
root_paths.extend(
html_config
.additional_css
.iter()
.chain(html_config.additional_js.iter())
.map(|path| book.root.join(path)),
);
}
self.root_paths = root_paths;
}
/// Scans for changes.
///
/// Returns the paths that have changed.
fn scan(&mut self) -> Vec<PathBuf> {
let ignore = &self.ignore;
let new_path_data: HashMap<_, _> = self
.root_paths
.iter()
.filter(|root| root.exists())
.flat_map(|root| {
WalkDir::new(root)
.follow_links(true)
.into_iter()
.filter_entry(|entry| {
if let Some((ignore_path, ignore)) = ignore {
let path = entry.path();
// Canonicalization helps with removing `..` and
// `.` entries, which can cause issues with
// diff_paths.
let path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
let relative_path = diff_paths(&path, &ignore_path)
.expect("One of the paths should be an absolute");
if ignore
.matched_path_or_any_parents(&relative_path, relative_path.is_dir())
.is_ignore()
{
trace!("ignoring {path:?}");
return false;
}
}
true
})
.filter_map(move |entry| {
let entry = match entry {
Ok(e) => e,
Err(e) => {
debug!("failed to scan {root:?}: {e}");
return None;
}
};
if entry.file_type().is_dir() {
// Changes to directories themselves aren't
// particularly interesting.
return None;
}
let path = entry.path().to_path_buf();
let meta = match entry.metadata() {
Ok(meta) => meta,
Err(e) => {
debug!("failed to scan {path:?}: {e}");
return None;
}
};
let mtime = meta.modified().unwrap_or(SystemTime::UNIX_EPOCH);
let pd = PathData {
file_type: meta.file_type(),
mtime,
size: meta.len(),
};
Some((path, pd))
})
})
.collect();
let mut paths = Vec::new();
for (new_path, new_data) in &new_path_data {
match self.path_data.get(new_path) {
Some(old_data) => {
if new_data != old_data {
paths.push(new_path.to_path_buf());
}
}
None => {
paths.push(new_path.clone());
}
}
}
for old_path in self.path_data.keys() {
if !new_path_data.contains_key(old_path) {
paths.push(old_path.to_path_buf());
}
}
self.path_data = new_path_data;
paths
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Helper for testing the watcher.
fn check_watch_behavior(
gitignore_path: &str,
gitignore: &str,
book_root_path: &str,
ignored: &[&str],
not_ignored: &[&str],
extra_setup: &dyn Fn(&Path),
) {
// Create the book and initialize things.
let temp = tempfile::Builder::new()
.prefix("mdbook-")
.tempdir()
.unwrap();
let root = temp.path();
let book_root = root.join(book_root_path);
// eprintln!("book_root={book_root:?}",);
MDBook::init(&book_root).build().unwrap();
std::fs::write(root.join(gitignore_path), gitignore).unwrap();
let create = |paths: &[&str]| {
let mut paths = paths
.iter()
.map(|path| root.join(path))
.inspect(|path| {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, "initial content").unwrap();
})
.map(|path| path.canonicalize().unwrap())
.collect::<Vec<_>>();
paths.sort();
paths
};
let ignored = create(ignored);
let not_ignored = create(not_ignored);
extra_setup(&book_root);
// Create a watcher and check its behavior.
let book = MDBook::load(&book_root).unwrap();
let mut watcher = Watcher::new(&book_root);
watcher.set_roots(&book);
// Do an initial scan to initialize its state.
watcher.scan();
// Verify the steady state is empty.
let changed = watcher.scan();
assert_eq!(changed, Vec::<PathBuf>::new());
// Modify all files, and verify that only not_ignored are detected.
for path in ignored.iter().chain(not_ignored.iter()) {
std::fs::write(path, "modified").unwrap();
}
let changed = watcher.scan();
let mut changed = changed
.into_iter()
.map(|p| p.canonicalize().unwrap())
.collect::<Vec<_>>();
changed.sort();
assert_eq!(changed, not_ignored);
// Verify again that steady state is empty.
let changed = watcher.scan();
assert_eq!(changed, Vec::<PathBuf>::new());
}
#[test]
fn test_ignore() {
// Basic gitignore test.
check_watch_behavior(
"foo/.gitignore",
"*.tmp",
"foo",
&["foo/src/somefile.tmp"],
&["foo/src/chapter.md"],
&|_book_root| {},
);
}
#[test]
fn test_ignore_in_parent() {
// gitignore is in the parent of the book
check_watch_behavior(
".gitignore",
"*.tmp\nsomedir/\n/inroot\n/foo/src/inbook\n",
"foo",
&[
"foo/src/somefile.tmp",
"foo/src/somedir/somefile",
"inroot/somefile",
"foo/src/inbook/somefile",
],
&["foo/src/inroot/somefile"],
&|_book_root| {},
);
}
#[test]
fn test_ignore_canonical() {
// test with path with ..
check_watch_behavior(
".gitignore",
"*.tmp\nsomedir/\n/foo/src/inbook\n",
"bar/../foo",
&[
"foo/src/somefile.tmp",
"foo/src/somedir/somefile",
"foo/src/inbook/somefile",
],
&["foo/src/chapter.md"],
&|_book_root| {},
);
}
#[test]
fn test_scan_extra_watch() {
// Check behavior with extra-watch-dirs
check_watch_behavior(
".gitignore",
"*.tmp\n/outside-root/ignoreme\n/foo/examples/ignoreme\n",
"foo",
&[
"foo/src/somefile.tmp",
"foo/examples/example.tmp",
"outside-root/somefile.tmp",
"outside-root/ignoreme",
"foo/examples/ignoreme",
],
&[
"foo/src/chapter.md",
"foo/examples/example.rs",
"foo/examples/example2.rs",
"outside-root/image.png",
],
&|book_root| {
std::fs::write(
book_root.join("book.toml"),
r#"
[book]
title = "foo"
[build]
extra-watch-dirs = [
"examples",
"../outside-root",
]
"#,
)
.unwrap();
},
);
}
}

View File

@@ -49,6 +49,7 @@
#![deny(missing_docs)]
use log::{debug, trace, warn};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;
use std::env;
@@ -57,7 +58,7 @@ use std::io::Read;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use toml::value::Table;
use toml::{self, Value};
use toml::Value;
use crate::errors::*;
use crate::utils::{self, toml_ext::TomlExt};
@@ -144,7 +145,7 @@ impl Config {
if let serde_json::Value::Object(ref map) = parsed_value {
// To `set` each `key`, we wrap them as `prefix.key`
for (k, v) in map {
let full_key = format!("{}.{}", key, k);
let full_key = format!("{key}.{k}");
self.set(&full_key, v).expect("unreachable");
}
return;
@@ -227,10 +228,10 @@ impl Config {
let value = Value::try_from(value)
.with_context(|| "Unable to represent the item as a JSON Value")?;
if index.starts_with("book.") {
self.book.update_value(&index[5..], value);
} else if index.starts_with("build.") {
self.build.update_value(&index[6..], value);
if let Some(key) = index.strip_prefix("book.") {
self.book.update_value(key, value);
} else if let Some(key) = index.strip_prefix("build.") {
self.build.update_value(key, value);
} else {
self.rest.insert(index, value);
}
@@ -295,7 +296,7 @@ impl Default for Config {
}
}
impl<'de> Deserialize<'de> for Config {
impl<'de> serde::Deserialize<'de> for Config {
fn deserialize<D: Deserializer<'de>>(de: D) -> std::result::Result<Self, D::Error> {
let raw = Value::deserialize(de)?;
@@ -307,7 +308,7 @@ impl<'de> Deserialize<'de> for Config {
warn!("`description` under a table called `[book]`, move the `destination` entry");
warn!("from `[output.html]`, renamed to `build-dir`, under a table called");
warn!("`[build]`, and it should all work.");
warn!("Documentation: http://rust-lang.github.io/mdBook/format/config.html");
warn!("Documentation: https://rust-lang.github.io/mdBook/format/config.html");
return Ok(Config::from_legacy(raw));
}
@@ -371,15 +372,8 @@ impl Serialize for Config {
}
fn parse_env(key: &str) -> Option<String> {
const PREFIX: &str = "MDBOOK_";
if key.starts_with(PREFIX) {
let key = &key[PREFIX.len()..];
Some(key.to_lowercase().replace("__", ".").replace("_", "-"))
} else {
None
}
key.strip_prefix("MDBOOK_")
.map(|key| key.to_lowercase().replace("__", ".").replace('_', "-"))
}
fn is_legacy_format(table: &Value) -> bool {
@@ -417,6 +411,9 @@ pub struct BookConfig {
pub multilingual: bool,
/// The main language of the book.
pub language: Option<String>,
/// The direction of text in the book: Left-to-right (LTR) or Right-to-left (RTL).
/// When not specified, the text direction is derived from [`BookConfig::language`].
pub text_direction: Option<TextDirection>,
}
impl Default for BookConfig {
@@ -428,6 +425,43 @@ impl Default for BookConfig {
src: PathBuf::from("src"),
multilingual: false,
language: Some(String::from("en")),
text_direction: None,
}
}
}
impl BookConfig {
/// Gets the realized text direction, either from [`BookConfig::text_direction`]
/// or derived from [`BookConfig::language`], to be used by templating engines.
pub fn realized_text_direction(&self) -> TextDirection {
if let Some(direction) = self.text_direction {
direction
} else {
TextDirection::from_lang_code(self.language.as_deref().unwrap_or_default())
}
}
}
/// Text direction to use for HTML output
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum TextDirection {
/// Left to right.
#[serde(rename = "ltr")]
LeftToRight,
/// Right to left
#[serde(rename = "rtl")]
RightToLeft,
}
impl TextDirection {
/// Gets the text direction from language code
pub fn from_lang_code(code: &str) -> Self {
match code {
// list sourced from here: https://github.com/abarrak/rtl/blob/master/lib/rtl/core.rb#L16
"ar" | "ara" | "arc" | "ae" | "ave" | "egy" | "he" | "heb" | "nqo" | "pal" | "phn"
| "sam" | "syc" | "syr" | "fa" | "per" | "fas" | "ku" | "kur" | "ur" | "urd"
| "pus" | "ps" | "yi" | "yid" => TextDirection::RightToLeft,
_ => TextDirection::LeftToRight,
}
}
}
@@ -444,6 +478,8 @@ pub struct BuildConfig {
/// Should the default preprocessors always be used when they are
/// compatible with the renderer?
pub use_default_preprocessors: bool,
/// Extra directories to trigger rebuild when watching/serving
pub extra_watch_dirs: Vec<PathBuf>,
}
impl Default for BuildConfig {
@@ -452,6 +488,7 @@ impl Default for BuildConfig {
build_dir: PathBuf::from("book"),
create_missing: true,
use_default_preprocessors: true,
extra_watch_dirs: Vec::new(),
}
}
}
@@ -467,6 +504,9 @@ pub struct RustConfig {
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
/// Rust edition to use for the code.
pub enum RustEdition {
/// The 2024 edition of Rust
#[serde(rename = "2024")]
E2024,
/// The 2021 edition of Rust
#[serde(rename = "2021")]
E2021,
@@ -489,7 +529,9 @@ pub struct HtmlConfig {
/// The theme to use if the browser requests the dark version of the site.
/// Defaults to 'navy'.
pub preferred_dark_theme: Option<String>,
/// Use "smart quotes" instead of the usual `"` character.
/// Supports smart quotes, apostrophes, ellipsis, en-dash, and em-dash.
pub smart_punctuation: bool,
/// Deprecated alias for `smart_punctuation`.
pub curly_quotes: bool,
/// Should mathjax be enabled?
pub mathjax_support: bool,
@@ -507,6 +549,8 @@ pub struct HtmlConfig {
/// Playground settings.
#[serde(alias = "playpen")]
pub playground: Playground,
/// Code settings.
pub code: Code,
/// Print settings.
pub print: Print,
/// Don't render section labels.
@@ -533,14 +577,13 @@ pub struct HtmlConfig {
/// directly jumping to editing the currently viewed page.
/// Contains {path} that is replaced with chapter source file path
pub edit_url_template: Option<String>,
/// This is used as a bit of a workaround for the `mdbook serve` command.
/// Basically, because you set the websocket port from the command line, the
/// `mdbook serve` command needs a way to let the HTML renderer know where
/// to point livereloading at, if it has been enabled.
/// Endpoint of websocket, for livereload usage. Value loaded from .toml
/// file is ignored, because our code overrides this field with an
/// internal value (`LIVE_RELOAD_ENDPOINT)
///
/// This config item *should not be edited* by the end user.
#[doc(hidden)]
pub livereload_url: Option<String>,
pub live_reload_endpoint: Option<String>,
/// The mapping from old pages to new pages/URLs to use when generating
/// redirects.
pub redirect: HashMap<String, String>,
@@ -552,6 +595,7 @@ impl Default for HtmlConfig {
theme: None,
default_theme: None,
preferred_dark_theme: None,
smart_punctuation: false,
curly_quotes: false,
mathjax_support: false,
copy_fonts: true,
@@ -560,6 +604,7 @@ impl Default for HtmlConfig {
additional_js: Vec::new(),
fold: Fold::default(),
playground: Playground::default(),
code: Code::default(),
print: Print::default(),
no_section_label: false,
search: None,
@@ -569,7 +614,7 @@ impl Default for HtmlConfig {
input_404: None,
site_url: None,
cname: None,
livereload_url: None,
live_reload_endpoint: None,
redirect: HashMap::new(),
}
}
@@ -584,19 +629,29 @@ impl HtmlConfig {
None => root.join("theme"),
}
}
/// Returns `true` if smart punctuation is enabled.
pub fn smart_punctuation(&self) -> bool {
self.smart_punctuation || self.curly_quotes
}
}
/// Configuration for how to render the print icon, print.html, and print.css.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[serde(default, rename_all = "kebab-case")]
pub struct Print {
/// Whether print support is enabled.
pub enable: bool,
/// Insert page breaks between chapters. Default: `true`.
pub page_break: bool,
}
impl Default for Print {
fn default() -> Self {
Self { enable: true }
Self {
enable: true,
page_break: true,
}
}
}
@@ -612,7 +667,7 @@ pub struct Fold {
pub level: u8,
}
/// Configuration for tweaking how the the HTML renderer handles the playground.
/// Configuration for tweaking how the HTML renderer handles the playground.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct Playground {
@@ -625,6 +680,8 @@ pub struct Playground {
pub copy_js: bool,
/// Display line numbers on playground snippets. Default: `false`.
pub line_numbers: bool,
/// Display the run button. Default: `true`
pub runnable: bool,
}
impl Default for Playground {
@@ -634,10 +691,19 @@ impl Default for Playground {
copyable: true,
copy_js: true,
line_numbers: false,
runnable: true,
}
}
}
/// Configuration for tweaking how the HTML renderer handles code blocks.
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct Code {
/// A prefix string to hide lines per language (one or more chars).
pub hidelines: HashMap<String, String>,
}
/// Configuration of the search functionality of the HTML renderer.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
@@ -669,6 +735,11 @@ pub struct Search {
/// Copy JavaScript files for the search functionality to the output directory?
/// Default: `true`.
pub copy_js: bool,
/// Specifies search settings for the given path.
///
/// The path can be for a specific chapter, or a directory. This will
/// merge recursively, with more specific paths taking precedence.
pub chapter: HashMap<String, SearchChapterSettings>,
}
impl Default for Search {
@@ -685,10 +756,19 @@ impl Default for Search {
expand: true,
heading_split_level: 3,
copy_js: true,
chapter: HashMap::new(),
}
}
}
/// Search options for chapters (or paths).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(default, rename_all = "kebab-case")]
pub struct SearchChapterSettings {
/// Whether or not indexing is enabled, default `true`.
pub enable: Option<bool>,
}
/// Allows you to "update" any arbitrary field in a struct by round-tripping via
/// a `toml::Value`.
///
@@ -699,7 +779,7 @@ trait Updateable<'de>: Serialize + Deserialize<'de> {
let mut raw = Value::try_from(&self).expect("unreachable");
if let Ok(value) = Value::try_from(value) {
let _ = raw.insert(key, value);
raw.insert(key, value);
} else {
return;
}
@@ -716,6 +796,7 @@ impl<'de, T> Updateable<'de> for T where T: Serialize + Deserialize<'de> {}
mod tests {
use super::*;
use crate::utils::fs::get_404_output_file;
use serde_json::json;
const COMPLEX_CONFIG: &str = r#"
[book]
@@ -734,7 +815,7 @@ mod tests {
[output.html]
theme = "./themedir"
default-theme = "rust"
curly-quotes = true
smart-punctuation = true
google-analytics = "123456"
additional-css = ["./foo/bar/baz.css"]
git-repository-url = "https://foo.com/"
@@ -764,11 +845,13 @@ mod tests {
multilingual: true,
src: PathBuf::from("source"),
language: Some(String::from("ja")),
text_direction: None,
};
let build_should_be = BuildConfig {
build_dir: PathBuf::from("outputs"),
create_missing: false,
use_default_preprocessors: true,
extra_watch_dirs: Vec::new(),
};
let rust_should_be = RustConfig { edition: None };
let playground_should_be = Playground {
@@ -776,9 +859,10 @@ mod tests {
copyable: true,
copy_js: true,
line_numbers: false,
runnable: true,
};
let html_should_be = HtmlConfig {
curly_quotes: true,
smart_punctuation: true,
google_analytics: Some(String::from("123456")),
additional_css: vec![PathBuf::from("./foo/bar/baz.css")],
theme: Some(PathBuf::from("./themedir")),
@@ -806,6 +890,22 @@ mod tests {
assert_eq!(got.html_config().unwrap(), html_should_be);
}
#[test]
fn disable_runnable() {
let src = r#"
[book]
title = "Some Book"
description = "book book book"
authors = ["Shogo Takata"]
[output.html.playground]
runnable = false
"#;
let got = Config::from_str(src).unwrap();
assert!(!got.html_config().unwrap().playground.runnable);
}
#[test]
fn edition_2015() {
let src = r#"
@@ -942,7 +1042,7 @@ mod tests {
[output.html]
destination = "my-book" # the output files will be generated in `root/my-book` instead of `root/book`
theme = "my-theme"
curly-quotes = true
smart-punctuation = true
google-analytics = "123456"
additional-css = ["custom.css", "custom2.css"]
additional-js = ["custom.js"]
@@ -962,11 +1062,12 @@ mod tests {
build_dir: PathBuf::from("my-book"),
create_missing: true,
use_default_preprocessors: true,
extra_watch_dirs: Vec::new(),
};
let html_should_be = HtmlConfig {
theme: Some(PathBuf::from("my-theme")),
curly_quotes: true,
smart_punctuation: true,
google_analytics: Some(String::from("123456")),
additional_css: vec![PathBuf::from("custom.css"), PathBuf::from("custom2.css")],
additional_js: vec![PathBuf::from("custom.js")],
@@ -1012,7 +1113,7 @@ mod tests {
fn encode_env_var(key: &str) -> String {
format!(
"MDBOOK_{}",
key.to_uppercase().replace('.', "__").replace("-", "_")
key.to_uppercase().replace('.', "__").replace('-', "_")
)
}
@@ -1036,11 +1137,10 @@ mod tests {
}
#[test]
#[allow(clippy::approx_constant)]
fn update_config_using_env_var_and_complex_value() {
let mut cfg = Config::default();
let key = "foo-bar.baz";
let value = json!({"array": [1, 2, 3], "number": 3.14});
let value = json!({"array": [1, 2, 3], "number": 13.37});
let value_str = serde_json::to_string(&value).unwrap();
assert!(cfg.get(key).is_none());
@@ -1098,6 +1198,73 @@ mod tests {
assert_eq!(&get_404_output_file(&html_config.input_404), "missing.html");
}
#[test]
fn text_direction_ltr() {
let src = r#"
[book]
text-direction = "ltr"
"#;
let got = Config::from_str(src).unwrap();
assert_eq!(got.book.text_direction, Some(TextDirection::LeftToRight));
}
#[test]
fn text_direction_rtl() {
let src = r#"
[book]
text-direction = "rtl"
"#;
let got = Config::from_str(src).unwrap();
assert_eq!(got.book.text_direction, Some(TextDirection::RightToLeft));
}
#[test]
fn text_direction_none() {
let src = r#"
[book]
"#;
let got = Config::from_str(src).unwrap();
assert_eq!(got.book.text_direction, None);
}
#[test]
fn test_text_direction() {
let mut cfg = BookConfig::default();
// test deriving the text direction from language codes
cfg.language = Some("ar".into());
assert_eq!(cfg.realized_text_direction(), TextDirection::RightToLeft);
cfg.language = Some("he".into());
assert_eq!(cfg.realized_text_direction(), TextDirection::RightToLeft);
cfg.language = Some("en".into());
assert_eq!(cfg.realized_text_direction(), TextDirection::LeftToRight);
cfg.language = Some("ja".into());
assert_eq!(cfg.realized_text_direction(), TextDirection::LeftToRight);
// test forced direction
cfg.language = Some("ar".into());
cfg.text_direction = Some(TextDirection::LeftToRight);
assert_eq!(cfg.realized_text_direction(), TextDirection::LeftToRight);
cfg.language = Some("ar".into());
cfg.text_direction = Some(TextDirection::RightToLeft);
assert_eq!(cfg.realized_text_direction(), TextDirection::RightToLeft);
cfg.language = Some("en".into());
cfg.text_direction = Some(TextDirection::LeftToRight);
assert_eq!(cfg.realized_text_direction(), TextDirection::LeftToRight);
cfg.language = Some("en".into());
cfg.text_direction = Some(TextDirection::RightToLeft);
assert_eq!(cfg.realized_text_direction(), TextDirection::RightToLeft);
}
#[test]
#[should_panic(expected = "Invalid configuration file")]
fn invalid_language_type_error() {
@@ -1150,4 +1317,57 @@ mod tests {
Config::from_str(src).unwrap();
}
#[test]
fn print_config() {
let src = r#"
[output.html.print]
enable = false
"#;
let got = Config::from_str(src).unwrap();
let html_config = got.html_config().unwrap();
assert!(!html_config.print.enable);
assert!(html_config.print.page_break);
let src = r#"
[output.html.print]
page-break = false
"#;
let got = Config::from_str(src).unwrap();
let html_config = got.html_config().unwrap();
assert!(html_config.print.enable);
assert!(!html_config.print.page_break);
}
#[test]
fn curly_quotes_or_smart_punctuation() {
let src = r#"
[book]
title = "mdBook Documentation"
[output.html]
smart-punctuation = true
"#;
let config = Config::from_str(src).unwrap();
assert_eq!(config.html_config().unwrap().smart_punctuation(), true);
let src = r#"
[book]
title = "mdBook Documentation"
[output.html]
curly-quotes = true
"#;
let config = Config::from_str(src).unwrap();
assert_eq!(config.html_config().unwrap().smart_punctuation(), true);
let src = r#"
[book]
title = "mdBook Documentation"
"#;
let config = Config::from_str(src).unwrap();
assert_eq!(
config.html_config().unwrap_or_default().smart_punctuation(),
false
);
}
}

View File

@@ -82,20 +82,6 @@
#![deny(missing_docs)]
#![deny(rust_2018_idioms)]
#![allow(clippy::comparison_chain)]
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate serde_json;
#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;
pub mod book;
pub mod config;

View File

@@ -5,14 +5,15 @@ extern crate log;
use anyhow::anyhow;
use chrono::Local;
use clap::{App, AppSettings, Arg, ArgMatches, Shell, SubCommand};
use clap::{Arg, ArgMatches, Command};
use clap_complete::Shell;
use env_logger::Builder;
use log::LevelFilter;
use mdbook::utils;
use std::env;
use std::ffi::OsStr;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
mod cmd;
@@ -21,29 +22,33 @@ const VERSION: &str = concat!("v", crate_version!());
fn main() {
init_logger();
let app = create_clap_app();
let command = create_clap_command();
// Check which subcomamnd the user ran...
let res = match app.get_matches().subcommand() {
("init", Some(sub_matches)) => cmd::init::execute(sub_matches),
("build", Some(sub_matches)) => cmd::build::execute(sub_matches),
("clean", Some(sub_matches)) => cmd::clean::execute(sub_matches),
// 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")]
("watch", Some(sub_matches)) => cmd::watch::execute(sub_matches),
Some(("watch", sub_matches)) => cmd::watch::execute(sub_matches),
#[cfg(feature = "serve")]
("serve", Some(sub_matches)) => cmd::serve::execute(sub_matches),
("test", Some(sub_matches)) => cmd::test::execute(sub_matches),
("completions", Some(sub_matches)) => (|| {
let shell: Shell = sub_matches
.value_of("shell")
.ok_or_else(|| anyhow!("Shell name missing."))?
.parse()
.map_err(|s| anyhow!("Invalid shell: {}", s))?;
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."))?;
create_clap_app().gen_completions_to("mdbook", shell, &mut std::io::stdout().lock());
let mut complete_app = create_clap_command();
clap_complete::generate(
*shell,
&mut complete_app,
"mdbook",
&mut std::io::stdout().lock(),
);
Ok(())
})(),
(_, _) => unreachable!(),
_ => unreachable!(),
};
if let Err(e) = res {
@@ -54,14 +59,13 @@ fn main() {
}
/// Create a list of valid arguments and sub-commands
fn create_clap_app<'a, 'b>() -> App<'a, 'b> {
let app = App::new(crate_name!())
fn create_clap_command() -> Command {
let app = Command::new(crate_name!())
.about(crate_description!())
.author("Mathieu David <mathieudavid@mathieudavid.org>")
.version(VERSION)
.setting(AppSettings::GlobalVersion)
.setting(AppSettings::ArgRequiredElseHelp)
.setting(AppSettings::ColoredHelp)
.propagate_version(true)
.arg_required_else_help(true)
.after_help(
"For more information about a specific command, try `mdbook <command> --help`\n\
The source code for mdBook is available at: https://github.com/rust-lang/mdBook",
@@ -71,12 +75,11 @@ fn create_clap_app<'a, 'b>() -> App<'a, 'b> {
.subcommand(cmd::test::make_subcommand())
.subcommand(cmd::clean::make_subcommand())
.subcommand(
SubCommand::with_name("completions")
Command::new("completions")
.about("Generate shell completions for your shell to stdout")
.arg(
Arg::with_name("shell")
.takes_value(true)
.possible_values(&Shell::variants())
Arg::new("shell")
.value_parser(clap::value_parser!(Shell))
.help("the shell to generate completions for")
.value_name("SHELL")
.required(true),
@@ -118,11 +121,10 @@ fn init_logger() {
}
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...
let p = Path::new(dir);
if p.is_relative() {
env::current_dir().unwrap().join(dir)
env::current_dir().unwrap().join(p)
} else {
p.to_path_buf()
}
@@ -137,3 +139,8 @@ fn open<P: AsRef<OsStr>>(path: P) {
error!("Error opening web browser: {}", e);
}
}
#[test]
fn verify_app() {
create_clap_command().debug_assert();
}

View File

@@ -1,6 +1,7 @@
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};

View File

@@ -1,10 +1,11 @@
use regex::Regex;
use std::path::Path;
use crate::errors::*;
use super::{Preprocessor, PreprocessorContext};
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
/// `README.md` is the de facto index file in markdown-based documentation.
@@ -67,9 +68,8 @@ fn warn_readme_name_conflict<P: AsRef<Path>>(readme_path: P, index_path: P) {
}
fn is_readme_file<P: AsRef<Path>>(path: P) -> bool {
lazy_static! {
static ref RE: Regex = Regex::new(r"(?i)^readme$").unwrap();
}
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)^readme$").unwrap());
RE.is_match(
path.as_ref()
.file_stem()

View File

@@ -10,6 +10,8 @@ use std::path::{Path, PathBuf};
use super::{Preprocessor, PreprocessorContext};
use crate::book::{Book, BookItem};
use log::{error, warn};
use once_cell::sync::Lazy;
const ESCAPE_CHAR: char = '\\';
const MAX_LINK_NESTED_DEPTH: usize = 10;
@@ -91,7 +93,7 @@ where
for link in find_links(s) {
replaced.push_str(&s[previous_end_index..link.start_index]);
match link.render_with_path(&path, chapter_title) {
match link.render_with_path(path, chapter_title) {
Ok(new_content) => {
if depth < MAX_LINK_NESTED_DEPTH {
if let Some(rel_path) = link.link_type.relative_path(path) {
@@ -146,6 +148,7 @@ enum RangeOrAnchor {
}
// 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>),
@@ -324,7 +327,7 @@ impl<'a> Link<'a> {
let base = base.as_ref();
match self.link_type {
// omit the escape char
LinkType::Escaped => Ok((&self.link_text[1..]).to_owned()),
LinkType::Escaped => Ok(self.link_text[1..].to_owned()),
LinkType::Include(ref pat, ref range_or_anchor) => {
let target = base.join(pat);
@@ -407,19 +410,20 @@ impl<'a> Iterator for LinkIter<'a> {
fn find_links(contents: &str) -> LinkIter<'_> {
// lazily compute following regex
// r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([^}]+)\}\}")?;
lazy_static! {
static ref RE: Regex = Regex::new(
static RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?x) # insignificant whitespace mode
\\\{\{\#.*\}\} # match escaped link
| # or
\{\{\s* # link opening parens and whitespace
\#([a-zA-Z0-9_]+) # link type
\s+ # separating whitespace
([^}]+) # link target path and space separated properties
\}\} # link closing parens"
\\\{\{\#.*\}\} # match escaped link
| # or
\{\{\s* # link opening parens and whitespace
\#([a-zA-Z0-9_]+) # link type
\s+ # separating whitespace
([^}]+) # link target path and space separated properties
\}\} # link closing parens",
)
.unwrap();
}
.unwrap()
});
LinkIter(RE.captures_iter(contents))
}
@@ -489,7 +493,7 @@ mod tests {
let s = "Some random text with {{#playground file.rs}} and {{#playground test.rs }}...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
println!("\nOUTPUT: {res:?}\n");
assert_eq!(
res,
@@ -515,7 +519,7 @@ mod tests {
let s = "Some random text with {{#playground foo-bar\\baz/_c++.rs}}...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
println!("\nOUTPUT: {res:?}\n");
assert_eq!(
res,
@@ -532,7 +536,7 @@ mod tests {
fn test_find_links_with_range() {
let s = "Some random text with {{#include file.rs:10:20}}...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
println!("\nOUTPUT: {res:?}\n");
assert_eq!(
res,
vec![Link {
@@ -551,7 +555,7 @@ mod tests {
fn test_find_links_with_line_number() {
let s = "Some random text with {{#include file.rs:10}}...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
println!("\nOUTPUT: {res:?}\n");
assert_eq!(
res,
vec![Link {
@@ -570,7 +574,7 @@ mod tests {
fn test_find_links_with_from_range() {
let s = "Some random text with {{#include file.rs:10:}}...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
println!("\nOUTPUT: {res:?}\n");
assert_eq!(
res,
vec![Link {
@@ -589,7 +593,7 @@ mod tests {
fn test_find_links_with_to_range() {
let s = "Some random text with {{#include file.rs::20}}...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
println!("\nOUTPUT: {res:?}\n");
assert_eq!(
res,
vec![Link {
@@ -608,7 +612,7 @@ mod tests {
fn test_find_links_with_full_range() {
let s = "Some random text with {{#include file.rs::}}...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
println!("\nOUTPUT: {res:?}\n");
assert_eq!(
res,
vec![Link {
@@ -627,7 +631,7 @@ mod tests {
fn test_find_links_with_no_range_specified() {
let s = "Some random text with {{#include file.rs}}...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
println!("\nOUTPUT: {res:?}\n");
assert_eq!(
res,
vec![Link {
@@ -646,7 +650,7 @@ mod tests {
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);
println!("\nOUTPUT: {res:?}\n");
assert_eq!(
res,
vec![Link {
@@ -666,7 +670,7 @@ mod tests {
let s = "Some random text with escaped playground \\{{#playground file.rs editable}} ...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
println!("\nOUTPUT: {res:?}\n");
assert_eq!(
res,
@@ -686,7 +690,7 @@ mod tests {
more\n text {{#playground my.rs editable no_run should_panic}} ...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
println!("\nOUTPUT: {res:?}\n");
assert_eq!(
res,
vec![
@@ -717,7 +721,7 @@ mod tests {
no_run should_panic}} ...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
println!("\nOUTPUT: {res:?}\n");
assert_eq!(res.len(), 3);
assert_eq!(
res[0],

View File

@@ -12,6 +12,7 @@ use crate::book::Book;
use crate::config::Config;
use crate::errors::*;
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::PathBuf;

View File

@@ -1,5 +1,5 @@
use crate::book::{Book, BookItem};
use crate::config::{BookConfig, Config, HtmlConfig, Playground, RustEdition};
use crate::config::{BookConfig, Code, Config, HtmlConfig, Playground, RustEdition};
use crate::errors::*;
use crate::renderer::html_handlebars::helpers;
use crate::renderer::{RenderContext, Renderer};
@@ -14,7 +14,10 @@ use std::path::{Path, PathBuf};
use crate::utils::fs::get_404_output_file;
use handlebars::Handlebars;
use log::{debug, trace, warn};
use once_cell::sync::Lazy;
use regex::{Captures, Regex};
use serde_json::json;
#[derive(Default)]
pub struct HtmlHandlebars;
@@ -51,12 +54,14 @@ impl HtmlHandlebars {
.insert("git_repository_edit_url".to_owned(), json!(edit_url));
}
let content = ch.content.clone();
let content = utils::render_markdown(&content, ctx.html_config.curly_quotes);
let content = utils::render_markdown(&ch.content, ctx.html_config.smart_punctuation());
let fixed_content =
utils::render_markdown_with_path(&ch.content, ctx.html_config.curly_quotes, Some(path));
if !ctx.is_index {
let fixed_content = utils::render_markdown_with_path(
&ch.content,
ctx.html_config.smart_punctuation(),
Some(path),
);
if !ctx.is_index && ctx.html_config.print.page_break {
// Add page break between chapters
// See https://developer.mozilla.org/en-US/docs/Web/CSS/break-before and https://developer.mozilla.org/en-US/docs/Web/CSS/page-break-before
// Add both two CSS properties because of the compatibility issue
@@ -96,7 +101,7 @@ impl HtmlHandlebars {
ctx.data.insert("title".to_owned(), json!(title));
ctx.data.insert(
"path_to_root".to_owned(),
json!(utils::fs::path_to_root(&path)),
json!(utils::fs::path_to_root(path)),
);
if let Some(ref section) = ch.number {
ctx.data
@@ -107,7 +112,12 @@ impl HtmlHandlebars {
debug!("Render template");
let rendered = ctx.handlebars.render("index", &ctx.data)?;
let rendered = self.post_process(rendered, &ctx.html_config.playground, ctx.edition);
let rendered = self.post_process(
rendered,
&ctx.html_config.playground,
&ctx.html_config.code,
ctx.edition,
);
// Write to file
debug!("Creating {}", filepath.display());
@@ -116,10 +126,14 @@ impl HtmlHandlebars {
if ctx.is_index {
ctx.data.insert("path".to_owned(), json!("index.md"));
ctx.data.insert("path_to_root".to_owned(), json!(""));
ctx.data.insert("is_index".to_owned(), json!("true"));
ctx.data.insert("is_index".to_owned(), json!(true));
let rendered_index = ctx.handlebars.render("index", &ctx.data)?;
let rendered_index =
self.post_process(rendered_index, &ctx.html_config.playground, ctx.edition);
let rendered_index = self.post_process(
rendered_index,
&ctx.html_config.playground,
&ctx.html_config.code,
ctx.edition,
);
debug!("Creating index.html from {}", ctx_path);
utils::fs::write_file(&ctx.destination, "index.html", rendered_index.as_bytes())?;
}
@@ -139,13 +153,13 @@ impl HtmlHandlebars {
let content_404 = if let Some(ref filename) = html_config.input_404 {
let path = src_dir.join(filename);
std::fs::read_to_string(&path)
.with_context(|| format!("unable to open 404 input file {:?}", path))?
.with_context(|| format!("unable to open 404 input file {path:?}"))?
} else {
// 404 input not explicitly configured try the default file 404.md
let default_404_location = src_dir.join("404.md");
if default_404_location.exists() {
std::fs::read_to_string(&default_404_location).with_context(|| {
format!("unable to open 404 input file {:?}", default_404_location)
format!("unable to open 404 input file {default_404_location:?}")
})?
} else {
"# Document not found (404)\n\nThis URL is invalid, sorry. Please use the \
@@ -153,7 +167,8 @@ impl HtmlHandlebars {
.to_string()
}
};
let html_content_404 = utils::render_markdown(&content_404, html_config.curly_quotes);
let html_content_404 =
utils::render_markdown(&content_404, html_config.smart_punctuation());
let mut data_404 = data.clone();
let base_url = if let Some(site_url) = &html_config.site_url {
@@ -170,26 +185,39 @@ impl HtmlHandlebars {
// Set a dummy path to ensure other paths (e.g. in the TOC) are generated correctly
data_404.insert("path".to_owned(), json!("404.md"));
data_404.insert("content".to_owned(), json!(html_content_404));
let mut title = String::from("Page not found");
if let Some(book_title) = &ctx.config.book.title {
title.push_str(" - ");
title.push_str(book_title);
}
data_404.insert("title".to_owned(), json!(title));
let rendered = handlebars.render("index", &data_404)?;
let rendered =
self.post_process(rendered, &html_config.playground, ctx.config.rust.edition);
let rendered = self.post_process(
rendered,
&html_config.playground,
&html_config.code,
ctx.config.rust.edition,
);
let output_file = get_404_output_file(&html_config.input_404);
utils::fs::write_file(destination, output_file, rendered.as_bytes())?;
debug!("Creating 404.html ✓");
Ok(())
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::let_and_return))]
#[allow(clippy::let_and_return)]
fn post_process(
&self,
rendered: String,
playground_config: &Playground,
code_config: &Code,
edition: Option<RustEdition>,
) -> String {
let rendered = build_header_links(&rendered);
let rendered = fix_code_blocks(&rendered);
let rendered = add_playground_pre(&rendered, playground_config, edition);
let rendered = hide_lines(&rendered, code_config);
rendered
}
@@ -209,7 +237,7 @@ impl HtmlHandlebars {
)?;
if let Some(cname) = &html_config.cname {
write_file(destination, "CNAME", format!("{}\n", cname).as_bytes())?;
write_file(destination, "CNAME", format!("{cname}\n").as_bytes())?;
}
write_file(destination, "book.js", &theme.js)?;
@@ -265,7 +293,8 @@ impl HtmlHandlebars {
"FontAwesome/fonts/FontAwesome.ttf",
theme::FONT_AWESOME_TTF,
)?;
if html_config.copy_fonts {
// Don't copy the stock fonts if the user has specified their own fonts to use.
if html_config.copy_fonts && theme.fonts_css.is_none() {
write_file(destination, "fonts/fonts.css", theme::fonts::CSS)?;
for (file_name, contents) in theme::fonts::LICENSES.iter() {
write_file(destination, file_name, contents)?;
@@ -279,6 +308,24 @@ impl HtmlHandlebars {
theme::fonts::SOURCE_CODE_PRO.1,
)?;
}
if let Some(fonts_css) = &theme.fonts_css {
if !fonts_css.is_empty() {
write_file(destination, "fonts/fonts.css", fonts_css)?;
}
}
if !html_config.copy_fonts && theme.fonts_css.is_none() {
warn!(
"output.html.copy-fonts is deprecated.\n\
This book appears to have copy-fonts=false in book.toml without a fonts.css file.\n\
Add an empty `theme/fonts/fonts.css` file to squelch this warning."
);
}
for font_file in &theme.font_files {
let contents = fs::read(font_file)?;
let filename = font_file.file_name().unwrap();
let filename = Path::new("fonts").join(filename);
write_file(destination, filename, &contents)?;
}
let playground_config = &html_config.playground;
@@ -330,6 +377,7 @@ impl HtmlHandlebars {
);
handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
handlebars.register_helper("next", Box::new(helpers::navigation::next));
// TODO: remove theme_option in 0.5, it is not needed.
handlebars.register_helper("theme_option", Box::new(helpers::theme::theme_option));
}
@@ -433,25 +481,6 @@ impl HtmlHandlebars {
}
}
// TODO(mattico): Remove some time after the 0.1.8 release
fn maybe_wrong_theme_dir(dir: &Path) -> Result<bool> {
fn entry_is_maybe_book_file(entry: fs::DirEntry) -> Result<bool> {
Ok(entry.file_type()?.is_file()
&& entry.path().extension().map_or(false, |ext| ext == "md"))
}
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
if entry_is_maybe_book_file(entry?).unwrap_or(false) {
return Ok(false);
}
}
Ok(true)
} else {
Ok(false)
}
}
impl Renderer for HtmlHandlebars {
fn name(&self) -> &str {
"html"
@@ -474,20 +503,16 @@ impl Renderer for HtmlHandlebars {
let mut handlebars = Handlebars::new();
let theme_dir = match html_config.theme {
Some(ref theme) => ctx.root.join(theme),
Some(ref theme) => {
let dir = ctx.root.join(theme);
if !dir.is_dir() {
bail!("theme dir {} does not exist", dir.display());
}
dir
}
None => ctx.root.join("theme"),
};
if html_config.theme.is_none()
&& maybe_wrong_theme_dir(&src_dir.join("theme")).unwrap_or(false)
{
warn!(
"Previous versions of mdBook erroneously accepted `./src/theme` as an automatic \
theme directory"
);
warn!("Please move your theme files to `./theme` for them to continue being used");
}
let theme = theme::Theme::new(theme_dir);
debug!("Register the index handlebars template");
@@ -503,6 +528,11 @@ impl Renderer for HtmlHandlebars {
debug!("Register the header handlebars template");
handlebars.register_partial("header", String::from_utf8(theme.header.clone())?)?;
debug!("Register the toc handlebars template");
handlebars.register_template_string("toc_js", String::from_utf8(theme.toc_js.clone())?)?;
handlebars
.register_template_string("toc_html", String::from_utf8(theme.toc_html.clone())?)?;
debug!("Register handlebars helpers");
self.register_hbs_helpers(&mut handlebars, &html_config);
@@ -511,7 +541,7 @@ impl Renderer for HtmlHandlebars {
// Print version
let mut print_content = String::new();
fs::create_dir_all(&destination)
fs::create_dir_all(destination)
.with_context(|| "Unexpected error when constructing destination path")?;
let mut is_index = true;
@@ -527,7 +557,8 @@ impl Renderer for HtmlHandlebars {
chapter_titles: &ctx.chapter_titles,
};
self.render_item(item, ctx, &mut print_content)?;
is_index = false;
// Only the first non-draft chapter item should be treated as the "index"
is_index &= !matches!(item, BookItem::Chapter(ch) if !ch.is_draft_chapter());
}
// Render 404 page
@@ -546,13 +577,29 @@ impl Renderer for HtmlHandlebars {
debug!("Render template");
let rendered = handlebars.render("index", &data)?;
let rendered =
self.post_process(rendered, &html_config.playground, ctx.config.rust.edition);
let rendered = self.post_process(
rendered,
&html_config.playground,
&html_config.code,
ctx.config.rust.edition,
);
utils::fs::write_file(destination, "print.html", rendered.as_bytes())?;
debug!("Creating print.html ✓");
}
debug!("Render toc");
{
let rendered_toc = handlebars.render("toc_js", &data)?;
utils::fs::write_file(destination, "toc.js", rendered_toc.as_bytes())?;
debug!("Creating toc.js ✓");
data.insert("is_toc_html".to_owned(), json!(true));
let rendered_toc = handlebars.render("toc_html", &data)?;
utils::fs::write_file(destination, "toc.html", rendered_toc.as_bytes())?;
debug!("Creating toc.html ✓");
data.remove("is_toc_html");
}
debug!("Copy static files");
self.copy_static_files(destination, &theme, &html_config)
.with_context(|| "Unable to copy across static files")?;
@@ -592,6 +639,10 @@ fn make_data(
"language".to_owned(),
json!(config.book.language.clone().unwrap_or_default()),
);
data.insert(
"text_direction".to_owned(),
json!(config.book.realized_text_direction()),
);
data.insert(
"book_title".to_owned(),
json!(config.book.title.clone().unwrap_or_default()),
@@ -606,10 +657,14 @@ fn make_data(
if theme.favicon_svg.is_some() {
data.insert("favicon_svg".to_owned(), json!("favicon.svg"));
}
if let Some(ref livereload) = html_config.livereload_url {
data.insert("livereload".to_owned(), json!(livereload));
if let Some(ref live_reload_endpoint) = html_config.live_reload_endpoint {
data.insert(
"live_reload_endpoint".to_owned(),
json!(live_reload_endpoint),
);
}
// TODO: remove default_theme in 0.5, it is not needed.
let default_theme = match html_config.default_theme {
Some(ref theme) => theme.to_lowercase(),
None => "light".to_string(),
@@ -634,7 +689,8 @@ fn make_data(
data.insert("mathjax_support".to_owned(), json!(true));
}
if html_config.copy_fonts {
// This `matches!` checks for a non-empty file.
if html_config.copy_fonts || matches!(theme.fonts_css.as_deref(), Some([_, ..])) {
data.insert("copy_fonts".to_owned(), json!(true));
}
@@ -747,16 +803,35 @@ fn make_data(
/// Goes through the rendered HTML, making sure all header tags have
/// an anchor respectively so people can link to sections directly.
fn build_header_links(html: &str) -> String {
let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();
static BUILD_HEADER_LINKS: Lazy<Regex> = Lazy::new(|| {
Regex::new(r#"<h(\d)(?: id="([^"]+)")?(?: class="([^"]+)")?>(.*?)</h\d>"#).unwrap()
});
static IGNORE_CLASS: &[&str] = &["menu-title"];
let mut id_counter = HashMap::new();
regex
BUILD_HEADER_LINKS
.replace_all(html, |caps: &Captures<'_>| {
let level = caps[1]
.parse()
.expect("Regex should ensure we only ever get numbers here");
insert_link_into_header(level, &caps[2], &mut id_counter)
// Ignore .menu-title because now it's getting detected by the regex.
if let Some(classes) = caps.get(3) {
for class in classes.as_str().split(" ") {
if IGNORE_CLASS.contains(&class) {
return caps[0].to_string();
}
}
}
insert_link_into_header(
level,
&caps[4],
caps.get(2).map(|x| x.as_str().to_string()),
caps.get(3).map(|x| x.as_str().to_string()),
&mut id_counter,
)
})
.into_owned()
}
@@ -766,24 +841,17 @@ fn build_header_links(html: &str) -> String {
fn insert_link_into_header(
level: usize,
content: &str,
id: Option<String>,
classes: Option<String>,
id_counter: &mut HashMap<String, usize>,
) -> String {
let raw_id = utils::id_from_content(content);
let id_count = id_counter.entry(raw_id.clone()).or_insert(0);
let id = match *id_count {
0 => raw_id,
other => format!("{}-{}", raw_id, other),
};
*id_count += 1;
let id = id.unwrap_or_else(|| utils::unique_id_from_content(content, id_counter));
let classes = classes
.map(|s| format!(" class=\"{s}\""))
.unwrap_or_default();
format!(
r##"<h{level} id="{id}"><a class="header" href="#{id}">{text}</a></h{level}>"##,
level = level,
id = id,
text = content
r##"<h{level} id="{id}"{classes}><a class="header" href="#{id}">{content}</a></h{level}>"##
)
}
@@ -796,84 +864,78 @@ fn insert_link_into_header(
// ```
// This function replaces all commas by spaces in the code block classes
fn fix_code_blocks(html: &str) -> String {
let regex = Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap();
regex
static FIX_CODE_BLOCKS: Lazy<Regex> =
Lazy::new(|| Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap());
FIX_CODE_BLOCKS
.replace_all(html, |caps: &Captures<'_>| {
let before = &caps[1];
let classes = &caps[2].replace(",", " ");
let classes = &caps[2].replace(',', " ");
let after = &caps[3];
format!(
r#"<code{before}class="{classes}"{after}>"#,
before = before,
classes = classes,
after = after
)
format!(r#"<code{before}class="{classes}"{after}>"#)
})
.into_owned()
}
static CODE_BLOCK_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap());
fn add_playground_pre(
html: &str,
playground_config: &Playground,
edition: Option<RustEdition>,
) -> String {
let regex = Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap();
regex
CODE_BLOCK_RE
.replace_all(html, |caps: &Captures<'_>| {
let text = &caps[1];
let classes = &caps[2];
let code = &caps[3];
if classes.contains("language-rust") {
if (!classes.contains("ignore")
if classes.contains("language-rust")
&& ((!classes.contains("ignore")
&& !classes.contains("noplayground")
&& !classes.contains("noplaypen"))
|| classes.contains("mdbook-runnable")
{
let contains_e2015 = classes.contains("edition2015");
let contains_e2018 = classes.contains("edition2018");
let contains_e2021 = classes.contains("edition2021");
let edition_class = if contains_e2015 || contains_e2018 || contains_e2021 {
// the user forced edition, we should not overwrite it
""
} else {
match edition {
Some(RustEdition::E2015) => " edition2015",
Some(RustEdition::E2018) => " edition2018",
Some(RustEdition::E2021) => " edition2021",
None => "",
}
};
// wrap the contents in an external pre block
format!(
"<pre class=\"playground\"><code class=\"{}{}\">{}</code></pre>",
classes,
edition_class,
{
let content: Cow<'_, str> = if playground_config.editable
&& classes.contains("editable")
|| text.contains("fn main")
|| text.contains("quick_main!")
{
code.into()
} else {
// we need to inject our own main
let (attrs, code) = partition_source(code);
format!(
"\n# #![allow(unused)]\n{}#fn main() {{\n{}#}}",
attrs, code
)
.into()
};
hide_lines(&content)
}
)
&& !classes.contains("noplaypen")
&& playground_config.runnable)
|| classes.contains("mdbook-runnable"))
{
let contains_e2015 = classes.contains("edition2015");
let contains_e2018 = classes.contains("edition2018");
let contains_e2021 = classes.contains("edition2021");
let edition_class = if contains_e2015 || contains_e2018 || contains_e2021 {
// the user forced edition, we should not overwrite it
""
} else {
format!("<code class=\"{}\">{}</code>", classes, hide_lines(code))
}
match edition {
Some(RustEdition::E2015) => " edition2015",
Some(RustEdition::E2018) => " edition2018",
Some(RustEdition::E2021) => " edition2021",
Some(RustEdition::E2024) => " edition2024",
None => "",
}
};
// wrap the contents in an external pre block
format!(
"<pre class=\"playground\"><code class=\"{}{}\">{}</code></pre>",
classes,
edition_class,
{
let content: Cow<'_, str> = if playground_config.editable
&& classes.contains("editable")
|| text.contains("fn main")
|| text.contains("quick_main!")
{
code.into()
} else {
// we need to inject our own main
let (attrs, code) = partition_source(code);
format!("# #![allow(unused)]\n{attrs}# fn main() {{\n{code}# }}").into()
};
content
}
)
} else {
// not language-rust, so no-op
text.to_owned()
@@ -882,33 +944,95 @@ fn add_playground_pre(
.into_owned()
}
lazy_static! {
static ref BORING_LINES_REGEX: Regex = Regex::new(r"^(\s*)#(.?)(.*)$").unwrap();
/// Modifies all `<code>` blocks to convert "hidden" lines and to wrap them in
/// a `<span class="boring">`.
fn hide_lines(html: &str, code_config: &Code) -> String {
static LANGUAGE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\blanguage-(\w+)\b").unwrap());
static HIDELINES_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\bhidelines=(\S+)").unwrap());
CODE_BLOCK_RE
.replace_all(html, |caps: &Captures<'_>| {
let text = &caps[1];
let classes = &caps[2];
let code = &caps[3];
if classes.contains("language-rust") {
format!(
"<code class=\"{}\">{}</code>",
classes,
hide_lines_rust(code)
)
} else {
// First try to get the prefix from the code block
let hidelines_capture = HIDELINES_REGEX.captures(classes);
let hidelines_prefix = match &hidelines_capture {
Some(capture) => Some(&capture[1]),
None => {
// Then look up the prefix by language
LANGUAGE_REGEX.captures(classes).and_then(|capture| {
code_config.hidelines.get(&capture[1]).map(|p| p.as_str())
})
}
};
match hidelines_prefix {
Some(prefix) => format!(
"<code class=\"{}\">{}</code>",
classes,
hide_lines_with_prefix(code, prefix)
),
None => text.to_owned(),
}
}
})
.into_owned()
}
fn hide_lines(content: &str) -> String {
fn hide_lines_rust(content: &str) -> String {
static BORING_LINES_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(\s*)#(.?)(.*)$").unwrap());
let mut result = String::with_capacity(content.len());
for line in content.lines() {
let mut lines = content.lines().peekable();
while let Some(line) = lines.next() {
// Don't include newline on the last line.
let newline = if lines.peek().is_none() { "" } else { "\n" };
if let Some(caps) = BORING_LINES_REGEX.captures(line) {
if &caps[2] == "#" {
result += &caps[1];
result += &caps[2];
result += &caps[3];
result += "\n";
result += newline;
continue;
} else if &caps[2] != "!" && &caps[2] != "[" {
} else if matches!(&caps[2], "" | " ") {
result += "<span class=\"boring\">";
result += &caps[1];
if &caps[2] != " " {
result += &caps[2];
}
result += &caps[3];
result += "\n";
result += newline;
result += "</span>";
continue;
}
}
result += line;
result += newline;
}
result
}
fn hide_lines_with_prefix(content: &str, prefix: &str) -> String {
let mut result = String::with_capacity(content.len());
for line in content.lines() {
if line.trim_start().starts_with(prefix) {
let pos = line.find(prefix).unwrap();
let (ws, rest) = (&line[..pos], &line[pos + prefix.len()..]);
result += "<span class=\"boring\">";
result += ws;
result += rest;
result += "\n";
result += "</span>";
continue;
}
result += line;
result += "\n";
}
result
@@ -948,7 +1072,10 @@ struct RenderItemContext<'a> {
#[cfg(test)]
mod tests {
use crate::config::TextDirection;
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn original_build_header_links() {
@@ -977,6 +1104,21 @@ mod tests {
"<h1>Foo</h1><h3>Foo</h3>",
r##"<h1 id="foo"><a class="header" href="#foo">Foo</a></h1><h3 id="foo-1"><a class="header" href="#foo-1">Foo</a></h3>"##,
),
// id only
(
r##"<h1 id="foobar">Foo</h1>"##,
r##"<h1 id="foobar"><a class="header" href="#foobar">Foo</a></h1>"##,
),
// class only
(
r##"<h1 class="class1 class2">Foo</h1>"##,
r##"<h1 id="foo" class="class1 class2"><a class="header" href="#foo">Foo</a></h1>"##,
),
// both id and class
(
r##"<h1 id="foobar" class="class1 class2">Foo</h1>"##,
r##"<h1 id="foobar" class="class1 class2"><a class="header" href="#foobar">Foo</a></h1>"##,
),
];
for (src, should_be) in inputs {
@@ -989,19 +1131,19 @@ mod tests {
fn add_playground() {
let inputs = [
("<code class=\"language-rust\">x()</code>",
"<pre class=\"playground\"><code class=\"language-rust\">\n<span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust\"># #![allow(unused)]\n# fn main() {\nx()\n# }</code></pre>"),
("<code class=\"language-rust\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust\">fn main() {}</code></pre>"),
("<code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code>",
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code></pre>"),
("<code class=\"language-rust editable\">let s = \"foo\n ## bar\n\";</code>",
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n\";\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n ## bar\n\";</code></pre>"),
("<code class=\"language-rust editable\">let s = \"foo\n # bar\n#\n\";</code>",
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span><span class=\"boring\">\n</span>\";\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n#\n\";</code></pre>"),
("<code class=\"language-rust ignore\">let s = \"foo\n # bar\n\";</code>",
"<code class=\"language-rust ignore\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";\n</code>"),
"<code class=\"language-rust ignore\">let s = \"foo\n # bar\n\";</code>"),
("<code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code>",
"<pre class=\"playground\"><code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code></pre>"),
];
for (src, should_be) in &inputs {
let got = add_playground_pre(
@@ -1019,13 +1161,13 @@ mod tests {
fn add_playground_edition2015() {
let inputs = [
("<code class=\"language-rust\">x()</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2015\">\n<span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2015\"># #![allow(unused)]\n# fn main() {\nx()\n# }</code></pre>"),
("<code class=\"language-rust\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
("<code class=\"language-rust edition2015\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
("<code class=\"language-rust edition2018\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
];
for (src, should_be) in &inputs {
let got = add_playground_pre(
@@ -1043,13 +1185,13 @@ mod tests {
fn add_playground_edition2018() {
let inputs = [
("<code class=\"language-rust\">x()</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2018\">\n<span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2018\"># #![allow(unused)]\n# fn main() {\nx()\n# }</code></pre>"),
("<code class=\"language-rust\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
("<code class=\"language-rust edition2015\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
("<code class=\"language-rust edition2018\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
];
for (src, should_be) in &inputs {
let got = add_playground_pre(
@@ -1067,13 +1209,13 @@ mod tests {
fn add_playground_edition2021() {
let inputs = [
("<code class=\"language-rust\">x()</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2021\">\n<span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2021\"># #![allow(unused)]\n# fn main() {\nx()\n# }</code></pre>"),
("<code class=\"language-rust\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2021\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2021\">fn main() {}</code></pre>"),
("<code class=\"language-rust edition2015\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
("<code class=\"language-rust edition2018\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
];
for (src, should_be) in &inputs {
let got = add_playground_pre(
@@ -1087,4 +1229,70 @@ mod tests {
assert_eq!(&*got, *should_be);
}
}
#[test]
fn hide_lines_language_rust() {
let inputs = [
(
"<pre class=\"playground\"><code class=\"language-rust\">\n# #![allow(unused)]\n# fn main() {\nx()\n# }</code></pre>",
"<pre class=\"playground\"><code class=\"language-rust\">\n<span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</span></code></pre>",),
// # must be followed by a space for a line to be hidden
(
"<pre class=\"playground\"><code class=\"language-rust\">\n#fn main() {\nx()\n#}</code></pre>",
"<pre class=\"playground\"><code class=\"language-rust\">\n#fn main() {\nx()\n#}</code></pre>",),
(
"<pre class=\"playground\"><code class=\"language-rust\">fn main() {}</code></pre>",
"<pre class=\"playground\"><code class=\"language-rust\">fn main() {}</code></pre>",),
(
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code></pre>",
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";</code></pre>",),
(
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n ## bar\n\";</code></pre>",
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code></pre>",),
(
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n#\n\";</code></pre>",
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span><span class=\"boring\">\n</span>\";</code></pre>",),
(
"<code class=\"language-rust ignore\">let s = \"foo\n # bar\n\";</code>",
"<code class=\"language-rust ignore\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";</code>",),
(
"<pre class=\"playground\"><code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code></pre>",
"<pre class=\"playground\"><code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code></pre>",),
];
for (src, should_be) in &inputs {
let got = hide_lines(src, &Code::default());
assert_eq!(&*got, *should_be);
}
}
#[test]
fn hide_lines_language_other() {
let inputs = [
(
"<code class=\"language-python\">~hidden()\nnothidden():\n~ hidden()\n ~hidden()\n nothidden()</code>",
"<code class=\"language-python\"><span class=\"boring\">hidden()\n</span>nothidden():\n<span class=\"boring\"> hidden()\n</span><span class=\"boring\"> hidden()\n</span> nothidden()\n</code>",),
(
"<code class=\"language-python hidelines=!!!\">!!!hidden()\nnothidden():\n!!! hidden()\n !!!hidden()\n nothidden()</code>",
"<code class=\"language-python hidelines=!!!\"><span class=\"boring\">hidden()\n</span>nothidden():\n<span class=\"boring\"> hidden()\n</span><span class=\"boring\"> hidden()\n</span> nothidden()\n</code>",),
];
for (src, should_be) in &inputs {
let got = hide_lines(
src,
&Code {
hidelines: {
let mut map = HashMap::new();
map.insert("python".to_string(), "~".to_string());
map
},
},
);
assert_eq!(&*got, *should_be);
}
}
#[test]
fn test_json_direction() {
assert_eq!(json!(TextDirection::RightToLeft), json!("rtl"));
assert_eq!(json!(TextDirection::LeftToRight), json!("ltr"));
}
}

View File

@@ -1,9 +1,13 @@
use std::collections::BTreeMap;
use std::path::Path;
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError, Renderable};
use handlebars::{
Context, Handlebars, Helper, Output, RenderContext, RenderError, RenderErrorReason, Renderable,
};
use crate::utils;
use log::{debug, trace};
use serde_json::json;
type StringMap = BTreeMap<String, String>;
@@ -24,9 +28,9 @@ impl Target {
) -> Result<Option<StringMap>, RenderError> {
match *self {
Target::Next => {
let previous_path = previous_item
.get("path")
.ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))?;
let previous_path = previous_item.get("path").ok_or_else(|| {
RenderErrorReason::Other("No path found for chapter in JSON data".to_owned())
})?;
if previous_path == base_path {
return Ok(Some(current_item.clone()));
@@ -52,16 +56,19 @@ fn find_chapter(
debug!("Get data from context");
let chapters = rc.evaluate(ctx, "@root/chapters").and_then(|c| {
serde_json::value::from_value::<Vec<StringMap>>(c.as_json().clone())
.map_err(|_| RenderError::new("Could not decode the JSON data"))
serde_json::value::from_value::<Vec<StringMap>>(c.as_json().clone()).map_err(|_| {
RenderErrorReason::Other("Could not decode the JSON data".to_owned()).into()
})
})?;
let base_path = rc
.evaluate(ctx, "@root/path")?
.as_json()
.as_str()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", "");
.ok_or_else(|| {
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.
@@ -96,7 +103,7 @@ fn find_chapter(
}
}
previous = Some(item.clone());
previous = Some(item);
}
_ => continue,
}
@@ -106,7 +113,7 @@ fn find_chapter(
}
fn render(
_h: &Helper<'_, '_>,
_h: &Helper<'_>,
r: &Handlebars<'_>,
ctx: &Context,
rc: &mut RenderContext<'_, '_>,
@@ -120,45 +127,50 @@ fn render(
.evaluate(ctx, "@root/path")?
.as_json()
.as_str()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", "");
.ok_or_else(|| {
RenderErrorReason::Other("Type error for `path`, string expected".to_owned())
})?
.replace('\"', "");
context.insert(
"path_to_root".to_owned(),
json!(utils::fs::path_to_root(&base_path)),
json!(utils::fs::path_to_root(base_path)),
);
chapter
.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)))?;
chapter
.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| {
Path::new(p)
.with_extension("html")
.to_str()
.ok_or_else(|| RenderError::new("Link could not be converted to str"))
.map(|p| context.insert("link".to_owned(), json!(p.replace("\\", "/"))))
.ok_or_else(|| {
RenderErrorReason::Other("Link could not be converted to str".to_owned())
})
.map(|p| context.insert("link".to_owned(), json!(p.replace('\\', "/"))))
})?;
trace!("Render template");
_h.template()
.ok_or_else(|| RenderError::new("Error with the handlebars template"))
.and_then(|t| {
let mut local_rc = rc.clone();
let local_ctx = Context::wraps(&context)?;
t.render(r, &local_ctx, &mut local_rc, out)
})?;
Ok(())
let t = _h
.template()
.ok_or_else(|| RenderErrorReason::Other("Error with the handlebars template".to_owned()))?;
let local_ctx = Context::wraps(&context)?;
let mut local_rc = rc.clone();
t.render(r, &local_ctx, &mut local_rc, out)
}
pub fn previous(
_h: &Helper<'_, '_>,
_h: &Helper<'_>,
r: &Handlebars<'_>,
ctx: &Context,
rc: &mut RenderContext<'_, '_>,
@@ -174,7 +186,7 @@ pub fn previous(
}
pub fn next(
_h: &Helper<'_, '_>,
_h: &Helper<'_>,
r: &Handlebars<'_>,
ctx: &Context,
rc: &mut RenderContext<'_, '_>,

View File

@@ -1,7 +1,10 @@
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError};
use handlebars::{
Context, Handlebars, Helper, Output, RenderContext, RenderError, RenderErrorReason,
};
use log::trace;
pub fn theme_option(
h: &Helper<'_, '_>,
h: &Helper<'_>,
_r: &Handlebars<'_>,
ctx: &Context,
rc: &mut RenderContext<'_, '_>,
@@ -10,14 +13,21 @@ pub fn theme_option(
trace!("theme_option (handlebars helper)");
let param = h.param(0).and_then(|v| v.value().as_str()).ok_or_else(|| {
RenderError::new("Param 0 with String type is required for theme_option helper.")
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(|| RenderError::new("Type error for `default_theme`, string expected"))?;
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() {

View File

@@ -1,11 +1,11 @@
use std::collections::BTreeMap;
use std::io;
use std::path::Path;
use std::{cmp::Ordering, collections::BTreeMap};
use crate::utils;
use crate::utils::special_escape;
use handlebars::{Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError};
use pulldown_cmark::{html, Event, Parser};
use handlebars::{
Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError, RenderErrorReason,
};
// Handlebars helper to construct TOC
#[derive(Clone, Copy)]
@@ -16,7 +16,7 @@ pub struct RenderToc {
impl HelperDef for RenderToc {
fn call<'reg: 'rc, 'rc>(
&self,
_h: &Helper<'reg, 'rc>,
_h: &Helper<'rc>,
_r: &'reg Handlebars<'_>,
ctx: &'rc Context,
rc: &mut RenderContext<'reg, 'rc>,
@@ -27,116 +27,111 @@ impl HelperDef for RenderToc {
// param is the key of value you want to display
let chapters = rc.evaluate(ctx, "@root/chapters").and_then(|c| {
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.as_json().clone())
.map_err(|_| RenderError::new("Could not decode the JSON data"))
.map_err(|_| {
RenderErrorReason::Other("Could not decode the JSON data".to_owned()).into()
})
})?;
let current_path = rc
.evaluate(ctx, "@root/path")?
.as_json()
.as_str()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.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(|| RenderError::new("Type error for `fold_enable`, bool expected"))?;
.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(|| RenderError::new("Type error for `fold_level`, u64 expected"))?;
.ok_or_else(|| {
RenderErrorReason::Other("Type error for `fold_level`, u64 expected".to_owned())
})?;
// If true, then this is the iframe and we need target="_parent"
let is_toc_html = rc
.evaluate(ctx, "@root/is_toc_html")?
.as_json()
.as_bool()
.unwrap_or(false);
out.write("<ol class=\"chapter\">")?;
let mut current_level = 1;
for item in chapters {
// Spacer
if item.get("spacer").is_some() {
out.write("<li class=\"spacer\"></li>")?;
continue;
}
let (section, level) = if let Some(s) = item.get("section") {
let (_section, level) = if let Some(s) = item.get("section") {
(s.as_str(), s.matches('.').count())
} else {
("", 1)
};
let is_expanded =
if !fold_enable || (!section.is_empty() && current_section.starts_with(section)) {
// Expand if folding is disabled, or if the section is an
// ancestor or the current section itself.
true
} else {
// Levels that are larger than this would be folded.
level - 1 < fold_level as usize
};
// Expand if folding is disabled, or if levels that are larger than this would not
// be folded.
let is_expanded = !fold_enable || level - 1 < (fold_level as usize);
if level > current_level {
while level > current_level {
out.write("<li>")?;
out.write("<ol class=\"section\">")?;
current_level += 1;
match level.cmp(&current_level) {
Ordering::Greater => {
while level > current_level {
out.write("<li>")?;
out.write("<ol class=\"section\">")?;
current_level += 1;
}
write_li_open_tag(out, is_expanded, false)?;
}
write_li_open_tag(out, is_expanded, false)?;
} else if level < current_level {
while level < current_level {
out.write("</ol>")?;
out.write("</li>")?;
current_level -= 1;
Ordering::Less => {
while level < current_level {
out.write("</ol>")?;
out.write("</li>")?;
current_level -= 1;
}
write_li_open_tag(out, is_expanded, false)?;
}
write_li_open_tag(out, is_expanded, false)?;
} else {
write_li_open_tag(out, is_expanded, item.get("section").is_none())?;
Ordering::Equal => {
write_li_open_tag(out, is_expanded, !item.contains_key("section"))?;
}
}
// Spacer
if item.contains_key("spacer") {
out.write("<li class=\"spacer\"></li>")?;
continue;
}
// Part title
if let Some(title) = item.get("part") {
out.write("<li class=\"part-title\">")?;
write_escaped(out, title)?;
out.write(&special_escape(title))?;
out.write("</li>")?;
continue;
}
// Link
let path_exists = if let Some(path) =
item.get("path")
.and_then(|p| if p.is_empty() { None } else { Some(p) })
{
out.write("<a href=\"")?;
let path_exists: bool;
match item.get("path") {
Some(path) if !path.is_empty() => {
out.write("<a href=\"")?;
let tmp = Path::new(path)
.with_extension("html")
.to_str()
.unwrap()
// Hack for windows who tends to use `\` as separator instead of `/`
.replace('\\', "/");
let tmp = Path::new(item.get("path").expect("Error: path should be Some(_)"))
.with_extension("html")
.to_str()
.unwrap()
// Hack for windows who tends to use `\` as separator instead of `/`
.replace("\\", "/");
// Add link
out.write(&utils::fs::path_to_root(&current_path))?;
out.write(&tmp)?;
out.write("\"")?;
if path == &current_path {
out.write(" class=\"active\"")?;
// Add link
out.write(&tmp)?;
out.write(if is_toc_html {
"\" target=\"_parent\">"
} else {
"\">"
})?;
path_exists = true;
}
out.write(">")?;
true
} else {
out.write("<div>")?;
false
};
_ => {
out.write("<div>")?;
path_exists = false;
}
}
if !self.no_section_label {
// Section does not necessarily exist
@@ -148,20 +143,7 @@ impl HelperDef for RenderToc {
}
if let Some(name) = item.get("name") {
// Render only inline code blocks
// filter all events that are not inline code blocks
let parser = Parser::new(name).filter(|event| match *event {
Event::Code(_) | Event::Html(_) | 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
write_escaped(out, &markdown_parsed_name)?;
out.write(&special_escape(name))?
}
if path_exists {
@@ -205,18 +187,3 @@ fn write_li_open_tag(
li.push_str("\">");
out.write(&li)
}
fn write_escaped(out: &mut dyn Output, mut title: &str) -> io::Result<()> {
let needs_escape: &[char] = &['<', '>'];
while let Some(next) = title.find(needs_escape) {
out.write(&title[..next])?;
match title.as_bytes()[next] {
b'<' => out.write("&lt;")?,
b'>' => out.write("&gt;")?,
_ => unreachable!(),
}
title = &title[next + 1..];
}
out.write(title)?;
Ok(())
}

View File

@@ -1,23 +1,55 @@
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::path::{Path, PathBuf};
use elasticlunr::Index;
use elasticlunr::{Index, IndexBuilder};
use once_cell::sync::Lazy;
use pulldown_cmark::*;
use crate::book::{Book, BookItem};
use crate::config::Search;
use crate::book::{Book, BookItem, Chapter};
use crate::config::{Search, SearchChapterSettings};
use crate::errors::*;
use crate::theme::searcher;
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.
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 chapter_configs = sort_search_config(&search_config.chapter);
validate_chapter_config(&chapter_configs, book)?;
for item in book.iter() {
render_item(&mut index, search_config, &mut doc_urls, item)?;
let chapter = match item {
BookItem::Chapter(ch) if !ch.is_draft_chapter() => ch,
_ => continue,
};
if let Some(path) = settings_path(chapter) {
let chapter_settings = get_chapter_settings(&chapter_configs, path);
if !chapter_settings.enable.unwrap_or(true) {
continue;
}
}
render_item(&mut index, search_config, &mut doc_urls, chapter)?;
}
let index = write_to_json(index, search_config, doc_urls)?;
@@ -31,7 +63,7 @@ pub fn create_files(search_config: &Search, destination: &Path, book: &Book) ->
utils::fs::write_file(
destination,
"searchindex.js",
format!("Object.assign(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, "mark.min.js", searcher::MARK_JS)?;
@@ -47,11 +79,24 @@ fn add_doc(
index: &mut Index,
doc_urls: &mut Vec<String>,
anchor_base: &str,
section_id: &Option<String>,
heading: &str,
id_counter: &mut HashMap<String, usize>,
section_id: &Option<CowStr<'_>>,
items: &[&str],
) {
let url = if let Some(ref id) = *section_id {
Cow::Owned(format!("{}#{}", anchor_base, 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}"))
} else {
Cow::Borrowed(anchor_base)
};
@@ -68,13 +113,8 @@ fn render_item(
index: &mut Index,
search_config: &Search,
doc_urls: &mut Vec<String>,
item: &BookItem,
chapter: &Chapter,
) -> Result<()> {
let chapter = match *item {
BookItem::Chapter(ref ch) if !ch.is_draft_chapter() => ch,
_ => return Ok(()),
};
let chapter_path = chapter
.path
.as_ref()
@@ -85,7 +125,7 @@ fn render_item(
.with_context(|| "Could not convert HTML path to str")?;
let anchor_base = utils::fs::normalize_path(filepath);
let mut p = utils::new_cmark_parser(&chapter.content).peekable();
let mut p = utils::new_cmark_parser(&chapter.content, false).peekable();
let mut in_heading = false;
let max_section_depth = u32::from(search_config.heading_split_level);
@@ -97,9 +137,10 @@ fn render_item(
breadcrumbs.push(chapter.name.clone());
let mut id_counter = HashMap::new();
while let Some(event) = p.next() {
match event {
Event::Start(Tag::Heading(i)) if i <= max_section_depth => {
Event::Start(Tag::Heading { level, id, .. }) if level as u32 <= max_section_depth => {
if !heading.is_empty() {
// Section finished, the next heading is following now
// Write the data to the index, and clear it for the next section
@@ -107,20 +148,21 @@ fn render_item(
index,
doc_urls,
&anchor_base,
&heading,
&mut id_counter,
&section_id,
&[&heading, &body, &breadcrumbs.join(" » ")],
);
section_id = None;
heading.clear();
body.clear();
breadcrumbs.pop();
}
section_id = id;
in_heading = true;
}
Event::End(Tag::Heading(i)) if i <= max_section_depth => {
Event::End(TagEnd::Heading(level)) if level as u32 <= max_section_depth => {
in_heading = false;
section_id = Some(utils::id_from_content(&heading));
breadcrumbs.push(heading.clone());
}
Event::Start(Tag::FootnoteDefinition(name)) => {
@@ -137,9 +179,19 @@ fn render_item(
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
@@ -159,25 +211,31 @@ fn render_item(
Event::FootnoteReference(name) => {
let len = footnote_numbers.len() + 1;
let number = footnote_numbers.entry(name).or_insert(len);
body.push_str(&format!(" [{}] ", number));
body.push_str(&format!(" [{number}] "));
}
Event::TaskListMarker(_checked) => {}
}
}
if !body.is_empty() || !heading.is_empty() {
if heading.is_empty() {
let title = if heading.is_empty() {
if let Some(chapter) = breadcrumbs.first() {
heading = chapter.clone();
chapter
} else {
""
}
}
} else {
&heading
};
// Make sure the last section is added to the index
add_doc(
index,
doc_urls,
&anchor_base,
&heading,
&mut id_counter,
&section_id,
&[&heading, &body, &breadcrumbs.join(" » ")],
&[title, &body, &breadcrumbs.join(" » ")],
);
}
@@ -208,12 +266,13 @@ fn write_to_json(index: Index, search_config: &Search, doc_urls: Vec<String>) ->
let mut fields = BTreeMap::new();
let mut opt = SearchOptionsField::default();
opt.boost = Some(search_config.boost_title);
fields.insert("title".into(), opt);
opt.boost = Some(search_config.boost_paragraph);
fields.insert("body".into(), opt);
opt.boost = Some(search_config.boost_hierarchy);
fields.insert("breadcrumbs".into(), opt);
let mut insert_boost = |key: &str, boost| {
opt.boost = Some(boost);
fields.insert(key.into(), opt);
};
insert_boost("title", search_config.boost_title);
insert_boost("body", search_config.boost_paragraph);
insert_boost("breadcrumbs", search_config.boost_hierarchy);
let search_options = SearchOptions {
bool: if search_config.use_boolean_and {
@@ -246,21 +305,98 @@ fn write_to_json(index: Index, search_config: &Search, doc_urls: Vec<String>) ->
}
fn clean_html(html: &str) -> String {
lazy_static! {
static ref AMMONIA: ammonia::Builder<'static> = {
let mut clean_content = HashSet::new();
clean_content.insert("script");
clean_content.insert("style");
let mut builder = ammonia::Builder::new();
builder
.tags(HashSet::new())
.tag_attributes(HashMap::new())
.generic_attributes(HashSet::new())
.link_rel(None)
.allowed_classes(HashMap::new())
.clean_content_tags(clean_content);
builder
};
}
static AMMONIA: Lazy<ammonia::Builder<'static>> = Lazy::new(|| {
let mut clean_content = HashSet::new();
clean_content.insert("script");
clean_content.insert("style");
let mut builder = ammonia::Builder::new();
builder
.tags(HashSet::new())
.tag_attributes(HashMap::new())
.generic_attributes(HashSet::new())
.link_rel(None)
.allowed_classes(HashMap::new())
.clean_content_tags(clean_content);
builder
});
AMMONIA.clean(html).to_string()
}
fn settings_path(ch: &Chapter) -> Option<&Path> {
ch.source_path.as_deref().or_else(|| ch.path.as_deref())
}
fn validate_chapter_config(
chapter_configs: &[(PathBuf, SearchChapterSettings)],
book: &Book,
) -> Result<()> {
for (path, _) in chapter_configs {
let found = book
.iter()
.filter_map(|item| match item {
BookItem::Chapter(ch) if !ch.is_draft_chapter() => settings_path(ch),
_ => None,
})
.any(|source_path| source_path.starts_with(path));
if !found {
bail!(
"[output.html.search.chapter] key `{}` does not match any chapter paths",
path.display()
);
}
}
Ok(())
}
fn sort_search_config(
map: &HashMap<String, SearchChapterSettings>,
) -> Vec<(PathBuf, SearchChapterSettings)> {
let mut settings: Vec<_> = map
.iter()
.map(|(key, value)| (PathBuf::from(key), value.clone()))
.collect();
// Note: This is case-sensitive, and assumes the author uses the same case
// as the actual filename.
settings.sort_by(|a, b| a.0.cmp(&b.0));
settings
}
fn get_chapter_settings(
chapter_configs: &[(PathBuf, SearchChapterSettings)],
source_path: &Path,
) -> SearchChapterSettings {
let mut result = SearchChapterSettings::default();
for (path, config) in chapter_configs {
if source_path.starts_with(path) {
result.enable = config.enable.or(result.enable);
}
}
result
}
#[test]
fn chapter_settings_priority() {
let cfg = r#"
[output.html.search.chapter]
"cli/watch.md" = { enable = true }
"cli" = { enable = false }
"cli/inner/foo.md" = { enable = false }
"cli/inner" = { enable = true }
"foo" = {} # Just to make sure empty table is allowed.
"#;
let cfg: crate::Config = toml::from_str(cfg).unwrap();
let html = cfg.html_config().unwrap();
let chapter_configs = sort_search_config(&html.search.unwrap().chapter);
for (path, enable) in [
("foo.md", None),
("cli/watch.md", Some(true)),
("cli/index.md", Some(false)),
("cli/inner/index.md", Some(true)),
("cli/inner/foo.md", Some(false)),
] {
assert_eq!(
get_chapter_settings(&chapter_configs, Path::new(path)),
SearchChapterSettings { enable }
);
}
}

View File

@@ -2,7 +2,7 @@ use crate::book::BookItem;
use crate::errors::*;
use crate::renderer::{RenderContext, Renderer};
use crate::utils;
use log::trace;
use std::fs;
#[derive(Default)]
@@ -37,14 +37,14 @@ impl Renderer for MarkdownRenderer {
if !ch.is_draft_chapter() {
utils::fs::write_file(
&ctx.destination,
&ch.path.as_ref().expect("Checked path exists before"),
ch.path.as_ref().expect("Checked path exists before"),
ch.content.as_bytes(),
)?;
}
}
}
fs::create_dir_all(&destination)
fs::create_dir_all(destination)
.with_context(|| "Unexpected error when constructing destination path")?;
Ok(())

View File

@@ -27,8 +27,11 @@ use std::process::{Command, Stdio};
use crate::book::Book;
use crate::config::Config;
use crate::errors::*;
use log::{error, info, trace, warn};
use toml::Value;
use serde::{Deserialize, Serialize};
/// An arbitrary `mdbook` backend.
///
/// Although it's quite possible for you to import `mdbook` as a library and

View File

@@ -8,7 +8,6 @@ Original by Dempfi (https://github.com/dempfi/ayu)
overflow-x: auto;
background: #191f26;
color: #e6e1cf;
padding: 0.5em;
}
.hljs-comment,

View File

@@ -4,14 +4,16 @@
window.onunload = function () { };
// Global variable, shared between modules
function playground_text(playground) {
function playground_text(playground, hidden = true) {
let code_block = playground.querySelector("code");
if (window.ace && code_block.classList.contains("editable")) {
let editor = window.ace.edit(code_block);
return editor.getValue();
} else {
} else if (hidden) {
return code_block.textContent;
} else {
return code_block.innerText;
}
}
@@ -66,7 +68,7 @@ function playground_text(playground) {
}
// updates the visibility of play button based on `no_run` class and
// used crates vs ones available on http://play.rust-lang.org
// used crates vs ones available on https://play.rust-lang.org
function update_play_button(pre_block, playground_crates) {
var play_button = pre_block.querySelector(".play-button");
@@ -166,7 +168,6 @@ function playground_text(playground) {
.filter(function (node) {return node.classList.contains("editable"); })
.forEach(function (block) { block.classList.remove('language-rust'); });
Array
code_nodes
.filter(function (node) {return !node.classList.contains("editable"); })
.forEach(function (block) { hljs.highlightBlock(block); });
@@ -178,7 +179,7 @@ function playground_text(playground) {
// even if highlighting doesn't apply
code_nodes.forEach(function (block) { block.classList.add('hljs'); });
Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) {
Array.from(document.querySelectorAll("code.hljs")).forEach(function (block) {
var lines = Array.from(block.querySelectorAll('.boring'));
// If no lines were hidden, return
@@ -224,7 +225,7 @@ function playground_text(playground) {
}
var clipButton = document.createElement('button');
clipButton.className = 'fa fa-copy clip-button';
clipButton.className = 'clip-button';
clipButton.title = 'Copy to clipboard';
clipButton.setAttribute('aria-label', clipButton.title);
clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
@@ -257,7 +258,7 @@ function playground_text(playground) {
if (window.playground_copyable) {
var copyCodeClipboardButton = document.createElement('button');
copyCodeClipboardButton.className = 'fa fa-copy clip-button';
copyCodeClipboardButton.className = 'clip-button';
copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
copyCodeClipboardButton.title = 'Copy to clipboard';
copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
@@ -288,6 +289,10 @@ function playground_text(playground) {
var themeToggleButton = document.getElementById('theme-toggle');
var themePopup = document.getElementById('theme-list');
var themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
var themeIds = [];
themePopup.querySelectorAll('button.theme').forEach(function (el) {
themeIds.push(el.id);
});
var stylesheets = {
ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"),
tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"),
@@ -300,6 +305,13 @@ function playground_text(playground) {
themePopup.querySelector("button#" + get_theme()).focus();
}
function updateThemeSelected() {
themePopup.querySelectorAll('.theme-selected').forEach(function (el) {
el.classList.remove('theme-selected');
});
themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected');
}
function hideThemes() {
themePopup.style.display = 'none';
themeToggleButton.setAttribute('aria-expanded', false);
@@ -309,7 +321,7 @@ function playground_text(playground) {
function get_theme() {
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { }
if (theme === null || theme === undefined) {
if (theme === null || theme === undefined || !themeIds.includes(theme)) {
return default_theme;
} else {
return theme;
@@ -338,7 +350,7 @@ function playground_text(playground) {
}
setTimeout(function () {
themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor;
themeColorMetaTag.content = getComputedStyle(document.documentElement).backgroundColor;
}, 1);
if (window.ace && window.editors) {
@@ -355,6 +367,7 @@ function playground_text(playground) {
html.classList.remove(previousTheme);
html.classList.add(theme);
updateThemeSelected();
}
// Set theme
@@ -432,16 +445,17 @@ function playground_text(playground) {
})();
(function sidebar() {
var html = document.querySelector("html");
var body = document.querySelector("body");
var sidebar = document.getElementById("sidebar");
var sidebarLinks = document.querySelectorAll('#sidebar a');
var sidebarToggleButton = document.getElementById("sidebar-toggle");
var sidebarToggleAnchor = document.getElementById("sidebar-toggle-anchor");
var sidebarResizeHandle = document.getElementById("sidebar-resize-handle");
var firstContact = null;
function showSidebar() {
html.classList.remove('sidebar-hidden')
html.classList.add('sidebar-visible');
body.classList.remove('sidebar-hidden')
body.classList.add('sidebar-visible');
Array.from(sidebarLinks).forEach(function (link) {
link.setAttribute('tabIndex', 0);
});
@@ -450,20 +464,9 @@ function playground_text(playground) {
try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }
}
var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle');
function toggleSection(ev) {
ev.currentTarget.parentElement.classList.toggle('expanded');
}
Array.from(sidebarAnchorToggles).forEach(function (el) {
el.addEventListener('click', toggleSection);
});
function hideSidebar() {
html.classList.remove('sidebar-visible')
html.classList.add('sidebar-hidden');
body.classList.remove('sidebar-visible')
body.classList.add('sidebar-hidden');
Array.from(sidebarLinks).forEach(function (link) {
link.setAttribute('tabIndex', -1);
});
@@ -473,22 +476,16 @@ function playground_text(playground) {
}
// Toggle sidebar
sidebarToggleButton.addEventListener('click', function sidebarToggle() {
if (html.classList.contains("sidebar-hidden")) {
sidebarToggleAnchor.addEventListener('change', function sidebarToggle() {
if (sidebarToggleAnchor.checked) {
var current_width = parseInt(
document.documentElement.style.getPropertyValue('--sidebar-width'), 10);
if (current_width < 150) {
document.documentElement.style.setProperty('--sidebar-width', '150px');
}
showSidebar();
} else if (html.classList.contains("sidebar-visible")) {
hideSidebar();
} else {
if (getComputedStyle(sidebar)['transform'] === 'none') {
hideSidebar();
} else {
showSidebar();
}
hideSidebar();
}
});
@@ -497,14 +494,14 @@ function playground_text(playground) {
function initResize(e) {
window.addEventListener('mousemove', resize, false);
window.addEventListener('mouseup', stopResize, false);
html.classList.add('sidebar-resizing');
body.classList.add('sidebar-resizing');
}
function resize(e) {
var pos = (e.clientX - sidebar.offsetLeft);
if (pos < 20) {
hideSidebar();
} else {
if (html.classList.contains("sidebar-hidden")) {
if (body.classList.contains("sidebar-hidden")) {
showSidebar();
}
pos = Math.min(pos, window.innerWidth - 100);
@@ -513,7 +510,7 @@ function playground_text(playground) {
}
//on mouseup remove windows functions mousemove & mouseup
function stopResize(e) {
html.classList.remove('sidebar-resizing');
body.classList.remove('sidebar-resizing');
window.removeEventListener('mousemove', resize, false);
window.removeEventListener('mouseup', stopResize, false);
}
@@ -542,33 +539,41 @@ function playground_text(playground) {
firstContact = null;
}
}, { passive: true });
// Scroll sidebar to current active section
var activeSection = document.getElementById("sidebar").querySelector(".active");
if (activeSection) {
// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
activeSection.scrollIntoView({ block: 'center' });
}
})();
(function chapterNavigation() {
document.addEventListener('keydown', function (e) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
if (window.search && window.search.hasFocus()) { return; }
var html = document.querySelector('html');
function next() {
var nextButton = document.querySelector('.nav-chapters.next');
if (nextButton) {
window.location.href = nextButton.href;
}
}
function prev() {
var previousButton = document.querySelector('.nav-chapters.previous');
if (previousButton) {
window.location.href = previousButton.href;
}
}
switch (e.key) {
case 'ArrowRight':
e.preventDefault();
var nextButton = document.querySelector('.nav-chapters.next');
if (nextButton) {
window.location.href = nextButton.href;
if (html.dir == 'rtl') {
prev();
} else {
next();
}
break;
case 'ArrowLeft':
e.preventDefault();
var previousButton = document.querySelector('.nav-chapters.previous');
if (previousButton) {
window.location.href = previousButton.href;
if (html.dir == 'rtl') {
next();
} else {
prev();
}
break;
}
@@ -580,19 +585,19 @@ function playground_text(playground) {
function hideTooltip(elem) {
elem.firstChild.innerText = "";
elem.className = 'fa fa-copy clip-button';
elem.className = 'clip-button';
}
function showTooltip(elem, msg) {
elem.firstChild.innerText = msg;
elem.className = 'fa fa-copy tooltipped';
elem.className = 'clip-button tooltipped';
}
var clipboardSnippets = new ClipboardJS('.clip-button', {
text: function (trigger) {
hideTooltip(trigger);
let playground = trigger.closest("pre");
return playground_text(playground);
return playground_text(playground, false);
}
});
@@ -667,13 +672,14 @@ function playground_text(playground) {
}, { passive: true });
})();
(function controllBorder() {
menu.classList.remove('bordered');
document.addEventListener('scroll', function () {
function updateBorder() {
if (menu.offsetTop === 0) {
menu.classList.remove('bordered');
} else {
menu.classList.add('bordered');
}
}, { passive: true });
}
updateBorder();
document.addEventListener('scroll', updateBorder, { passive: true });
})();
})();

View File

@@ -1,13 +1,5 @@
/* CSS for UI elements (a.k.a. chrome) */
@import 'variables.css';
::-webkit-scrollbar {
background: var(--bg);
}
::-webkit-scrollbar-thumb {
background: var(--scrollbar);
}
html {
scrollbar-color: var(--scrollbar) var(--bg);
}
@@ -18,6 +10,19 @@ a > .hljs {
color: var(--links);
}
/*
body-container is necessary because mobile browsers don't seem to like
overflow-x on the body tag when there is a <meta name="viewport"> tag.
*/
#body-container {
/*
This is used when the sidebar pushes the body content off the side of
the screen on small screens. Without it, dragging on mobile Safari
will want to reposition the viewport in a weird way.
*/
overflow-x: clip;
}
/* Menu Bar */
#menu-bar,
@@ -30,14 +35,14 @@ a > .hljs {
display: flex;
flex-wrap: wrap;
background-color: var(--bg);
border-bottom-color: var(--bg);
border-bottom-width: 1px;
border-bottom-style: solid;
border-block-end-color: var(--bg);
border-block-end-width: 1px;
border-block-end-style: solid;
}
#menu-bar.sticky,
.js #menu-bar-hover-placeholder:hover + #menu-bar,
.js #menu-bar:hover,
.js.sidebar-visible #menu-bar {
#menu-bar-hover-placeholder:hover + #menu-bar,
#menu-bar:hover,
html.sidebar-visible #menu-bar {
position: -webkit-sticky;
position: sticky;
top: 0 !important;
@@ -49,7 +54,7 @@ a > .hljs {
height: var(--menu-bar-height);
}
#menu-bar.bordered {
border-bottom-color: var(--table-border-color);
border-block-end-color: var(--table-border-color);
}
#menu-bar i, #menu-bar .icon-button {
position: relative;
@@ -86,7 +91,7 @@ a > .hljs {
display: flex;
margin: 0 5px;
}
.no-js .left-buttons {
html:not(.js) .left-buttons button {
display: none;
}
@@ -102,7 +107,7 @@ a > .hljs {
overflow: hidden;
text-overflow: ellipsis;
}
.js .menu-title {
.menu-title {
cursor: pointer;
}
@@ -153,7 +158,7 @@ a > .hljs {
}
.nav-wrapper {
margin-top: 50px;
margin-block-start: 50px;
display: none;
}
@@ -166,23 +171,34 @@ a > .hljs {
background-color: var(--sidebar-bg);
}
.previous {
float: left;
}
/* Only Firefox supports flow-relative values */
.previous { float: left; }
[dir=rtl] .previous { float: right; }
/* Only Firefox supports flow-relative values */
.next {
float: right;
right: var(--page-padding);
}
[dir=rtl] .next {
float: left;
right: unset;
left: var(--page-padding);
}
/* Use the correct buttons for RTL layouts*/
[dir=rtl] .previous i.fa-angle-left:before {content:"\f105";}
[dir=rtl] .next i.fa-angle-right:before { content:"\f104"; }
@media only screen and (max-width: 1080px) {
.nav-wide-wrapper { display: none; }
.nav-wrapper { display: block; }
}
/* sidebar-visible */
@media only screen and (max-width: 1380px) {
.sidebar-visible .nav-wide-wrapper { display: none; }
.sidebar-visible .nav-wrapper { display: block; }
#sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wide-wrapper { display: none; }
#sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wrapper { display: block; }
}
/* Inline code */
@@ -208,26 +224,92 @@ pre {
pre > .buttons {
position: absolute;
z-index: 100;
right: 5px;
top: 5px;
right: 0px;
top: 2px;
margin: 0px;
padding: 2px 0px;
color: var(--sidebar-fg);
cursor: pointer;
visibility: hidden;
opacity: 0;
transition: visibility 0.1s linear, opacity 0.1s linear;
}
pre:hover > .buttons {
visibility: visible;
opacity: 1
}
pre > .buttons :hover {
color: var(--sidebar-active);
border-color: var(--icons-hover);
background-color: var(--theme-hover);
}
pre > .buttons i {
margin-left: 8px;
margin-inline-start: 8px;
}
pre > .buttons button {
color: inherit;
background: transparent;
border: none;
cursor: inherit;
margin: 0px 5px;
padding: 4px 4px 3px 5px;
font-size: 23px;
border-style: solid;
border-width: 1px;
border-radius: 4px;
border-color: var(--icons);
background-color: var(--theme-popup-bg);
transition: 100ms;
transition-property: color,border-color,background-color;
color: var(--icons);
}
pre > .buttons button.clip-button {
padding: 2px 4px 0px 6px;
}
pre > .buttons button.clip-button::before {
/* clipboard image from octicons (https://github.com/primer/octicons/tree/v2.0.0) MIT license
*/
content: url('data:image/svg+xml,<svg width="21" height="20" viewBox="0 0 24 25" \
xmlns="http://www.w3.org/2000/svg" aria-label="Copy to clipboard">\
<path d="M18 20h2v3c0 1-1 2-2 2H2c-.998 0-2-1-2-2V5c0-.911.755-1.667 1.667-1.667h5A3.323 3.323 0 \
0110 0a3.323 3.323 0 013.333 3.333h5C19.245 3.333 20 4.09 20 5v8.333h-2V9H2v14h16v-3zM3 \
7h14c0-.911-.793-1.667-1.75-1.667H13.5c-.957 0-1.75-.755-1.75-1.666C11.75 2.755 10.957 2 10 \
2s-1.75.755-1.75 1.667c0 .911-.793 1.666-1.75 1.666H4.75C3.793 5.333 3 6.09 3 7z"/>\
<path d="M4 19h6v2H4zM12 11H4v2h8zM4 17h4v-2H4zM15 15v-3l-4.5 4.5L15 21v-3l8.027-.032L23 15z"/>\
</svg>');
filter: var(--copy-button-filter);
}
pre > .buttons button.clip-button:hover::before {
filter: var(--copy-button-filter-hover);
}
@media (pointer: coarse) {
pre > .buttons button {
/* On mobile, make it easier to tap buttons. */
padding: 0.3rem 1rem;
}
.sidebar-resize-indicator {
/* Hide resize indicator on devices with limited accuracy */
display: none;
}
}
pre > code {
display: block;
padding: 1rem;
}
/* FIXME: ACE editors overlap their buttons because ACE does absolute
positioning within the code block which breaks padding. The only solution I
can think of is to move the padding to the outer pre tag (or insert a div
wrapper), but that would require fixing a whole bunch of CSS rules.
*/
.hljs.ace_editor {
padding: 0rem 0rem;
}
pre > .result {
margin-top: 10px;
margin-block-start: 10px;
}
/* Search */
@@ -238,8 +320,14 @@ pre > .result {
mark {
border-radius: 2px;
padding: 0 3px 1px 3px;
margin: 0 -3px -1px -3px;
padding-block-start: 0;
padding-block-end: 1px;
padding-inline-start: 3px;
padding-inline-end: 3px;
margin-block-start: 0;
margin-block-end: -1px;
margin-inline-start: -3px;
margin-inline-end: -3px;
background-color: var(--search-mark-bg);
transition: background-color 300ms linear;
cursor: pointer;
@@ -251,14 +339,17 @@ mark.fade-out {
}
.searchbar-outer {
margin-left: auto;
margin-right: auto;
margin-inline-start: auto;
margin-inline-end: auto;
max-width: var(--content-max-width);
}
#searchbar {
width: 100%;
margin: 5px auto 0px auto;
margin-block-start: 5px;
margin-block-end: 0;
margin-inline-start: auto;
margin-inline-end: auto;
padding: 10px 16px;
transition: box-shadow 300ms ease-in-out;
border: 1px solid var(--searchbar-border-color);
@@ -274,20 +365,23 @@ mark.fade-out {
.searchresults-header {
font-weight: bold;
font-size: 1em;
padding: 18px 0 0 5px;
padding-block-start: 18px;
padding-block-end: 0;
padding-inline-start: 5px;
padding-inline-end: 0;
color: var(--searchresults-header-fg);
}
.searchresults-outer {
margin-left: auto;
margin-right: auto;
margin-inline-start: auto;
margin-inline-end: auto;
max-width: var(--content-max-width);
border-bottom: 1px dashed var(--searchresults-border-color);
border-block-end: 1px dashed var(--searchresults-border-color);
}
ul#searchresults {
list-style: none;
padding-left: 20px;
padding-inline-start: 20px;
}
ul#searchresults li {
margin: 10px 0px;
@@ -300,7 +394,10 @@ ul#searchresults li.focus {
ul#searchresults span.teaser {
display: block;
clear: both;
margin: 5px 0 0 20px;
margin-block-start: 5px;
margin-block-end: 0;
margin-inline-start: 20px;
margin-inline-end: 0;
font-size: 0.8em;
}
ul#searchresults span.teaser em {
@@ -323,13 +420,33 @@ ul#searchresults span.teaser em {
background-color: var(--sidebar-bg);
color: var(--sidebar-fg);
}
.sidebar-iframe-inner {
--padding: 10px;
background-color: var(--sidebar-bg);
padding: var(--padding);
margin: 0;
font-size: 1.4rem;
color: var(--sidebar-fg);
min-height: calc(100vh - var(--padding) * 2);
}
.sidebar-iframe-outer {
border: none;
height: 100%;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
[dir=rtl] .sidebar { left: unset; right: 0; }
.sidebar-resizing {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.js:not(.sidebar-resizing) .sidebar {
html:not(.sidebar-resizing) .sidebar {
transition: transform 0.3s; /* Animation: slide away */
}
.sidebar code {
@@ -348,16 +465,35 @@ ul#searchresults span.teaser em {
position: absolute;
cursor: col-resize;
width: 0;
right: 0;
right: calc(var(--sidebar-resize-indicator-width) * -1);
top: 0;
bottom: 0;
display: flex;
align-items: center;
}
.sidebar-resize-handle .sidebar-resize-indicator {
width: 100%;
height: 12px;
background-color: var(--icons);
margin-inline-start: var(--sidebar-resize-indicator-space);
}
[dir=rtl] .sidebar .sidebar-resize-handle {
left: calc(var(--sidebar-resize-indicator-width) * -1);
right: unset;
}
.js .sidebar .sidebar-resize-handle {
cursor: col-resize;
width: 5px;
width: calc(var(--sidebar-resize-indicator-width) - var(--sidebar-resize-indicator-space));
}
.sidebar-hidden .sidebar {
transform: translateX(calc(0px - var(--sidebar-width)));
/* sidebar-hidden */
#sidebar-toggle-anchor:not(:checked) ~ .sidebar {
transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width)));
z-index: -1;
}
[dir=rtl] #sidebar-toggle-anchor:not(:checked) ~ .sidebar {
transform: translateX(calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width)));
}
.sidebar::-webkit-scrollbar {
background: var(--sidebar-bg);
@@ -366,19 +502,26 @@ ul#searchresults span.teaser em {
background: var(--scrollbar);
}
.sidebar-visible .page-wrapper {
transform: translateX(var(--sidebar-width));
/* sidebar-visible */
#sidebar-toggle-anchor:checked ~ .page-wrapper {
transform: translateX(calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width)));
}
[dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper {
transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width)));
}
@media only screen and (min-width: 620px) {
.sidebar-visible .page-wrapper {
#sidebar-toggle-anchor:checked ~ .page-wrapper {
transform: none;
margin-inline-start: calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width));
}
[dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper {
transform: none;
margin-left: var(--sidebar-width);
}
}
.chapter {
list-style: none outside none;
padding-left: 0;
padding-inline-start: 0;
line-height: 2.2em;
}
@@ -408,7 +551,7 @@ ul#searchresults span.teaser em {
.chapter li > a.toggle {
cursor: pointer;
display: block;
margin-left: auto;
margin-inline-start: auto;
padding: 0 10px;
user-select: none;
opacity: 0.68;
@@ -425,7 +568,7 @@ ul#searchresults span.teaser em {
.chapter li.chapter-item {
line-height: 1.5em;
margin-top: 0.6em;
margin-block-start: 0.6em;
}
.chapter li.expanded > a.toggle div {
@@ -448,7 +591,7 @@ ul#searchresults span.teaser em {
.section {
list-style: none outside none;
padding-left: 20px;
padding-inline-start: 20px;
line-height: 1.9em;
}
@@ -468,7 +611,10 @@ ul#searchresults span.teaser em {
padding: 0;
list-style: none;
display: none;
/* Don't let the children's background extend past the rounded corners. */
overflow: hidden;
}
[dir=rtl] .theme-popup { left: unset; right: 10px; }
.theme-popup .default {
color: var(--icons);
}
@@ -476,10 +622,10 @@ ul#searchresults span.teaser em {
width: 100%;
border: 0;
margin: 0;
padding: 2px 10px;
padding: 2px 20px;
line-height: 25px;
white-space: nowrap;
text-align: left;
text-align: start;
cursor: pointer;
color: inherit;
background: inherit;
@@ -488,8 +634,10 @@ ul#searchresults span.teaser em {
.theme-popup .theme:hover {
background-color: var(--theme-hover);
}
.theme-popup .theme:hover:first-child,
.theme-popup .theme:hover:last-child {
border-top-left-radius: inherit;
border-top-right-radius: inherit;
.theme-selected::before {
display: inline-block;
content: "✓";
margin-inline-start: -14px;
width: 14px;
}

View File

@@ -1,10 +1,9 @@
/* Base styles and content styles */
@import 'variables.css';
:root {
/* Browser default font-size is 16px, this way 1 rem = 10px */
font-size: 62.5%;
color-scheme: var(--color-scheme);
}
html {
@@ -12,6 +11,7 @@ html {
color: var(--fg);
background-color: var(--bg);
text-size-adjust: none;
-webkit-text-size-adjust: none;
}
body {
@@ -21,8 +21,19 @@ body {
}
code {
font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important;
font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */
font-family: var(--mono-font) !important;
font-size: var(--code-font-size);
direction: ltr !important;
}
/* make long words/inline code not x overflow */
main {
overflow-wrap: break-word;
}
/* make wide tables scroll if they overflow */
.table-wrapper {
overflow-x: auto;
}
/* Don't change font size in headers. */
@@ -36,13 +47,13 @@ h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
.hide-boring .boring { display: none; }
.hidden { display: none !important; }
h2, h3 { margin-top: 2.5em; }
h4, h5 { margin-top: 2em; }
h2, h3 { margin-block-start: 2.5em; }
h4, h5 { margin-block-start: 2em; }
.header + .header h3,
.header + .header h4,
.header + .header h5 {
margin-top: 1em;
margin-block-start: 1em;
}
h1:target::before,
@@ -53,7 +64,7 @@ h5:target::before,
h6:target::before {
display: inline-block;
content: "»";
margin-left: -30px;
margin-inline-start: -30px;
width: 30px;
}
@@ -62,29 +73,34 @@ h6:target::before {
https://bugs.webkit.org/show_bug.cgi?id=218076
*/
:target {
/* Safari does not support logical properties */
scroll-margin-top: calc(var(--menu-bar-height) + 0.5em);
}
.page {
outline: 0;
padding: 0 var(--page-padding);
margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */
margin-block-start: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */
}
.page-wrapper {
box-sizing: border-box;
background-color: var(--bg);
}
.no-js .page-wrapper,
.js:not(.sidebar-resizing) .page-wrapper {
transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */
}
[dir=rtl] .js:not(.sidebar-resizing) .page-wrapper {
transition: margin-right 0.3s ease, transform 0.3s ease; /* Animation: slide away */
}
.content {
overflow-y: auto;
padding: 0 15px;
padding-bottom: 50px;
padding: 0 5px 50px 5px;
}
.content main {
margin-left: auto;
margin-right: auto;
margin-inline-start: auto;
margin-inline-end: auto;
max-width: var(--content-max-width);
}
.content p { line-height: 1.45em; }
@@ -134,14 +150,61 @@ blockquote {
padding: 0 20px;
color: var(--fg);
background-color: var(--quote-bg);
border-top: .1em solid var(--quote-border);
border-bottom: .1em solid var(--quote-border);
border-block-start: .1em solid var(--quote-border);
border-block-end: .1em solid var(--quote-border);
}
.warning {
margin: 20px;
padding: 0 20px;
border-inline-start: 2px solid var(--warning-border);
}
:not(.footnote-definition) + .footnote-definition,
.footnote-definition + :not(.footnote-definition) {
margin-top: 2em;
.warning:before {
position: absolute;
width: 3rem;
height: 3rem;
margin-inline-start: calc(-1.5rem - 21px);
content: "ⓘ";
text-align: center;
background-color: var(--bg);
color: var(--warning-border);
font-weight: bold;
font-size: 2rem;
}
blockquote .warning:before {
background-color: var(--quote-bg);
}
kbd {
background-color: var(--table-border-color);
border-radius: 4px;
border: solid 1px var(--theme-popup-border);
box-shadow: inset 0 -1px 0 var(--theme-hover);
display: inline-block;
font-size: var(--code-font-size);
font-family: var(--mono-font);
line-height: 10px;
padding: 4px 5px;
vertical-align: middle;
}
sup {
/* Set the line-height for superscript and footnote references so that there
isn't an awkward space appearing above lines that contain the footnote.
See https://github.com/rust-lang/mdBook/pull/2443#discussion_r1813773583
for an explanation.
*/
line-height: 0;
}
:not(.footnote-definition) + .footnote-definition {
margin-block-start: 2em;
}
.footnote-definition:not(:has(+ .footnote-definition)) {
margin-block-end: 2em;
}
.footnote-definition {
font-size: 0.9em;

View File

@@ -7,8 +7,8 @@
}
#page-wrapper.page-wrapper {
transform: none;
margin-left: 0px;
transform: none !important;
margin-inline-start: 0px;
overflow-y: initial;
}
@@ -23,11 +23,7 @@
}
code {
background-color: #666666;
border-radius: 5px;
/* Force background to be printed in Chrome */
-webkit-print-color-adjust: exact;
direction: ltr !important;
}
pre > .buttons {

View File

@@ -3,9 +3,13 @@
:root {
--sidebar-width: 300px;
--sidebar-resize-indicator-width: 8px;
--sidebar-resize-indicator-space: 2px;
--page-padding: 15px;
--content-max-width: 750px;
--menu-bar-height: 50px;
--mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace;
--code-font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */
}
/* Themes */
@@ -36,6 +40,8 @@
--quote-bg: hsl(226, 15%, 17%);
--quote-border: hsl(226, 15%, 22%);
--warning-border: #ff8e00;
--table-border-color: hsl(210, 25%, 13%);
--table-header-bg: hsl(210, 25%, 28%);
--table-alternate-bg: hsl(210, 25%, 11%);
@@ -48,6 +54,13 @@
--searchresults-border-color: #888;
--searchresults-li-bg: #252932;
--search-mark-bg: #e3b171;
--color-scheme: dark;
/* Same as `--icons` */
--copy-button-filter: invert(45%) sepia(6%) saturate(621%) hue-rotate(198deg) brightness(99%) contrast(85%);
/* Same as `--sidebar-active` */
--copy-button-filter-hover: invert(68%) sepia(55%) saturate(531%) hue-rotate(341deg) brightness(104%) contrast(101%);
}
.coal {
@@ -76,6 +89,8 @@
--quote-bg: hsl(234, 21%, 18%);
--quote-border: hsl(234, 21%, 23%);
--warning-border: #ff8e00;
--table-border-color: hsl(200, 7%, 13%);
--table-header-bg: hsl(200, 7%, 28%);
--table-alternate-bg: hsl(200, 7%, 11%);
@@ -88,9 +103,16 @@
--searchresults-border-color: #98a3ad;
--searchresults-li-bg: #2b2b2f;
--search-mark-bg: #355c7d;
--color-scheme: dark;
/* Same as `--icons` */
--copy-button-filter: invert(26%) sepia(8%) saturate(575%) hue-rotate(169deg) brightness(87%) contrast(82%);
/* Same as `--sidebar-active` */
--copy-button-filter-hover: invert(36%) sepia(70%) saturate(503%) hue-rotate(167deg) brightness(98%) contrast(89%);
}
.light {
.light, html:not(.js) {
--bg: hsl(0, 0%, 100%);
--fg: hsl(0, 0%, 0%);
@@ -116,6 +138,8 @@
--quote-bg: hsl(197, 37%, 96%);
--quote-border: hsl(197, 37%, 91%);
--warning-border: #ff8e00;
--table-border-color: hsl(0, 0%, 95%);
--table-header-bg: hsl(0, 0%, 80%);
--table-alternate-bg: hsl(0, 0%, 97%);
@@ -128,6 +152,13 @@
--searchresults-border-color: #888;
--searchresults-li-bg: #e4f2fe;
--search-mark-bg: #a2cff5;
--color-scheme: light;
/* Same as `--icons` */
--copy-button-filter: invert(45.49%);
/* Same as `--sidebar-active` */
--copy-button-filter-hover: invert(14%) sepia(93%) saturate(4250%) hue-rotate(243deg) brightness(99%) contrast(130%);
}
.navy {
@@ -156,6 +187,8 @@
--quote-bg: hsl(226, 15%, 17%);
--quote-border: hsl(226, 15%, 22%);
--warning-border: #ff8e00;
--table-border-color: hsl(226, 23%, 16%);
--table-header-bg: hsl(226, 23%, 31%);
--table-alternate-bg: hsl(226, 23%, 14%);
@@ -168,6 +201,13 @@
--searchresults-border-color: #5c5c68;
--searchresults-li-bg: #242430;
--search-mark-bg: #a2cff5;
--color-scheme: dark;
/* Same as `--icons` */
--copy-button-filter: invert(51%) sepia(10%) saturate(393%) hue-rotate(198deg) brightness(86%) contrast(87%);
/* Same as `--sidebar-active` */
--copy-button-filter-hover: invert(46%) sepia(20%) saturate(1537%) hue-rotate(156deg) brightness(85%) contrast(90%);
}
.rust {
@@ -196,6 +236,8 @@
--quote-bg: hsl(60, 5%, 75%);
--quote-border: hsl(60, 5%, 70%);
--warning-border: #ff8e00;
--table-border-color: hsl(60, 9%, 82%);
--table-header-bg: #b3a497;
--table-alternate-bg: hsl(60, 9%, 84%);
@@ -208,10 +250,15 @@
--searchresults-border-color: #888;
--searchresults-li-bg: #dec2a2;
--search-mark-bg: #e69f67;
/* Same as `--icons` */
--copy-button-filter: invert(51%) sepia(10%) saturate(393%) hue-rotate(198deg) brightness(86%) contrast(87%);
/* Same as `--sidebar-active` */
--copy-button-filter-hover: invert(77%) sepia(16%) saturate(1798%) hue-rotate(328deg) brightness(98%) contrast(83%);
}
@media (prefers-color-scheme: dark) {
.light.no-js {
html:not(.js) {
--bg: hsl(200, 7%, 8%);
--fg: #98a3ad;
@@ -237,6 +284,8 @@
--quote-bg: hsl(234, 21%, 18%);
--quote-border: hsl(234, 21%, 23%);
--warning-border: #ff8e00;
--table-border-color: hsl(200, 7%, 13%);
--table-header-bg: hsl(200, 7%, 28%);
--table-alternate-bg: hsl(200, 7%, 11%);
@@ -249,5 +298,12 @@
--searchresults-border-color: #98a3ad;
--searchresults-li-bg: #2b2b2f;
--search-mark-bg: #355c7d;
--color-scheme: dark;
/* Same as `--icons` */
--copy-button-filter: invert(26%) sepia(8%) saturate(575%) hue-rotate(169deg) brightness(87%) contrast(82%);
/* Same as `--sidebar-active` */
--copy-button-filter-hover: invert(36%) sepia(70%) saturate(503%) hue-rotate(167deg) brightness(98%) contrast(89%);
}
}

View File

@@ -16,6 +16,7 @@
.hljs-variable,
.hljs-template-variable,
.hljs-attribute,
.hljs-attr,
.hljs-tag,
.hljs-name,
.hljs-regexp,
@@ -61,7 +62,6 @@
overflow-x: auto;
background: #f6f7f6;
color: #000;
padding: 0.5em;
}
.hljs-emphasis {

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +1,11 @@
<!DOCTYPE HTML>
<html lang="{{ language }}" class="sidebar-visible no-js {{ default_theme }}">
<html lang="{{ language }}" class="{{ default_theme }} sidebar-visible" dir="{{ text_direction }}">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>{{ title }}</title>
{{#if is_print }}
<meta name="robots" content="noindex" />
<meta name="robots" content="noindex">
{{/if}}
{{#if base_url}}
<base href="{{ base_url }}">
@@ -15,10 +15,9 @@
<!-- Custom HTML head -->
{{> head}}
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="{{ description }}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<meta name="theme-color" content="#ffffff">
{{#if favicon_svg}}
<link rel="icon" href="{{ path_to_root }}favicon.svg">
@@ -51,18 +50,21 @@
{{#if mathjax_support}}
<!-- MathJax -->
<script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
{{/if}}
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
<script>
var path_to_root = "{{ path_to_root }}";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
</script>
<!-- Start loading toc.js asap -->
<script src="{{ path_to_root }}toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
@@ -78,34 +80,42 @@
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
const html = document.documentElement;
html.classList.remove('{{ default_theme }}')
html.classList.add(theme);
html.classList.add('js');
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
<script>
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
{{#toc}}{{/toc}}
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="{{ path_to_root }}toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
@@ -113,20 +123,20 @@
<div class="page">
{{> header}}
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky bordered">
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">{{ theme_option "Light" }}</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">{{ theme_option "Rust" }}</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">{{ theme_option "Coal" }}</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">{{ theme_option "Navy" }}</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">{{ theme_option "Ayu" }}</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
{{#if search_enabled}}
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
@@ -171,7 +181,7 @@
{{/if}}
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
@@ -193,7 +203,7 @@
{{/previous}}
{{#next}}
<a rel="next" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
{{/next}}
@@ -211,7 +221,7 @@
{{/previous}}
{{#next}}
<a rel="next" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
{{/next}}
@@ -219,10 +229,12 @@
</div>
{{#if livereload}}
{{#if live_reload_endpoint}}
<!-- Livereload script (if served using the cli tool) -->
<script type="text/javascript">
var socket = new WebSocket("{{{livereload}}}");
<script>
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
const socket = new WebSocket(wsAddress);
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
@@ -238,7 +250,7 @@
{{#if google_analytics}}
<!-- Google Analytics Tag -->
<script type="text/javascript">
<script>
var localAddrs = ["localhost", "127.0.0.1", ""];
// make sure we don't activate google analytics if the developer is
@@ -256,43 +268,43 @@
{{/if}}
{{#if playground_line_numbers}}
<script type="text/javascript">
<script>
window.playground_line_numbers = true;
</script>
{{/if}}
{{#if playground_copyable}}
<script type="text/javascript">
<script>
window.playground_copyable = true;
</script>
{{/if}}
{{#if playground_js}}
<script src="{{ path_to_root }}ace.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ path_to_root }}editor.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ path_to_root }}mode-rust.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ path_to_root }}theme-dawn.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ path_to_root }}theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ path_to_root }}ace.js"></script>
<script src="{{ path_to_root }}editor.js"></script>
<script src="{{ path_to_root }}mode-rust.js"></script>
<script src="{{ path_to_root }}theme-dawn.js"></script>
<script src="{{ path_to_root }}theme-tomorrow_night.js"></script>
{{/if}}
{{#if search_js}}
<script src="{{ path_to_root }}elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ path_to_root }}mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ path_to_root }}searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ path_to_root }}elasticlunr.min.js"></script>
<script src="{{ path_to_root }}mark.min.js"></script>
<script src="{{ path_to_root }}searcher.js"></script>
{{/if}}
<script src="{{ path_to_root }}clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ path_to_root }}highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ path_to_root }}book.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ path_to_root }}clipboard.min.js"></script>
<script src="{{ path_to_root }}highlight.js"></script>
<script src="{{ path_to_root }}book.js"></script>
<!-- Custom JS scripts -->
{{#each additional_js}}
<script type="text/javascript" src="{{ ../path_to_root }}{{this}}"></script>
<script src="{{ ../path_to_root }}{{this}}"></script>
{{/each}}
{{#if is_print}}
{{#if mathjax_support}}
<script type="text/javascript">
<script>
window.addEventListener('load', function() {
MathJax.Hub.Register.StartupHook('End', function() {
window.setTimeout(window.print, 100);
@@ -300,7 +312,7 @@
});
</script>
{{else}}
<script type="text/javascript">
<script>
window.addEventListener('load', function() {
window.setTimeout(window.print, 100);
});
@@ -308,5 +320,6 @@
{{/if}}
{{/if}}
</div>
</body>
</html>

View File

@@ -9,14 +9,16 @@ pub mod searcher;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::path::{Path, PathBuf};
use crate::errors::*;
use log::warn;
pub static INDEX: &[u8] = include_bytes!("index.hbs");
pub static HEAD: &[u8] = include_bytes!("head.hbs");
pub static REDIRECT: &[u8] = include_bytes!("redirect.hbs");
pub static HEADER: &[u8] = include_bytes!("header.hbs");
pub static TOC_JS: &[u8] = include_bytes!("toc.js.hbs");
pub static TOC_HTML: &[u8] = include_bytes!("toc.html.hbs");
pub static CHROME_CSS: &[u8] = include_bytes!("css/chrome.css");
pub static GENERAL_CSS: &[u8] = include_bytes!("css/general.css");
pub static PRINT_CSS: &[u8] = include_bytes!("css/print.css");
@@ -50,10 +52,14 @@ pub struct Theme {
pub head: Vec<u8>,
pub redirect: Vec<u8>,
pub header: Vec<u8>,
pub toc_js: Vec<u8>,
pub toc_html: Vec<u8>,
pub chrome_css: Vec<u8>,
pub general_css: Vec<u8>,
pub print_css: Vec<u8>,
pub variables_css: Vec<u8>,
pub fonts_css: Option<Vec<u8>>,
pub font_files: Vec<PathBuf>,
pub favicon_png: Option<Vec<u8>>,
pub favicon_svg: Option<Vec<u8>>,
pub js: Vec<u8>,
@@ -83,6 +89,8 @@ impl Theme {
(theme_dir.join("head.hbs"), &mut theme.head),
(theme_dir.join("redirect.hbs"), &mut theme.redirect),
(theme_dir.join("header.hbs"), &mut theme.header),
(theme_dir.join("toc.js.hbs"), &mut theme.toc_js),
(theme_dir.join("toc.html.hbs"), &mut theme.toc_html),
(theme_dir.join("book.js"), &mut theme.js),
(theme_dir.join("css/chrome.css"), &mut theme.chrome_css),
(theme_dir.join("css/general.css"), &mut theme.general_css),
@@ -104,7 +112,7 @@ impl Theme {
),
];
let load_with_warn = |filename: &Path, dest| {
let load_with_warn = |filename: &Path, dest: &mut Vec<u8>| {
if !filename.exists() {
// Don't warn if the file doesn't exist.
return false;
@@ -121,6 +129,29 @@ impl Theme {
load_with_warn(&filename, dest);
}
let fonts_dir = theme_dir.join("fonts");
if fonts_dir.exists() {
let mut fonts_css = Vec::new();
if load_with_warn(&fonts_dir.join("fonts.css"), &mut fonts_css) {
theme.fonts_css.replace(fonts_css);
}
if let Ok(entries) = fonts_dir.read_dir() {
theme.font_files = entries
.filter_map(|entry| {
let entry = entry.ok()?;
if entry.file_name() == "fonts.css" {
None
} else if entry.file_type().ok()?.is_dir() {
log::info!("skipping font directory {:?}", entry.path());
None
} else {
Some(entry.path())
}
})
.collect();
}
}
// If the user overrides one favicon, but not the other, do not
// copy the default for the other.
let favicon_png = &mut theme.favicon_png.as_mut().unwrap();
@@ -149,10 +180,14 @@ impl Default for Theme {
head: HEAD.to_owned(),
redirect: REDIRECT.to_owned(),
header: HEADER.to_owned(),
toc_js: TOC_JS.to_owned(),
toc_html: TOC_HTML.to_owned(),
chrome_css: CHROME_CSS.to_owned(),
general_css: GENERAL_CSS.to_owned(),
print_css: PRINT_CSS.to_owned(),
variables_css: VARIABLES_CSS.to_owned(),
fonts_css: None,
font_files: Vec::new(),
favicon_png: Some(FAVICON_PNG.to_owned()),
favicon_svg: Some(FAVICON_SVG.to_owned()),
js: JS.to_owned(),
@@ -185,7 +220,6 @@ fn load_file_contents<P: AsRef<Path>>(filename: P, dest: &mut Vec<u8>) -> Result
mod tests {
use super::*;
use std::fs;
use std::path::PathBuf;
use tempfile::Builder as TempFileBuilder;
#[test]
@@ -206,13 +240,15 @@ mod tests {
"head.hbs",
"redirect.hbs",
"header.hbs",
"toc.js.hbs",
"toc.html.hbs",
"favicon.png",
"favicon.svg",
"css/chrome.css",
"css/fonts.css",
"css/general.css",
"css/print.css",
"css/variables.css",
"fonts/fonts.css",
"book.js",
"highlight.js",
"tomorrow-night.css",
@@ -223,6 +259,7 @@ mod tests {
let temp = TempFileBuilder::new().prefix("mdbook-").tempdir().unwrap();
fs::create_dir(temp.path().join("css")).unwrap();
fs::create_dir(temp.path().join("fonts")).unwrap();
// "touch" all of the special files so we have empty copies
for file in &files {
@@ -236,10 +273,14 @@ mod tests {
head: Vec::new(),
redirect: Vec::new(),
header: Vec::new(),
toc_js: Vec::new(),
toc_html: Vec::new(),
chrome_css: Vec::new(),
general_css: Vec::new(),
print_css: Vec::new(),
variables_css: Vec::new(),
fonts_css: Some(Vec::new()),
font_files: Vec::new(),
favicon_png: Some(Vec::new()),
favicon_svg: Some(Vec::new()),
js: Vec::new(),

View File

@@ -3,8 +3,8 @@
<head>
<meta charset="utf-8">
<title>Redirecting...</title>
<meta http-equiv="refresh" content="0;URL='{{url}}'">
<meta rel="canonical" href="{{url}}">
<meta http-equiv="refresh" content="0; URL={{url}}">
<link rel="canonical" href="{{url}}">
</head>
<body>
<p>Redirecting to... <a href="{{url}}">{{url}}</a>.</p>

View File

@@ -316,7 +316,7 @@ window.search = window.search || {};
// Eventhandler for keyevents on `document`
function globalKeyHandler(e) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea' || e.target.type === 'text') { return; }
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea' || e.target.type === 'text' || !hasFocus() && /^(?:input|select|textarea)$/i.test(e.target.nodeName)) { return; }
if (e.keyCode === ESCAPE_KEYCODE) {
e.preventDefault();

43
src/theme/toc.html.hbs Normal file
View File

@@ -0,0 +1,43 @@
<!DOCTYPE HTML>
<html lang="{{ language }}" class="{{ default_theme }}" dir="{{ text_direction }}">
<head>
<!-- sidebar iframe generated using mdBook
This is a frame, and not included directly in the page, to control the total size of the
book. The TOC contains an entry for each page, so if each page includes a copy of the TOC,
the total size of the page becomes O(n**2).
The frame is only used as a fallback when JS is turned off. When it's on, the sidebar is
instead added to the main page by `toc.js` instead. The JavaScript mode is better
because, when running in a `file:///` URL, the iframed page would not be Same-Origin as
the rest of the page, so the sidebar and the main page theme would fall out of sync.
-->
<meta charset="UTF-8">
<meta name="robots" content="noindex">
{{#if base_url}}
<base href="{{ base_url }}">
{{/if}}
<!-- Custom HTML head -->
{{> head}}
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="{{ path_to_root }}css/variables.css">
<link rel="stylesheet" href="{{ path_to_root }}css/general.css">
<link rel="stylesheet" href="{{ path_to_root }}css/chrome.css">
{{#if print_enable}}
<link rel="stylesheet" href="{{ path_to_root }}css/print.css" media="print">
{{/if}}
<!-- Fonts -->
<link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
{{#if copy_fonts}}
<link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
{{/if}}
<!-- Custom theme stylesheets -->
{{#each additional_css}}
<link rel="stylesheet" href="{{ ../path_to_root }}{{ this }}">
{{/each}}
</head>
<body class="sidebar-iframe-inner">
{{#toc}}{{/toc}}
</body>
</html>

70
src/theme/toc.js.hbs Normal file
View File

@@ -0,0 +1,70 @@
// Populate the sidebar
//
// This is a script, and not included directly in the page, to control the total size of the book.
// The TOC contains an entry for each page, so if each page includes a copy of the TOC,
// the total size of the page becomes O(n**2).
class MDBookSidebarScrollbox extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.innerHTML = '{{#toc}}{{/toc}}';
// Set the current, active page, and reveal it if it's hidden
let current_page = document.location.href.toString().split("#")[0];
if (current_page.endsWith("/")) {
current_page += "index.html";
}
var links = Array.prototype.slice.call(this.querySelectorAll("a"));
var l = links.length;
for (var i = 0; i < l; ++i) {
var link = links[i];
var href = link.getAttribute("href");
if (href && !href.startsWith("#") && !/^(?:[a-z+]+:)?\/\//.test(href)) {
link.href = path_to_root + href;
}
// The "index" page is supposed to alias the first chapter in the book.
if (link.href === current_page || (i === 0 && path_to_root === "" && current_page.endsWith("/index.html"))) {
link.classList.add("active");
var parent = link.parentElement;
if (parent && parent.classList.contains("chapter-item")) {
parent.classList.add("expanded");
}
while (parent) {
if (parent.tagName === "LI" && parent.previousElementSibling) {
if (parent.previousElementSibling.classList.contains("chapter-item")) {
parent.previousElementSibling.classList.add("expanded");
}
}
parent = parent.parentElement;
}
}
}
// Track and set sidebar scroll position
this.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', this.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
this.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
// Toggle buttons
var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle');
function toggleSection(ev) {
ev.currentTarget.parentElement.classList.toggle('expanded');
}
Array.from(sidebarAnchorToggles).forEach(function (el) {
el.addEventListener('click', toggleSection);
});
}
}
window.customElements.define("mdbook-sidebar-scrollbox", MDBookSidebarScrollbox);

View File

@@ -1,7 +1,7 @@
/* Tomorrow Night Theme */
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* https://github.com/jmblog/color-themes-for-highlightjs */
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* https://github.com/jmblog/color-themes-for-highlightjs */
/* Tomorrow Comment */
.hljs-comment {
@@ -11,6 +11,7 @@
/* Tomorrow Red */
.hljs-variable,
.hljs-attribute,
.hljs-attr,
.hljs-tag,
.hljs-regexp,
.ruby .hljs-constant,
@@ -54,6 +55,7 @@
/* Tomorrow Aqua */
.hljs-title,
.hljs-section,
.css .hljs-hexcolor {
color: #8abeb7;
}
@@ -81,8 +83,6 @@
overflow-x: auto;
background: #1d1f21;
color: #c5c8c6;
padding: 0.5em;
-webkit-text-size-adjust: none;
}
.coffeescript .javascript,

View File

@@ -1,5 +1,5 @@
use crate::errors::*;
use std::convert::Into;
use log::{debug, trace};
use std::fs::{self, File};
use std::io::Write;
use std::path::{Component, Path, PathBuf};
@@ -37,7 +37,6 @@ pub fn write_file<P: AsRef<Path>>(build_dir: &Path, filename: P, content: &[u8])
/// Consider [submitting a new issue](https://github.com/rust-lang/mdBook/issues)
/// or a [pull-request](https://github.com/rust-lang/mdBook/pulls) to improve it.
pub fn path_to_root<P: Into<PathBuf>>(path: P) -> String {
debug!("path_to_root");
// Remove filename and add "../" for every directory
path.into()
@@ -73,14 +72,12 @@ pub fn create_file(path: &Path) -> Result<File> {
/// Removes all the content of a directory but not the directory itself
pub fn remove_dir_content(dir: &Path) -> Result<()> {
for item in fs::read_dir(dir)? {
if let Ok(item) = item {
let item = item.path();
if item.is_dir() {
fs::remove_dir_all(item)?;
} else {
fs::remove_file(item)?;
}
for item in fs::read_dir(dir)?.flatten() {
let item = item.path();
if item.is_dir() {
fs::remove_dir_all(item)?;
} else {
fs::remove_file(item)?;
}
}
Ok(())
@@ -109,77 +106,102 @@ pub fn copy_files_except_ext(
}
for entry in fs::read_dir(from)? {
let entry = entry?;
let entry = entry?.path();
let metadata = entry
.path()
.metadata()
.with_context(|| format!("Failed to read {:?}", entry.path()))?;
.with_context(|| format!("Failed to read {entry:?}"))?;
let entry_file_name = entry.file_name().unwrap();
let target_file_path = to.join(entry_file_name);
// If the entry is a dir and the recursive option is enabled, call itself
if metadata.is_dir() && recursive {
if entry.path() == to.to_path_buf() {
if entry == to.as_os_str() {
continue;
}
if let Some(avoid) = avoid_dir {
if entry.path() == *avoid {
if entry == *avoid {
continue;
}
}
// check if output dir already exists
if !to.join(entry.file_name()).exists() {
fs::create_dir(&to.join(entry.file_name()))?;
if !target_file_path.exists() {
fs::create_dir(&target_file_path)?;
}
copy_files_except_ext(
&from.join(entry.file_name()),
&to.join(entry.file_name()),
true,
avoid_dir,
ext_blacklist,
)?;
copy_files_except_ext(&entry, &target_file_path, true, avoid_dir, ext_blacklist)?;
} else if metadata.is_file() {
// Check if it is in the blacklist
if let Some(ext) = entry.path().extension() {
if let Some(ext) = entry.extension() {
if ext_blacklist.contains(&ext.to_str().unwrap()) {
continue;
}
}
debug!(
"creating path for file: {:?}",
&to.join(
entry
.path()
.file_name()
.expect("a file should have a file name...")
)
);
debug!(
"Copying {:?} to {:?}",
entry.path(),
&to.join(
entry
.path()
.file_name()
.expect("a file should have a file name...")
)
);
fs::copy(
entry.path(),
&to.join(
entry
.path()
.file_name()
.expect("a file should have a file name..."),
),
)?;
debug!("Copying {entry:?} to {target_file_path:?}");
copy(&entry, &target_file_path)?;
}
}
Ok(())
}
/// Copies a file.
fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
let from = from.as_ref();
let to = to.as_ref();
return copy_inner(from, to)
.with_context(|| format!("failed to copy `{}` to `{}`", from.display(), to.display()));
// This is a workaround for an issue with the macOS file watcher.
// Rust's `std::fs::copy` function uses `fclonefileat`, which creates
// clones on APFS. Unfortunately fs events seem to trigger on both
// sides of the clone, and there doesn't seem to be a way to differentiate
// which side it is.
// https://github.com/notify-rs/notify/issues/465#issuecomment-1657261035
// contains more information.
//
// This is essentially a copy of the simple copy code path in Rust's
// standard library.
#[cfg(target_os = "macos")]
fn copy_inner(from: &Path, to: &Path) -> Result<()> {
use std::fs::OpenOptions;
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
let mut reader = File::open(from)?;
let metadata = reader.metadata()?;
if !metadata.is_file() {
anyhow::bail!(
"expected a file, `{}` appears to be {:?}",
from.display(),
metadata.file_type()
);
}
let perm = metadata.permissions();
let mut writer = OpenOptions::new()
.mode(perm.mode())
.write(true)
.create(true)
.truncate(true)
.open(to)?;
let writer_metadata = writer.metadata()?;
if writer_metadata.is_file() {
// Set the correct file permissions, in case the file already existed.
// Don't set the permissions on already existing non-files like
// pipes/FIFOs or device nodes.
writer.set_permissions(perm)?;
}
std::io::copy(&mut reader, &mut writer)?;
Ok(())
}
#[cfg(not(target_os = "macos"))]
fn copy_inner(from: &Path, to: &Path) -> Result<()> {
fs::copy(from, to)?;
Ok(())
}
}
pub fn get_404_output_file(input_404: &Option<String>) -> String {
input_404
.as_ref()
@@ -206,69 +228,66 @@ mod tests {
fn copy_files_except_ext_test() {
let tmp = match tempfile::TempDir::new() {
Ok(t) => t,
Err(e) => panic!("Could not create a temp dir: {}", e),
Err(e) => panic!("Could not create a temp dir: {e}"),
};
// Create a couple of files
if let Err(err) = fs::File::create(&tmp.path().join("file.txt")) {
panic!("Could not create file.txt: {}", err);
if let Err(err) = fs::File::create(tmp.path().join("file.txt")) {
panic!("Could not create file.txt: {err}");
}
if let Err(err) = fs::File::create(&tmp.path().join("file.md")) {
panic!("Could not create file.md: {}", err);
if let Err(err) = fs::File::create(tmp.path().join("file.md")) {
panic!("Could not create file.md: {err}");
}
if let Err(err) = fs::File::create(&tmp.path().join("file.png")) {
panic!("Could not create file.png: {}", err);
if let Err(err) = fs::File::create(tmp.path().join("file.png")) {
panic!("Could not create file.png: {err}");
}
if let Err(err) = fs::create_dir(&tmp.path().join("sub_dir")) {
panic!("Could not create sub_dir: {}", err);
if let Err(err) = fs::create_dir(tmp.path().join("sub_dir")) {
panic!("Could not create sub_dir: {err}");
}
if let Err(err) = fs::File::create(&tmp.path().join("sub_dir/file.png")) {
panic!("Could not create sub_dir/file.png: {}", err);
if let Err(err) = fs::File::create(tmp.path().join("sub_dir/file.png")) {
panic!("Could not create sub_dir/file.png: {err}");
}
if let Err(err) = fs::create_dir(&tmp.path().join("sub_dir_exists")) {
panic!("Could not create sub_dir_exists: {}", err);
if let Err(err) = fs::create_dir(tmp.path().join("sub_dir_exists")) {
panic!("Could not create sub_dir_exists: {err}");
}
if let Err(err) = fs::File::create(&tmp.path().join("sub_dir_exists/file.txt")) {
panic!("Could not create sub_dir_exists/file.txt: {}", err);
if let Err(err) = fs::File::create(tmp.path().join("sub_dir_exists/file.txt")) {
panic!("Could not create sub_dir_exists/file.txt: {err}");
}
if let Err(err) = symlink(
&tmp.path().join("file.png"),
&tmp.path().join("symlink.png"),
) {
panic!("Could not symlink file.png: {}", err);
if let Err(err) = symlink(tmp.path().join("file.png"), tmp.path().join("symlink.png")) {
panic!("Could not symlink file.png: {err}");
}
// Create output dir
if let Err(err) = fs::create_dir(&tmp.path().join("output")) {
panic!("Could not create output: {}", err);
if let Err(err) = fs::create_dir(tmp.path().join("output")) {
panic!("Could not create output: {err}");
}
if let Err(err) = fs::create_dir(&tmp.path().join("output/sub_dir_exists")) {
panic!("Could not create output/sub_dir_exists: {}", err);
if let Err(err) = fs::create_dir(tmp.path().join("output/sub_dir_exists")) {
panic!("Could not create output/sub_dir_exists: {err}");
}
if let Err(e) =
copy_files_except_ext(tmp.path(), &tmp.path().join("output"), true, None, &["md"])
{
panic!("Error while executing the function:\n{:?}", e);
panic!("Error while executing the function:\n{e:?}");
}
// Check if the correct files where created
if !(&tmp.path().join("output/file.txt")).exists() {
if !tmp.path().join("output/file.txt").exists() {
panic!("output/file.txt should exist")
}
if (&tmp.path().join("output/file.md")).exists() {
if tmp.path().join("output/file.md").exists() {
panic!("output/file.md should not exist")
}
if !(&tmp.path().join("output/file.png")).exists() {
if !tmp.path().join("output/file.png").exists() {
panic!("output/file.png should exist")
}
if !(&tmp.path().join("output/sub_dir/file.png")).exists() {
if !tmp.path().join("output/sub_dir/file.png").exists() {
panic!("output/sub_dir/file.png should exist")
}
if !(&tmp.path().join("output/sub_dir_exists/file.txt")).exists() {
if !tmp.path().join("output/sub_dir_exists/file.txt").exists() {
panic!("output/sub_dir/file.png should exist")
}
if !(&tmp.path().join("output/symlink.png")).exists() {
if !tmp.path().join("output/symlink.png").exists() {
panic!("output/symlink.png should exist")
}
}

View File

@@ -4,11 +4,13 @@ pub mod fs;
mod string;
pub(crate) mod toml_ext;
use crate::errors::Error;
use log::error;
use once_cell::sync::Lazy;
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag, TagEnd};
use regex::Regex;
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag};
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::Write;
use std::path::Path;
@@ -19,9 +21,7 @@ pub use self::string::{
/// Replaces multiple consecutive whitespace characters with a single space character.
pub fn collapse_whitespace(text: &str) -> Cow<'_, str> {
lazy_static! {
static ref RE: Regex = Regex::new(r"\s\s+").unwrap();
}
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\s\s+").unwrap());
RE.replace_all(text, " ")
}
@@ -44,33 +44,45 @@ pub fn normalize_id(content: &str) -> String {
/// Generate an ID for use with anchors which is derived from a "normalised"
/// string.
// This function should be made private when the deprecation expires.
#[deprecated(since = "0.4.16", note = "use unique_id_from_content instead")]
pub fn id_from_content(content: &str) -> String {
let mut content = content.to_string();
// Skip any tags or html-encoded stuff
const REPL_SUB: &[&str] = &[
"<em>",
"</em>",
"<code>",
"</code>",
"<strong>",
"</strong>",
"&lt;",
"&gt;",
"&amp;",
"&#39;",
"&quot;",
];
static HTML: Lazy<Regex> = Lazy::new(|| Regex::new(r"(<.*?>)").unwrap());
content = HTML.replace_all(&content, "").into();
const REPL_SUB: &[&str] = &["&lt;", "&gt;", "&amp;", "&#39;", "&quot;"];
for sub in REPL_SUB {
content = content.replace(sub, "");
}
// Remove spaces and hashes indicating a header
let trimmed = content.trim().trim_start_matches('#').trim();
normalize_id(trimmed)
}
/// Generate an ID for use with anchors which is derived from a "normalised"
/// string.
///
/// Each ID returned will be unique, if the same `id_counter` is provided on
/// each call.
pub fn unique_id_from_content(content: &str, id_counter: &mut HashMap<String, usize>) -> String {
let id = {
#[allow(deprecated)]
id_from_content(content)
};
// If we have headers with the same normalized id, append an incrementing counter
let id_count = id_counter.entry(id.clone()).or_insert(0);
let unique_id = match *id_count {
0 => id,
id_count => format!("{id}-{id_count}"),
};
*id_count += 1;
unique_id
}
/// Fix links to the correct location.
///
/// This adjusts links, such as turning `.md` extensions to `.html`.
@@ -81,10 +93,9 @@ pub fn id_from_content(content: &str) -> String {
/// None. Ideally, print page links would link to anchors on the print page,
/// but that is very difficult.
fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
lazy_static! {
static ref SCHEME_LINK: Regex = Regex::new(r"^[a-z][a-z0-9+.-]*:").unwrap();
static ref MD_LINK: Regex = Regex::new(r"(?P<link>.*)\.md(?P<anchor>#.*)?").unwrap();
}
static SCHEME_LINK: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[a-z][a-z0-9+.-]*:").unwrap());
static MD_LINK: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?P<link>.*)\.md(?P<anchor>#.*)?").unwrap());
fn fix<'a>(dest: CowStr<'a>, path: Option<&Path>) -> CowStr<'a> {
if dest.starts_with('#') {
@@ -94,7 +105,7 @@ fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
if base.ends_with(".md") {
base.replace_range(base.len() - 3.., ".html");
}
return format!("{}{}", base, dest).into();
return format!("{base}{dest}").into();
} else {
return dest;
}
@@ -110,7 +121,7 @@ fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
.to_str()
.expect("utf-8 paths only");
if !base.is_empty() {
write!(fixed_link, "{}/", base).unwrap();
write!(fixed_link, "{base}/").unwrap();
}
}
@@ -137,10 +148,8 @@ fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
// There are dozens of HTML tags/attributes that contain paths, so
// feel free to add more tags if desired; these are the only ones I
// care about right now.
lazy_static! {
static ref HTML_LINK: Regex =
Regex::new(r#"(<(?:a|img) [^>]*?(?:src|href)=")([^"]+?)""#).unwrap();
}
static HTML_LINK: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"(<(?:a|img) [^>]*?(?:src|href)=")([^"]+?)""#).unwrap());
HTML_LINK
.replace_all(&html, |caps: &regex::Captures<'_>| {
@@ -152,76 +161,80 @@ fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
}
match event {
Event::Start(Tag::Link(link_type, dest, title)) => {
Event::Start(Tag::Link(link_type, fix(dest, path), title))
}
Event::Start(Tag::Image(link_type, dest, title)) => {
Event::Start(Tag::Image(link_type, fix(dest, path), title))
}
Event::Start(Tag::Link {
link_type,
dest_url,
title,
id,
}) => Event::Start(Tag::Link {
link_type,
dest_url: fix(dest_url, path),
title,
id,
}),
Event::Start(Tag::Image {
link_type,
dest_url,
title,
id,
}) => Event::Start(Tag::Image {
link_type,
dest_url: fix(dest_url, path),
title,
id,
}),
Event::Html(html) => Event::Html(fix_html(html, path)),
Event::InlineHtml(html) => Event::InlineHtml(fix_html(html, path)),
_ => event,
}
}
/// Wrapper around the pulldown-cmark parser for rendering markdown to HTML.
pub fn render_markdown(text: &str, curly_quotes: bool) -> String {
render_markdown_with_path(text, curly_quotes, None)
pub fn render_markdown(text: &str, smart_punctuation: bool) -> String {
render_markdown_with_path(text, smart_punctuation, None)
}
pub fn new_cmark_parser(text: &str) -> Parser<'_> {
pub fn new_cmark_parser(text: &str, smart_punctuation: bool) -> Parser<'_> {
let mut opts = Options::empty();
opts.insert(Options::ENABLE_TABLES);
opts.insert(Options::ENABLE_FOOTNOTES);
opts.insert(Options::ENABLE_STRIKETHROUGH);
opts.insert(Options::ENABLE_TASKLISTS);
opts.insert(Options::ENABLE_HEADING_ATTRIBUTES);
if smart_punctuation {
opts.insert(Options::ENABLE_SMART_PUNCTUATION);
}
Parser::new_ext(text, opts)
}
pub fn render_markdown_with_path(text: &str, curly_quotes: bool, path: Option<&Path>) -> String {
pub fn render_markdown_with_path(
text: &str,
smart_punctuation: bool,
path: Option<&Path>,
) -> String {
let mut s = String::with_capacity(text.len() * 3 / 2);
let p = new_cmark_parser(text);
let mut converter = EventQuoteConverter::new(curly_quotes);
let p = new_cmark_parser(text, smart_punctuation);
let events = p
.map(clean_codeblock_headers)
.map(|event| adjust_links(event, path))
.map(|event| converter.convert(event));
.flat_map(|event| {
let (a, b) = wrap_tables(event);
a.into_iter().chain(b)
});
html::push_html(&mut s, events);
s
}
struct EventQuoteConverter {
enabled: bool,
convert_text: bool,
}
impl EventQuoteConverter {
fn new(enabled: bool) -> Self {
EventQuoteConverter {
enabled,
convert_text: true,
}
}
fn convert<'a>(&mut self, event: Event<'a>) -> Event<'a> {
if !self.enabled {
return event;
}
match event {
Event::Start(Tag::CodeBlock(_)) => {
self.convert_text = false;
event
}
Event::End(Tag::CodeBlock(_)) => {
self.convert_text = true;
event
}
Event::Text(ref text) if self.convert_text => {
Event::Text(CowStr::from(convert_quotes_to_curly(text)))
}
_ => event,
}
/// Wraps tables in a `.table-wrapper` class to apply overflow-x rules to.
fn wrap_tables(event: Event<'_>) -> (Option<Event<'_>>, Option<Event<'_>>) {
match event {
Event::Start(Tag::Table(_)) => (
Some(Event::Html(r#"<div class="table-wrapper">"#.into())),
Some(event),
),
Event::End(TagEnd::Table) => (Some(event), Some(Event::Html(r#"</div>"#.into()))),
_ => (Some(event), None),
}
}
@@ -243,38 +256,6 @@ fn clean_codeblock_headers(event: Event<'_>) -> Event<'_> {
}
}
fn convert_quotes_to_curly(original_text: &str) -> String {
// We'll consider the start to be "whitespace".
let mut preceded_by_whitespace = true;
original_text
.chars()
.map(|original_char| {
let converted_char = match original_char {
'\'' => {
if preceded_by_whitespace {
''
} else {
''
}
}
'"' => {
if preceded_by_whitespace {
'“'
} else {
'”'
}
}
_ => original_char,
};
preceded_by_whitespace = original_char.is_whitespace();
converted_char
})
.collect()
}
/// Prints a "backtrace" of some `Error`.
pub fn log_backtrace(e: &Error) {
error!("Error: {}", e);
@@ -284,8 +265,45 @@ pub fn log_backtrace(e: &Error) {
}
}
pub(crate) fn special_escape(mut s: &str) -> String {
let mut escaped = String::with_capacity(s.len());
let needs_escape: &[char] = &['<', '>', '\'', '\\', '&'];
while let Some(next) = s.find(needs_escape) {
escaped.push_str(&s[..next]);
match s.as_bytes()[next] {
b'<' => escaped.push_str("&lt;"),
b'>' => escaped.push_str("&gt;"),
b'\'' => escaped.push_str("&#39;"),
b'\\' => escaped.push_str("&#92;"),
b'&' => escaped.push_str("&amp;"),
_ => unreachable!(),
}
s = &s[next + 1..];
}
escaped.push_str(s);
escaped
}
pub(crate) fn bracket_escape(mut s: &str) -> String {
let mut escaped = String::with_capacity(s.len());
let needs_escape: &[char] = &['<', '>'];
while let Some(next) = s.find(needs_escape) {
escaped.push_str(&s[..next]);
match s.as_bytes()[next] {
b'<' => escaped.push_str("&lt;"),
b'>' => escaped.push_str("&gt;"),
_ => unreachable!(),
}
s = &s[next + 1..];
}
escaped.push_str(s);
escaped
}
#[cfg(test)]
mod tests {
use super::{bracket_escape, special_escape};
mod render_markdown {
use super::super::render_markdown;
@@ -315,6 +333,22 @@ mod tests {
);
}
#[test]
fn it_can_wrap_tables() {
let src = r#"
| Original | Punycode | Punycode + Encoding |
|-----------------|-----------------|---------------------|
| føø | f-5gaa | f_5gaa |
"#;
let out = r#"
<div class="table-wrapper"><table><thead><tr><th>Original</th><th>Punycode</th><th>Punycode + Encoding</th></tr></thead><tbody>
<tr><td>føø</td><td>f-5gaa</td><td>f_5gaa</td></tr>
</tbody></table>
</div>
"#.trim();
assert_eq!(render_markdown(src, false), out);
}
#[test]
fn it_can_keep_quotes_straight() {
assert_eq!(render_markdown("'one'", false), "<p>'one'</p>\n");
@@ -406,8 +440,9 @@ more text with spaces
}
}
mod html_munging {
use super::super::{id_from_content, normalize_id};
#[allow(deprecated)]
mod id_from_content {
use super::super::id_from_content;
#[test]
fn it_generates_anchors() {
@@ -417,6 +452,10 @@ more text with spaces
);
assert_eq!(id_from_content("## **Bold** title"), "bold-title");
assert_eq!(id_from_content("## `Code` title"), "code-title");
assert_eq!(
id_from_content("## title <span dir=rtl>foo</span>"),
"title-foo"
);
}
#[test]
@@ -431,6 +470,10 @@ more text with spaces
);
assert_eq!(id_from_content("## Über"), "Über");
}
}
mod html_munging {
use super::super::{normalize_id, unique_id_from_content};
#[test]
fn it_normalizes_ids() {
@@ -449,24 +492,53 @@ more text with spaces
assert_eq!(normalize_id("한국어"), "한국어");
assert_eq!(normalize_id(""), "");
}
#[test]
fn it_generates_unique_ids_from_content() {
// Same id if not given shared state
assert_eq!(
unique_id_from_content("## 中文標題 CJK title", &mut Default::default()),
"中文標題-cjk-title"
);
assert_eq!(
unique_id_from_content("## 中文標題 CJK title", &mut Default::default()),
"中文標題-cjk-title"
);
// Different id if given shared state
let mut id_counter = Default::default();
assert_eq!(unique_id_from_content("## Über", &mut id_counter), "Über");
assert_eq!(
unique_id_from_content("## 中文標題 CJK title", &mut id_counter),
"中文標題-cjk-title"
);
assert_eq!(unique_id_from_content("## Über", &mut id_counter), "Über-1");
assert_eq!(unique_id_from_content("## Über", &mut id_counter), "Über-2");
}
}
mod convert_quotes_to_curly {
use super::super::convert_quotes_to_curly;
#[test]
fn escaped_brackets() {
assert_eq!(bracket_escape(""), "");
assert_eq!(bracket_escape("<"), "&lt;");
assert_eq!(bracket_escape(">"), "&gt;");
assert_eq!(bracket_escape("<>"), "&lt;&gt;");
assert_eq!(bracket_escape("<test>"), "&lt;test&gt;");
assert_eq!(bracket_escape("a<test>b"), "a&lt;test&gt;b");
assert_eq!(bracket_escape("'"), "'");
assert_eq!(bracket_escape("\\"), "\\");
}
#[test]
fn it_converts_single_quotes() {
assert_eq!(convert_quotes_to_curly("'one', 'two'"), "one, two");
}
#[test]
fn it_converts_double_quotes() {
assert_eq!(convert_quotes_to_curly(r#""one", "two""#), "“one”, “two”");
}
#[test]
fn it_treats_tab_as_whitespace() {
assert_eq!(convert_quotes_to_curly("\t'one'"), "\tone");
}
#[test]
fn escaped_special() {
assert_eq!(special_escape(""), "");
assert_eq!(special_escape("<"), "&lt;");
assert_eq!(special_escape(">"), "&gt;");
assert_eq!(special_escape("<>"), "&lt;&gt;");
assert_eq!(special_escape("<test>"), "&lt;test&gt;");
assert_eq!(special_escape("a<test>b"), "a&lt;test&gt;b");
assert_eq!(special_escape("'"), "&#39;");
assert_eq!(special_escape("\\"), "&#92;");
assert_eq!(special_escape("&"), "&amp;");
}
}

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