Compare commits

..

278 Commits

Author SHA1 Message Date
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
Ivan Tham
c9f1d01346 Add prefetch to next link
Fix #1975
2023-08-17 00:50:47 +08:00
Eric Huss
56c225bd34 Merge pull request #2158 from ehuss/bump-version
Update to 0.4.34
2023-08-05 21:54:12 +00:00
Eric Huss
55c017cad1 Update to 0.4.34 2023-08-05 14:34:42 -07:00
Eric Huss
7849d55b99 Merge pull request #2157 from ehuss/macos-notify-copy
Add workaround for macOS notify problem.
2023-08-05 20:35:33 +00:00
Eric Huss
c903cc8827 Add workaround for macOS notify problem. 2023-08-05 13:23:17 -07:00
Eric Huss
4a797b9565 Revert "Merge pull request #2152 from ehuss/macos-notify-kqueue"
This reverts commit 347e7886e1, reversing
changes made to a8fd6038f1.
2023-08-05 12:53:23 -07:00
Eric Huss
57b487eaa3 Merge pull request #2154 from ehuss/bump-version
Update to 0.4.33
2023-08-04 00:11:45 +00:00
Eric Huss
891b7c06f2 Update to 0.4.33 2023-08-03 17:03:40 -07:00
Eric Huss
f7e212ec9c Merge pull request #2150 from proski/header-code-background
Don't use distinct background for code in headers when printing
2023-08-03 22:46:50 +00:00
Pavel Roskin
228538ea62 Don't use distinct background for code in headers when printing
Fixes #1933
2023-08-02 23:31:46 -07:00
Eric Huss
347e7886e1 Merge pull request #2152 from ehuss/macos-notify-kqueue
Switch macOS notify to kqueue.
2023-08-02 22:13:09 +00:00
Eric Huss
bfa5fb8844 Switch macOS notify to kqueue. 2023-08-02 09:21:25 -07:00
Eric Huss
a8fd6038f1 Merge pull request #2151 from ehuss/revert-toml
Revert toml update
2023-08-02 15:49:44 +00:00
Eric Huss
fbfe887084 Add note to not update toml. 2023-08-02 08:39:23 -07:00
Eric Huss
aed991f75f Revert "Merge pull request #2125 from ehuss/update-toml"
This reverts commit 89797064b8, reversing
changes made to 7824aed878.
2023-08-02 08:32:44 -07:00
Eric Huss
ab2cb71c00 Merge pull request #2134 from GiorgioReale/master
Enhancing themes with `color-scheme: light | dark;` implementation in CSS
2023-07-30 22:50:08 +00:00
Giorgio Reale
fcfde083e7 Enhancing themes with color-scheme: light | dark; implementation in CSS 2023-07-30 15:43:51 -07:00
Eric Huss
4614a3637a Merge pull request #2146 from riverbl/fix-extra-watch-dirs
Fix issues with extra-watch-dirs
2023-07-30 17:08:46 +00:00
Eric Huss
d450544d6b Merge pull request #2148 from ehuss/fix-merge-queue
Use a better merge-queue success check.
2023-07-29 16:23:04 +00:00
Eric Huss
9340e6a78d Use a better merge-queue success check. 2023-07-29 09:13:55 -07:00
riverbl
e00b8835cc Fix issues with extra-watch-dirs
Fix paths specified in extra-watch-dirs being relative to the current working directory rather than the book root

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Notes on changes:

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

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

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

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

    <pre>
      Some code
    </pre>

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

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

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

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

Possibly related to `drop` order, which I *think* is related to the
order of binding statements?
2022-07-22 15:15:12 +10:00
Eric Huss
92afe9bd3c Merge pull request #1857 from ehuss/bump-version
Update to 0.4.20
2022-07-14 14:26:20 -07:00
Eric Huss
4c1aca0abb Update to 0.4.20 2022-07-14 13:46:56 -07:00
Eric Huss
da166e051d Merge pull request #1855 from stevenengler/code-fmt
Fix code padding in headings
2022-07-13 18:06:49 -07:00
Steven Engler
6a4ba95926 Fixed code padding in headings 2022-07-13 19:12:33 -04:00
Eric Huss
6688bd8d7b Merge pull request #1849 from FauconFan/fix_author(s)_in_user_guide
authors -> author in user guide
2022-07-09 13:55:51 -07:00
Dylan DPC
01313a39cc Merge pull request #1851 from FauconFan/revert-1848-remove_non_exhaustive
Revert "remove __non_exhaustive members, add non_exhaustive attribute instead"
2022-07-09 21:51:04 +05:30
Joseph Priou
f92911b8aa Revert "remove __non_exhaustive members, add non_exhaustive attribute instead" 2022-07-09 17:43:17 +02:00
Dylan DPC
42d6fd5804 Merge pull request #1848 from FauconFan/remove_non_exhaustive
remove __non_exhaustive members, add non_exhaustive attribute instead
2022-07-09 19:18:25 +05:30
Fauconfan
a8a45a5fbe authors -> author in user guide 2022-07-09 15:31:00 +02:00
Fauconfan
11781f0c1b remove __non_exhaustive members, add non_exhaustive attribute instead 2022-07-09 15:05:06 +02:00
83 changed files with 3698 additions and 1680 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,26 +3,46 @@ on:
release:
types: [created]
defaults:
run:
shell: bash
permissions:
contents: write
jobs:
release:
name: Deploy Release
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
target:
- aarch64-unknown-linux-musl
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
- x86_64-apple-darwin
- x86_64-pc-windows-msvc
include:
- target: aarch64-unknown-linux-musl
os: ubuntu-20.04
- target: x86_64-unknown-linux-gnu
os: ubuntu-20.04
- target: x86_64-unknown-linux-musl
os: ubuntu-20.04
- target: x86_64-apple-darwin
os: macos-latest
- target: x86_64-pc-windows-msvc
os: windows-latest
steps:
- uses: actions/checkout@master
- name: Install hub
run: ci/install-hub.sh ${{ matrix.os }}
shell: bash
- name: Install Rust
run: ci/install-rust.sh stable
shell: bash
- name: Build and deploy artifacts
run: ci/install-rust.sh stable ${{ matrix.target }}
- name: Build asset
run: ci/make-release-asset.sh ${{ matrix.os }} ${{ matrix.target }}
- name: Update release with new asset
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ci/make-release.sh ${{ matrix.os }}
shell: bash
run: gh release upload $MDBOOK_TAG $MDBOOK_ASSET
pages:
name: GitHub Pages
runs-on: ubuntu-latest
@@ -40,3 +60,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@master
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable
- name: Publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish --no-verify

View File

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

View File

