Compare commits

..

439 Commits

Author SHA1 Message Date
Eric Huss
1a5286b25c Merge pull request #2578 from ehuss/bump-version
Update to 0.4.46
2025-03-08 22:06:23 +00:00
Eric Huss
c493d3b5e3 Update to 0.4.46 2025-03-08 14:00:42 -08:00
Eric Huss
a68091a84c Merge pull request #2571 from szabgab/missing_backends_are_fatal
Check content of the error message.
2025-03-08 20:46:53 +00:00
Eric Huss
b0cf568ba4 Merge pull request #2569 from szabgab/suffix_items_cannot_be_followed_by_a_list
check content of the error message
2025-03-05 17:56:16 +00:00
Gabor Szabo
bf544be282 Check content of the error message.
In missing_backends_are_fatal
2025-03-05 17:38:23 +02:00
Gabor Szabo
4f0dba8fdb check content of the error message
in suffix_items_cannot_be_followed_by_a_list
2025-03-05 17:27:15 +02:00
Eric Huss
5390e44dec Merge pull request #2566 from szabgab/remove-dots-from-docs
remove unnecessary dots from docs
2025-03-04 17:34:42 +00:00
Gabor Szabo
e7e3317ff0 remove unnecessary dots from docs 2025-03-04 17:06:21 +02:00
Eric Huss
d68a596455 Merge pull request #2561 from szabgab/test-failure-in-summary
Test failure in SUMMARY.md when item is not a link
2025-03-03 18:43:36 +00:00
Eric Huss
ace2abff34 Merge pull request #2563 from jofas/patch-1
Enhanced wording for editable code blocks docs
2025-03-03 18:41:44 +00:00
Jonas Fassbender
0c6439faad Enhanced wording for editable code blocks docs 2025-03-03 17:21:34 +01:00
Gabor Szabo
e7418f21f9 Test failure in SUMMARY.md when item is not a link 2025-03-03 10:33:27 +02:00
Eric Huss
19146c403e Merge pull request #2557 from ehuss/fix-playground-edition
Fix playground edition detection
2025-02-26 14:02:34 +00:00
Eric Huss
66ded2302f Fix playground edition detection 2025-02-26 05:50:25 -08:00
Eric Huss
98abb22be1 Merge pull request #1368 from notriddle/hash-files
feat(html): cache bust static files by adding hashes to file names
2025-02-20 18:32:17 +00:00
Eric Huss
ab304e7d38 More code simplification 2025-02-20 10:25:14 -08:00
Eric Huss
fbc21592af Some clippy cleanup 2025-02-20 10:23:47 -08:00
Eric Huss
e7b69114ed Remove some code duplication 2025-02-20 10:19:04 -08:00
Michael Howell
8a9ecd212d Fix, and test, the no-js toc sidebar with hashed resources
To make this work, I need to break the circular dependency and
stop hashing toc.html itself.
2025-02-20 10:27:18 -07:00
Eric Huss
ec157cd1cd Use full patch description for hex 2025-02-20 08:54:01 -08:00
Eric Huss
d3bcb359fa Update sha2 to latest 2025-02-20 08:52:58 -08:00
Eric Huss
2a4e5583ab Rewrite test to use tempfile
We don't want to be writing to arbitrary directories, and this
seems to make the test a little simpler.
2025-02-20 08:48:16 -08:00
Eric Huss
3978612611 Update some comments and formatting 2025-02-20 08:47:03 -08:00
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
Michael Howell
82db7f5b93 Add a bit more to the configuration docs 2025-02-13 14:22:54 -07:00
Michael Howell
879449447f feat(html): cache bust static files by adding hashes to file names
Closes rust-lang#1254
2025-02-13 10:39:22 -07: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
112 changed files with 6494 additions and 2158 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,47 +3,47 @@ on:
release:
types: [created]
defaults:
run:
shell: bash
permissions:
contents: write
jobs:
release:
name: Deploy Release
runs-on: ${{ matrix.os }}
strategy:
matrix:
target:
- aarch64-unknown-linux-musl
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
- x86_64-apple-darwin
- x86_64-pc-windows-msvc
include:
- target: aarch64-unknown-linux-musl
os: ubuntu-20.04
os: ubuntu-22.04
- target: x86_64-unknown-linux-gnu
os: ubuntu-20.04
os: ubuntu-22.04
- target: x86_64-unknown-linux-musl
os: ubuntu-20.04
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 ${{ matrix.target }}
shell: bash
- name: Build and deploy artifacts
- 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 }} ${{ matrix.target }}
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
@@ -56,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,52 +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-20.04
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.60.0
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
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

5
.gitignore vendored
View File

@@ -16,3 +16,8 @@ test_book/book/
# Ignore Vim temporary and swap files.
*.sw?
*~
# GUI tests
node_modules
package-lock.json
package.json

View File

@@ -1,5 +1,357 @@
# Changelog
## mdBook 0.4.46
[v0.4.45...v0.4.46](https://github.com/rust-lang/mdBook/compare/v0.4.45...v0.4.46)
### Changed
- The `output.html.hash-files` config option has been added to add hashes to static filenames to bust any caches when a book is updated. `{{resource}}` template tags have been added so that links can be properly generated to those files.
[#1368](https://github.com/rust-lang/mdBook/pull/1368)
### Fixed
- Playground links for Rust 2024 now set the edition correctly.
[#2557](https://github.com/rust-lang/mdBook/pull/2557)
## 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)
@@ -135,7 +487,7 @@
[#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 which handles CLI option parsing.
- Migrated to clap 3.0 which handles CLI option parsing.
[#1731](https://github.com/rust-lang/mdBook/pull/1731)
### Fixed

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,8 +138,23 @@ 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:
```
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
@@ -123,8 +163,28 @@ The following are instructions for updating [highlight.js](https://highlightjs.o
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. 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."
```

1876
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,9 @@
[workspace]
members = [".", "examples/remove-emphasis/mdbook-remove-emphasis"]
[package]
name = "mdbook"
version = "0.4.25"
version = "0.4.46"
authors = [
"Mathieu David <mathieudavid@mathieudavid.org>",
"Michael-F-Bryan <michaelfbryan@gmail.com>",
@@ -14,55 +17,59 @@ license = "MPL-2.0"
readme = "README.md"
repository = "https://github.com/rust-lang/mdBook"
description = "Creates a book from markdown files"
rust-version = "1.60"
rust-version = "1.77" # Keep in sync with installation.md and .github/workflows/main.yml
[dependencies]
anyhow = "1.0.28"
chrono = "0.4"
clap = { version = "4.0.29", features = ["cargo", "wrap_help"] }
clap_complete = "4.0.6"
once_cell = "1"
env_logger = "0.10.0"
handlebars = "4.0"
log = "0.4"
memchr = "2.0"
opener = "0.5"
pulldown-cmark = { version = "0.9.1", default-features = false }
regex = "1.5.5"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
shlex = "1"
tempfile = "3.0"
toml = "0.5.1"
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"
hex = "0.4.3"
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"
sha2 = "0.10.8"
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 = "5.0.0", optional = true }
notify-debouncer-mini = { version = "0.2.1", 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.2", 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 = "3.0.0", optional = true }
ammonia = { version = "3", optional = true }
elasticlunr-rs = { version = "3.0.2", optional = true }
ammonia = { version = "4.0.0", optional = true }
[dev-dependencies]
assert_cmd = "2.0.7"
predicates = "2"
assert_cmd = "2.0.11"
predicates = "3.0.3"
select = "0.6.0"
semver = "1.0"
pretty_assertions = "1.2.1"
walkdir = "2.0"
semver = "1.0.17"
pretty_assertions = "1.3.0"
walkdir = "2.3.3"
[features]
default = ["watch", "serve", "search"]
watch = ["notify", "notify-debouncer-mini", "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
@@ -71,3 +78,16 @@ 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"]

View File

@@ -1,6 +1,6 @@
# 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)

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

@@ -22,6 +22,19 @@ 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

View File

@@ -12,12 +12,8 @@ TAG=${GITHUB_REF#*/tags/}
host=$(rustc -Vv | grep ^host: | sed -e "s/host: //g")
target=$2
if [ "$host" != "$target" ]
then
export "CARGO_TARGET_$(echo $target | tr a-z- A-Z_)_LINKER"=rust-lld
fi
export CARGO_PROFILE_RELEASE_LTO=true
cargo build --bin mdbook --release --target $target
cargo build --locked --bin mdbook --release --target $target
cd target/$target/release
case $1 in
ubuntu*)
@@ -44,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

@@ -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);
}
}

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,15 +8,20 @@ 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"
edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
hash-files = true
[output.html.playground]
editable = true
line-numbers = true
[output.html.code.hidelines]
python = "~"
[output.html.search]
limit-results = 20
use-boolean-and = true

View File

@@ -5,10 +5,10 @@ After you have [installed](../guide/installation.md) `mdbook`, you can run the `
This following sections provide in-depth information on the different commands available.
* [`mdbook init <directory>`](init.md) Creates a new book with minimal boilerplate to start with.
* [`mdbook build`](build.md) Renders the book.
* [`mdbook watch`](watch.md) Rebuilds the book any time a source file changes.
* [`mdbook serve`](serve.md) Runs a web server to view the book, and rebuilds on changes.
* [`mdbook test`](test.md) Tests Rust code samples.
* [`mdbook clean`](clean.md) Deletes the rendered output.
* [`mdbook completions`](completions.md) Support for shell auto-completion.
* [`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

@@ -6,7 +6,11 @@ This means when you type `mdbook` in your shell, you can then press your shell's
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.

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,7 +37,7 @@ 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
@@ -55,14 +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
#### `--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.
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

@@ -21,7 +21,7 @@ A simple approach would be to use the popular `curl` CLI tool to download the ex
```sh
mkdir bin
curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.25/mdbook-v0.4.25-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.46/mdbook-v0.4.46-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
bin/mdbook build
```
@@ -51,13 +51,13 @@ cargo install mdbook --no-default-features --features search --vers "^0.4" --loc
This includes several recommended options:
* `--no-default-features` Disables features like the HTTP server used by `mdbook serve` that is likely not needed on CI.
* `--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.
* `--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.
* `--locked` --- This will use the dependencies that were used when mdBook was released.
Without `--locked`, it will use the latest version of all dependencies, which may include some fixes since the last release, but may also (rarely) cause build problems.
You will likely want to investigate caching options, as building mdBook can be somewhat slow.
@@ -83,7 +83,7 @@ Or if you have your own style checks, spell checker, or any other tests it might
## Deploying
You may want to automatically deploy your book.
Some may want to do this with every time a change is pushed, and others may want to only deploy when a specific release is tagged.
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.

View File

@@ -287,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);
}
+ }
}
}
}

View File

@@ -68,33 +68,10 @@ 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
@@ -122,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

@@ -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
@@ -68,7 +72,7 @@ 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
@@ -97,7 +101,7 @@ extra-watch-dirs = [] # directories to watch for triggering builds
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
@@ -111,4 +115,4 @@ extra-watch-dirs = [] # directories to watch for triggering builds
`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.
trigger rebuilds. Useful if your book depends on files outside its `src` directory.

View File

@@ -35,7 +35,7 @@ For example, if you have a preprocessor called `mdbook-example`, then you can in
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 prepocessor needed some extra configuration options:
For example, if our example preprocessor needed some extra configuration options:
```toml
[preprocessor.example]

View File

@@ -4,9 +4,9 @@ Renderers (also called "backends") are responsible for creating the output of th
The following backends are built-in:
* [`html`](#html-renderer-options) This renders the book to HTML.
* [`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.
* [`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.
@@ -97,7 +97,7 @@ description = "The example book covers examples."
theme = "my-theme"
default-theme = "light"
preferred-dark-theme = "navy"
curly-quotes = true
smart-punctuation = true
mathjax-support = false
copy-fonts = true
additional-css = ["custom.css", "custom2.css"]
@@ -120,13 +120,18 @@ 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`.
- **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
@@ -147,9 +152,9 @@ The following configuration options are available:
- **edit-url-template:** Edit url template, when provided shows a
"Suggest an edit" button (which looks like <i class="fa fa-edit"></i>) for directly jumping to editing the currently
viewed page. For e.g. GitHub projects set this to
`https://github.com/<owner>/<repo>/edit/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.
- **input-404:** The name of the markdown file used for missing files.
@@ -163,6 +168,12 @@ The following configuration options are available:
This string will be written to a file named CNAME in the root of your site, as
required by GitHub Pages (see [*Managing a custom domain for your GitHub Pages
site*][custom domain]).
- **hash-files:** Include a cryptographic "fingerprint" of the files' contents in static asset filenames,
so that if the contents of the file are changed, the name of the file will also change.
For example, `css/chrome.css` may become `css/chrome-9b8f428e.css`.
Chapter HTML files are not renamed.
Static CSS and JS files can reference each other using `{{ resource "filename" }}` directives.
Defaults to `false` (in a future release, this may change to `true`).
[custom domain]: https://docs.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site
@@ -179,7 +190,7 @@ 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`.
- **page-break:** Insert page breaks between chapters. Defaults to `true`.
### `[output.html.fold]`
@@ -215,11 +226,25 @@ runnable = true # displays a run button for rust code
- **copyable:** Display the copy button on code snippets. Defaults to `true`.
- **copy-js:** Copy JavaScript files for the editor to the output directory.
Defaults to `true`.
- **line-numbers** Display line numbers on editable sections of code. Requires both `editable` and `copy-js` to be `true`. Defaults to `false`.
- **runnable** Displays a run button for rust code snippets. Changing this to `false` will disable the run in playground feature globally. Defaults to `true`.
- **line-numbers:** Display line numbers on editable sections of code. Requires both `editable` and `copy-js` to be `true`. Defaults to `false`.
- **runnable:** Displays a run button for rust code snippets. Changing this to `false` will disable the run in playground feature globally. Defaults to `true`.
[Ace]: https://ace.c9.io/
### `[output.html.code]`
The `[output.html.code]` table provides options for controlling code blocks.
```toml
[output.html.code]
# A prefix string per language (one or more chars).
# Any line starting with whitespace+prefix is hidden.
hidelines = { python = "~" }
```
- **hidelines:** A table that defines how [hidden code lines](../mdbook.md#hiding-code-lines) work for each language.
The key is the language and the value is a string that will cause code lines starting with that prefix to be hidden.
### `[output.html.search]`
The `[output.html.search]` table provides options for controlling the built-in text [search].
@@ -262,6 +287,20 @@ copy-js = true # include Javascript code for search
- **copy-js:** Copy JavaScript files for the search implementation to the output
directory. Defaults to `true`.
#### `[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
[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 }
```
- **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.redirect]`
The `[output.html.redirect]` table provides a way to add redirects.

View File

@@ -73,14 +73,14 @@ Linking to a URL or local file is easy:
```markdown
Use [mdBook](https://github.com/rust-lang/mdBook).
Read about [mdBook](mdBook.md).
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).
Read about [mdBook](mdbook.md).
A bare url: <https://www.rust-lang.org>.
@@ -124,7 +124,7 @@ 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 two tilde characters on each side:
text with one or two tilde characters on each side:
```text
An example of ~~strikethrough text~~.
@@ -214,9 +214,22 @@ characters:
So, no need to manually enter those Unicode characters!
This feature is disabled by default.
To enable it, see the [`output.html.curly-quotes`] config option.
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.curly-quotes`]: configuration/renderers.md#html-renderer-options
[`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,11 +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].
This currently only works with Rust language code blocks.
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() {
@@ -28,7 +29,47 @@ Will render as
# }
```
The code block has an eyeball icon (<i class="fa fa-eye"></i>) which will toggle the visibility of the hidden lines.
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
@@ -72,16 +113,16 @@ 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.
* `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.
* `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.
* `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
@@ -274,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

@@ -29,11 +29,12 @@ 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 Title

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

View File

@@ -9,8 +9,8 @@ be added to the ***book.toml***:
editable = true
```
To make a specific block available for editing, the attribute `editable` needs
to be added to it:
After enabling editable code blocks, the `editable` attribute must be added to a
code block to make it editable:
~~~markdown
```rust,editable

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}}
```
@@ -99,3 +99,13 @@ Of course the inner html can be changed to your liking.
*If you would like other properties or helpers exposed, please [create a new
issue](https://github.com/rust-lang/mdBook/issues)*
### 3. resource
The path to a static file.
It implicitly includes `path_to_root`,
and accounts for files that are renamed with a hash in their filename.
```handlebars
<link rel="stylesheet" href="{{ resource "css/chrome.css" }}">
```

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

@@ -20,7 +20,7 @@ To make it easier to run, put the path to the binary into your `PATH`.
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.60.
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:
@@ -30,6 +30,9 @@ 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
@@ -47,6 +50,8 @@ 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

@@ -18,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")?;
@@ -39,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);
@@ -162,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>,
@@ -277,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())
@@ -317,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);
@@ -331,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)
@@ -341,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,7 @@ 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.
@@ -158,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(())
}
@@ -184,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.");
@@ -198,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

@@ -15,10 +15,10 @@ pub use self::init::BookBuilder;
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
use log::{debug, error, info, log_enabled, trace, warn};
use std::io::Write;
use std::path::PathBuf;
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;
@@ -71,22 +71,28 @@ impl MDBook {
config.update_from_env();
if config
.html_config()
.map_or(false, |html| html.google_analytics.is_some())
{
warn!(
"The output.html.google-analytics field has been deprecated; \
it will be removed in a future release.\n\
Consider placing the appropriate site tag code into the \
theme/head.hbs file instead.\n\
The tracking code may be found in the Google Analytics Admin page.\n\
"
);
if 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);
}
}
@@ -99,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)?;
@@ -122,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)?;
@@ -196,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);
@@ -254,24 +265,45 @@ impl MDBook {
/// 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 library_args: Vec<&str> = (0..library_paths.len())
.map(|_| "-L")
.zip(library_paths.into_iter())
.flat_map(|x| vec![x.0, x.1])
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()?;
let mut chapter_found = false;
// FIXME: Is "test" the proper renderer name to use here?
let preprocess_context =
PreprocessorContext::new(self.root.clone(), self.config.clone(), "test".to_string());
struct TestRenderer;
impl Renderer for TestRenderer {
// FIXME: Is "test" the proper renderer name to use here?
fn name(&self) -> &str {
"test"
}
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.
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 {
@@ -292,29 +324,41 @@ impl MDBook {
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"]);
cmd.args(["--edition", "2021"]);
}
RustEdition::E2024 => {
cmd.args(["--edition", "2024"]);
}
}
}
if color_output {
cmd.args(["--color", "always"]);
}
debug!("running {:?}", cmd);
let output = cmd.output()?;
let output = cmd
.output()
.with_context(|| "failed to execute `rustdoc`")?;
if !output.status.success() {
failed = true;
@@ -441,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"
))
})?;
@@ -468,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"
))
})?;
@@ -539,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> {
@@ -550,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))
}
@@ -588,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() {
@@ -744,7 +782,7 @@ mod tests {
for preprocessor in &preprocessors {
eprintln!(" {}", preprocessor.name());
}
panic!("{} should come before {}", before, after);
panic!("{before} should come before {after}");
}
};

View File

@@ -1,10 +1,9 @@
use crate::errors::*;
use log::{debug, trace, warn};
use memchr::{self, Memchr};
use pulldown_cmark::{self, Event, HeadingLevel, Tag};
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};
@@ -163,7 +162,7 @@ impl From<Link> for SummaryItem {
/// > match the following regex: "[^<>\n[]]+".
struct SummaryParser<'a> {
src: &'a str,
stream: pulldown_cmark::OffsetIter<'a, '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
@@ -210,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 {
@@ -265,7 +264,12 @@ impl<'a> SummaryParser<'a> {
loop {
match self.next_event() {
Some(ev @ Event::Start(Tag::List(..)))
| Some(ev @ Event::Start(Tag::Heading(HeadingLevel::H1, ..))) => {
| 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.
@@ -275,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),
@@ -304,10 +308,13 @@ impl<'a> SummaryParser<'a> {
break;
}
Some(Event::Start(Tag::Heading(HeadingLevel::H1, ..))) => {
Some(Event::Start(Tag::Heading {
level: HeadingLevel::H1,
..
})) => {
debug!("Found a h1 in the SUMMARY");
let tags = collect_events!(self.stream, end Tag::Heading(HeadingLevel::H1, ..));
let tags = collect_events!(self.stream, end TagEnd::Heading(HeadingLevel::H1));
Some(stringify_events(tags))
}
@@ -336,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() {
@@ -377,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(HeadingLevel::H1, ..))) => {
Some(
ev @ Event::Start(Tag::Heading {
level: HeadingLevel::H1,
..
}),
) => {
// we're starting a new part
self.back(ev);
break;
@@ -398,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;
}
}
@@ -469,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,
}
@@ -486,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);
@@ -529,14 +541,18 @@ impl<'a> SummaryParser<'a> {
fn parse_title(&mut self) -> Option<String> {
loop {
match self.next_event() {
Some(Event::Start(Tag::Heading(HeadingLevel::H1, ..))) => {
Some(Event::Start(Tag::Heading {
level: HeadingLevel::H1,
..
})) => {
debug!("Found a h1 in the SUMMARY");
let tags = collect_events!(self.stream, end Tag::Heading(HeadingLevel::H1, ..));
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);
@@ -567,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
@@ -598,7 +616,7 @@ impl Display for SectionNumber {
write!(f, "0")
} else {
for item in &self.0 {
write!(f, "{}.", item)?;
write!(f, "{item}.")?;
}
Ok(())
}
@@ -729,6 +747,20 @@ mod tests {
let got = parser.parse_affix(false);
assert!(got.is_err());
let error_message = got.err().unwrap().to_string();
assert_eq!(error_message, "failed to parse SUMMARY.md line 2, column 1: Suffix chapters cannot be followed by a list");
}
#[test]
fn expected_a_start_of_a_link() {
let src = "- Title\n";
let mut parser = SummaryParser::new(src);
let got = parser.parse_affix(false);
assert!(got.is_err());
let error_message = got.err().unwrap().to_string();
assert_eq!(error_message, "failed to parse SUMMARY.md line 1, column 0: Suffix chapters cannot be followed by a list");
}
#[test]
@@ -744,8 +776,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

@@ -16,7 +16,7 @@ pub fn make_subcommand() -> Command {
// 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.get_one::<PathBuf>("dest-dir") {
book.config.build.build_dir = dest_dir.into();

View File

@@ -2,8 +2,9 @@ use super::command_prelude::*;
use crate::get_book_dir;
use anyhow::Context;
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() -> Command {
@@ -16,17 +17,95 @@ pub fn make_subcommand() -> Command {
// 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.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

@@ -36,6 +36,20 @@ pub trait CommandExt: Sized {
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 {

View File

@@ -56,7 +56,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
"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);
@@ -65,6 +65,8 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
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()
};
@@ -72,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...");
@@ -84,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()?;
@@ -114,5 +116,5 @@ fn confirm() -> bool {
io::stdout().flush().unwrap();
let mut s = String::new();
io::stdin().read_line(&mut s).ok();
matches!(&*s.trim(), "Y" | "y" | "yes" | "Yes")
matches!(s.trim(), "Y" | "y" | "yes" | "Yes")
}

View File

@@ -6,7 +6,6 @@ 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};
@@ -43,6 +42,7 @@ pub fn make_subcommand() -> Command {
.help("Port to use for HTTP connections"),
)
.arg_open()
.arg_watcher()
}
// Serve command implementation
@@ -54,7 +54,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
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 update_config = |book: &mut MDBook| {
book.config
@@ -89,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 {
@@ -97,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,7 +1,7 @@
use super::command_prelude::*;
use crate::get_book_dir;
use clap::builder::NonEmptyStringValueParser;
use clap::{Arg, ArgAction, ArgMatches, Command};
use clap::ArgAction;
use mdbook::errors::Result;
use mdbook::MDBook;
use std::path::PathBuf;
@@ -44,7 +44,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
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.get_one::<PathBuf>("dest-dir") {
book.config.build.build_dir = dest_dir.to_path_buf();

View File

@@ -1,12 +1,11 @@
use super::command_prelude::*;
use crate::{get_book_dir, open};
use mdbook::errors::Result;
use mdbook::utils;
use mdbook::MDBook;
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() -> Command {
@@ -15,6 +14,22 @@ pub fn make_subcommand() -> Command {
.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
@@ -39,42 +54,21 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
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),
}
}
@@ -84,97 +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::RecursiveMode::*;
// Create a channel to receive the events.
let (tx, rx) = channel();
let mut debouncer = match notify_debouncer_mini::new_debouncer(Duration::from_secs(1), None, 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 = dir.canonicalize().unwrap();
if let Err(e) = watcher.watch(&path, Recursive) {
error!(
"Error while watching extra directory {:?}:\n {:?}",
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(errors) => {
for error in errors {
log::warn!("error while watching for changes: {error}");
}
None
}
})
.flatten()
.map(|event| event.path)
.collect();
// If we are watching files outside the current repository (via extra-watch-dirs), then they are definitionally
// ignored by gitignore. So we handle this case by including such files into the watched paths list.
let any_external_paths = paths.iter().filter(|p| !p.starts_with(&book.root)).cloned();
let mut paths = remove_ignored_files(&book.root, &paths[..]);
paths.extend(any_external_paths);
if !paths.is_empty() {
closure(paths, &book.root);
}
}
}

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

@@ -58,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};
@@ -145,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;
@@ -308,7 +308,7 @@ impl<'de> serde::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));
}
@@ -411,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 {
@@ -422,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,
}
}
}
@@ -464,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,
@@ -486,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,
@@ -504,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.
@@ -540,6 +587,9 @@ pub struct HtmlConfig {
/// The mapping from old pages to new pages/URLs to use when generating
/// redirects.
pub redirect: HashMap<String, String>,
/// If this option is turned on, "cache bust" static files by adding
/// hashes to their file names.
pub hash_files: bool,
}
impl Default for HtmlConfig {
@@ -548,6 +598,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,
@@ -556,6 +607,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,
@@ -567,6 +619,7 @@ impl Default for HtmlConfig {
cname: None,
live_reload_endpoint: None,
redirect: HashMap::new(),
hash_files: false,
}
}
}
@@ -580,6 +633,11 @@ 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.
@@ -613,7 +671,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 {
@@ -642,6 +700,14 @@ impl Default for Playground {
}
}
/// 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")]
@@ -673,6 +739,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 {
@@ -689,10 +760,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`.
///
@@ -703,7 +783,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;
}
@@ -739,7 +819,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/"
@@ -769,6 +849,7 @@ 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"),
@@ -785,7 +866,7 @@ mod tests {
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")),
@@ -965,7 +1046,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"]
@@ -990,7 +1071,7 @@ mod tests {
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")],
@@ -1121,6 +1202,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() {
@@ -1193,4 +1341,37 @@ mod tests {
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

@@ -19,9 +19,9 @@ const MAX_LINK_NESTED_DEPTH: usize = 10;
/// A preprocessor for expanding helpers in a chapter. Supported helpers are:
///
/// - `{{# include}}` - Insert an external file of any type. Include the whole file, only particular
///. lines, or only between the specified anchors.
/// lines, or only between the specified anchors.
/// - `{{# rustdoc_include}}` - Insert an external Rust file, showing the particular lines
///. specified or the lines between specified anchors, and include the rest of the file behind `#`.
/// specified or the lines between specified anchors, and include the rest of the file behind `#`.
/// This hides the lines from initial display but shows them when the reader expands the code
/// block and provides them to Rustdoc for testing.
/// - `{{# playground}}` - Insert runnable Rust files
@@ -93,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) {
@@ -327,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);
@@ -493,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,
@@ -519,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,
@@ -536,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 {
@@ -555,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 {
@@ -574,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 {
@@ -593,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 {
@@ -612,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 {
@@ -631,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 {
@@ -650,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 {
@@ -670,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,
@@ -690,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![
@@ -721,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

@@ -1,9 +1,10 @@
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::html_handlebars::StaticFiles;
use crate::renderer::{RenderContext, Renderer};
use crate::theme::{self, playground_editor, Theme};
use crate::theme::{self, Theme};
use crate::utils;
use std::borrow::Cow;
@@ -54,11 +55,13 @@ 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));
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
@@ -99,7 +102,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
@@ -110,7 +113,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());
@@ -121,8 +129,12 @@ impl HtmlHandlebars {
ctx.data.insert("path_to_root".to_owned(), json!(""));
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())?;
}
@@ -142,13 +154,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 \
@@ -156,7 +168,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 {
@@ -182,137 +195,34 @@ impl HtmlHandlebars {
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
}
fn copy_static_files(
&self,
destination: &Path,
theme: &Theme,
html_config: &HtmlConfig,
) -> Result<()> {
use crate::utils::fs::write_file;
write_file(
destination,
".nojekyll",
b"This file makes sure that Github Pages doesn't process mdBook's output.\n",
)?;
if let Some(cname) = &html_config.cname {
write_file(destination, "CNAME", format!("{}\n", cname).as_bytes())?;
}
write_file(destination, "book.js", &theme.js)?;
write_file(destination, "css/general.css", &theme.general_css)?;
write_file(destination, "css/chrome.css", &theme.chrome_css)?;
if html_config.print.enable {
write_file(destination, "css/print.css", &theme.print_css)?;
}
write_file(destination, "css/variables.css", &theme.variables_css)?;
if let Some(contents) = &theme.favicon_png {
write_file(destination, "favicon.png", contents)?;
}
if let Some(contents) = &theme.favicon_svg {
write_file(destination, "favicon.svg", contents)?;
}
write_file(destination, "highlight.css", &theme.highlight_css)?;
write_file(destination, "tomorrow-night.css", &theme.tomorrow_night_css)?;
write_file(destination, "ayu-highlight.css", &theme.ayu_highlight_css)?;
write_file(destination, "highlight.js", &theme.highlight_js)?;
write_file(destination, "clipboard.min.js", &theme.clipboard_js)?;
write_file(
destination,
"FontAwesome/css/font-awesome.css",
theme::FONT_AWESOME,
)?;
write_file(
destination,
"FontAwesome/fonts/fontawesome-webfont.eot",
theme::FONT_AWESOME_EOT,
)?;
write_file(
destination,
"FontAwesome/fonts/fontawesome-webfont.svg",
theme::FONT_AWESOME_SVG,
)?;
write_file(
destination,
"FontAwesome/fonts/fontawesome-webfont.ttf",
theme::FONT_AWESOME_TTF,
)?;
write_file(
destination,
"FontAwesome/fonts/fontawesome-webfont.woff",
theme::FONT_AWESOME_WOFF,
)?;
write_file(
destination,
"FontAwesome/fonts/fontawesome-webfont.woff2",
theme::FONT_AWESOME_WOFF2,
)?;
write_file(
destination,
"FontAwesome/fonts/FontAwesome.ttf",
theme::FONT_AWESOME_TTF,
)?;
if html_config.copy_fonts {
write_file(destination, "fonts/fonts.css", theme::fonts::CSS)?;
for (file_name, contents) in theme::fonts::LICENSES.iter() {
write_file(destination, file_name, contents)?;
}
for (file_name, contents) in theme::fonts::OPEN_SANS.iter() {
write_file(destination, file_name, contents)?;
}
write_file(
destination,
theme::fonts::SOURCE_CODE_PRO.0,
theme::fonts::SOURCE_CODE_PRO.1,
)?;
}
let playground_config = &html_config.playground;
// Ace is a very large dependency, so only load it when requested
if playground_config.editable && playground_config.copy_js {
// Load the editor
write_file(destination, "editor.js", playground_editor::JS)?;
write_file(destination, "ace.js", playground_editor::ACE_JS)?;
write_file(destination, "mode-rust.js", playground_editor::MODE_RUST_JS)?;
write_file(
destination,
"theme-dawn.js",
playground_editor::THEME_DAWN_JS,
)?;
write_file(
destination,
"theme-tomorrow_night.js",
playground_editor::THEME_TOMORROW_NIGHT_JS,
)?;
}
Ok(())
}
/// Update the context with data for this file
fn configure_print_version(
&self,
@@ -344,43 +254,6 @@ impl HtmlHandlebars {
handlebars.register_helper("theme_option", Box::new(helpers::theme::theme_option));
}
/// Copy across any additional CSS and JavaScript files which the book
/// has been configured to use.
fn copy_additional_css_and_js(
&self,
html: &HtmlConfig,
root: &Path,
destination: &Path,
) -> Result<()> {
let custom_files = html.additional_css.iter().chain(html.additional_js.iter());
debug!("Copying additional CSS and JS");
for custom_file in custom_files {
let input_location = root.join(custom_file);
let output_location = destination.join(custom_file);
if let Some(parent) = output_location.parent() {
fs::create_dir_all(parent)
.with_context(|| format!("Unable to create {}", parent.display()))?;
}
debug!(
"Copying {} -> {}",
input_location.display(),
output_location.display()
);
fs::copy(&input_location, &output_location).with_context(|| {
format!(
"Unable to copy {} to {}",
input_location.display(),
output_location.display()
)
})?;
}
Ok(())
}
fn emit_redirects(
&self,
root: &Path,
@@ -444,25 +317,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"
@@ -495,16 +349,6 @@ impl Renderer for HtmlHandlebars {
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");
@@ -520,6 +364,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);
@@ -528,9 +377,60 @@ 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 static_files = StaticFiles::new(&theme, &html_config, &ctx.root)?;
// Render search index
#[cfg(feature = "search")]
{
let default = crate::config::Search::default();
let search = html_config.search.as_ref().unwrap_or(&default);
if search.enable {
super::search::create_files(&search, &mut static_files, &book)?;
}
}
debug!("Render toc js");
{
let rendered_toc = handlebars.render("toc_js", &data)?;
static_files.add_builtin("toc.js", rendered_toc.as_bytes());
debug!("Creating toc.js ✓");
}
if html_config.hash_files {
static_files.hash_files()?;
}
debug!("Copy static files");
let resource_helper = static_files
.write_files(&destination)
.with_context(|| "Unable to copy across static files")?;
handlebars.register_helper("resource", Box::new(resource_helper));
debug!("Render toc html");
{
data.insert("is_toc_html".to_owned(), json!(true));
data.insert("path".to_owned(), json!("toc.html"));
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("path");
data.remove("is_toc_html");
}
utils::fs::write_file(
destination,
".nojekyll",
b"This file makes sure that Github Pages doesn't process mdBook's output.\n",
)?;
if let Some(cname) = &html_config.cname {
utils::fs::write_file(destination, "CNAME", format!("{cname}\n").as_bytes())?;
}
let mut is_index = true;
for item in book.iter() {
let ctx = RenderItemContext {
@@ -564,28 +464,17 @@ 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!("Copy static files");
self.copy_static_files(destination, &theme, &html_config)
.with_context(|| "Unable to copy across static files")?;
self.copy_additional_css_and_js(&html_config, &ctx.root, destination)
.with_context(|| "Unable to copy across additional CSS and JS")?;
// Render search index
#[cfg(feature = "search")]
{
let search = html_config.search.unwrap_or_default();
if search.enable {
super::search::create_files(&search, destination, book)?;
}
}
self.emit_redirects(&ctx.destination, &handlebars, &html_config.redirect)
.context("Unable to emit redirects")?;
@@ -610,6 +499,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()),
@@ -656,7 +549,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));
}
@@ -769,8 +663,10 @@ 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 {
static BUILD_HEADER_LINKS: Lazy<Regex> =
Lazy::new(|| 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();
@@ -780,7 +676,22 @@ fn build_header_links(html: &str) -> String {
.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()
}
@@ -790,15 +701,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 id = utils::unique_id_from_content(content, id_counter);
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}>"##
)
}
@@ -820,77 +733,69 @@ fn fix_code_blocks(html: &str) -> String {
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 {
static ADD_PLAYGROUND_PRE: Lazy<Regex> =
Lazy::new(|| Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap());
ADD_PLAYGROUND_PRE
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")
&& 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 {
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!("# #![allow(unused)]\n{}#fn main() {{\n{}#}}", attrs, code)
.into()
};
hide_lines(&content)
}
)
|| 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()
@@ -899,7 +804,51 @@ fn add_playground_pre(
.into_owned()
}
fn hide_lines(content: &str) -> String {
/// 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_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());
@@ -914,12 +863,9 @@ fn hide_lines(content: &str) -> String {
result += &caps[3];
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 += newline;
result += "</span>";
@@ -932,6 +878,26 @@ fn hide_lines(content: &str) -> String {
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
}
fn partition_source(s: &str) -> (String, String) {
let mut after_header = false;
let mut before = String::new();
@@ -966,7 +932,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() {
@@ -995,6 +964,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 {
@@ -1007,17 +991,17 @@ mod tests {
fn add_playground() {
let inputs = [
("<code class=\"language-rust\">x()</code>",
"<pre class=\"playground\"><code class=\"language-rust\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</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() {}</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>\";</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\";</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\";</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>\";</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>\";</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]</code></pre>"),
];
@@ -1037,7 +1021,7 @@ mod tests {
fn add_playground_edition2015() {
let inputs = [
("<code class=\"language-rust\">x()</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2015\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</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() {}</code></pre>"),
("<code class=\"language-rust edition2015\">fn main() {}</code>",
@@ -1061,7 +1045,7 @@ mod tests {
fn add_playground_edition2018() {
let inputs = [
("<code class=\"language-rust\">x()</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2018\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</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() {}</code></pre>"),
("<code class=\"language-rust edition2015\">fn main() {}</code>",
@@ -1085,7 +1069,7 @@ mod tests {
fn add_playground_edition2021() {
let inputs = [
("<code class=\"language-rust\">x()</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2021\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</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() {}</code></pre>"),
("<code class=\"language-rust edition2015\">fn main() {}</code>",
@@ -1105,4 +1089,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,3 +1,4 @@
pub mod navigation;
pub mod resources;
pub mod theme;
pub mod toc;

View File

@@ -1,7 +1,9 @@
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};
@@ -26,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()));
@@ -54,15 +56,18 @@ 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"))?
.ok_or_else(|| {
RenderErrorReason::Other("Type error for `path`, string expected".to_owned())
})?
.replace('\"', "");
if !rc.evaluate(ctx, "@root/is_index")?.is_missing() {
@@ -98,7 +103,7 @@ fn find_chapter(
}
}
previous = Some(item.clone());
previous = Some(item);
}
_ => continue,
}
@@ -108,7 +113,7 @@ fn find_chapter(
}
fn render(
_h: &Helper<'_, '_>,
_h: &Helper<'_>,
r: &Handlebars<'_>,
ctx: &Context,
rc: &mut RenderContext<'_, '_>,
@@ -122,27 +127,35 @@ fn render(
.evaluate(ctx, "@root/path")?
.as_json()
.as_str()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.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"))
.ok_or_else(|| {
RenderErrorReason::Other("Link could not be converted to str".to_owned())
})
.map(|p| context.insert("link".to_owned(), json!(p.replace('\\', "/"))))
})?;
@@ -150,14 +163,14 @@ fn render(
let t = _h
.template()
.ok_or_else(|| RenderError::new("Error with the handlebars 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<'_, '_>,
@@ -173,7 +186,7 @@ pub fn previous(
}
pub fn next(
_h: &Helper<'_, '_>,
_h: &Helper<'_>,
r: &Handlebars<'_>,
ctx: &Context,
rc: &mut RenderContext<'_, '_>,

View File

@@ -0,0 +1,50 @@
use std::collections::HashMap;
use crate::utils;
use handlebars::{
Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError, RenderErrorReason,
};
// Handlebars helper to find filenames with hashes in them
#[derive(Clone)]
pub struct ResourceHelper {
pub hash_map: HashMap<String, String>,
}
impl HelperDef for ResourceHelper {
fn call<'reg: 'rc, 'rc>(
&self,
h: &Helper<'rc>,
_r: &'reg Handlebars<'_>,
ctx: &'rc Context,
rc: &mut RenderContext<'reg, 'rc>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
let param = h.param(0).and_then(|v| v.value().as_str()).ok_or_else(|| {
RenderErrorReason::Other(
"Param 0 with String type is required for theme_option helper.".to_owned(),
)
})?;
let base_path = rc
.evaluate(ctx, "@root/path")?
.as_json()
.as_str()
.ok_or_else(|| {
RenderErrorReason::Other("Type error for `path`, string expected".to_owned())
})?
.replace("\"", "");
let path_to_root = utils::fs::path_to_root(&base_path);
out.write(&path_to_root)?;
out.write(
self.hash_map
.get(&param[..])
.map(|p| &p[..])
.unwrap_or(&param),
)?;
Ok(())
}
}

View File

@@ -1,8 +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<'_, '_>,
@@ -11,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,10 +1,11 @@
use std::path::Path;
use std::{cmp::Ordering, collections::BTreeMap};
use crate::utils;
use crate::utils::bracket_escape;
use crate::utils::special_escape;
use handlebars::{Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError};
use handlebars::{
Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError, RenderErrorReason,
};
// Handlebars helper to construct TOC
#[derive(Clone, Copy)]
@@ -15,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>,
@@ -26,65 +27,48 @@ 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;
// The "index" page, which has this attribute set, is supposed to alias the first chapter in
// the book, i.e. the first link. There seems to be no easy way to determine which chapter
// the "index" is aliasing from within the renderer, so this is used instead to force the
// first link to be active. See further below.
let mut is_first_chapter = ctx.data().get("is_index").is_some();
for item in chapters {
// 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);
match level.cmp(&current_level) {
Ordering::Greater => {
@@ -104,14 +88,20 @@ impl HelperDef for RenderToc {
write_li_open_tag(out, is_expanded, false)?;
}
Ordering::Equal => {
write_li_open_tag(out, is_expanded, item.get("section").is_none())?;
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\">")?;
out.write(&bracket_escape(title))?;
out.write(&special_escape(title))?;
out.write("</li>")?;
continue;
}
@@ -129,16 +119,12 @@ impl HelperDef for RenderToc {
.replace('\\', "/");
// Add link
out.write(&utils::fs::path_to_root(&current_path))?;
out.write(&tmp)?;
out.write("\"")?;
if path == &current_path || is_first_chapter {
is_first_chapter = false;
out.write(" class=\"active\"")?;
}
out.write(">")?;
out.write(if is_toc_html {
"\" target=\"_parent\">"
} else {
"\">"
})?;
path_exists = true;
}
_ => {
@@ -157,7 +143,7 @@ impl HelperDef for RenderToc {
}
if let Some(name) = item.get("name") {
out.write(&bracket_escape(name))?
out.write(&special_escape(name))?
}
if path_exists {

View File

@@ -1,9 +1,11 @@
#![allow(missing_docs)] // FIXME: Document this
pub use self::hbs_renderer::HtmlHandlebars;
pub use self::static_files::StaticFiles;
mod hbs_renderer;
mod helpers;
mod static_files;
#[cfg(feature = "search")]
mod search;

View File

@@ -1,14 +1,15 @@
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::path::{Path, PathBuf};
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::renderer::html_handlebars::StaticFiles;
use crate::theme::searcher;
use crate::utils;
use log::{debug, warn};
@@ -26,7 +27,11 @@ fn tokenize(text: &str) -> Vec<String> {
}
/// Creates all files required for search.
pub fn create_files(search_config: &Search, destination: &Path, book: &Book) -> Result<()> {
pub fn create_files(
search_config: &Search,
static_files: &mut StaticFiles,
book: &Book,
) -> Result<()> {
let mut index = IndexBuilder::new()
.add_field_with_tokenizer("title", Box::new(&tokenize))
.add_field_with_tokenizer("body", Box::new(&tokenize))
@@ -35,8 +40,21 @@ pub fn create_files(search_config: &Search, destination: &Path, book: &Book) ->
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)?;
@@ -46,15 +64,14 @@ pub fn create_files(search_config: &Search, destination: &Path, book: &Book) ->
}
if search_config.copy_js {
utils::fs::write_file(destination, "searchindex.json", index.as_bytes())?;
utils::fs::write_file(
destination,
static_files.add_builtin("searchindex.json", index.as_bytes());
static_files.add_builtin(
"searchindex.js",
format!("Object.assign(window.search, {});", index).as_bytes(),
)?;
utils::fs::write_file(destination, "searcher.js", searcher::JS)?;
utils::fs::write_file(destination, "mark.min.js", searcher::MARK_JS)?;
utils::fs::write_file(destination, "elasticlunr.min.js", searcher::ELASTICLUNR_JS)?;
);
static_files.add_builtin("searcher.js", searcher::JS);
static_files.add_builtin("mark.min.js", searcher::MARK_JS);
static_files.add_builtin("elasticlunr.min.js", searcher::ELASTICLUNR_JS);
debug!("Copying search files ✓");
}
@@ -66,11 +83,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)
};
@@ -87,13 +117,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()
@@ -119,7 +144,7 @@ fn render_item(
let mut id_counter = HashMap::new();
while let Some(event) = p.next() {
match event {
Event::Start(Tag::Heading(i, ..)) if i as u32 <= 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
@@ -127,20 +152,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 as u32 <= max_section_depth => {
Event::End(TagEnd::Heading(level)) if level as u32 <= max_section_depth => {
in_heading = false;
section_id = Some(utils::unique_id_from_content(&heading, &mut id_counter));
breadcrumbs.push(heading.clone());
}
Event::Start(Tag::FootnoteDefinition(name)) => {
@@ -157,9 +183,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
@@ -179,25 +215,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(" » ")],
);
}
@@ -283,3 +325,82 @@ fn clean_html(html: &str) -> String {
});
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

@@ -0,0 +1,358 @@
//! Support for writing static files.
use log::{debug, warn};
use once_cell::sync::Lazy;
use crate::config::HtmlConfig;
use crate::errors::*;
use crate::renderer::html_handlebars::helpers::resources::ResourceHelper;
use crate::theme::{self, playground_editor, Theme};
use crate::utils;
use std::borrow::Cow;
use std::collections::HashMap;
use std::fs::{self, File};
use std::path::{Path, PathBuf};
/// Map static files to their final names and contents.
///
/// It performs [fingerprinting], if you call the `hash_files` method.
/// If hash-files is turned off, then the files will not be renamed.
/// It also writes files to their final destination, when `write_files` is called,
/// and interprets the `{{ resource }}` directives to allow assets to name each other.
///
/// [fingerprinting]: https://guides.rubyonrails.org/asset_pipeline.html#fingerprinting-versioning-with-digest-based-urls
pub struct StaticFiles {
static_files: Vec<StaticFile>,
hash_map: HashMap<String, String>,
}
enum StaticFile {
Builtin {
data: Vec<u8>,
filename: String,
},
Additional {
input_location: PathBuf,
filename: String,
},
}
impl StaticFiles {
pub fn new(theme: &Theme, html_config: &HtmlConfig, root: &Path) -> Result<StaticFiles> {
let static_files = Vec::new();
let mut this = StaticFiles {
hash_map: HashMap::new(),
static_files,
};
this.add_builtin("book.js", &theme.js);
this.add_builtin("css/general.css", &theme.general_css);
this.add_builtin("css/chrome.css", &theme.chrome_css);
if html_config.print.enable {
this.add_builtin("css/print.css", &theme.print_css);
}
this.add_builtin("css/variables.css", &theme.variables_css);
if let Some(contents) = &theme.favicon_png {
this.add_builtin("favicon.png", contents);
}
if let Some(contents) = &theme.favicon_svg {
this.add_builtin("favicon.svg", contents);
}
this.add_builtin("highlight.css", &theme.highlight_css);
this.add_builtin("tomorrow-night.css", &theme.tomorrow_night_css);
this.add_builtin("ayu-highlight.css", &theme.ayu_highlight_css);
this.add_builtin("highlight.js", &theme.highlight_js);
this.add_builtin("clipboard.min.js", &theme.clipboard_js);
this.add_builtin("FontAwesome/css/font-awesome.css", theme::FONT_AWESOME);
this.add_builtin(
"FontAwesome/fonts/fontawesome-webfont.eot",
theme::FONT_AWESOME_EOT,
);
this.add_builtin(
"FontAwesome/fonts/fontawesome-webfont.svg",
theme::FONT_AWESOME_SVG,
);
this.add_builtin(
"FontAwesome/fonts/fontawesome-webfont.ttf",
theme::FONT_AWESOME_TTF,
);
this.add_builtin(
"FontAwesome/fonts/fontawesome-webfont.woff",
theme::FONT_AWESOME_WOFF,
);
this.add_builtin(
"FontAwesome/fonts/fontawesome-webfont.woff2",
theme::FONT_AWESOME_WOFF2,
);
this.add_builtin("FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF);
if html_config.copy_fonts && theme.fonts_css.is_none() {
this.add_builtin("fonts/fonts.css", theme::fonts::CSS);
for (file_name, contents) in theme::fonts::LICENSES.iter() {
this.add_builtin(file_name, contents);
}
for (file_name, contents) in theme::fonts::OPEN_SANS.iter() {
this.add_builtin(file_name, contents);
}
this.add_builtin(
theme::fonts::SOURCE_CODE_PRO.0,
theme::fonts::SOURCE_CODE_PRO.1,
);
} else if let Some(fonts_css) = &theme.fonts_css {
if !fonts_css.is_empty() {
this.add_builtin("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."
);
}
let playground_config = &html_config.playground;
// Ace is a very large dependency, so only load it when requested
if playground_config.editable && playground_config.copy_js {
// Load the editor
this.add_builtin("editor.js", playground_editor::JS);
this.add_builtin("ace.js", playground_editor::ACE_JS);
this.add_builtin("mode-rust.js", playground_editor::MODE_RUST_JS);
this.add_builtin("theme-dawn.js", playground_editor::THEME_DAWN_JS);
this.add_builtin(
"theme-tomorrow_night.js",
playground_editor::THEME_TOMORROW_NIGHT_JS,
);
}
let custom_files = html_config
.additional_css
.iter()
.chain(html_config.additional_js.iter());
for custom_file in custom_files {
let input_location = root.join(custom_file);
this.static_files.push(StaticFile::Additional {
input_location,
filename: custom_file
.to_str()
.with_context(|| "resource file names must be valid utf8")?
.to_owned(),
});
}
for input_location in theme.font_files.iter().cloned() {
let filename = Path::new("fonts")
.join(input_location.file_name().unwrap())
.to_str()
.with_context(|| "resource file names must be valid utf8")?
.to_owned();
this.static_files.push(StaticFile::Additional {
input_location,
filename,
});
}
Ok(this)
}
pub fn add_builtin(&mut self, filename: &str, data: &[u8]) {
self.static_files.push(StaticFile::Builtin {
filename: filename.to_owned(),
data: data.to_owned(),
});
}
/// Updates this [`StaticFiles`] to hash the contents for determining the
/// filename for each resource.
pub fn hash_files(&mut self) -> Result<()> {
use sha2::{Digest, Sha256};
use std::io::Read;
for static_file in &mut self.static_files {
match static_file {
StaticFile::Builtin {
ref mut filename,
ref data,
} => {
let mut parts = filename.splitn(2, '.');
let parts = parts.next().and_then(|p| Some((p, parts.next()?)));
if let Some((name, suffix)) = parts {
// FontAwesome already does its own cache busting with the ?v=4.7.0 thing,
// and I don't want to have to patch its CSS file to use `{{ resource }}`
if name != ""
&& suffix != ""
&& suffix != "txt"
&& !name.starts_with("FontAwesome/fonts/")
{
let hex = hex::encode(&Sha256::digest(data)[..4]);
let new_filename = format!("{}-{}.{}", name, hex, suffix);
self.hash_map.insert(filename.clone(), new_filename.clone());
*filename = new_filename;
}
}
}
StaticFile::Additional {
ref mut filename,
ref input_location,
} => {
let mut parts = filename.splitn(2, '.');
let parts = parts.next().and_then(|p| Some((p, parts.next()?)));
if let Some((name, suffix)) = parts {
if name != "" && suffix != "" {
let mut digest = Sha256::new();
let mut input_file = File::open(input_location)
.with_context(|| "open static file for hashing")?;
let mut buf = vec![0; 1024];
loop {
let amt = input_file
.read(&mut buf)
.with_context(|| "read static file for hashing")?;
if amt == 0 {
break;
};
digest.update(&buf[..amt]);
}
let hex = hex::encode(&digest.finalize()[..4]);
let new_filename = format!("{}-{}.{}", name, hex, suffix);
self.hash_map.insert(filename.clone(), new_filename.clone());
*filename = new_filename;
}
}
}
}
}
Ok(())
}
pub fn write_files(self, destination: &Path) -> Result<ResourceHelper> {
use crate::utils::fs::write_file;
use regex::bytes::{Captures, Regex};
// The `{{ resource "name" }}` directive in static resources look like
// handlebars syntax, even if they technically aren't.
static RESOURCE: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"\{\{ resource "([^"]+)" \}\}"#).unwrap());
fn replace_all<'a>(
hash_map: &HashMap<String, String>,
data: &'a [u8],
filename: &str,
) -> Cow<'a, [u8]> {
RESOURCE.replace_all(data, move |captures: &Captures<'_>| {
let name = captures
.get(1)
.expect("capture 1 in resource regex")
.as_bytes();
let name = std::str::from_utf8(name).expect("resource name with invalid utf8");
let resource_filename = hash_map.get(name).map(|s| &s[..]).unwrap_or(name);
let path_to_root = utils::fs::path_to_root(filename);
format!("{}{}", path_to_root, resource_filename)
.as_bytes()
.to_owned()
})
}
for static_file in &self.static_files {
match static_file {
StaticFile::Builtin { filename, data } => {
debug!("Writing builtin -> {}", filename);
let data = if filename.ends_with(".css") || filename.ends_with(".js") {
replace_all(&self.hash_map, data, filename)
} else {
Cow::Borrowed(&data[..])
};
write_file(destination, filename, &data)?;
}
StaticFile::Additional {
ref input_location,
ref filename,
} => {
let output_location = destination.join(filename);
debug!(
"Copying {} -> {}",
input_location.display(),
output_location.display()
);
if let Some(parent) = output_location.parent() {
fs::create_dir_all(parent)
.with_context(|| format!("Unable to create {}", parent.display()))?;
}
if filename.ends_with(".css") || filename.ends_with(".js") {
let data = fs::read(input_location)?;
let data = replace_all(&self.hash_map, &data, filename);
write_file(destination, filename, &data)?;
} else {
fs::copy(input_location, &output_location).with_context(|| {
format!(
"Unable to copy {} to {}",
input_location.display(),
output_location.display()
)
})?;
}
}
}
}
let hash_map = self.hash_map;
Ok(ResourceHelper { hash_map })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::HtmlConfig;
use crate::theme::Theme;
use crate::utils::fs::write_file;
use tempfile::TempDir;
#[test]
fn test_write_directive() {
let theme = Theme {
index: Vec::new(),
head: Vec::new(),
redirect: Vec::new(),
header: Vec::new(),
chrome_css: Vec::new(),
general_css: Vec::new(),
print_css: Vec::new(),
variables_css: Vec::new(),
favicon_png: Some(Vec::new()),
favicon_svg: Some(Vec::new()),
js: Vec::new(),
highlight_css: Vec::new(),
tomorrow_night_css: Vec::new(),
ayu_highlight_css: Vec::new(),
highlight_js: Vec::new(),
clipboard_js: Vec::new(),
toc_js: Vec::new(),
toc_html: Vec::new(),
fonts_css: None,
font_files: Vec::new(),
};
let temp_dir = TempDir::with_prefix("mdbook-").unwrap();
let reference_js = Path::new("static-files-test-case-reference.js");
let mut html_config = HtmlConfig::default();
html_config.additional_js.push(reference_js.to_owned());
write_file(
temp_dir.path(),
reference_js,
br#"{{ resource "book.js" }}"#,
)
.unwrap();
let mut static_files = StaticFiles::new(&theme, &html_config, temp_dir.path()).unwrap();
static_files.hash_files().unwrap();
static_files.write_files(temp_dir.path()).unwrap();
// custom JS winds up referencing book.js
let reference_js_content = std::fs::read_to_string(
temp_dir
.path()
.join("static-files-test-case-reference-635c9cdc.js"),
)
.unwrap();
assert_eq!("book-e3b0c442.js", reference_js_content);
// book.js winds up empty
let book_js_content =
std::fs::read_to_string(temp_dir.path().join("book-e3b0c442.js")).unwrap();
assert_eq!("", book_js_content);
}
}

View File

@@ -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

@@ -68,7 +68,7 @@ function playground_text(playground, hidden = true) {
}
// 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");
@@ -111,11 +111,11 @@ function playground_text(playground, hidden = true) {
let text = playground_text(code_block);
let classes = code_block.querySelector('code').classList;
let edition = "2015";
if(classes.contains("edition2018")) {
edition = "2018";
} else if(classes.contains("edition2021")) {
edition = "2021";
}
classes.forEach(className => {
if (className.startsWith("edition")) {
edition = className.slice(7);
}
});
var params = {
version: "stable",
optimize: "0",
@@ -179,7 +179,7 @@ function playground_text(playground, hidden = true) {
// 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
@@ -225,7 +225,7 @@ function playground_text(playground, hidden = true) {
}
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>';
@@ -258,7 +258,7 @@ function playground_text(playground, hidden = true) {
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);
@@ -289,10 +289,14 @@ function playground_text(playground, hidden = true) {
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']"),
highlight: document.querySelector("[href$='highlight.css']"),
ayuHighlight: document.querySelector("#ayu-highlight-css"),
tomorrowNight: document.querySelector("#tomorrow-night-css"),
highlight: document.querySelector("#highlight-css"),
};
function showThemes() {
@@ -317,7 +321,7 @@ function playground_text(playground, hidden = true) {
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;
@@ -346,7 +350,7 @@ function playground_text(playground, hidden = true) {
}
setTimeout(function () {
themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor;
themeColorMetaTag.content = getComputedStyle(document.documentElement).backgroundColor;
}, 1);
if (window.ace && window.editors) {
@@ -441,16 +445,17 @@ function playground_text(playground, hidden = true) {
})();
(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);
});
@@ -459,20 +464,9 @@ function playground_text(playground, hidden = true) {
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);
});
@@ -482,22 +476,16 @@ function playground_text(playground, hidden = true) {
}
// 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();
}
});
@@ -506,14 +494,14 @@ function playground_text(playground, hidden = true) {
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);
@@ -522,7 +510,7 @@ function playground_text(playground, hidden = true) {
}
//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);
}
@@ -551,33 +539,41 @@ function playground_text(playground, hidden = true) {
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;
}
@@ -589,12 +585,12 @@ function playground_text(playground, hidden = true) {
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', {
@@ -676,13 +672,14 @@ function playground_text(playground, hidden = true) {
}, { 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 */
@@ -229,13 +245,13 @@ pre > .buttons :hover {
background-color: var(--theme-hover);
}
pre > .buttons i {
margin-left: 8px;
margin-inline-start: 8px;
}
pre > .buttons button {
cursor: inherit;
margin: 0px 5px;
padding: 3px 5px;
font-size: 14px;
padding: 4px 4px 3px 5px;
font-size: 23px;
border-style: solid;
border-width: 1px;
@@ -246,13 +262,40 @@ pre > .buttons button {
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;
}
@@ -266,7 +309,7 @@ pre > code {
}
pre > .result {
margin-top: 10px;
margin-block-start: 10px;
}
/* Search */
@@ -277,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;
@@ -290,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);
@@ -313,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;
@@ -339,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 {
@@ -362,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 {
@@ -387,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);
@@ -405,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;
}
@@ -447,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;
@@ -464,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 {
@@ -487,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;
}
@@ -510,6 +614,7 @@ ul#searchresults span.teaser em {
/* 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);
}
@@ -520,7 +625,7 @@ ul#searchresults span.teaser em {
padding: 2px 20px;
line-height: 25px;
white-space: nowrap;
text-align: left;
text-align: start;
cursor: pointer;
color: inherit;
background: inherit;
@@ -533,6 +638,6 @@ ul#searchresults span.teaser em {
.theme-selected::before {
display: inline-block;
content: "✓";
margin-left: -14px;
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 {
@@ -24,6 +23,7 @@ body {
code {
font-family: var(--mono-font) !important;
font-size: var(--code-font-size);
direction: ltr !important;
}
/* make long words/inline code not x overflow */
@@ -47,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,
@@ -64,7 +64,7 @@ h5:target::before,
h6:target::before {
display: inline-block;
content: "»";
margin-left: -30px;
margin-inline-start: -30px;
width: 30px;
}
@@ -73,28 +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 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; }
@@ -144,8 +150,31 @@ 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);
}
.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 {
@@ -161,9 +190,21 @@ kbd {
vertical-align: middle;
}
:not(.footnote-definition) + .footnote-definition,
.footnote-definition + :not(.footnote-definition) {
margin-top: 2em;
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,11 +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 */
--code-font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */
}
/* Themes */
@@ -38,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%);
@@ -50,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 {
@@ -78,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%);
@@ -90,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%);
@@ -118,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%);
@@ -130,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 {
@@ -158,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%);
@@ -170,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 {
@@ -198,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%);
@@ -210,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;
@@ -239,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%);
@@ -251,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

@@ -7,7 +7,7 @@
font-style: normal;
font-weight: 300;
src: local('Open Sans Light'), local('OpenSans-Light'),
url('open-sans-v17-all-charsets-300.woff2') format('woff2');
url('{{ resource "fonts/open-sans-v17-all-charsets-300.woff2" }}') format('woff2');
}
/* open-sans-300italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -16,7 +16,7 @@
font-style: italic;
font-weight: 300;
src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'),
url('open-sans-v17-all-charsets-300italic.woff2') format('woff2');
url('{{ resource "fonts/open-sans-v17-all-charsets-300italic.woff2" }}') format('woff2');
}
/* open-sans-regular - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -25,7 +25,7 @@
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'),
url('open-sans-v17-all-charsets-regular.woff2') format('woff2');
url('{{ resource "fonts/open-sans-v17-all-charsets-regular.woff2" }}') format('woff2');
}
/* open-sans-italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -34,7 +34,7 @@
font-style: italic;
font-weight: 400;
src: local('Open Sans Italic'), local('OpenSans-Italic'),
url('open-sans-v17-all-charsets-italic.woff2') format('woff2');
url('{{ resource "fonts/open-sans-v17-all-charsets-italic.woff2" }}') format('woff2');
}
/* open-sans-600 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -43,7 +43,7 @@
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'),
url('open-sans-v17-all-charsets-600.woff2') format('woff2');
url('{{ resource "fonts/open-sans-v17-all-charsets-600.woff2" }}') format('woff2');
}
/* open-sans-600italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -52,7 +52,7 @@
font-style: italic;
font-weight: 600;
src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'),
url('open-sans-v17-all-charsets-600italic.woff2') format('woff2');
url('{{ resource "fonts/open-sans-v17-all-charsets-600italic.woff2" }}') format('woff2');
}
/* open-sans-700 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -61,7 +61,7 @@
font-style: normal;
font-weight: 700;
src: local('Open Sans Bold'), local('OpenSans-Bold'),
url('open-sans-v17-all-charsets-700.woff2') format('woff2');
url('{{ resource "fonts/open-sans-v17-all-charsets-700.woff2" }}') format('woff2');
}
/* open-sans-700italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -70,7 +70,7 @@
font-style: italic;
font-weight: 700;
src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'),
url('open-sans-v17-all-charsets-700italic.woff2') format('woff2');
url('{{ resource "fonts/open-sans-v17-all-charsets-700italic.woff2" }}') format('woff2');
}
/* open-sans-800 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -79,7 +79,7 @@
font-style: normal;
font-weight: 800;
src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'),
url('open-sans-v17-all-charsets-800.woff2') format('woff2');
url('{{ resource "fonts/open-sans-v17-all-charsets-800.woff2" }}') format('woff2');
}
/* open-sans-800italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -88,7 +88,7 @@
font-style: italic;
font-weight: 800;
src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'),
url('open-sans-v17-all-charsets-800italic.woff2') format('woff2');
url('{{ resource "fonts/open-sans-v17-all-charsets-800italic.woff2" }}') format('woff2');
}
/* source-code-pro-500 - latin_vietnamese_latin-ext_greek_cyrillic-ext_cyrillic */
@@ -96,5 +96,5 @@
font-family: 'Source Code Pro';
font-style: normal;
font-weight: 500;
src: url('source-code-pro-v11-all-charsets-500.woff2') format('woff2');
src: url('{{ resource "fonts/source-code-pro-v11-all-charsets-500.woff2" }}') format('woff2');
}

View File

@@ -16,6 +16,7 @@
.hljs-variable,
.hljs-template-variable,
.hljs-attribute,
.hljs-attr,
.hljs-tag,
.hljs-name,
.hljs-regexp,

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 }}">
@@ -17,49 +17,52 @@
<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">
<link rel="icon" href="{{ resource "favicon.svg" }}">
{{/if}}
{{#if favicon_png}}
<link rel="shortcut icon" href="{{ path_to_root }}favicon.png">
<link rel="shortcut icon" href="{{ resource "favicon.png" }}">
{{/if}}
<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">
<link rel="stylesheet" href="{{ resource "css/variables.css" }}">
<link rel="stylesheet" href="{{ resource "css/general.css" }}">
<link rel="stylesheet" href="{{ resource "css/chrome.css" }}">
{{#if print_enable}}
<link rel="stylesheet" href="{{ path_to_root }}css/print.css" media="print">
<link rel="stylesheet" href="{{ resource "css/print.css" }}" media="print">
{{/if}}
<!-- Fonts -->
<link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="{{ resource "FontAwesome/css/font-awesome.css" }}">
{{#if copy_fonts}}
<link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
<link rel="stylesheet" href="{{ resource "fonts/fonts.css" }}">
{{/if}}
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="{{ path_to_root }}highlight.css">
<link rel="stylesheet" href="{{ path_to_root }}tomorrow-night.css">
<link rel="stylesheet" href="{{ path_to_root }}ayu-highlight.css">
<link rel="stylesheet" id="highlight-css" href="{{ resource "highlight.css" }}">
<link rel="stylesheet" id="tomorrow-night-css" href="{{ resource "tomorrow-night.css" }}">
<link rel="stylesheet" id="ayu-highlight-css" href="{{ resource "ayu-highlight.css" }}">
<!-- Custom theme stylesheets -->
{{#each additional_css}}
<link rel="stylesheet" href="{{ ../path_to_root }}{{ this }}">
<link rel="stylesheet" href="{{ resource this }}">
{{/each}}
{{#if mathjax_support}}
<!-- MathJax -->
<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>
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="{{ resource "toc.js" }}"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
@@ -81,30 +84,38 @@
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>
var html = document.querySelector('html');
var sidebar = 'hidden';
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">
@@ -112,11 +123,11 @@
<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>
@@ -192,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}}
@@ -210,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}}
@@ -269,26 +280,26 @@
{{/if}}
{{#if playground_js}}
<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>
<script src="{{ resource "ace.js" }}"></script>
<script src="{{ resource "editor.js" }}"></script>
<script src="{{ resource "mode-rust.js" }}"></script>
<script src="{{ resource "theme-dawn.js" }}"></script>
<script src="{{ resource "theme-tomorrow_night.js" }}"></script>
{{/if}}
{{#if search_js}}
<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>
<script src="{{ resource "elasticlunr.min.js" }}"></script>
<script src="{{ resource "mark.min.js" }}"></script>
<script src="{{ resource "searcher.js" }}"></script>
{{/if}}
<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>
<script src="{{ resource "clipboard.min.js" }}"></script>
<script src="{{ resource "highlight.js" }}"></script>
<script src="{{ resource "book.js" }}"></script>
<!-- Custom JS scripts -->
{{#each additional_js}}
<script src="{{ ../path_to_root }}{{this}}"></script>
<script src="{{ resource this}}"></script>
{{/each}}
{{#if is_print}}
@@ -309,5 +320,6 @@
{{/if}}
{{/if}}
</div>
</body>
</html>

View File

@@ -9,7 +9,7 @@ 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;
@@ -17,6 +17,8 @@ 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

@@ -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();
@@ -468,12 +468,12 @@ window.search = window.search || {};
showResults(true);
}
fetch(path_to_root + 'searchindex.json')
fetch('{{ resource "searchindex.json" }}')
.then(response => response.json())
.then(json => init(json))
.catch(error => { // Try to load searchindex.js if fetch failed
var script = document.createElement('script');
script.src = path_to_root + 'searchindex.js';
script.src = '{{ resource "searchindex.js" }}';
script.onload = () => init(window.search);
document.head.appendChild(script);
});

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="{{ resource "css/variables.css" }}">
<link rel="stylesheet" href="{{ resource "css/general.css" }}">
<link rel="stylesheet" href="{{ resource "css/chrome.css" }}">
{{#if print_enable}}
<link rel="stylesheet" href="{{ resource "css/print.css" }}" media="print">
{{/if}}
<!-- Fonts -->
<link rel="stylesheet" href="{{ resource "FontAwesome/css/font-awesome.css" }}">
{{#if copy_fonts}}
<link rel="stylesheet" href="{{ resource "fonts/fonts.css" }}">
{{/if}}
<!-- Custom theme stylesheets -->
{{#each additional_css}}
<link rel="stylesheet" href="{{ resource 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;
}

View File

@@ -1,6 +1,5 @@
use crate::errors::*;
use log::{debug, trace};
use std::convert::Into;
use std::fs::{self, File};
use std::io::Write;
use std::path::{Component, Path, PathBuf};
@@ -38,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()
@@ -74,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(())
@@ -110,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()
@@ -207,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

@@ -6,7 +6,7 @@ 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};
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag, TagEnd};
use regex::Regex;
use std::borrow::Cow;
@@ -77,7 +77,7 @@ pub fn unique_id_from_content(content: &str, id_counter: &mut HashMap<String, us
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 => format!("{id}-{id_count}"),
};
*id_count += 1;
unique_id
@@ -105,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;
}
@@ -121,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();
}
}
@@ -161,37 +161,59 @@ 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, curly_quotes: bool) -> 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);
if curly_quotes {
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, curly_quotes);
let p = new_cmark_parser(text, smart_punctuation);
let events = p
.map(clean_codeblock_headers)
.map(|event| adjust_links(event, path))
@@ -211,7 +233,7 @@ fn wrap_tables(event: Event<'_>) -> (Option<Event<'_>>, Option<Event<'_>>) {
Some(Event::Html(r#"<div class="table-wrapper">"#.into())),
Some(event),
),
Event::End(Tag::Table(_)) => (Some(event), Some(Event::Html(r#"</div>"#.into()))),
Event::End(TagEnd::Table) => (Some(event), Some(Event::Html(r#"</div>"#.into()))),
_ => (Some(event), None),
}
}
@@ -243,6 +265,25 @@ 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] = &['<', '>'];
@@ -261,7 +302,7 @@ pub(crate) fn bracket_escape(mut s: &str) -> String {
#[cfg(test)]
mod tests {
use super::bracket_escape;
use super::{bracket_escape, special_escape};
mod render_markdown {
use super::super::render_markdown;
@@ -484,5 +525,20 @@ more text with spaces
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 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;");
}
}

View File

@@ -9,6 +9,7 @@ edition = "2018"
[output.html]
mathjax-support = true
hash-files = true
[output.html.playground]
editable = true

View File

@@ -22,6 +22,7 @@
- [Tables](individual/table.md)
- [Tasks](individual/task.md)
- [Strikethrough](individual/strikethrough.md)
- [MathJax](individual/mathjax.md)
- [Mixed](individual/mixed.md)
- [Languages](languages/README.md)
- [Syntax Highlight](languages/highlight.md)

View File

@@ -10,7 +10,9 @@ This is a codeblock
---
This line contains `inline code`
This line contains `inline code` mixed with some other stuff. (LTR)
ושורה זו מכילה `inline code` אבל עם טקסט בשפה שנכתבת מימין לשמאל. (RTL)
---

View File

@@ -13,3 +13,9 @@
##### Really Small Heading
###### Is it even a heading anymore - heading
## Custom id {#example-id}
## Custom class {.class1 .class2}
## Both id and class {#example-id2 .class1 .class2}

View File

@@ -4,19 +4,19 @@ For copyright and trademark information on these images, please check [rust-artw
## A 16x16 image
![16x16 rust-lang logo](http://rust-lang.org/logos/rust-logo-16x16.png)
![16x16 rust-lang logo](https://rust-lang.org/logos/rust-logo-16x16.png)
## A 32x32 image
![32x32 rust-lang logo](http://rust-lang.org/logos/rust-logo-32x32-blk.png)
![32x32 rust-lang logo](https://rust-lang.org/logos/rust-logo-32x32-blk.png)
## A 256x256 image
![256x256 rust-lang logo](http://rust-lang.org/logos/rust-logo-256x256.png)
![256x256 rust-lang logo](https://rust-lang.org/logos/rust-logo-256x256.png)
## A 512x512 image
![512x512 rust-lang logo](http://rust-lang.org/logos/rust-logo-512x512-blk.png)
![512x512 rust-lang logo](https://rust-lang.org/logos/rust-logo-512x512-blk.png)
## A large image

View File

@@ -0,0 +1,42 @@
# MathJax
Fourier Transform
\\[
\begin{aligned}
f(x) &= \int_{-\infty}^{\infty}F(s)(-1)^{ 2xs}ds \\\\
F(s) &= \int_{-\infty}^{\infty}f(x)(-1)^{-2xs}dx
\end{aligned}
\\]
The kernel can also be written as \\(e^{2i\pi xs}\\) which is more frequently used in literature.
> Proof that \\(e^{ix} = \cos x + i\sin x\\) a.k.a Euler's Formula:
>
> \\(
\begin{aligned}
e^x &= \sum_{n=0}^\infty \frac{x^n}{n!} \implies e^{ix} = \sum_{n=0}^\infty \frac{(ix)^n}{n!} \\\\
\cos x &= \sum_{m=0}^\infty \frac{(-1)^m x^{2m}}{(2m)!} = \sum_{m=0}^\infty \frac{(ix)^{2m}}{(2m)!} \\\\
\sin x &= \sum_{s=0}^\infty \frac{(-1)^s x^{2s+1}}{(2s+1)!} = \sum_{s=0}^\infty \frac{(ix)^{2s+1}}{i(2s+1)!} \\\\
\cos x + i\sin x &= \sum_{l=0}^\infty \frac{(ix)^{2l}}{(2l)!} + \sum_{s=0}^\infty \frac{(ix)^{2s+1}}{(2s+1)!} = \sum_{n=0}^\infty \frac{(ix)^{n}}{n!} \\\\
&= e^{ix}
\end{aligned}
\\)
>
Pauli Matrices
\\[
\begin{aligned}
\sigma_x &= \begin{pmatrix}
1 & 0 \\\\ 0 & 1
\end{pmatrix} \\\\
\sigma_y &= \begin{pmatrix}
0 & -i \\\\ i & 0
\end{pmatrix} \\\\
\sigma_z &= \begin{pmatrix}
1 & 0 \\\\ 0 & -1
\end{pmatrix}
\end{aligned}
\\]

View File

@@ -31,7 +31,7 @@ fn main(){
A random image sprinkled in between
![16x16 rust-lang logo](http://rust-lang.org/logos/rust-logo-16x16.png)
![16x16 rust-lang logo](https://rust-lang.org/logos/rust-logo-16x16.png)
---

View File

@@ -1,5 +1,7 @@
# Strikethrough
~Single strike~
~~This is Striked~~
~~This is **strong**, _italic_ , **_both_** and striked~~

View File

@@ -27,6 +27,8 @@ This Currently contains following languages
- makefile
- markdown
- nginx
- nim
- nix
- objectivec
- perl
- php

View File

@@ -57,7 +57,7 @@ _start:
## bash
```
```bash
#!/bin/bash
###### CONFIG
@@ -529,6 +529,26 @@ http {
var doors {.compileTime.}: array[1..numDoors, bool]
proc calcDoors(): string =
for pass in 1..numDoors:
for door in countup(pass, numDoors, pass):
doors[door] = not doors[door]
for door in 1..numDoors:
result.add("Door $1 is $2.\n" % [$door, if doors[door]: "open" else: "closed"])
const outputString: string = calcDoors()
echo outputString
```
## objectivec
```objectivec
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@mylak {
NSLog(@"Hello World!");
}
return 0;
}
@@ -543,6 +563,15 @@ int main(int argc, const char * argv[]) {
"Hello " + world
```
## perl
```perl
print "Hello World!\n";
```
## php
```php
<?php
echo "Hello World!";
?>

View File

@@ -23,7 +23,10 @@ fn failing_alternate_backend() {
#[test]
fn missing_backends_are_fatal() {
let (md, _temp) = dummy_book_with_backend("missing", "trduyvbhijnorgevfuhn", false);
assert!(md.build().is_err());
let got = md.build();
assert!(got.is_err());
let error_message = got.err().unwrap().to_string();
assert_eq!(error_message, "Rendering failed");
}
#[test]
@@ -39,29 +42,27 @@ fn alternate_backend_with_arguments() {
md.build().unwrap();
}
/// Get a command which will pipe `stdin` to the provided file.
#[cfg(not(windows))]
fn tee_command<P: AsRef<Path>>(out_file: P) -> String {
let out_file = out_file.as_ref();
if cfg!(windows) {
format!("cmd.exe /c \"type > {}\"", out_file.display())
} else {
format!("tee {}", out_file.display())
}
}
#[test]
#[cfg(not(windows))]
fn backends_receive_render_context_via_stdin() {
use mdbook::renderer::RenderContext;
use std::fs::File;
let temp = TempFileBuilder::new().prefix("output").tempdir().unwrap();
let out_file = temp.path().join("out.txt");
let cmd = tee_command(&out_file);
let (md, temp) = dummy_book_with_backend("cat-to-file", "renderers/myrenderer", false);
let (md, _temp) = dummy_book_with_backend("cat-to-file", &cmd, false);
let renderers = temp.path().join("renderers");
fs::create_dir(&renderers).unwrap();
rust_exe(
&renderers,
"myrenderer",
r#"fn main() {
use std::io::Read;
let mut s = String::new();
std::io::stdin().read_to_string(&mut s).unwrap();
std::fs::write("out.txt", s).unwrap();
}"#,
);
let out_file = temp.path().join("book/out.txt");
assert!(!out_file.exists());
md.build().unwrap();
@@ -90,7 +91,7 @@ fn relative_command_path() {
.set("output.html", toml::value::Table::new())
.unwrap();
config.set("output.myrenderer.command", cmd_path).unwrap();
let md = MDBook::init(&temp.path())
let md = MDBook::init(temp.path())
.with_config(config)
.build()
.unwrap();
@@ -119,13 +120,11 @@ fn dummy_book_with_backend(
let mut config = Config::default();
config
.set(format!("output.{}.command", name), command)
.set(format!("output.{name}.command"), command)
.unwrap();
if backend_is_optional {
config
.set(format!("output.{}.optional", name), true)
.unwrap();
config.set(format!("output.{name}.optional"), true).unwrap();
}
let md = MDBook::init(temp.path())

47
tests/cli/init.rs Normal file
View File

@@ -0,0 +1,47 @@
use crate::cli::cmd::mdbook_cmd;
use crate::dummy_book::DummyBook;
use mdbook::config::Config;
/// Run `mdbook init` with `--force` to skip the confirmation prompts
#[test]
fn base_mdbook_init_can_skip_confirmation_prompts() {
let temp = DummyBook::new().build().unwrap();
// doesn't exist before
assert!(!temp.path().join("book").exists());
let mut cmd = mdbook_cmd();
cmd.args(["init", "--force"]).current_dir(temp.path());
cmd.assert()
.success()
.stdout(predicates::str::contains("\nAll done, no errors...\n"));
let config = Config::from_disk(temp.path().join("book.toml")).unwrap();
assert_eq!(config.book.title, None);
assert!(!temp.path().join(".gitignore").exists());
}
/// Run `mdbook init` with `--title` without git config.
///
/// Regression test for https://github.com/rust-lang/mdBook/issues/2485
#[test]
fn no_git_config_with_title() {
let temp = DummyBook::new().build().unwrap();
// doesn't exist before
assert!(!temp.path().join("book").exists());
let mut cmd = mdbook_cmd();
cmd.args(["init", "--title", "Example title"])
.current_dir(temp.path())
.env("GIT_CONFIG_GLOBAL", "")
.env("GIT_CONFIG_NOSYSTEM", "1");
cmd.assert()
.success()
.stdout(predicates::str::contains("\nAll done, no errors...\n"));
let config = Config::from_disk(temp.path().join("book.toml")).unwrap();
assert_eq!(config.book.title.as_deref(), Some("Example title"));
}

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