@@ -1,7 +1,226 @@
# Changelog
## mdBook 0.4.35
[v0.4.34...v0.4.35](https://github.com/rust-lang/mdBook/compare/v0.4.34...v0.4.35)
### Added
- Added the `book.text-direction` setting for explicit support for right-to-left languages.
[#1641](https://github.com/rust-lang/mdBook/pull/1641)
- Added `rel=prefetch` to the "next" links to potentially improve browser performance.
[#2168](https://github.com/rust-lang/mdBook/pull/2168)
- Added a `.warning` CSS class which is styled for displaying warning blocks.
[#2187](https://github.com/rust-lang/mdBook/pull/2187)
### Changed
- Better support of the sidebar when JavaScript is disabled.
[#2175](https://github.com/rust-lang/mdBook/pull/2175)
## mdBook 0.4.34
[v0.4.33...v0.4.34](https://github.com/rust-lang/mdBook/compare/v0.4.33...v0.4.34)
### Fixed
- Fixed file change watcher failing on macOS with a large number of files.
[#2157](https://github.com/rust-lang/mdBook/pull/2157)
## mdBook 0.4.33
[v0.4.32...v0.4.33](https://github.com/rust-lang/mdBook/compare/v0.4.32...v0.4.33)
### Added
- The `color-scheme` CSS property is now set based on the light/dark theme, which applies some slight color differences in browser elements like scroll bars on some browsers.
[#2134](https://github.com/rust-lang/mdBook/pull/2134)
### Fixed
- Fixed watching of extra-watch-dirs when not running in the book root directory.
[#2146](https://github.com/rust-lang/mdBook/pull/2146)
- Reverted the dependency update to the `toml` crate (again!). This was an unintentional breaking change in 0.4.32.
[#2021](https://github.com/rust-lang/mdBook/pull/2021)
- Changed macOS change notifications to use the kqueue implementation which should fix some issues with repeated rebuilds when a file changed.
[#2152](https://github.com/rust-lang/mdBook/pull/2152)
- Don't set a background color in the print page for code blocks in a header.
[#2150](https://github.com/rust-lang/mdBook/pull/2150)
## mdBook 0.4.32
[v0.4.31...v0.4.32](https://github.com/rust-lang/mdBook/compare/v0.4.31...v0.4.32)
### Fixed
- Fixed theme-color meta tag not syncing with the theme.
[#2118](https://github.com/rust-lang/mdBook/pull/2118)
### Changed
- Updated all dependencies.
[#2121](https://github.com/rust-lang/mdBook/pull/2121)
[#2122](https://github.com/rust-lang/mdBook/pull/2122)
[#2123](https://github.com/rust-lang/mdBook/pull/2123)
[#2124](https://github.com/rust-lang/mdBook/pull/2124)
[#2125](https://github.com/rust-lang/mdBook/pull/2125)
[#2126](https://github.com/rust-lang/mdBook/pull/2126)
## mdBook 0.4.31
[v0.4.30...v0.4.31](https://github.com/rust-lang/mdBook/compare/v0.4.30...v0.4.31)
### Fixed
- Fixed menu border render flash during page navigation.
[#2101](https://github.com/rust-lang/mdBook/pull/2101)
- Fixed flicker setting sidebar scroll position.
[#2104](https://github.com/rust-lang/mdBook/pull/2104)
- Fixed compile error with proc-macro2 on latest Rust nightly.
[#2109](https://github.com/rust-lang/mdBook/pull/2109)
## mdBook 0.4.30
[v0.4.29...v0.4.30](https://github.com/rust-lang/mdBook/compare/v0.4.29...v0.4.30)
### Added
- Added support for heading attributes.
Attributes are specified in curly braces just after the heading text.
An HTML ID can be specified with `#` and classes with `.`.
For example: `## My heading {#custom-id .class1 .class2}`
[#2013](https://github.com/rust-lang/mdBook/pull/2013)
- Added support for hidden code lines for languages other than Rust.
The `output.html.code.hidelines` table allows you to define the prefix character that will be used to hide code lines based on the language.
[#2093](https://github.com/rust-lang/mdBook/pull/2093)
### Fixed
- Fixed a few minor markdown rendering issues.
[#2092](https://github.com/rust-lang/mdBook/pull/2092)
## mdBook 0.4.29
[v0.4.28...v0.4.29](https://github.com/rust-lang/mdBook/compare/v0.4.28...v0.4.29)
### Changed
- Built-in fonts are no longer copied when `fonts/fonts.css` is overridden in the theme directory.
Additionally, the warning about `copy-fonts` has been removed if `fonts/fonts.css` is specified.
[#2080](https://github.com/rust-lang/mdBook/pull/2080)
- `mdbook init --force` now skips all interactive prompts as intended.
[#2057](https://github.com/rust-lang/mdBook/pull/2057)
- Updated dependencies
[#2063](https://github.com/rust-lang/mdBook/pull/2063)
[#2086](https://github.com/rust-lang/mdBook/pull/2086)
[#2082](https://github.com/rust-lang/mdBook/pull/2082)
[#2084](https://github.com/rust-lang/mdBook/pull/2084)
[#2085](https://github.com/rust-lang/mdBook/pull/2085)
### Fixed
- Switched from the `gitignore` library to `ignore`. This should bring some improvements with gitignore handling.
[#2076](https://github.com/rust-lang/mdBook/pull/2076)
## mdBook 0.4.28
[v0.4.27...v0.4.28](https://github.com/rust-lang/mdBook/compare/v0.4.27...v0.4.28)
### Changed
- The sidebar is now shown on wide screens when localstorage is disabled.
[#2017](https://github.com/rust-lang/mdBook/pull/2017)
- Preprocessors are now run with `mdbook test`.
[#1986](https://github.com/rust-lang/mdBook/pull/1986)
### Fixed
- Fixed regression in 0.4.26 that prevented the title bar from scrolling properly on smaller screens.
[#2039](https://github.com/rust-lang/mdBook/pull/2039)
## mdBook 0.4.27
[v0.4.26...v0.4.27](https://github.com/rust-lang/mdBook/compare/v0.4.26...v0.4.27)
### Changed
- Reverted the dependency update to the `toml` crate. This was an unintentional breaking change in 0.4.26.
[#2021](https://github.com/rust-lang/mdBook/pull/2021)
## mdBook 0.4.26
[v0.4.25...v0.4.26](https://github.com/rust-lang/mdBook/compare/v0.4.25...v0.4.26)
**The 0.4.26 release has been yanked due to an unintentional breaking change.**
### Changed
- Removed custom scrollbars for webkit browsers
[#1961](https://github.com/rust-lang/mdBook/pull/1961)
- Updated some dependencies
[#1998](https://github.com/rust-lang/mdBook/pull/1998)
[#2009](https://github.com/rust-lang/mdBook/pull/2009)
[#2011](https://github.com/rust-lang/mdBook/pull/2011)
- Fonts are now part of the theme.
The `output.html.copy-fonts` option has been deprecated.
To define custom fonts, be sure to define `theme/fonts.css`.
[#1987](https://github.com/rust-lang/mdBook/pull/1987)
### Fixed
- Fixed overflow viewport issue with mobile Safari
[#1994](https://github.com/rust-lang/mdBook/pull/1994)
## mdBook 0.4.25
[e14d381...1ba74a3](https://github.com/rust-lang/mdBook/compare/e14d381...1ba74a3)
### Fixed
- Fixed a regression where `mdbook test -L deps path-to-book` would not work.
[#1959](https://github.com/rust-lang/mdBook/pull/1959)
## mdBook 0.4.24
[eb77083...8767ebf](https://github.com/rust-lang/mdBook/compare/eb77083...8767ebf)
### Fixed
- The precompiled linux-gnu mdbook binary available on [GitHub Releases](https://github.com/rust-lang/mdBook/releases) inadvertently switched to a newer version of glibc. This release goes back to an older version that should be more compatible on older versions of Linux.
[#1955](https://github.com/rust-lang/mdBook/pull/1955)
## mdBook 0.4.23
[678b469...68a75da](https://github.com/rust-lang/mdBook/compare/678b469...68a75da)
### Changed
- Updated all dependencies
[#1951](https://github.com/rust-lang/mdBook/pull/1951)
[#1952](https://github.com/rust-lang/mdBook/pull/1952)
[#1844](https://github.com/rust-lang/mdBook/pull/1844)
- Updated minimum Rust version to 1.60.
[#1951](https://github.com/rust-lang/mdBook/pull/1951)
### Fixed
- Fixed a regression where playground code was missing hidden lines, preventing it from compiling correctly.
[#1950](https://github.com/rust-lang/mdBook/pull/1950)
## mdBook 0.4.22
[40c06f5...4844f72](https://github.com/rust-lang/mdBook/compare/40c06f5...4844f72)
### Added
- Added a `--chapter` option to `mdbook test` to specify a specific chapter to test.
[#1741](https://github.com/rust-lang/mdBook/pull/1741)
- Added CSS styling for `<kbd>` tags.
[#1906](https://github.com/rust-lang/mdBook/pull/1906)
- Added pre-compiled binaries for `x86_64-unknown-linux-musl` and `aarch64-unknown-linux-musl` (see [Releases](https://github.com/rust-lang/mdBook/releases)).
[#1862](https://github.com/rust-lang/mdBook/pull/1862)
- Added `build.extra-watch-dirs` which is an array of additional directories to watch for changes when running `mdbook serve`.
[#1884](https://github.com/rust-lang/mdBook/pull/1884)
### Changed
- Removed the `type="text/javascript"` attribute from `<script>` tags.
[#1881](https://github.com/rust-lang/mdBook/pull/1881)
- Switched to building with Rust Edition 2021.
This raises the minimum supported Rust version to 1.56.
[#1887](https://github.com/rust-lang/mdBook/pull/1887)
- When hidden code is hidden, the hidden parts are no longer copied to the clipboard via the copy button.
[#1911](https://github.com/rust-lang/mdBook/pull/1911)
- Various HTML changes and fixes to be more compliant with HTML5.
[#1924](https://github.com/rust-lang/mdBook/pull/1924)
- The theme picker now shows which theme is currently selected.
[#1935](https://github.com/rust-lang/mdBook/pull/1935)
### Fixed
- Avoid blank line at the end of an ACE code block
[#1836](https://github.com/rust-lang/mdBook/pull/1836)
## mdBook 0.4.21
[92afe9b...8f01d02](https://github.com/rust-lang/mdBook/compare/92afe9b...8f01d02)
### Fixed
- Fixed an issue where mdBook would fail to compile with Rust nightly-2022-07-22.
[#1861](https://github.com/rust-lang/mdBook/pull/1861)
## mdBook 0.4.20
[53055e0...da166e0](https://github.com/rust-lang/mdBook/compare/53055e0...da166e0)
### Fixed
- Fixed a regression in 0.4.19 where inline code would have excessive padding
in some situations such as headings.
[#1855](https://github.com/rust-lang/mdBook/pull/1855)
## mdBook 0.4.19
[ae275ad...eb82ddc](https://github.com/rust-lang/mdBook/compare/ae275ad...eb82ddc)
[ae275ad...53055e0](https://github.com/rust-lang/mdBook/compare/ae275ad...53055e0)
### Added
- The `serve` command now supports HEAD requests.
@@ -60,7 +279,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
@@ -116,7 +141,7 @@ If possible, do your best to avoid breaking older browser releases.
Any change to the HTML or styling is encouraged to manually check on as many browsers and platforms that you can.
Unfortunately at this time we don't have any automated UI or browser testing, so your assistance in testing is appreciated.
## Updating higlight.js
## Updating highlight.js
The following are instructions for updating [highlight.js](https://highlightjs.org/).
@@ -128,3 +153,23 @@ The following are instructions for updating [highlight.js](https://highlightjs.o
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."
```

1740
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,67 +1,73 @@
[package]
name = "mdbook"
version = "0.4.19"
version = "0.4.35"
authors = [
"Mathieu David <mathieudavid@mathieudavid.org>",
"Michael-F-Bryan <michaelfbryan@gmail.com>",
"Matt Ickstadt <mattico8@gmail.com>"
]
documentation = "http://rust-lang.github.io/mdBook/index.html"
edition = "2018"
documentation = "https://rust-lang.github.io/mdBook/index.html"
edition = "2021"
exclude = ["/guide/*"]
keywords = ["book", "gitbook", "rustbook", "markdown"]
license = "MPL-2.0"
readme = "README.md"
repository = "https://github.com/rust-lang/mdBook"
description = "Creates a book from markdown files"
rust-version = "1.66"
[dependencies]
anyhow = "1.0.28"
chrono = "0.4"
clap = { version = "3.0", features = ["cargo"] }
clap_complete = "3.0"
env_logger = "0.9.0"
handlebars = "4.0"
lazy_static = "1.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"
topological-sort = "0.1.0"
anyhow = "1.0.71"
chrono = { version = "0.4.24", default-features = false, features = ["clock"] }
clap = { version = "4.3.12", features = ["cargo", "wrap_help"] }
clap_complete = "4.3.2"
once_cell = "1.17.1"
env_logger = "0.10.0"
handlebars = "4.3.7"
log = "0.4.17"
memchr = "2.5.0"
opener = "0.6.1"
pulldown-cmark = { version = "0.9.3", default-features = false }
regex = "1.8.1"
serde = { version = "1.0.163", features = ["derive"] }
serde_json = "1.0.96"
shlex = "1.1.0"
tempfile = "3.4.0"
toml = "0.5.11" # Do not update, see https://github.com/rust-lang/mdBook/issues/2037
topological-sort = "0.2.2"
# Watch feature
notify = { version = "4.0", optional = true }
gitignore = { version = "1.0", optional = true }
notify = { version = "6.0.1", optional = true }
notify-debouncer-mini = { version = "0.3.0", optional = true }
ignore = { version = "0.4.20", 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.5", 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 = "3.3.0", optional = true }
[dev-dependencies]
assert_cmd = "1"
predicates = "2"
select = "0.5"
semver = "1.0"
pretty_assertions = "1.2.1"
walkdir = "2.0"
assert_cmd = "2.0.11"
predicates = "3.0.3"
select = "0.6.0"
semver = "1.0.17"
pretty_assertions = "1.3.0"
walkdir = "2.3.3"
[features]
default = ["watch", "serve", "search"]
watch = ["notify", "gitignore"]
serve = ["futures-util", "tokio", "warp"]
search = ["elasticlunr-rs", "ammonia"]
watch = ["dep:notify", "dep:notify-debouncer-mini", "dep:ignore"]
serve = ["dep:futures-util", "dep:tokio", "dep:warp"]
search = ["dep:elasticlunr-rs", "dep:ammonia"]
[[bin]]
doc = false
name = "mdbook"
[[example]]
name = "nop-preprocessor"
test = true

View File

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

View File

@@ -13,6 +13,17 @@ TOOLCHAIN="$1"
rustup set profile minimal
rustup component remove --toolchain=$TOOLCHAIN rust-docs || echo "already removed"
rustup update --no-self-update $TOOLCHAIN
if [ -n "$2" ]
then
TARGET="$2"
HOST=$(rustc -Vv | grep ^host: | sed -e "s/host: //g")
if [ "$HOST" != "$TARGET" ]
then
rustup component add llvm-tools-preview --toolchain=$TOOLCHAIN
rustup component add rust-std-$TARGET --toolchain=$TOOLCHAIN
fi
fi
rustup default $TOOLCHAIN
rustup -V
rustc -Vv

View File

@@ -11,16 +11,21 @@ fi
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
cd target/release
cargo build --locked --bin mdbook --release --target $target
cd target/$target/release
case $1 in
ubuntu*)
asset="mdbook-$TAG-$host.tar.gz"
asset="mdbook-$TAG-$target.tar.gz"
tar czf ../../$asset mdbook
;;
macos*)
asset="mdbook-$TAG-$host.tar.gz"
asset="mdbook-$TAG-$target.tar.gz"
# There is a bug with BSD tar on macOS where the first 8MB of the file are
# sometimes all NUL bytes. See https://github.com/actions/cache/issues/403
# and https://github.com/rust-lang/cargo/issues/8603 for some more
@@ -30,7 +35,7 @@ case $1 in
tar czf ../../$asset mdbook
;;
windows*)
asset="mdbook-$TAG-$host.zip"
asset="mdbook-$TAG-$target.zip"
7z a ../../$asset mdbook.exe
;;
*)
@@ -39,9 +44,10 @@ case $1 in
esac
cd ../..
if [[ -z "$GITHUB_TOKEN" ]]
if [[ -z "$GITHUB_ENV" ]]
then
echo "$GITHUB_TOKEN not set, skipping deploy."
echo "GITHUB_ENV not set, run: gh release upload $TAG target/$asset"
else
hub release edit -m "" --attach $asset $TAG
echo "MDBOOK_TAG=$TAG" >> $GITHUB_ENV
echo "MDBOOK_ASSET=target/$asset" >> $GITHUB_ENV
fi

View File

@@ -1,5 +1,5 @@
use crate::nop_lib::Nop;
use clap::{App, Arg, ArgMatches};
use clap::{Arg, ArgMatches, Command};
use mdbook::book::Book;
use mdbook::errors::Error;
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
@@ -7,11 +7,11 @@ use semver::{Version, VersionReq};
use std::io;
use std::process;
pub fn make_app() -> App<'static> {
App::new("nop-preprocessor")
pub fn make_app() -> Command {
Command::new("nop-preprocessor")
.about("A mdbook preprocessor which does precisely nothing")
.subcommand(
App::new("supports")
Command::new("supports")
.arg(Arg::new("renderer").required(true))
.about("Check whether a renderer is supported by this preprocessor"),
)
@@ -54,7 +54,9 @@ fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
}
fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! {
let renderer = sub_args.value_of("renderer").expect("Required argument");
let renderer = sub_args
.get_one::<String>("renderer")
.expect("Required argument");
let supported = pre.supports_renderer(renderer);
// Signal whether the renderer is supported by exiting with 1 or 0.
@@ -101,4 +103,58 @@ mod nop_lib {
renderer != "not-supported"
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn nop_preprocessor_run() {
let input_json = r##"[
{
"root": "/path/to/book",
"config": {
"book": {
"authors": ["AUTHOR"],
"language": "en",
"multilingual": false,
"src": "src",
"title": "TITLE"
},
"preprocessor": {
"nop": {}
}
},
"renderer": "html",
"mdbook_version": "0.4.21"
},
{
"sections": [
{
"Chapter": {
"name": "Chapter 1",
"content": "# Chapter 1\n",
"number": [1],
"sub_items": [],
"path": "chapter_1.md",
"source_path": "chapter_1.md",
"parent_names": []
}
}
],
"__non_exhaustive": null
}
]"##;
let input_json = input_json.as_bytes();
let (ctx, book) = mdbook::preprocess::CmdPreprocessor::parse_input(input_json).unwrap();
let expected_book = book.clone();
let result = Nop::new().run(&ctx, book);
assert!(result.is_ok());
// The nop-preprocessor should not have made any changes to the book content.
let actual_book = result.unwrap();
assert_eq!(actual_book, expected_book);
}
}
}

View File

@@ -17,6 +17,9 @@ edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path
editable = true
line-numbers = true
[output.html.code.hidelines]
python = "~"
[output.html.search]
limit-results = 20
use-boolean-and = true

View File

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

@@ -67,4 +67,16 @@ mdbook init --title="my amazing book"
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

@@ -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
@@ -43,7 +42,7 @@ mdbook test path/to/book
The `--library-path` (`-L`) option allows you to add directories to the library
search path used by `rustdoc` when it builds and tests the examples. Multiple
directories can be specified with multiple options (`-L foo -L bar`) or with a
comma-delimited list (`-L foo,bar`). The path should point to the Cargo
comma-delimited list (`-L foo,bar`). The path should point to the Cargo
[build cache](https://doc.rust-lang.org/cargo/guide/build-cache.html) `deps` directory that
contains the build output of your project. For example, if your Rust project's book is in a directory
named `my-book`, the following command would include the crate's dependencies when running `test`:
@@ -61,3 +60,8 @@ The `--dest-dir` (`-d`) option allows you to change the output directory for the
book. Relative paths are interpreted relative to the book's root directory. If
not specified it will default to the value of the `build.build-dir` key in
`book.toml`, or to `./book`.
#### --chapter
The `--chapter` (`-c`) option allows you to test a specific chapter of the
book using the chapter name or the relative path to the chapter.

View File

@@ -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.19/mdbook-v0.4.19-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.35/mdbook-v0.4.35-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
bin/mdbook build
```
@@ -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

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

View File

@@ -7,7 +7,7 @@ Here is an example of what a ***book.toml*** file might look like:
```toml
[book]
title = "Example book"
author = "John Doe"
authors = ["John Doe"]
description = "The example book covers examples."
[rust]
@@ -46,6 +46,9 @@ This is general information about your book.
`src` directly under the root folder. But this is configurable with the `src`
key in the configuration file.
- **language:** The main language of the book, which is used as a language attribute `<html lang="en">` for example.
This is also used to derive the direction of text (RTL, LTR) within the book.
- **text-direction**: The direction of text in the book: Left-to-right (LTR) or Right-to-left (RTL). Possible values: `ltr`, `rtl`.
When not specified, the text direction is derived from the book's `language` attribute.
**book.toml**
```toml
@@ -55,6 +58,7 @@ authors = ["John Doe", "Jane Doe"]
description = "The example book covers examples."
src = "my-src" # the source files will be found in `root/my-src` instead of `root/src`
language = "en"
text-direction = "ltr"
```
### Rust options
@@ -87,6 +91,7 @@ This controls the build process of your book.
build-dir = "book" # the directory where the output is placed
create-missing = true # whether or not to create missing pages
use-default-preprocessors = true # use the default preprocessors
extra-watch-dirs = [] # directories to watch for triggering builds
```
- **build-dir:** The directory to put the rendered book in. By default this is
@@ -96,7 +101,7 @@ use-default-preprocessors = true # use the default preprocessors
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
@@ -108,3 +113,6 @@ use-default-preprocessors = true # use the default preprocessors
default preprocessors from running.
- Adding `[preprocessor.links]`, for example, will ensure, regardless of
`use-default-preprocessors` that `links` it will run.
- **extra-watch-dirs**: A list of paths to directories that will be watched in
the `watch` and `serve` commands. Changes to files under these directories will
trigger rebuilds. Useful if your book depends on files outside its `src` directory.

View File

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

@@ -126,7 +126,10 @@ The following configuration options are available:
that occur in code blocks and code spans. Defaults to `false`.
- **mathjax-support:** Adds support for [MathJax](../mathjax.md). Defaults to
`false`.
- **copy-fonts:** Copies fonts.css and respective font files to the output directory and use them in the default theme. Defaults to `true`.
- **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 +150,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.
@@ -157,7 +160,8 @@ The following configuration options are available:
Defaults to `404.md`.
- **site-url:** The url where the book will be hosted. This is required to ensure
navigation links and script/css imports in the 404 file work correctly, even when accessing
urls in subdirectories. Defaults to `/`.
urls in subdirectories. Defaults to `/`. If `site-url` is set,
make sure to use document relative links for your assets, meaning they should not start with `/`.
- **cname:** The DNS subdomain or apex domain at which your book will be hosted.
This string will be written to a file named CNAME in the root of your site, as
required by GitHub Pages (see [*Managing a custom domain for your GitHub Pages
@@ -178,7 +182,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]`
@@ -214,11 +218,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].

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~~.
@@ -220,3 +220,16 @@ To enable it, see the [`output.html.curly-quotes`] config option.
[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
### 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/specs/heading_attrs.txt).

View File

@@ -2,11 +2,11 @@
## 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 use the `#` character as a prefix which will hide lines [like you would with Rustdoc][rustdoc-hide].
[rustdoc-hide]: https://doc.rust-lang.org/stable/rustdoc/write-documentation/documentation-tests.html#hiding-portions-of-the-example
```bash
# fn main() {
@@ -28,7 +28,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
@@ -274,3 +314,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
fatique," 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
fatique," where people are trained to ignore them because they usually don't
matter for what they're doing.
</div>

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

@@ -77,38 +77,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.54.
mdBook currently requires at least Rust version 1.66.
Once you have installed Rust, the following command can be used to build and install mdBook:

View File

@@ -8,7 +8,7 @@ use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
use crate::config::BuildConfig;
use crate::errors::*;
use crate::utils::bracket_escape;
use log::debug;
use serde::{Deserialize, Serialize};
/// Load a book into memory from its `src/` directory.
@@ -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);
@@ -277,7 +275,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 +315,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);

View File

@@ -6,6 +6,8 @@ use super::MDBook;
use crate::config::Config;
use crate::errors::*;
use crate::theme;
use crate::utils::fs::write_file;
use log::{debug, error, info, trace};
/// A helper for setting up a new book and its directory structure.
#[derive(Debug, Clone, PartialEq)]
@@ -157,6 +159,19 @@ impl BookBuilder {
let mut highlight_js = File::create(themedir.join("highlight.js"))?;
highlight_js.write_all(theme::HIGHLIGHT_JS)?;
write_file(&themedir.join("fonts"), "fonts.css", theme::fonts::CSS)?;
for (file_name, contents) in theme::fonts::LICENSES {
write_file(&themedir, file_name, contents)?;
}
for (file_name, contents) in theme::fonts::OPEN_SANS.iter() {
write_file(&themedir, file_name, contents)?;
}
write_file(
&themedir,
theme::fonts::SOURCE_CODE_PRO.0,
theme::fonts::SOURCE_CODE_PRO.1,
)?;
Ok(())
}
@@ -183,8 +198,7 @@ impl BookBuilder {
writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
let chapter_1 = src_dir.join("chapter_1.md");
let mut f =
File::create(&chapter_1).with_context(|| "Unable to create chapter_1.md")?;
let mut f = File::create(chapter_1).with_context(|| "Unable to create chapter_1.md")?;
writeln!(f, "# Chapter 1")?;
} else {
trace!("Existing summary found, no need to create stub files.");
@@ -197,10 +211,10 @@ impl BookBuilder {
fs::create_dir_all(&self.root)?;
let src = self.root.join(&self.config.book.src);
fs::create_dir_all(&src)?;
fs::create_dir_all(src)?;
let build = self.root.join(&self.config.build.build_dir);
fs::create_dir_all(&build)?;
fs::create_dir_all(build)?;
Ok(())
}

View File

@@ -14,6 +14,7 @@ pub use self::book::{load_book, Book, BookItem, BookItems, Chapter};
pub use self::init::BookBuilder;
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
use log::{debug, error, info, log_enabled, trace, warn};
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
@@ -98,7 +99,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)?;
@@ -121,7 +122,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)?;
@@ -195,21 +196,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);
@@ -246,6 +252,13 @@ impl MDBook {
/// Run `rustdoc` tests on the book, linking against the provided libraries.
pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
// test_chapter with chapter:None will run all tests.
self.test_chapter(library_paths, None)
}
/// Run `rustdoc` tests on a specific chapter of the book, linking against the provided libraries.
/// If `chapter` is `None`, all tests will be run.
pub fn test_chapter(&mut self, library_paths: Vec<&str>, chapter: Option<&str>) -> Result<()> {
let library_args: Vec<&str> = (0..library_paths.len())
.map(|_| "-L")
.zip(library_paths.into_iter())
@@ -254,13 +267,27 @@ impl MDBook {
let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
// FIXME: Is "test" the proper renderer name to use here?
let preprocess_context =
PreprocessorContext::new(self.root.clone(), self.config.clone(), "test".to_string());
let mut chapter_found = false;
let book = LinkPreprocessor::new().run(&preprocess_context, self.book.clone())?;
// Index Preprocessor is disabled so that chapter paths continue to point to the
// actual markdown files.
struct TestRenderer;
impl Renderer for TestRenderer {
// FIXME: Is "test" the proper renderer name to use here?
fn name(&self) -> &str {
"test"
}
fn render(&self, _: &RenderContext) -> Result<()> {
Ok(())
}
}
// Index Preprocessor is disabled so that chapter paths
// continue to point to the actual markdown files.
self.preprocessors = determine_preprocessors(&self.config)?
.into_iter()
.filter(|pre| pre.name() != IndexPreprocessor::NAME)
.collect();
let (book, _) = self.preprocess_book(&TestRenderer)?;
let mut failed = false;
for item in book.iter() {
@@ -270,11 +297,19 @@ impl MDBook {
_ => continue,
};
let path = self.source_dir().join(&chapter_path);
info!("Testing file: {:?}", path);
if let Some(chapter) = chapter {
if ch.name != chapter && chapter_path.to_str() != Some(chapter) {
if chapter == "?" {
info!("Skipping chapter '{}'...", ch.name);
}
continue;
}
}
chapter_found = true;
info!("Testing chapter '{}': {:?}", ch.name, chapter_path);
// write preprocessed file to tempdir
let path = temp_dir.path().join(&chapter_path);
let path = temp_dir.path().join(chapter_path);
let mut tmpf = utils::fs::create_file(&path)?;
tmpf.write_all(ch.content.as_bytes())?;
@@ -284,17 +319,18 @@ impl MDBook {
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"]);
}
}
}
debug!("running {:?}", cmd);
let output = cmd.output()?;
if !output.status.success() {
@@ -311,6 +347,11 @@ impl MDBook {
if failed {
bail!("One or more tests failed");
}
if let Some(chapter) = chapter {
if !chapter_found {
bail!("Chapter not found: {}", chapter);
}
}
Ok(())
}

View File

@@ -1,4 +1,5 @@
use crate::errors::*;
use log::{debug, trace, warn};
use memchr::{self, Memchr};
use pulldown_cmark::{self, Event, HeadingLevel, Tag};
use serde::{Deserialize, Serialize};
@@ -453,7 +454,7 @@ impl<'a> SummaryParser<'a> {
items.push(item);
}
Some(Event::Start(Tag::List(..))) => {
// Skip this tag after comment bacause it is not nested.
// Skip this tag after comment because it is not nested.
if items.is_empty() {
continue;
}

View File

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

View File

@@ -1,36 +1,24 @@
use super::command_prelude::*;
use crate::get_book_dir;
use anyhow::Context;
use clap::{arg, App, Arg, ArgMatches};
use mdbook::MDBook;
use std::fs;
use std::path::PathBuf;
// Create clap subcommand arguments
pub fn make_subcommand<'help>() -> App<'help> {
App::new("clean")
pub fn make_subcommand() -> Command {
Command::new("clean")
.about("Deletes a built book")
.arg(
Arg::new("dest-dir")
.short('d')
.long("dest-dir")
.value_name("dest-dir")
.help(
"Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
),
)
.arg(arg!([dir]
"Root directory for the book{n}\
(Defaults to the Current Directory when omitted)"
))
.arg_dest_dir()
.arg_root_dir()
}
// Clean command implementation
pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> {
let book_dir = get_book_dir(args);
let book = MDBook::load(&book_dir)?;
let book = MDBook::load(book_dir)?;
let dir_to_remove = match args.value_of("dest-dir") {
let dir_to_remove = match args.get_one::<PathBuf>("dest-dir") {
Some(dest_dir) => dest_dir.into(),
None => book.root.join(&book.config.build.build_dir),
};

View File

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

View File

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

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

View File

@@ -1,7 +1,8 @@
use super::command_prelude::*;
#[cfg(feature = "watch")]
use super::watch;
use crate::{get_book_dir, open};
use clap::{arg, App, Arg, ArgMatches};
use clap::builder::NonEmptyStringValueParser;
use futures_util::sink::SinkExt;
use futures_util::StreamExt;
use mdbook::errors::*;
@@ -18,61 +19,48 @@ use warp::Filter;
const LIVE_RELOAD_ENDPOINT: &str = "__livereload";
// Create clap subcommand arguments
pub fn make_subcommand<'help>() -> App<'help> {
App::new("serve")
pub fn make_subcommand() -> Command {
Command::new("serve")
.about("Serves a book at http://localhost:3000, and rebuilds it on changes")
.arg(
Arg::new("dest-dir")
.short('d')
.long("dest-dir")
.value_name("dest-dir")
.help(
"Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
),
)
.arg(arg!([dir]
"Root directory for the book{n}\
(Defaults to the Current Directory when omitted)"
))
.arg_dest_dir()
.arg_root_dir()
.arg(
Arg::new("hostname")
.short('n')
.long("hostname")
.takes_value(true)
.num_args(1)
.default_value("localhost")
.forbid_empty_values(true)
.value_parser(NonEmptyStringValueParser::new())
.help("Hostname to listen on for HTTP connections"),
)
.arg(
Arg::new("port")
.short('p')
.long("port")
.takes_value(true)
.num_args(1)
.default_value("3000")
.forbid_empty_values(true)
.value_parser(NonEmptyStringValueParser::new())
.help("Port to use for HTTP connections"),
)
.arg(arg!(-o --open "Opens the compiled book in a web browser"))
.arg_open()
}
// Serve 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)?;
let port = args.value_of("port").unwrap();
let hostname = args.value_of("hostname").unwrap();
let open_browser = args.is_present("open");
let port = args.get_one::<String>("port").unwrap();
let hostname = args.get_one::<String>("hostname").unwrap();
let open_browser = args.get_flag("open");
let address = format!("{}:{}", hostname, port);
let update_config = |book: &mut MDBook| {
book.config
.set("output.html.live-reload-endpoint", &LIVE_RELOAD_ENDPOINT)
.set("output.html.live-reload-endpoint", LIVE_RELOAD_ENDPOINT)
.expect("live-reload-endpoint update failed");
if let Some(dest_dir) = args.value_of("dest-dir") {
if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") {
book.config.build.build_dir = dest_dir.into();
}
// Override site-url for local serving of the 404 file
@@ -89,8 +77,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
let input_404 = book
.config
.get("output.html.input-404")
.map(toml::Value::as_str)
.and_then(std::convert::identity) // flatten
.and_then(toml::Value::as_str)
.map(ToString::to_string);
let file_404 = get_404_output_file(&input_404);
@@ -115,7 +102,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
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| {
let result = MDBook::load(book_dir).and_then(|mut b| {
update_config(&mut b);
b.build()
});

View File

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

View File

@@ -1,49 +1,36 @@
use super::command_prelude::*;
use crate::{get_book_dir, open};
use clap::{arg, App, Arg, ArgMatches};
use ignore::gitignore::Gitignore;
use mdbook::errors::Result;
use mdbook::utils;
use mdbook::MDBook;
use notify::Watcher;
use std::path::{Path, PathBuf};
use std::sync::mpsc::channel;
use std::thread::sleep;
use std::time::Duration;
// Create clap subcommand arguments
pub fn make_subcommand<'help>() -> App<'help> {
App::new("watch")
pub fn make_subcommand() -> Command {
Command::new("watch")
.about("Watches a book's files and rebuilds it on changes")
.arg(
Arg::new("dest-dir")
.short('d')
.long("dest-dir")
.value_name("dest-dir")
.help(
"Output directory for the book{n}\
Relative paths are interpreted relative to the book's root directory.{n}\
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
),
)
.arg(arg!([dir]
"Root directory for the book{n}\
(Defaults to the Current Directory when omitted)"
))
.arg(arg!(-o --open "Opens the compiled book in a web browser"))
.arg_dest_dir()
.arg_root_dir()
.arg_open()
}
// Watch 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)?;
let update_config = |book: &mut MDBook| {
if let Some(dest_dir) = args.value_of("dest-dir") {
if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") {
book.config.build.build_dir = dest_dir.into();
}
};
update_config(&mut book);
if args.is_present("open") {
if args.get_flag("open") {
book.build()?;
let path = book.build_dir_for("html").join("index.html");
if !path.exists() {
@@ -55,7 +42,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
trigger_on_change(&book, |paths, book_dir| {
info!("Files changed: {:?}\nBuilding book...\n", paths);
let result = MDBook::load(&book_dir).and_then(|mut b| {
let result = MDBook::load(book_dir).and_then(|mut b| {
update_config(&mut b);
b.build()
});
@@ -76,14 +63,14 @@ fn remove_ignored_files(book_root: &Path, paths: &[PathBuf]) -> Vec<PathBuf> {
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()
}
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.
@@ -99,18 +86,13 @@ fn find_gitignore(book_root: &Path) -> Option<PathBuf> {
.find(|p| p.exists())
}
fn filter_ignored_files(exclusion_checker: gitignore::File, paths: &[PathBuf]) -> Vec<PathBuf> {
fn filter_ignored_files(ignore: Gitignore, 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
}
.filter(|path| {
!ignore
.matched_path_or_any_parents(path, path.is_dir())
.is_ignore()
})
.map(|path| path.to_path_buf())
.collect()
@@ -121,30 +103,47 @@ pub fn trigger_on_change<F>(book: &MDBook, closure: F)
where
F: Fn(Vec<PathBuf>, &Path),
{
use notify::DebouncedEvent::*;
use notify::RecursiveMode::*;
// Create a channel to receive the events.
let (tx, rx) = channel();
let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) {
Ok(w) => w,
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) {
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);
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);
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...");
@@ -155,18 +154,25 @@ where
let all_events = std::iter::once(first_event).chain(other_events);
let paths = all_events
.filter_map(|event| {
debug!("Received filesystem event: {:?}", event);
match event {
Create(path) | Write(path) | Remove(path) | Rename(_, path) => Some(path),
_ => None,
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
}
})
.collect::<Vec<_>>();
.flatten()
.map(|event| event.path)
.collect();
let paths = remove_ignored_files(&book.root, &paths[..]);
// 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);

View File

@@ -49,6 +49,7 @@
#![deny(missing_docs)]
use log::{debug, trace, warn};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;
use std::env;
@@ -295,7 +296,7 @@ impl Default for Config {
}
}
impl<'de> Deserialize<'de> for Config {
impl<'de> serde::Deserialize<'de> for Config {
fn deserialize<D: Deserializer<'de>>(de: D) -> std::result::Result<Self, D::Error> {
let raw = Value::deserialize(de)?;
@@ -307,7 +308,7 @@ impl<'de> Deserialize<'de> for Config {
warn!("`description` under a table called `[book]`, move the `destination` entry");
warn!("from `[output.html]`, renamed to `build-dir`, under a table called");
warn!("`[build]`, and it should all work.");
warn!("Documentation: http://rust-lang.github.io/mdBook/format/config.html");
warn!("Documentation: https://rust-lang.github.io/mdBook/format/config.html");
return Ok(Config::from_legacy(raw));
}
@@ -410,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 {
@@ -421,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,
}
}
}
@@ -437,6 +478,8 @@ pub struct BuildConfig {
/// Should the default preprocessors always be used when they are
/// compatible with the renderer?
pub use_default_preprocessors: bool,
/// Extra directories to trigger rebuild when watching/serving
pub extra_watch_dirs: Vec<PathBuf>,
}
impl Default for BuildConfig {
@@ -445,6 +488,7 @@ impl Default for BuildConfig {
build_dir: PathBuf::from("book"),
create_missing: true,
use_default_preprocessors: true,
extra_watch_dirs: Vec::new(),
}
}
}
@@ -500,6 +544,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.
@@ -526,10 +572,9 @@ pub struct HtmlConfig {
/// directly jumping to editing the currently viewed page.
/// Contains {path} that is replaced with chapter source file path
pub edit_url_template: Option<String>,
/// Endpoint of websocket, for livereload usage. Value loaded from .toml file
/// is ignored, because our code overrides this field with the value [`LIVE_RELOAD_ENDPOINT`]
///
/// [`LIVE_RELOAD_ENDPOINT`]: cmd::serve::LIVE_RELOAD_ENDPOINT
/// Endpoint of websocket, for livereload usage. Value loaded from .toml
/// file is ignored, because our code overrides this field with an
/// internal value (`LIVE_RELOAD_ENDPOINT)
///
/// This config item *should not be edited* by the end user.
#[doc(hidden)]
@@ -553,6 +598,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,
@@ -639,6 +685,22 @@ impl Default for Playground {
}
}
/// Configuration for tweaking how the the HTML renderer handles code blocks.
#[derive(Debug, 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>,
}
impl Default for Code {
fn default() -> Code {
Code {
hidelines: HashMap::new(),
}
}
}
/// Configuration of the search functionality of the HTML renderer.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
@@ -700,7 +762,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;
}
@@ -717,6 +779,7 @@ impl<'de, T> Updateable<'de> for T where T: Serialize + Deserialize<'de> {}
mod tests {
use super::*;
use crate::utils::fs::get_404_output_file;
use serde_json::json;
const COMPLEX_CONFIG: &str = r#"
[book]
@@ -765,11 +828,13 @@ mod tests {
multilingual: true,
src: PathBuf::from("source"),
language: Some(String::from("ja")),
text_direction: None,
};
let build_should_be = BuildConfig {
build_dir: PathBuf::from("outputs"),
create_missing: false,
use_default_preprocessors: true,
extra_watch_dirs: Vec::new(),
};
let rust_should_be = RustConfig { edition: None };
let playground_should_be = Playground {
@@ -980,6 +1045,7 @@ mod tests {
build_dir: PathBuf::from("my-book"),
create_missing: true,
use_default_preprocessors: true,
extra_watch_dirs: Vec::new(),
};
let html_should_be = HtmlConfig {
@@ -1115,6 +1181,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() {

View File

@@ -83,17 +83,6 @@
#![deny(missing_docs)]
#![deny(rust_2018_idioms)]
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_json;
#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;
pub mod book;
pub mod config;
pub mod preprocess;

View File

@@ -5,7 +5,7 @@ extern crate log;
use anyhow::anyhow;
use chrono::Local;
use clap::{App, AppSettings, Arg, ArgMatches};
use clap::{Arg, ArgMatches, Command};
use clap_complete::Shell;
use env_logger::Builder;
use log::LevelFilter;
@@ -13,7 +13,7 @@ use mdbook::utils;
use std::env;
use std::ffi::OsStr;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
mod cmd;
@@ -22,10 +22,10 @@ const VERSION: &str = concat!("v", crate_version!());
fn main() {
init_logger();
let app = create_clap_app();
let command = create_clap_command();
// Check which subcomamnd the user ran...
let res = match app.get_matches().subcommand() {
// Check which subcommand the user ran...
let res = match command.get_matches().subcommand() {
Some(("init", sub_matches)) => cmd::init::execute(sub_matches),
Some(("build", sub_matches)) => cmd::build::execute(sub_matches),
Some(("clean", sub_matches)) => cmd::clean::execute(sub_matches),
@@ -35,15 +35,13 @@ fn main() {
Some(("serve", sub_matches)) => cmd::serve::execute(sub_matches),
Some(("test", sub_matches)) => cmd::test::execute(sub_matches),
Some(("completions", sub_matches)) => (|| {
let shell: Shell = sub_matches
.value_of("shell")
.ok_or_else(|| anyhow!("Shell name missing."))?
.parse()
.map_err(|s| anyhow!("Invalid shell: {}", s))?;
let shell = sub_matches
.get_one::<Shell>("shell")
.ok_or_else(|| anyhow!("Shell name missing."))?;
let mut complete_app = create_clap_app();
let mut complete_app = create_clap_command();
clap_complete::generate(
shell,
*shell,
&mut complete_app,
"mdbook",
&mut std::io::stdout().lock(),
@@ -61,13 +59,13 @@ fn main() {
}
/// Create a list of valid arguments and sub-commands
fn create_clap_app() -> App<'static> {
let app = App::new(crate_name!())
fn create_clap_command() -> Command {
let app = Command::new(crate_name!())
.about(crate_description!())
.author("Mathieu David <mathieudavid@mathieudavid.org>")
.version(VERSION)
.setting(AppSettings::PropagateVersion)
.setting(AppSettings::ArgRequiredElseHelp)
.propagate_version(true)
.arg_required_else_help(true)
.after_help(
"For more information about a specific command, try `mdbook <command> --help`\n\
The source code for mdBook is available at: https://github.com/rust-lang/mdBook",
@@ -77,12 +75,11 @@ fn create_clap_app() -> App<'static> {
.subcommand(cmd::test::make_subcommand())
.subcommand(cmd::clean::make_subcommand())
.subcommand(
App::new("completions")
Command::new("completions")
.about("Generate shell completions for your shell to stdout")
.arg(
Arg::new("shell")
.takes_value(true)
.possible_values(Shell::possible_values())
.value_parser(clap::value_parser!(Shell))
.help("the shell to generate completions for")
.value_name("SHELL")
.required(true),
@@ -124,11 +121,10 @@ fn init_logger() {
}
fn get_book_dir(args: &ArgMatches) -> PathBuf {
if let Some(dir) = args.value_of("dir") {
if let Some(p) = args.get_one::<PathBuf>("dir") {
// Check if path is relative from current dir, or absolute...
let p = Path::new(dir);
if p.is_relative() {
env::current_dir().unwrap().join(dir)
env::current_dir().unwrap().join(p)
} else {
p.to_path_buf()
}
@@ -146,5 +142,5 @@ fn open<P: AsRef<OsStr>>(path: P) {
#[test]
fn verify_app() {
create_clap_app().debug_assert();
create_clap_command().debug_assert();
}

View File

@@ -1,6 +1,7 @@
use super::{Preprocessor, PreprocessorContext};
use crate::book::Book;
use crate::errors::*;
use log::{debug, trace, warn};
use shlex::Shlex;
use std::io::{self, Read, Write};
use std::process::{Child, Command, Stdio};

View File

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

View File

@@ -10,6 +10,8 @@ use std::path::{Path, PathBuf};
use super::{Preprocessor, PreprocessorContext};
use crate::book::{Book, BookItem};
use log::{error, warn};
use once_cell::sync::Lazy;
const ESCAPE_CHAR: char = '\\';
const MAX_LINK_NESTED_DEPTH: usize = 10;
@@ -91,7 +93,7 @@ where
for link in find_links(s) {
replaced.push_str(&s[previous_end_index..link.start_index]);
match link.render_with_path(&path, chapter_title) {
match link.render_with_path(path, chapter_title) {
Ok(new_content) => {
if depth < MAX_LINK_NESTED_DEPTH {
if let Some(rel_path) = link.link_type.relative_path(path) {
@@ -325,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);
@@ -408,19 +410,20 @@ impl<'a> Iterator for LinkIter<'a> {
fn find_links(contents: &str) -> LinkIter<'_> {
// lazily compute following regex
// r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([^}]+)\}\}")?;
lazy_static! {
static ref RE: Regex = Regex::new(
static RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?x) # insignificant whitespace mode
\\\{\{\#.*\}\} # match escaped link
| # or
\{\{\s* # link opening parens and whitespace
\#([a-zA-Z0-9_]+) # link type
\s+ # separating whitespace
([^}]+) # link target path and space separated properties
\}\} # link closing parens"
\\\{\{\#.*\}\} # match escaped link
| # or
\{\{\s* # link opening parens and whitespace
\#([a-zA-Z0-9_]+) # link type
\s+ # separating whitespace
([^}]+) # link target path and space separated properties
\}\} # link closing parens",
)
.unwrap();
}
.unwrap()
});
LinkIter(RE.captures_iter(contents))
}

View File

@@ -12,12 +12,11 @@ use crate::book::Book;
use crate::config::Config;
use crate::errors::*;
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
/// Extra information for a `Preprocessor` to give them more context when
/// processing a book.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]

View File

@@ -1,5 +1,5 @@
use crate::book::{Book, BookItem};
use crate::config::{BookConfig, Config, HtmlConfig, Playground, RustEdition};
use crate::config::{BookConfig, Code, Config, HtmlConfig, Playground, RustEdition};
use crate::errors::*;
use crate::renderer::html_handlebars::helpers;
use crate::renderer::{RenderContext, Renderer};
@@ -14,7 +14,10 @@ use std::path::{Path, PathBuf};
use crate::utils::fs::get_404_output_file;
use handlebars::Handlebars;
use log::{debug, trace, warn};
use once_cell::sync::Lazy;
use regex::{Captures, Regex};
use serde_json::json;
#[derive(Default)]
pub struct HtmlHandlebars;
@@ -96,7 +99,7 @@ impl HtmlHandlebars {
ctx.data.insert("title".to_owned(), json!(title));
ctx.data.insert(
"path_to_root".to_owned(),
json!(utils::fs::path_to_root(&path)),
json!(utils::fs::path_to_root(path)),
);
if let Some(ref section) = ch.number {
ctx.data
@@ -107,7 +110,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());
@@ -118,8 +126,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())?;
}
@@ -179,8 +191,12 @@ 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 ✓");
@@ -192,11 +208,13 @@ impl HtmlHandlebars {
&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
}
@@ -272,7 +290,8 @@ impl HtmlHandlebars {
"FontAwesome/fonts/FontAwesome.ttf",
theme::FONT_AWESOME_TTF,
)?;
if html_config.copy_fonts {
// Don't copy the stock fonts if the user has specified their own fonts to use.
if html_config.copy_fonts && theme.fonts_css.is_none() {
write_file(destination, "fonts/fonts.css", theme::fonts::CSS)?;
for (file_name, contents) in theme::fonts::LICENSES.iter() {
write_file(destination, file_name, contents)?;
@@ -286,6 +305,24 @@ impl HtmlHandlebars {
theme::fonts::SOURCE_CODE_PRO.1,
)?;
}
if let Some(fonts_css) = &theme.fonts_css {
if !fonts_css.is_empty() {
write_file(destination, "fonts/fonts.css", fonts_css)?;
}
}
if !html_config.copy_fonts && theme.fonts_css.is_none() {
warn!(
"output.html.copy-fonts is deprecated.\n\
This book appears to have copy-fonts=false in book.toml without a fonts.css file.\n\
Add an empty `theme/fonts/fonts.css` file to squelch this warning."
);
}
for font_file in &theme.font_files {
let contents = fs::read(font_file)?;
let filename = font_file.file_name().unwrap();
let filename = Path::new("fonts").join(filename);
write_file(destination, filename, &contents)?;
}
let playground_config = &html_config.playground;
@@ -337,6 +374,7 @@ impl HtmlHandlebars {
);
handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
handlebars.register_helper("next", Box::new(helpers::navigation::next));
// TODO: remove theme_option in 0.5, it is not needed.
handlebars.register_helper("theme_option", Box::new(helpers::theme::theme_option));
}
@@ -524,7 +562,7 @@ impl Renderer for HtmlHandlebars {
// Print version
let mut print_content = String::new();
fs::create_dir_all(&destination)
fs::create_dir_all(destination)
.with_context(|| "Unexpected error when constructing destination path")?;
let mut is_index = true;
@@ -560,8 +598,12 @@ 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 ✓");
@@ -606,6 +648,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()),
@@ -627,6 +673,7 @@ fn make_data(
);
}
// TODO: remove default_theme in 0.5, it is not needed.
let default_theme = match html_config.default_theme {
Some(ref theme) => theme.to_lowercase(),
None => "light".to_string(),
@@ -651,7 +698,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));
}
@@ -764,9 +812,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 {
lazy_static! {
static ref BUILD_HEADER_LINKS: Regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();
}
static BUILD_HEADER_LINKS: Lazy<Regex> = Lazy::new(|| {
Regex::new(r#"<h(\d)(?: id="([^"]+)")?(?: class="([^"]+)")?>(.*?)</h\d>"#).unwrap()
});
static IGNORE_CLASS: &[&str] = &["menu-title"];
let mut id_counter = HashMap::new();
@@ -776,7 +825,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()
}
@@ -786,15 +850,21 @@ 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}>"##,
r##"<h{level} id="{id}"{classes}><a class="header" href="#{id}">{text}</a></h{level}>"##,
level = level,
id = id,
text = content
text = content,
classes = classes
)
}
@@ -807,10 +877,8 @@ fn insert_link_into_header(
// ```
// This function replaces all commas by spaces in the code block classes
fn fix_code_blocks(html: &str) -> String {
lazy_static! {
static ref FIX_CODE_BLOCKS: Regex =
Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap();
}
static FIX_CODE_BLOCKS: Lazy<Regex> =
Lazy::new(|| Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap());
FIX_CODE_BLOCKS
.replace_all(html, |caps: &Captures<'_>| {
@@ -828,68 +896,64 @@ fn fix_code_blocks(html: &str) -> String {
.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 {
lazy_static! {
static ref ADD_PLAYGROUND_PRE: Regex =
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",
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()
};
content
}
)
} else {
// not language-rust, so no-op
text.to_owned()
@@ -898,19 +962,63 @@ fn add_playground_pre(
.into_owned()
}
fn hide_lines(content: &str) -> String {
lazy_static! {
static ref BORING_LINES_REGEX: Regex = Regex::new(r"^(\s*)#(.?)(.*)$").unwrap();
}
/// Modifies all `<code>` blocks to convert "hidden" lines and to wrap them in
/// a `<span class="boring">`.
fn hide_lines(html: &str, code_config: &Code) -> String {
let language_regex = Regex::new(r"\blanguage-(\w+)\b").unwrap();
let hidelines_regex = 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());
for line in content.lines() {
let mut lines = content.lines().peekable();
while let Some(line) = lines.next() {
// Don't include newline on the last line.
let newline = if lines.peek().is_none() { "" } else { "\n" };
if let Some(caps) = BORING_LINES_REGEX.captures(line) {
if &caps[2] == "#" {
result += &caps[1];
result += &caps[2];
result += &caps[3];
result += "\n";
result += newline;
continue;
} else if &caps[2] != "!" && &caps[2] != "[" {
result += "<span class=\"boring\">";
@@ -919,12 +1027,32 @@ fn hide_lines(content: &str) -> String {
result += &caps[2];
}
result += &caps[3];
result += "\n";
result += newline;
result += "</span>";
continue;
}
}
result += line;
result += newline;
}
result
}
fn hide_lines_with_prefix(content: &str, prefix: &str) -> String {
let mut result = String::with_capacity(content.len());
for line in content.lines() {
if line.trim_start().starts_with(prefix) {
let pos = line.find(prefix).unwrap();
let (ws, rest) = (&line[..pos], &line[pos + prefix.len()..]);
result += "<span class=\"boring\">";
result += ws;
result += rest;
result += "\n";
result += "</span>";
continue;
}
result += line;
result += "\n";
}
result
@@ -964,7 +1092,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() {
@@ -993,6 +1124,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 {
@@ -1005,19 +1151,19 @@ 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\">}\n</span></code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust\"># #![allow(unused)]\n#fn main() {\nx()\n#}</code></pre>"),
("<code class=\"language-rust\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust\">fn main() {}</code></pre>"),
("<code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code>",
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code></pre>"),
("<code class=\"language-rust editable\">let s = \"foo\n ## bar\n\";</code>",
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n\";\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n ## bar\n\";</code></pre>"),
("<code class=\"language-rust editable\">let s = \"foo\n # bar\n#\n\";</code>",
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span><span class=\"boring\">\n</span>\";\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n#\n\";</code></pre>"),
("<code class=\"language-rust ignore\">let s = \"foo\n # bar\n\";</code>",
"<code class=\"language-rust ignore\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";\n</code>"),
"<code class=\"language-rust ignore\">let s = \"foo\n # bar\n\";</code>"),
("<code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code>",
"<pre class=\"playground\"><code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code></pre>"),
];
for (src, should_be) in &inputs {
let got = add_playground_pre(
@@ -1035,13 +1181,13 @@ 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\">}\n</span></code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2015\"># #![allow(unused)]\n#fn main() {\nx()\n#}</code></pre>"),
("<code class=\"language-rust\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
("<code class=\"language-rust edition2015\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
("<code class=\"language-rust edition2018\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
];
for (src, should_be) in &inputs {
let got = add_playground_pre(
@@ -1059,13 +1205,13 @@ 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\">}\n</span></code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2018\"># #![allow(unused)]\n#fn main() {\nx()\n#}</code></pre>"),
("<code class=\"language-rust\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
("<code class=\"language-rust edition2015\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
("<code class=\"language-rust edition2018\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
];
for (src, should_be) in &inputs {
let got = add_playground_pre(
@@ -1083,13 +1229,13 @@ 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\">}\n</span></code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2021\"># #![allow(unused)]\n#fn main() {\nx()\n#}</code></pre>"),
("<code class=\"language-rust\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2021\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2021\">fn main() {}</code></pre>"),
("<code class=\"language-rust edition2015\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
("<code class=\"language-rust edition2018\">fn main() {}</code>",
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
];
for (src, should_be) in &inputs {
let got = add_playground_pre(
@@ -1103,4 +1249,66 @@ 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>",),
(
"<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

@@ -4,6 +4,8 @@ use std::path::Path;
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError, Renderable};
use crate::utils;
use log::{debug, trace};
use serde_json::json;
type StringMap = BTreeMap<String, String>;
@@ -125,7 +127,7 @@ fn render(
context.insert(
"path_to_root".to_owned(),
json!(utils::fs::path_to_root(&base_path)),
json!(utils::fs::path_to_root(base_path)),
);
chapter
@@ -146,15 +148,12 @@ fn render(
trace!("Render template");
_h.template()
.ok_or_else(|| RenderError::new("Error with the handlebars template"))
.and_then(|t| {
let mut local_rc = rc.clone();
let local_ctx = Context::wraps(&context)?;
t.render(r, &local_ctx, &mut local_rc, out)
})?;
Ok(())
let t = _h
.template()
.ok_or_else(|| RenderError::new("Error with the handlebars template"))?;
let local_ctx = Context::wraps(&context)?;
let mut local_rc = rc.clone();
t.render(r, &local_ctx, &mut local_rc, out)
}
pub fn previous(

View File

@@ -1,4 +1,5 @@
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError};
use log::trace;
pub fn theme_option(
h: &Helper<'_, '_>,

View File

@@ -117,35 +117,35 @@ impl HelperDef for RenderToc {
}
// Link
let path_exists = if let Some(path) =
item.get("path")
.and_then(|p| if p.is_empty() { None } else { Some(p) })
{
out.write("<a href=\"")?;
let path_exists: bool;
match item.get("path") {
Some(path) if !path.is_empty() => {
out.write("<a href=\"")?;
let tmp = Path::new(path)
.with_extension("html")
.to_str()
.unwrap()
// Hack for windows who tends to use `\` as separator instead of `/`
.replace('\\', "/");
let tmp = Path::new(item.get("path").expect("Error: path should be Some(_)"))
.with_extension("html")
.to_str()
.unwrap()
// Hack for windows who tends to use `\` as separator instead of `/`
.replace('\\', "/");
// Add link
out.write(&utils::fs::path_to_root(&current_path))?;
out.write(&tmp)?;
out.write("\"")?;
// 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\"")?;
}
if path == &current_path || is_first_chapter {
is_first_chapter = false;
out.write(" class=\"active\"")?;
out.write(">")?;
path_exists = true;
}
out.write(">")?;
true
} else {
out.write("<div>")?;
false
};
_ => {
out.write("<div>")?;
path_exists = false;
}
}
if !self.no_section_label {
// Section does not necessarily exist

View File

@@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet};
use std::path::Path;
use elasticlunr::{Index, IndexBuilder};
use once_cell::sync::Lazy;
use pulldown_cmark::*;
use crate::book::{Book, BookItem};
@@ -10,7 +11,7 @@ use crate::config::Search;
use crate::errors::*;
use crate::theme::searcher;
use crate::utils;
use log::{debug, warn};
use serde::Serialize;
const MAX_WORD_LENGTH_TO_INDEX: usize = 80;
@@ -137,9 +138,11 @@ fn render_item(
in_heading = true;
}
Event::End(Tag::Heading(i, ..)) if i as u32 <= max_section_depth => {
Event::End(Tag::Heading(i, id, _classes)) if i as u32 <= max_section_depth => {
in_heading = false;
section_id = Some(utils::unique_id_from_content(&heading, &mut id_counter));
section_id = id
.map(|id| id.to_string())
.or_else(|| Some(utils::unique_id_from_content(&heading, &mut id_counter)));
breadcrumbs.push(heading.clone());
}
Event::Start(Tag::FootnoteDefinition(name)) => {
@@ -266,21 +269,19 @@ fn write_to_json(index: Index, search_config: &Search, doc_urls: Vec<String>) ->
}
fn clean_html(html: &str) -> String {
lazy_static! {
static ref AMMONIA: ammonia::Builder<'static> = {
let mut clean_content = HashSet::new();
clean_content.insert("script");
clean_content.insert("style");
let mut builder = ammonia::Builder::new();
builder
.tags(HashSet::new())
.tag_attributes(HashMap::new())
.generic_attributes(HashSet::new())
.link_rel(None)
.allowed_classes(HashMap::new())
.clean_content_tags(clean_content);
builder
};
}
static AMMONIA: Lazy<ammonia::Builder<'static>> = Lazy::new(|| {
let mut clean_content = HashSet::new();
clean_content.insert("script");
clean_content.insert("style");
let mut builder = ammonia::Builder::new();
builder
.tags(HashSet::new())
.tag_attributes(HashMap::new())
.generic_attributes(HashSet::new())
.link_rel(None)
.allowed_classes(HashMap::new())
.clean_content_tags(clean_content);
builder
});
AMMONIA.clean(html).to_string()
}

View File

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

View File

@@ -27,6 +27,7 @@ use std::process::{Command, Stdio};
use crate::book::Book;
use crate::config::Config;
use crate::errors::*;
use log::{error, info, trace, warn};
use toml::Value;
use serde::{Deserialize, Serialize};

View File

@@ -4,14 +4,16 @@
window.onunload = function () { };
// Global variable, shared between modules
function playground_text(playground) {
function playground_text(playground, hidden = true) {
let code_block = playground.querySelector("code");
if (window.ace && code_block.classList.contains("editable")) {
let editor = window.ace.edit(code_block);
return editor.getValue();
} else {
} else if (hidden) {
return code_block.textContent;
} else {
return code_block.innerText;
}
}
@@ -66,7 +68,7 @@ function playground_text(playground) {
}
// updates the visibility of play button based on `no_run` class and
// used crates vs ones available on http://play.rust-lang.org
// used crates vs ones available on https://play.rust-lang.org
function update_play_button(pre_block, playground_crates) {
var play_button = pre_block.querySelector(".play-button");
@@ -166,7 +168,6 @@ function playground_text(playground) {
.filter(function (node) {return node.classList.contains("editable"); })
.forEach(function (block) { block.classList.remove('language-rust'); });
Array
code_nodes
.filter(function (node) {return !node.classList.contains("editable"); })
.forEach(function (block) { hljs.highlightBlock(block); });
@@ -178,7 +179,7 @@ function playground_text(playground) {
// even if highlighting doesn't apply
code_nodes.forEach(function (block) { block.classList.add('hljs'); });
Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) {
Array.from(document.querySelectorAll("code.hljs")).forEach(function (block) {
var lines = Array.from(block.querySelectorAll('.boring'));
// If no lines were hidden, return
@@ -300,6 +301,13 @@ function playground_text(playground) {
themePopup.querySelector("button#" + get_theme()).focus();
}
function updateThemeSelected() {
themePopup.querySelectorAll('.theme-selected').forEach(function (el) {
el.classList.remove('theme-selected');
});
themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected');
}
function hideThemes() {
themePopup.style.display = 'none';
themeToggleButton.setAttribute('aria-expanded', false);
@@ -338,7 +346,7 @@ function playground_text(playground) {
}
setTimeout(function () {
themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor;
themeColorMetaTag.content = getComputedStyle(document.documentElement).backgroundColor;
}, 1);
if (window.ace && window.editors) {
@@ -355,6 +363,7 @@ function playground_text(playground) {
html.classList.remove(previousTheme);
html.classList.add(theme);
updateThemeSelected();
}
// Set theme
@@ -432,7 +441,7 @@ function playground_text(playground) {
})();
(function sidebar() {
var html = document.querySelector("html");
var body = document.querySelector("body");
var sidebar = document.getElementById("sidebar");
var sidebarLinks = document.querySelectorAll('#sidebar a');
var sidebarToggleButton = document.getElementById("sidebar-toggle");
@@ -440,8 +449,8 @@ function playground_text(playground) {
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);
});
@@ -462,8 +471,8 @@ function playground_text(playground) {
});
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);
});
@@ -474,14 +483,14 @@ function playground_text(playground) {
// Toggle sidebar
sidebarToggleButton.addEventListener('click', function sidebarToggle() {
if (html.classList.contains("sidebar-hidden")) {
if (body.classList.contains("sidebar-hidden")) {
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")) {
} else if (body.classList.contains("sidebar-visible")) {
hideSidebar();
} else {
if (getComputedStyle(sidebar)['transform'] === 'none') {
@@ -497,14 +506,14 @@ function playground_text(playground) {
function initResize(e) {
window.addEventListener('mousemove', resize, false);
window.addEventListener('mouseup', stopResize, false);
html.classList.add('sidebar-resizing');
body.classList.add('sidebar-resizing');
}
function resize(e) {
var pos = (e.clientX - sidebar.offsetLeft);
if (pos < 20) {
hideSidebar();
} else {
if (html.classList.contains("sidebar-hidden")) {
if (body.classList.contains("sidebar-hidden")) {
showSidebar();
}
pos = Math.min(pos, window.innerWidth - 100);
@@ -513,7 +522,7 @@ function playground_text(playground) {
}
//on mouseup remove windows functions mousemove & mouseup
function stopResize(e) {
html.classList.remove('sidebar-resizing');
body.classList.remove('sidebar-resizing');
window.removeEventListener('mousemove', resize, false);
window.removeEventListener('mouseup', stopResize, false);
}
@@ -542,33 +551,41 @@ function playground_text(playground) {
firstContact = null;
}
}, { passive: true });
// Scroll sidebar to current active section
var activeSection = document.getElementById("sidebar").querySelector(".active");
if (activeSection) {
// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
activeSection.scrollIntoView({ block: 'center' });
}
})();
(function chapterNavigation() {
document.addEventListener('keydown', function (e) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
if (window.search && window.search.hasFocus()) { return; }
var html = document.querySelector('html');
function next() {
var nextButton = document.querySelector('.nav-chapters.next');
if (nextButton) {
window.location.href = nextButton.href;
}
}
function prev() {
var previousButton = document.querySelector('.nav-chapters.previous');
if (previousButton) {
window.location.href = previousButton.href;
}
}
switch (e.key) {
case 'ArrowRight':
e.preventDefault();
var nextButton = document.querySelector('.nav-chapters.next');
if (nextButton) {
window.location.href = nextButton.href;
if (html.dir == 'rtl') {
prev();
} else {
next();
}
break;
case 'ArrowLeft':
e.preventDefault();
var previousButton = document.querySelector('.nav-chapters.previous');
if (previousButton) {
window.location.href = previousButton.href;
if (html.dir == 'rtl') {
next();
} else {
prev();
}
break;
}
@@ -592,7 +609,7 @@ function playground_text(playground) {
text: function (trigger) {
hideTooltip(trigger);
let playground = trigger.closest("pre");
return playground_text(playground);
return playground_text(playground, false);
}
});
@@ -667,13 +684,14 @@ function playground_text(playground) {
}, { passive: true });
})();
(function controllBorder() {
menu.classList.remove('bordered');
document.addEventListener('scroll', function () {
function updateBorder() {
if (menu.offsetTop === 0) {
menu.classList.remove('bordered');
} else {
menu.classList.add('bordered');
}
}, { passive: true });
}
updateBorder();
document.addEventListener('scroll', updateBorder, { passive: true });
})();
})();

View File

@@ -2,12 +2,6 @@
@import 'variables.css';
::-webkit-scrollbar {
background: var(--bg);
}
::-webkit-scrollbar-thumb {
background: var(--scrollbar);
}
html {
scrollbar-color: var(--scrollbar) var(--bg);
}
@@ -18,6 +12,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,9 +37,9 @@ 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,
@@ -49,7 +56,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 +93,7 @@ a > .hljs {
display: flex;
margin: 0 5px;
}
.no-js .left-buttons {
.no-js .left-buttons button {
display: none;
}
@@ -153,7 +160,7 @@ a > .hljs {
}
.nav-wrapper {
margin-top: 50px;
margin-block-start: 50px;
display: none;
}
@@ -166,23 +173,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,7 +247,7 @@ pre > .buttons :hover {
background-color: var(--theme-hover);
}
pre > .buttons i {
margin-left: 8px;
margin-inline-start: 8px;
}
pre > .buttons button {
cursor: inherit;
@@ -252,7 +270,7 @@ pre > .buttons button {
padding: 0.3rem 1rem;
}
}
code {
pre > code {
padding: 1rem;
}
@@ -266,7 +284,7 @@ code {
}
pre > .result {
margin-top: 10px;
margin-block-start: 10px;
}
/* Search */
@@ -277,8 +295,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 +314,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 +340,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 +369,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,12 +395,14 @@ ul#searchresults span.teaser em {
background-color: var(--sidebar-bg);
color: var(--sidebar-fg);
}
[dir=rtl] .sidebar { left: unset; right: 0; }
.sidebar-resizing {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.no-js .sidebar,
.js:not(.sidebar-resizing) .sidebar {
transition: transform 0.3s; /* Animation: slide away */
}
@@ -391,12 +426,18 @@ ul#searchresults span.teaser em {
top: 0;
bottom: 0;
}
[dir=rtl] .sidebar .sidebar-resize-handle { right: unset; left: 0; }
.js .sidebar .sidebar-resize-handle {
cursor: col-resize;
width: 5px;
}
.sidebar-hidden .sidebar {
/* sidebar-hidden */
#sidebar-toggle-anchor:not(:checked) ~ .sidebar {
transform: translateX(calc(0px - var(--sidebar-width)));
z-index: -1;
}
[dir=rtl] #sidebar-toggle-anchor:not(:checked) ~ .sidebar {
transform: translateX(var(--sidebar-width));
}
.sidebar::-webkit-scrollbar {
background: var(--sidebar-bg);
@@ -405,19 +446,26 @@ ul#searchresults span.teaser em {
background: var(--scrollbar);
}
.sidebar-visible .page-wrapper {
/* sidebar-visible */
#sidebar-toggle-anchor:checked ~ .page-wrapper {
transform: translateX(var(--sidebar-width));
}
[dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper {
transform: translateX(calc(0px - var(--sidebar-width)));
}
@media only screen and (min-width: 620px) {
.sidebar-visible .page-wrapper {
#sidebar-toggle-anchor:checked ~ .page-wrapper {
transform: none;
margin-inline-start: var(--sidebar-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 +495,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 +512,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 +535,7 @@ ul#searchresults span.teaser em {
.section {
list-style: none outside none;
padding-left: 20px;
padding-inline-start: 20px;
line-height: 1.9em;
}
@@ -507,7 +555,10 @@ ul#searchresults span.teaser em {
padding: 0;
list-style: none;
display: none;
/* Don't let the children's background extend past the rounded corners. */
overflow: hidden;
}
[dir=rtl] .theme-popup { left: unset; right: 10px; }
.theme-popup .default {
color: var(--icons);
}
@@ -515,10 +566,10 @@ ul#searchresults span.teaser em {
width: 100%;
border: 0;
margin: 0;
padding: 2px 10px;
padding: 2px 20px;
line-height: 25px;
white-space: nowrap;
text-align: left;
text-align: start;
cursor: pointer;
color: inherit;
background: inherit;
@@ -527,8 +578,10 @@ ul#searchresults span.teaser em {
.theme-popup .theme:hover {
background-color: var(--theme-hover);
}
.theme-popup .theme:hover:first-child,
.theme-popup .theme:hover:last-child {
border-top-left-radius: inherit;
border-top-right-radius: inherit;
.theme-selected::before {
display: inline-block;
content: "✓";
margin-inline-start: -14px;
width: 14px;
}

View File

@@ -5,6 +5,7 @@
:root {
/* Browser default font-size is 16px, this way 1 rem = 10px */
font-size: 62.5%;
color-scheme: var(--color-scheme);
}
html {
@@ -22,8 +23,9 @@ body {
}
code {
font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important;
font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */
font-family: var(--mono-font) !important;
font-size: var(--code-font-size);
direction: ltr !important;
}
/* make long words/inline code not x overflow */
@@ -47,13 +49,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 +66,7 @@ h5:target::before,
h6:target::before {
display: inline-block;
content: "»";
margin-left: -30px;
margin-inline-start: -30px;
width: 30px;
}
@@ -73,28 +75,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,14 +152,49 @@ 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 {
background-color: var(--table-border-color);
border-radius: 4px;
border: solid 1px var(--theme-popup-border);
box-shadow: inset 0 -1px 0 var(--theme-hover);
display: inline-block;
font-size: var(--code-font-size);
font-family: var(--mono-font);
line-height: 10px;
padding: 4px 5px;
vertical-align: middle;
}
:not(.footnote-definition) + .footnote-definition,
.footnote-definition + :not(.footnote-definition) {
margin-top: 2em;
margin-block-start: 2em;
}
.footnote-definition {
font-size: 0.9em;

View File

@@ -8,7 +8,7 @@
#page-wrapper.page-wrapper {
transform: none;
margin-left: 0px;
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

@@ -6,6 +6,8 @@
--page-padding: 15px;
--content-max-width: 750px;
--menu-bar-height: 50px;
--mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace;
--code-font-size: 0.875em /* please adjust the ace font size accordingly in editor.js */
}
/* Themes */
@@ -36,6 +38,8 @@
--quote-bg: hsl(226, 15%, 17%);
--quote-border: hsl(226, 15%, 22%);
--warning-border: #ff8e00;
--table-border-color: hsl(210, 25%, 13%);
--table-header-bg: hsl(210, 25%, 28%);
--table-alternate-bg: hsl(210, 25%, 11%);
@@ -48,6 +52,8 @@
--searchresults-border-color: #888;
--searchresults-li-bg: #252932;
--search-mark-bg: #e3b171;
--color-scheme: dark;
}
.coal {
@@ -76,6 +82,8 @@
--quote-bg: hsl(234, 21%, 18%);
--quote-border: hsl(234, 21%, 23%);
--warning-border: #ff8e00;
--table-border-color: hsl(200, 7%, 13%);
--table-header-bg: hsl(200, 7%, 28%);
--table-alternate-bg: hsl(200, 7%, 11%);
@@ -88,6 +96,8 @@
--searchresults-border-color: #98a3ad;
--searchresults-li-bg: #2b2b2f;
--search-mark-bg: #355c7d;
--color-scheme: dark;
}
.light {
@@ -116,6 +126,8 @@
--quote-bg: hsl(197, 37%, 96%);
--quote-border: hsl(197, 37%, 91%);
--warning-border: #ff8e00;
--table-border-color: hsl(0, 0%, 95%);
--table-header-bg: hsl(0, 0%, 80%);
--table-alternate-bg: hsl(0, 0%, 97%);
@@ -128,6 +140,8 @@
--searchresults-border-color: #888;
--searchresults-li-bg: #e4f2fe;
--search-mark-bg: #a2cff5;
--color-scheme: light;
}
.navy {
@@ -156,6 +170,8 @@
--quote-bg: hsl(226, 15%, 17%);
--quote-border: hsl(226, 15%, 22%);
--warning-border: #ff8e00;
--table-border-color: hsl(226, 23%, 16%);
--table-header-bg: hsl(226, 23%, 31%);
--table-alternate-bg: hsl(226, 23%, 14%);
@@ -168,6 +184,8 @@
--searchresults-border-color: #5c5c68;
--searchresults-li-bg: #242430;
--search-mark-bg: #a2cff5;
--color-scheme: dark;
}
.rust {
@@ -196,6 +214,8 @@
--quote-bg: hsl(60, 5%, 75%);
--quote-border: hsl(60, 5%, 70%);
--warning-border: #ff8e00;
--table-border-color: hsl(60, 9%, 82%);
--table-header-bg: #b3a497;
--table-alternate-bg: hsl(60, 9%, 84%);
@@ -208,6 +228,8 @@
--searchresults-border-color: #888;
--searchresults-li-bg: #dec2a2;
--search-mark-bg: #e69f67;
--color-scheme: light;
}
@media (prefers-color-scheme: dark) {
@@ -237,6 +259,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%);

View File

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

View File

@@ -9,10 +9,10 @@ pub mod searcher;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::path::{Path, PathBuf};
use crate::errors::*;
use log::warn;
pub static INDEX: &[u8] = include_bytes!("index.hbs");
pub static HEAD: &[u8] = include_bytes!("head.hbs");
pub static REDIRECT: &[u8] = include_bytes!("redirect.hbs");
@@ -54,6 +54,8 @@ pub struct Theme {
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>,
@@ -104,7 +106,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 +123,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();
@@ -153,6 +178,8 @@ impl Default for Theme {
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(),
@@ -209,10 +236,10 @@ mod tests {
"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 +250,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 {
@@ -240,6 +268,8 @@ mod tests {
general_css: Vec::new(),
print_css: Vec::new(),
variables_css: Vec::new(),
fonts_css: Some(Vec::new()),
font_files: Vec::new(),
favicon_png: Some(Vec::new()),
favicon_svg: Some(Vec::new()),
js: Vec::new(),

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
use crate::errors::*;
use log::{debug, trace};
use std::convert::Into;
use std::fs::{self, File};
use std::io::Write;
@@ -37,7 +38,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()
@@ -166,7 +166,7 @@ pub fn copy_files_except_ext(
.expect("a file should have a file name...")
)
);
fs::copy(
copy(
entry.path(),
&to.join(
entry
@@ -180,6 +180,62 @@ pub fn copy_files_except_ext(
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()
@@ -210,39 +266,36 @@ mod tests {
};
// Create a couple of files
if let Err(err) = fs::File::create(&tmp.path().join("file.txt")) {
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")) {
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")) {
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")) {
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")) {
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")) {
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")) {
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"),
) {
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")) {
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")) {
if let Err(err) = fs::create_dir(tmp.path().join("output/sub_dir_exists")) {
panic!("Could not create output/sub_dir_exists: {}", err);
}
@@ -253,22 +306,22 @@ mod tests {
}
// Check if the correct files where created
if !(&tmp.path().join("output/file.txt")).exists() {
if !tmp.path().join("output/file.txt").exists() {
panic!("output/file.txt should exist")
}
if (&tmp.path().join("output/file.md")).exists() {
if tmp.path().join("output/file.md").exists() {
panic!("output/file.md should not exist")
}
if !(&tmp.path().join("output/file.png")).exists() {
if !tmp.path().join("output/file.png").exists() {
panic!("output/file.png should exist")
}
if !(&tmp.path().join("output/sub_dir/file.png")).exists() {
if !tmp.path().join("output/sub_dir/file.png").exists() {
panic!("output/sub_dir/file.png should exist")
}
if !(&tmp.path().join("output/sub_dir_exists/file.txt")).exists() {
if !tmp.path().join("output/sub_dir_exists/file.txt").exists() {
panic!("output/sub_dir/file.png should exist")
}
if !(&tmp.path().join("output/symlink.png")).exists() {
if !tmp.path().join("output/symlink.png").exists() {
panic!("output/symlink.png should exist")
}
}

View File

@@ -4,9 +4,10 @@ pub mod fs;
mod string;
pub(crate) mod toml_ext;
use crate::errors::Error;
use regex::Regex;
use log::error;
use once_cell::sync::Lazy;
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag};
use regex::Regex;
use std::borrow::Cow;
use std::collections::HashMap;
@@ -20,9 +21,7 @@ pub use self::string::{
/// Replaces multiple consecutive whitespace characters with a single space character.
pub fn collapse_whitespace(text: &str) -> Cow<'_, str> {
lazy_static! {
static ref RE: Regex = Regex::new(r"\s\s+").unwrap();
}
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\s\s+").unwrap());
RE.replace_all(text, " ")
}
@@ -51,9 +50,7 @@ pub fn id_from_content(content: &str) -> String {
let mut content = content.to_string();
// Skip any tags or html-encoded stuff
lazy_static! {
static ref HTML: Regex = Regex::new(r"(<.*?>)").unwrap();
}
static HTML: Lazy<Regex> = Lazy::new(|| Regex::new(r"(<.*?>)").unwrap());
content = HTML.replace_all(&content, "").into();
const REPL_SUB: &[&str] = &["&lt;", "&gt;", "&amp;", "&#39;", "&quot;"];
for sub in REPL_SUB {
@@ -96,10 +93,9 @@ pub fn unique_id_from_content(content: &str, id_counter: &mut HashMap<String, us
/// None. Ideally, print page links would link to anchors on the print page,
/// but that is very difficult.
fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
lazy_static! {
static ref SCHEME_LINK: Regex = Regex::new(r"^[a-z][a-z0-9+.-]*:").unwrap();
static ref MD_LINK: Regex = Regex::new(r"(?P<link>.*)\.md(?P<anchor>#.*)?").unwrap();
}
static SCHEME_LINK: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[a-z][a-z0-9+.-]*:").unwrap());
static MD_LINK: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?P<link>.*)\.md(?P<anchor>#.*)?").unwrap());
fn fix<'a>(dest: CowStr<'a>, path: Option<&Path>) -> CowStr<'a> {
if dest.starts_with('#') {
@@ -152,10 +148,8 @@ fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
// There are dozens of HTML tags/attributes that contain paths, so
// feel free to add more tags if desired; these are the only ones I
// care about right now.
lazy_static! {
static ref HTML_LINK: Regex =
Regex::new(r#"(<(?:a|img) [^>]*?(?:src|href)=")([^"]+?)""#).unwrap();
}
static HTML_LINK: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"(<(?:a|img) [^>]*?(?:src|href)=")([^"]+?)""#).unwrap());
HTML_LINK
.replace_all(&html, |caps: &regex::Captures<'_>| {
@@ -189,6 +183,7 @@ pub fn new_cmark_parser(text: &str, curly_quotes: bool) -> Parser<'_, '_> {
opts.insert(Options::ENABLE_FOOTNOTES);
opts.insert(Options::ENABLE_STRIKETHROUGH);
opts.insert(Options::ENABLE_TASKLISTS);
opts.insert(Options::ENABLE_HEADING_ATTRIBUTES);
if curly_quotes {
opts.insert(Options::ENABLE_SMART_PUNCTUATION);
}

View File

@@ -1,3 +1,4 @@
use once_cell::sync::Lazy;
use regex::Regex;
use std::ops::Bound::{Excluded, Included, Unbounded};
use std::ops::RangeBounds;
@@ -23,10 +24,10 @@ pub fn take_lines<R: RangeBounds<usize>>(s: &str, range: R) -> String {
}
}
lazy_static! {
static ref ANCHOR_START: Regex = Regex::new(r"ANCHOR:\s*(?P<anchor_name>[\w_-]+)").unwrap();
static ref ANCHOR_END: Regex = Regex::new(r"ANCHOR_END:\s*(?P<anchor_name>[\w_-]+)").unwrap();
}
static ANCHOR_START: Lazy<Regex> =
Lazy::new(|| Regex::new(r"ANCHOR:\s*(?P<anchor_name>[\w_-]+)").unwrap());
static ANCHOR_END: Lazy<Regex> =
Lazy::new(|| Regex::new(r"ANCHOR_END:\s*(?P<anchor_name>[\w_-]+)").unwrap());
/// Take anchored lines from a string.
/// Lines containing anchor are ignored.

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

@@ -27,9 +27,11 @@ fn main(){
}
```
<kbd>Ctrl</kbd> + <kbd>S</kbd> saves a file.
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)
---
@@ -41,6 +43,7 @@ A random image sprinkled in between
2. be
3. `put`
4. here?
5. **<kbd>Ctrl</kbd> + <kbd>S</kbd> saves a file.**
| col1 | col2 | col 3 | col 4 | col 5 | col 6 |
| ---- | ---- | ----- | ----- | ----- | ----- |

View File

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

View File

@@ -57,7 +57,7 @@ _start:
## bash
```
```bash
#!/bin/bash
###### CONFIG

View File

@@ -90,7 +90,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();

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

@@ -0,0 +1,24 @@
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());
}

View File

@@ -1,3 +1,4 @@
mod build;
mod cmd;
mod init;
mod test;

View File

@@ -10,11 +10,11 @@ fn mdbook_cli_can_correctly_test_a_passing_book() {
let mut cmd = mdbook_cmd();
cmd.arg("test").current_dir(temp.path());
cmd.assert().success()
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]README.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]intro.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]index.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]nested.md""##).unwrap())
.stderr(predicates::str::is_match(r##"rustdoc returned an error:\n\n"##).unwrap().not())
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "README.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "intro.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]index.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]nested.md""##).unwrap())
.stderr(predicates::str::is_match(r##"returned an error:\n\n"##).unwrap().not())
.stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap().not());
}
@@ -25,10 +25,10 @@ fn mdbook_cli_detects_book_with_failing_tests() {
let mut cmd = mdbook_cmd();
cmd.arg("test").current_dir(temp.path());
cmd.assert().failure()
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]README.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]intro.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]index.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]nested.md""##).unwrap())
.stderr(predicates::str::is_match(r##"rustdoc returned an error:\n\n"##).unwrap())
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "README.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "intro.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]index.md""##).unwrap())
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]nested.md""##).unwrap())
.stderr(predicates::str::is_match(r##"returned an error:\n\n"##).unwrap())
.stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap());
}

View File

@@ -112,12 +112,12 @@ fn recursive_copy<A: AsRef<Path>, B: AsRef<Path>>(from: A, to: B) -> Result<()>
let from = from.as_ref();
let to = to.as_ref();
for entry in WalkDir::new(&from) {
for entry in WalkDir::new(from) {
let entry = entry.with_context(|| "Unable to inspect directory entry")?;
let original_location = entry.path();
let relative = original_location
.strip_prefix(&from)
.strip_prefix(from)
.expect("`original_location` is inside the `from` directory");
let new_location = to.join(relative);
@@ -126,7 +126,7 @@ fn recursive_copy<A: AsRef<Path>, B: AsRef<Path>>(from: A, to: B) -> Result<()>
fs::create_dir_all(parent).with_context(|| "Couldn't create directory")?;
}
fs::copy(&original_location, &new_location)
fs::copy(original_location, &new_location)
.with_context(|| "Unable to copy file contents")?;
}
}

View File

@@ -14,6 +14,7 @@
- [Unicode](first/unicode.md)
- [No Headers](first/no-headers.md)
- [Duplicate Headers](first/duplicate-headers.md)
- [Heading Attributes](first/heading-attributes.md)
- [Second Chapter](second.md)
- [Nested Chapter](second/nested.md)

View File

@@ -0,0 +1,5 @@
# Heading Attributes {#attrs}
## Heading with classes {.class1 .class2}
## Heading with id and classes {#both .class1 .class2}

View File

@@ -1,5 +1,6 @@
use mdbook::config::Config;
use mdbook::MDBook;
use pretty_assertions::assert_eq;
use std::fs;
use std::fs::File;
use std::io::prelude::*;
@@ -95,7 +96,7 @@ fn run_mdbook_init_with_custom_book_and_src_locations() {
let contents = fs::read_to_string(temp.path().join("book.toml")).unwrap();
assert_eq!(
contents,
"[book]\nauthors = []\nlanguage = \"en\"\nmultilingual = false\nsrc = \"in\"\n\n[build]\nbuild-dir = \"out\"\ncreate-missing = true\nuse-default-preprocessors = true\n"
"[book]\nauthors = []\nlanguage = \"en\"\nmultilingual = false\nsrc = \"in\"\n\n[build]\nbuild-dir = \"out\"\ncreate-missing = true\nextra-watch-dirs = []\nuse-default-preprocessors = true\n"
);
}
@@ -121,6 +122,20 @@ fn copy_theme() {
"css/variables.css",
"favicon.png",
"favicon.svg",
"fonts/OPEN-SANS-LICENSE.txt",
"fonts/SOURCE-CODE-PRO-LICENSE.txt",
"fonts/fonts.css",
"fonts/open-sans-v17-all-charsets-300.woff2",
"fonts/open-sans-v17-all-charsets-300italic.woff2",
"fonts/open-sans-v17-all-charsets-600.woff2",
"fonts/open-sans-v17-all-charsets-600italic.woff2",
"fonts/open-sans-v17-all-charsets-700.woff2",
"fonts/open-sans-v17-all-charsets-700italic.woff2",
"fonts/open-sans-v17-all-charsets-800.woff2",
"fonts/open-sans-v17-all-charsets-800italic.woff2",
"fonts/open-sans-v17-all-charsets-italic.woff2",
"fonts/open-sans-v17-all-charsets-regular.woff2",
"fonts/source-code-pro-v11-all-charsets-500.woff2",
"highlight.css",
"highlight.js",
"index.hbs",

View File

@@ -1,6 +1,3 @@
#[macro_use]
extern crate pretty_assertions;
mod dummy_book;
use crate::dummy_book::{assert_contains_strings, assert_doesnt_contain_strings, DummyBook};
@@ -10,6 +7,7 @@ use mdbook::config::Config;
use mdbook::errors::*;
use mdbook::utils::fs::write_file;
use mdbook::MDBook;
use pretty_assertions::assert_eq;
use select::document::Document;
use select::predicate::{Class, Name, Predicate};
use std::collections::HashMap;
@@ -37,6 +35,7 @@ const TOC_SECOND_LEVEL: &[&str] = &[
"1.5. Unicode",
"1.6. No Headers",
"1.7. Duplicate Headers",
"1.8. Heading Attributes",
"2.1. Nested Chapter",
];
@@ -277,7 +276,7 @@ fn root_index_html() -> Result<Document> {
.with_context(|| "Book building failed")?;
let index_page = temp.path().join("book").join("index.html");
let html = fs::read_to_string(&index_page).with_context(|| "Unable to read index.html")?;
let html = fs::read_to_string(index_page).with_context(|| "Unable to read index.html")?;
Ok(Document::from(html.as_str()))
}
@@ -414,7 +413,7 @@ fn recursive_includes_are_capped() {
let content = &["Around the world, around the world
Around the world, around the world
Around the world, around the world"];
assert_contains_strings(&recursive, content);
assert_contains_strings(recursive, content);
}
#[test]
@@ -464,7 +463,7 @@ fn by_default_mdbook_use_index_preprocessor_to_convert_readme_to_index() {
let second_index = temp.path().join("book").join("second").join("index.html");
let unexpected_strings = vec!["Second README"];
assert_doesnt_contain_strings(&second_index, &unexpected_strings);
assert_doesnt_contain_strings(second_index, &unexpected_strings);
}
#[test]
@@ -630,10 +629,8 @@ fn edit_url_has_configured_src_dir_edit_url() {
}
fn remove_absolute_components(path: &Path) -> impl Iterator<Item = Component> + '_ {
path.components().skip_while(|c| match c {
Component::Prefix(_) | Component::RootDir => true,
_ => false,
})
path.components()
.skip_while(|c| matches!(c, Component::Prefix(_) | Component::RootDir))
}
/// Checks formatting of summary names with inline elements.
@@ -758,6 +755,7 @@ mod search {
let no_headers = get_doc_ref("first/no-headers.html");
let duplicate_headers_1 = get_doc_ref("first/duplicate-headers.html#header-text-1");
let conclusion = get_doc_ref("conclusion.html#conclusion");
let heading_attrs = get_doc_ref("first/heading-attributes.html#both");
let bodyidx = &index["index"]["index"]["body"]["root"];
let textidx = &bodyidx["t"]["e"]["x"]["t"];
@@ -770,7 +768,7 @@ mod search {
assert_eq!(docs[&some_section]["body"], "");
assert_eq!(
docs[&summary]["body"],
"Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode No Headers Duplicate Headers Second Chapter Nested Chapter Conclusion"
"Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode No Headers Duplicate Headers Heading Attributes Second Chapter Nested Chapter Conclusion"
);
assert_eq!(
docs[&summary]["breadcrumbs"],
@@ -789,6 +787,10 @@ mod search {
docs[&no_headers]["body"],
"Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex."
);
assert_eq!(
docs[&heading_attrs]["breadcrumbs"],
"First Chapter » Heading Attributes » Heading with id and classes"
);
}
// Setting this to `true` may cause issues with `cargo watch`,
@@ -805,7 +807,7 @@ mod search {
let src = read_book_index(temp.path());
let dest = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/searchindex_fixture.json");
let dest = File::create(&dest).unwrap();
let dest = File::create(dest).unwrap();
serde_json::to_writer_pretty(dest, &src).unwrap();
src
@@ -842,3 +844,127 @@ mod search {
}
}
}
#[test]
fn custom_fonts() {
// Tests to ensure custom fonts are copied as expected.
let builtin_fonts = [
"OPEN-SANS-LICENSE.txt",
"SOURCE-CODE-PRO-LICENSE.txt",
"fonts.css",
"open-sans-v17-all-charsets-300.woff2",
"open-sans-v17-all-charsets-300italic.woff2",
"open-sans-v17-all-charsets-600.woff2",
"open-sans-v17-all-charsets-600italic.woff2",
"open-sans-v17-all-charsets-700.woff2",
"open-sans-v17-all-charsets-700italic.woff2",
"open-sans-v17-all-charsets-800.woff2",
"open-sans-v17-all-charsets-800italic.woff2",
"open-sans-v17-all-charsets-italic.woff2",
"open-sans-v17-all-charsets-regular.woff2",
"source-code-pro-v11-all-charsets-500.woff2",
];
let actual_files = |path: &Path| -> Vec<String> {
let mut actual: Vec<_> = path
.read_dir()
.unwrap()
.map(|entry| entry.unwrap().file_name().into_string().unwrap())
.collect();
actual.sort();
actual
};
let has_fonts_css = |path: &Path| -> bool {
let contents = fs::read_to_string(path.join("book/index.html")).unwrap();
contents.contains("fonts/fonts.css")
};
// No theme:
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
let p = temp.path();
MDBook::init(p).build().unwrap();
MDBook::load(p).unwrap().build().unwrap();
assert_eq!(actual_files(&p.join("book/fonts")), &builtin_fonts);
assert!(has_fonts_css(p));
// Full theme.
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
let p = temp.path();
MDBook::init(p).copy_theme(true).build().unwrap();
assert_eq!(actual_files(&p.join("theme/fonts")), &builtin_fonts);
MDBook::load(p).unwrap().build().unwrap();
assert_eq!(actual_files(&p.join("book/fonts")), &builtin_fonts);
assert!(has_fonts_css(p));
// Mixed with copy-fonts=true
// Should ignore the copy-fonts setting since the user has provided their own fonts.css.
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
let p = temp.path();
MDBook::init(p).build().unwrap();
write_file(&p.join("theme/fonts"), "fonts.css", b"/*custom*/").unwrap();
write_file(&p.join("theme/fonts"), "myfont.woff", b"").unwrap();
MDBook::load(p).unwrap().build().unwrap();
assert!(has_fonts_css(p));
assert_eq!(
actual_files(&p.join("book/fonts")),
["fonts.css", "myfont.woff"]
);
// copy-fonts=false, no theme
// This should generate a deprecation warning.
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
let p = temp.path();
MDBook::init(p).build().unwrap();
let config = Config::from_str("output.html.copy-fonts = false").unwrap();
MDBook::load_with_config(p, config)
.unwrap()
.build()
.unwrap();
assert!(!has_fonts_css(p));
assert!(!p.join("book/fonts").exists());
// copy-fonts=false with empty fonts.css
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
let p = temp.path();
MDBook::init(p).build().unwrap();
write_file(&p.join("theme/fonts"), "fonts.css", b"").unwrap();
let config = Config::from_str("output.html.copy-fonts = false").unwrap();
MDBook::load_with_config(p, config)
.unwrap()
.build()
.unwrap();
assert!(!has_fonts_css(p));
assert!(!p.join("book/fonts").exists());
// copy-fonts=false with fonts theme
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
let p = temp.path();
MDBook::init(p).build().unwrap();
write_file(&p.join("theme/fonts"), "fonts.css", b"/*custom*/").unwrap();
write_file(&p.join("theme/fonts"), "myfont.woff", b"").unwrap();
let config = Config::from_str("output.html.copy-fonts = false").unwrap();
MDBook::load_with_config(p, config)
.unwrap()
.build()
.unwrap();
assert!(has_fonts_css(p));
assert_eq!(
actual_files(&p.join("book/fonts")),
&["fonts.css", "myfont.woff"]
);
}
#[test]
fn custom_header_attributes() {
let temp = DummyBook::new().build().unwrap();
let md = MDBook::load(temp.path()).unwrap();
md.build().unwrap();
let contents = temp.path().join("book/first/heading-attributes.html");
let summary_strings = &[
r##"<h1 id="attrs"><a class="header" href="#attrs">Heading Attributes</a></h1>"##,
r##"<h2 id="heading-with-classes" class="class1 class2"><a class="header" href="#heading-with-classes">Heading with classes</a></h2>"##,
r##"<h2 id="both" class="class1 class2"><a class="header" href="#both">Heading with id and classes</a></h2>"##,
];
assert_contains_strings(&contents, summary_strings);
}

File diff suppressed because it is too large Load Diff

View File

@@ -24,3 +24,24 @@ fn mdbook_detects_book_with_failing_tests() {
assert!(md.test(vec![]).is_err());
}
#[test]
fn mdbook_test_chapter() {
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
let mut md = MDBook::load(temp.path()).unwrap();
let result = md.test_chapter(vec![], Some("Introduction"));
assert!(
result.is_ok(),
"test_chapter failed with {}",
result.err().unwrap()
);
}
#[test]
fn mdbook_test_chapter_not_found() {
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
let mut md = MDBook::load(temp.path()).unwrap();
assert!(md.test_chapter(vec![], Some("Bogus Chapter Name")).is_err());
}

View File

@@ -1,12 +1,23 @@
# This will allow users to self assign, and/or drop assignment
[assign]
# Allows @rustbot ready, review, author, or blocked
[shortcut]
[relabel]
allow-unauthenticated = [
# For Issue areas
# For Issue areas
"A-*",
"E-Help-Wanted",
"Bug",
"Feature-Request"
]
# Categories
"C-*",
# Commands
"Command-*",
# Status
"S-*",
"regression",
"Breaking Change",
"msrv-bump",
]
[autolabel."S-waiting-on-review"]
new_pr = true