Compare commits

...

168 Commits

Author SHA1 Message Date
Eric Huss
760c9b0767 Bump quote and failure.
quote 1.0.2 was yanked, update failure to stay compatible.
2020-03-19 12:48:54 -07:00
Eric Huss
6016e12b90 Release 0.3.6. 2020-03-19 12:43:22 -07:00
Eric Huss
88684d843d Merge pull request #1167 from dalance/fix_summary_comment
Fix SUMMARY's parse_numbered with comment
2020-03-17 21:39:25 -07:00
dalance
b82562fe8a Fix SUMMARY's parse_numbered with comment 2020-03-18 12:28:27 +09:00
Eric Huss
44c3213f5d Merge pull request #1130 from sunng87/feature/handlebars-3.0
Update handlebars to 3.0
2020-03-08 18:54:39 -07:00
Dylan DPC
fd56a53e76 ui: improve menu folding (#989)
* ui: improve menu folding

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

* refactor: use a variable for the menu bar height

* Fix menu scroll jittering, remove hover folding smoothness

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

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

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

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

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

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

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

* Add Rust edition support to rustdoc

* Run rustfmt

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

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

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

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

* Auto enable dark mode on no-js

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

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

* test: update rendered output css selector

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

* renderer: fold according to configs

* doc: add `output.html.fold`

* refactor: tidy fold config

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

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

* html -> html_config

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

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

* Add some tests for include anchors

* Include parts of Rust files and hide the rest

Fixes #618.

* Increase min supported Rust version to 1.35

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

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

* Ran cargo fmt.

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

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

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

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

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

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

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

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

Links can now be more than only playpen links

* Rename a field to match the enum type it holds

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

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

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

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

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

2
.gitignore vendored
View File

@@ -9,3 +9,5 @@ book-example/book
.vscode
tests/dummy_book/book/
# Ignore Jetbrains specific files.
.idea/

View File

@@ -1,82 +0,0 @@
language: rust
cache:
directories:
- "$HOME/.cargo"
- "$HOME/.cache/sccache"
before_cache:
- rm -rf "$HOME/.cargo/registry"
env:
global:
- CRATE_NAME=mdbook
matrix:
include:
- rust: stable
env: TARGET=x86_64-unknown-linux-gnu
- rust: beta
env: TARGET=x86_64-unknown-linux-gnu
- rust: nightly
env: TARGET=x86_64-unknown-linux-gnu
- rust: stable
os: osx
env: TARGET=x86_64-apple-darwin
before_install:
- export SCCACHE_VER=0.2.8 RUSTC_WRAPPER=sccache
- case "$TRAVIS_OS_NAME" in
linux )
cd /tmp
&& travis_retry curl -sSL "https://github.com/mozilla/sccache/releases/download/${SCCACHE_VER}/sccache-${SCCACHE_VER}-x86_64-unknown-linux-musl.tar.gz" | tar xzf -
&& sudo mv "sccache-${SCCACHE_VER}-x86_64-unknown-linux-musl/sccache" /usr/local/bin/sccache;
;;
osx )
cd "${TMPDIR}"
&& travis_retry curl -sSL "https://github.com/mozilla/sccache/releases/download/${SCCACHE_VER}/sccache-${SCCACHE_VER}-x86_64-apple-darwin.tar.gz" | tar xzf -
&& sudo mv "sccache-${SCCACHE_VER}-x86_64-apple-darwin/sccache" /usr/local/bin/sccache;
;;
* ) unset RUSTC_WRAPPER;;
esac
- cd "$TRAVIS_BUILD_DIR"
script:
- cargo test --all
- cargo test --all --no-default-features
- if [ "$TARGET" = x86_64-unknown-linux-gnu ] && [ "$TRAVIS_RUST_VERSION" = stable ]; then
rustup component add rustfmt;
rustfmt -vV;
cargo fmt --all -- --check;
fi
before_deploy:
- cargo run -- build book-example
- sh ci/before_deploy.sh
deploy:
- provider: releases
api_key: "$GITHUB_TOKEN"
file_glob: true
file: "$CRATE_NAME-$TRAVIS_TAG-$TARGET.*"
on:
condition: "$TRAVIS_RUST_VERSION = stable"
tags: true
skip_cleanup: true
- provider: pages
local_dir: book-example/book
skip_cleanup: true
github_token: "$GITHUB_TOKEN"
keep_history: true
on:
condition: $TRAVIS_OS_NAME = "linux" && $TRAVIS_RUST_VERSION = "stable"
tags: true
branches:
only:
- master
- /^v\d+\.\d+\.\d+.*$/
notifications:
email:
on_success: never

View File

@@ -1,105 +1,273 @@
# Changelog
## mdBook 0.3.6
[efdb832...88684d8](https://github.com/rust-lang/mdBook/compare/efdb832...88684d8)
### Added
- `MDBook::execute_build_process` is now publicly accessible in the API so
that plugins can more easily initiate the build process.
[#1099](https://github.com/rust-lang/mdBook/pull/1099)
### Changed
- Use a different color for Ayu theme's highlighting for Rust attributes (uses
a bright color instead of the comment color).
[#1133](https://github.com/rust-lang/mdBook/pull/1133)
- Adjusted spacing of sidebar entries.
[#1137](https://github.com/rust-lang/mdBook/pull/1137)
- Slightly adjusted line-height of `<p>`, `<ul>`, and `<ol>`.
[#1136](https://github.com/rust-lang/mdBook/pull/1136)
- Handlebars updated to 3.0.
[#1130](https://github.com/rust-lang/mdBook/pull/1130)
### Fixed
- Fix an issue with sidebar scroll position on reload.
[#1108](https://github.com/rust-lang/mdBook/pull/1108)
- `mdbook serve` will retain the current scroll position when the page is reloaded.
[#1097](https://github.com/rust-lang/mdBook/pull/1097)
- Fixed the page name if the book didn't have a title to not be prefixed with ` - `.
[#1145](https://github.com/rust-lang/mdBook/pull/1145)
- HTML attributes `rel=next` and `rel=previous` are now supported in "wide"
mode (previously they were only set in narrow mode).
[#1150](https://github.com/rust-lang/mdBook/pull/1150)
- Prevent recursive copies when the destination directory is contained in the
source directory.
[#1135](https://github.com/rust-lang/mdBook/pull/1135)
- Adjusted the menu bar animation to not immediately obscure the top content.
[#989](https://github.com/rust-lang/mdBook/pull/989)
- Fix for comments in SUMMARY.md that appear between items.
[#1167](https://github.com/rust-lang/mdBook/pull/1167)
## mdBook 0.3.5
[6e0d0fa...efdb832](https://github.com/rust-lang/mdBook/compare/6e0d0fa...efdb832)
### Changed
- The `default-theme` config setting is now case-insensitive.
[#1079](https://github.com/rust-lang/mdBook/pull/1079)
### Fixed
- Fixed `#` hidden Rust code lines not rendering properly.
[#1088](https://github.com/rust-lang/mdBook/pull/1088)
- Updated pulldown-cmark to 0.6.1, fixing several issues.
[#1021](https://github.com/rust-lang/mdBook/pull/1021)
## mdBook 0.3.4
[e5f77aa...6e0d0fa](https://github.com/rust-lang/mdBook/compare/e5f77aa...6e0d0fa)
### Changed
- Switch to relative `rem` font sizes from `px`.
[#894](https://github.com/rust-lang/mdBook/pull/894)
- Migrated repository to https://github.com/rust-lang/mdBook/
[#1083](https://github.com/rust-lang/mdBook/pull/1083)
## mdBook 0.3.3
[2b649fe...e5f77aa](https://github.com/rust-lang/mdBook/compare/2b649fe...e5f77aa)
### Changed
- Improvements to the automatic dark theme selection.
[#1069](https://github.com/rust-lang/mdBook/pull/1069)
- Fragment links now prevent scrolling the header behind the menu bar.
[#1077](https://github.com/rust-lang/mdBook/pull/1077)
### Fixed
- Fixed error when building a book that has a spacer immediately after the
first chapter.
[#1075](https://github.com/rust-lang/mdBook/pull/1075)
## mdBook 0.3.2
[9cd47eb...2b649fe](https://github.com/rust-lang/mdBook/compare/9cd47eb...2b649fe)
### Added
- Added a markdown renderer, which is off by default. This may be useful for
debugging preprocessors.
[#1018](https://github.com/rust-lang/mdBook/pull/1018)
- Code samples may now include line numbers with the
`output.html.playpen.line-numbers` configuration value.
[#1035](https://github.com/rust-lang/mdBook/pull/1035)
- The `watch` and `serve` commands will now ignore files listed in
`.gitignore`.
[#1044](https://github.com/rust-lang/mdBook/pull/1044)
- Added automatic dark-theme detection based on the CSS `prefers-color-scheme`
feature. This may be enabled by setting `output.html.preferred-dark-theme`
to your preferred dark theme.
[#1037](https://github.com/rust-lang/mdBook/pull/1037)
- Added `rustdoc_include` preprocessor. This makes it easier to include
portions of an external Rust source file. The rest of the file is hidden,
but the user may expand it to see the entire file, and will continue to work
with `mdbook test`.
[#1003](https://github.com/rust-lang/mdBook/pull/1003)
- Added Ctrl-Enter shortcut to the playpen editor to automatically run the
sample.
[#1066](https://github.com/rust-lang/mdBook/pull/1066)
- Added `output.html.playpen.copyable` configuration option to disable
the copy button.
[#1050](https://github.com/rust-lang/mdBook/pull/1050)
- Added ability to dynamically expand and fold sections within the sidebar.
See the `output.html.fold` configuration to enable this feature.
[#1027](https://github.com/rust-lang/mdBook/pull/1027)
### Changed
- Use standard `scrollbar-color` CSS along with webkit extension
[#816](https://github.com/rust-lang/mdBook/pull/816)
- The renderer build directory is no longer deleted before the renderer is
run. This allows a backend to cache results between runs.
[#985](https://github.com/rust-lang/mdBook/pull/985)
- Next/prev links now highlight on hover to indicate it is clickable.
[#994](https://github.com/rust-lang/mdBook/pull/994)
- Increase padding of table headers.
[#824](https://github.com/rust-lang/mdBook/pull/824)
- Errors in `[output.html]` config are no longer ignored.
[#1033](https://github.com/rust-lang/mdBook/pull/1033)
- Updated highlight.js for syntax highlighting updates (primarily to add
async/await to Rust highlighting).
[#1041](https://github.com/rust-lang/mdBook/pull/1041)
- Raised minimum supported rust version to 1.35.
[#1003](https://github.com/rust-lang/mdBook/pull/1003)
- Hidden code lines are no longer dynamically removed via JavaScript, but
instead managed with CSS.
[#846](https://github.com/rust-lang/mdBook/pull/846)
[#1065](https://github.com/rust-lang/mdBook/pull/1065)
- Changed the default font set for the ACE editor, giving preference to
"Source Code Pro".
[#1062](https://github.com/rust-lang/mdBook/pull/1062)
- Windows 32-bit releases are no longer published.
[#1071](https://github.com/rust-lang/mdBook/pull/1071)
### Fixed
- Fixed sidebar auto-scrolling.
[#1052](https://github.com/rust-lang/mdBook/pull/1052)
- Fixed error message when running `clean` multiple times.
[#1055](https://github.com/rust-lang/mdBook/pull/1055)
- Actually fix the "next" link on index.html. The previous fix didn't work.
[#1005](https://github.com/rust-lang/mdBook/pull/1005)
- Stop using `inline-block` for `inline code`, fixing selection highlighting
and some rendering issues.
[#1058](https://github.com/rust-lang/mdBook/pull/1058)
- Fix header auto-hide on browsers with momentum scrolling that allows
negative `scrollTop`.
[#1070](https://github.com/rust-lang/mdBook/pull/1070)
## mdBook 0.3.1
[69a08ef...9cd47eb](https://github.com/rust-lang/mdBook/compare/69a08ef...9cd47eb)
### Added
- 🔥 Added ability to include files using anchor points instead of line numbers.
[#851](https://github.com/rust-lang/mdBook/pull/851)
- Added `language` configuration value to set the language of the book, which
will affect things like the `<html lang="en">` tag.
[#941](https://github.com/rust-lang/mdBook/pull/941)
### Changed
- Updated to handlebars 2.0.
[#977](https://github.com/rust-lang/mdBook/pull/977)
### Fixed
- Fixed memory leak warning.
[#967](https://github.com/rust-lang/mdBook/pull/967)
- Fix more print.html links.
[#963](https://github.com/rust-lang/mdBook/pull/963)
- Fixed crash on some unicode input.
[#978](https://github.com/rust-lang/mdBook/pull/978)
## mdBook 0.3.0
[6cbc41d...84d4063](https://github.com/rust-lang-nursery/mdBook/compare/6cbc41d...84d4063)
[6cbc41d...69a08ef](https://github.com/rust-lang/mdBook/compare/6cbc41d...69a08ef)
### Added
- Added ability to resize the sidebar.
[#849](https://github.com/rust-lang-nursery/mdBook/pull/849)
[#849](https://github.com/rust-lang/mdBook/pull/849)
- Added `load_with_config_and_summary` function to `MDBook` to be able to
build a book with a custom `Summary`.
[#883](https://github.com/rust-lang-nursery/mdBook/pull/883)
[#883](https://github.com/rust-lang/mdBook/pull/883)
- Set `noindex` on `print.html` page to prevent robots from indexing it.
[#844](https://github.com/rust-lang-nursery/mdBook/pull/844)
[#844](https://github.com/rust-lang/mdBook/pull/844)
- Added support for ~~strikethrough~~ and GitHub-style tasklists.
[#952](https://github.com/rust-lang-nursery/mdBook/pull/952)
[#952](https://github.com/rust-lang/mdBook/pull/952)
### Changed
- Command-line help output is now colored.
[#861](https://github.com/rust-lang-nursery/mdBook/pull/861)
[#861](https://github.com/rust-lang/mdBook/pull/861)
- The build directory is now deleted before rendering starts, instead of after
if finishes.
[#878](https://github.com/rust-lang-nursery/mdBook/pull/878)
[#878](https://github.com/rust-lang/mdBook/pull/878)
- Removed dependency on `same-file` crate.
[#903](https://github.com/rust-lang-nursery/mdBook/pull/903)
[#903](https://github.com/rust-lang/mdBook/pull/903)
- 💥 Renamed `with_preprecessor` to `with_preprocessor`.
[#906](https://github.com/rust-lang-nursery/mdBook/pull/906)
[#906](https://github.com/rust-lang/mdBook/pull/906)
- Updated ACE editor to 1.4.4, should remove a JavaScript console warning.
[#935](https://github.com/rust-lang-nursery/mdBook/pull/935)
[#935](https://github.com/rust-lang/mdBook/pull/935)
- Dependencies have been updated.
[#934](https://github.com/rust-lang-nursery/mdBook/pull/934)
[#945](https://github.com/rust-lang-nursery/mdBook/pull/945)
[#934](https://github.com/rust-lang/mdBook/pull/934)
[#945](https://github.com/rust-lang/mdBook/pull/945)
- Highlight.js has been updated. This fixes some TOML highlighting, and adds
Julia support.
[#942](https://github.com/rust-lang-nursery/mdBook/pull/942)
[#942](https://github.com/rust-lang/mdBook/pull/942)
- 🔥 Updated to pulldown-cmark 0.5. This may have significant changes to the
formatting of existing books, as the newer version has more accurate
interpretation of the CommonMark spec and a large number of bug fixes and
changes.
[#898](https://github.com/rust-lang-nursery/mdBook/pull/898)
[#898](https://github.com/rust-lang/mdBook/pull/898)
- The `diff` language should now highlight correctly.
[#943](https://github.com/rust-lang-nursery/mdBook/pull/943)
[#943](https://github.com/rust-lang/mdBook/pull/943)
- Make the blank region of a header not clickable.
[#948](https://github.com/rust-lang-nursery/mdBook/pull/948)
[#948](https://github.com/rust-lang/mdBook/pull/948)
- Rustdoc tests now use the preprocessed content instead of the raw,
unpreprocessed content.
[#891](https://github.com/rust-lang-nursery/mdBook/pull/891)
[#891](https://github.com/rust-lang/mdBook/pull/891)
### Fixed
- Fixed file change detection so that `mdbook serve` only reloads once when
multiple files are changed at once.
[#870](https://github.com/rust-lang-nursery/mdBook/pull/870)
[#870](https://github.com/rust-lang/mdBook/pull/870)
- Fixed on-hover color highlighting for links in sidebar.
[#834](https://github.com/rust-lang-nursery/mdBook/pull/834)
[#834](https://github.com/rust-lang/mdBook/pull/834)
- Fixed loss of focus when clicking the "Copy" button in code blocks.
[#867](https://github.com/rust-lang-nursery/mdBook/pull/867)
[#867](https://github.com/rust-lang/mdBook/pull/867)
- Fixed incorrectly stripping the path for `additional-js` files.
[#796](https://github.com/rust-lang-nursery/mdBook/pull/796)
[#796](https://github.com/rust-lang/mdBook/pull/796)
- Fixed color of `code spans` that are links.
[#905](https://github.com/rust-lang-nursery/mdBook/pull/905)
[#905](https://github.com/rust-lang/mdBook/pull/905)
- Fixed "next" navigation on index.html.
[#916](https://github.com/rust-lang-nursery/mdBook/pull/916)
[#916](https://github.com/rust-lang/mdBook/pull/916)
- Fixed keyboard chapter navigation for `file` urls.
[#915](https://github.com/rust-lang-nursery/mdBook/pull/915)
[#915](https://github.com/rust-lang/mdBook/pull/915)
- Fixed bad wrapping for inline code on some browsers.
[#818](https://github.com/rust-lang-nursery/mdBook/pull/818)
[#818](https://github.com/rust-lang/mdBook/pull/818)
- Properly load an existing `SUMMARY.md` in `mdbook init`.
[#841](https://github.com/rust-lang-nursery/mdBook/pull/841)
[#841](https://github.com/rust-lang/mdBook/pull/841)
- Fixed some broken links in `print.html`.
[#871](https://github.com/rust-lang-nursery/mdBook/pull/871)
[#871](https://github.com/rust-lang/mdBook/pull/871)
- The Rust Playground link now supports the 2018 edition.
[#946](https://github.com/rust-lang-nursery/mdBook/pull/946)
[#946](https://github.com/rust-lang/mdBook/pull/946)
## mdBook 0.2.3 (2018-01-18)
[2c20c99...6cbc41d](https://github.com/rust-lang-nursery/mdBook/compare/2c20c99...6cbc41d)
[2c20c99...6cbc41d](https://github.com/rust-lang/mdBook/compare/2c20c99...6cbc41d)
### Added
- Added an optional button to the top of the page which will link to a git
repository. Use the `git-repository-url` and `git-repository-icon` options
in the `[output.html]` section to enable it and set its appearance.
[#802](https://github.com/rust-lang-nursery/mdBook/pull/802)
[#802](https://github.com/rust-lang/mdBook/pull/802)
- Added a `default-theme` option to the `[output.html]` section.
[#804](https://github.com/rust-lang-nursery/mdBook/pull/804)
[#804](https://github.com/rust-lang/mdBook/pull/804)
### Changed
- 💥 Header ID anchors no longer add an arbitrary `a` character for headers
that start with a non-ascii-alphabetic character.
[#788](https://github.com/rust-lang-nursery/mdBook/pull/788)
[#788](https://github.com/rust-lang/mdBook/pull/788)
### Fixed
- Fix websocket hostname usage
[#865](https://github.com/rust-lang-nursery/mdBook/pull/865)
[#865](https://github.com/rust-lang/mdBook/pull/865)
- Fixing links in print.html
[#866](https://github.com/rust-lang-nursery/mdBook/pull/866)
[#866](https://github.com/rust-lang/mdBook/pull/866)
## mdBook 0.2.2 (2018-10-19)
[7e2e095...2c20c99](https://github.com/rust-lang-nursery/mdBook/compare/7e2e095...2c20c99)
[7e2e095...2c20c99](https://github.com/rust-lang/mdBook/compare/7e2e095...2c20c99)
### Added
- 🎉 Process-based custom preprocessors. See [the
docs](https://rust-lang-nursery.github.io/mdBook/for_developers/preprocessors.html)
docs](https://rust-lang.github.io/mdBook/for_developers/preprocessors.html)
for more.
[#792](https://github.com/rust-lang-nursery/mdBook/pull/792)
[#792](https://github.com/rust-lang/mdBook/pull/792)
- 🎉 Configurable preprocessors.
@@ -121,26 +289,26 @@
Added `PreprocessorContext::renderer` to indicate the renderer being used.
[#658](https://github.com/rust-lang-nursery/mdBook/pull/658)
[#787](https://github.com/rust-lang-nursery/mdBook/pull/787)
[#658](https://github.com/rust-lang/mdBook/pull/658)
[#787](https://github.com/rust-lang/mdBook/pull/787)
### Fixed
- Fix paths to additional CSS and JavaScript files
[#777](https://github.com/rust-lang-nursery/mdBook/pull/777)
[#777](https://github.com/rust-lang/mdBook/pull/777)
- Ensure section numbers are correctly incremented after a horizontal
separator
[#790](https://github.com/rust-lang-nursery/mdBook/pull/790)
[#790](https://github.com/rust-lang/mdBook/pull/790)
## mdBook 0.2.1 (2018-08-22)
[91ffca1...7e2e095](https://github.com/rust-lang-nursery/mdBook/compare/91ffca1...7e2e095)
[91ffca1...7e2e095](https://github.com/rust-lang/mdBook/compare/91ffca1...7e2e095)
### Changed
- Update to handlebars-rs 1.0
[#761](https://github.com/rust-lang-nursery/mdBook/pull/761)
[#761](https://github.com/rust-lang/mdBook/pull/761)
### Fixed
- Fix table colors, broken by Stylus -> CSS transition
[#765](https://github.com/rust-lang-nursery/mdBook/pull/765)
[#765](https://github.com/rust-lang/mdBook/pull/765)
## mdBook 0.2.0 (2018-08-02)
@@ -148,7 +316,7 @@
- 💥 This release changes how links are handled in mdBook. Previously, relative
links were interpreted relative to the book's root. In `0.2.0`+ links are
relative to the page they are in, and use the `.md` extension. This has [several
advantages](https://github.com/rust-lang-nursery/mdBook/pull/603#issue-166701447),
advantages](https://github.com/rust-lang/mdBook/pull/603#issue-166701447),
such as making links work in other markdown viewers like GitHub. You will
likely have to change links in your book to accommodate this change. For
example, a book with this layout:
@@ -180,11 +348,11 @@
help serve` for details.
- Embedded rust playpens now use the "stable" playground API.
[#754](https://github.com/rust-lang-nursery/mdBook/pull/754)
[#754](https://github.com/rust-lang/mdBook/pull/754)
### Fixed
- Escaped includes (`\{{#include file.rs}}`) will now render correctly.
[f30ce01](https://github.com/rust-lang-nursery/mdBook/commit/f30ce0184d71e342141145472bf816419d30a2c5)
[f30ce01](https://github.com/rust-lang/mdBook/commit/f30ce0184d71e342141145472bf816419d30a2c5)
- `index.html` will now render correctly when the book's first section is
inside a subdirectory.
[#756](https://github.com/rust-lang-nursery/mdBook/pull/756)
[#756](https://github.com/rust-lang/mdBook/pull/756)

View File

@@ -5,22 +5,22 @@ Welcome stranger!
If you have come here to learn how to contribute to mdBook, we have some tips for you!
First of all, don't hesitate to ask questions!
Use the [issue tracker](https://github.com/rust-lang-nursery/mdBook/issues), no question is too simple.
Use the [issue tracker](https://github.com/rust-lang/mdBook/issues), no question is too simple.
If we don't respond in a couple of days, ping us @Michael-F-Bryan, @budziq, @steveklabnik, @frewsxcv it might just be that we forgot. :wink:
### Issues to work on
Any issue is up for the grabbing, but if you are starting out, you might be interested in the
[E-Easy issues](https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AE-Easy).
[E-Easy issues](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AE-Easy).
Those are issues that are considered more straightforward for beginners to Rust or the codebase itself.
These issues can be a good launching pad for more involved issues. Easy tasks for a first time contribution
include documentation improvements, new tests, examples, updating dependencies, etc.
If you come from a web development background, you might be interested in issues related to web technologies tagged
[A-JavaScript](https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-JavaScript),
[A-Style](https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-Style),
[A-HTML](https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-HTML) or
[A-Mobile](https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-Mobile).
[A-JavaScript](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-JavaScript),
[A-Style](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-Style),
[A-HTML](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-HTML) or
[A-Mobile](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-Mobile).
When you decide you want to work on a specific issue, ping us on that issue so that we can assign it to you.
Again, do not hesitate to ask questions. We will gladly mentor anyone that want to tackle an issue.
@@ -41,7 +41,7 @@ mdBook builds on stable Rust, if you want to build mdBook from source, here are
0. Clone this repository with git.
```
git clone https://github.com/rust-lang-nursery/mdBook.git
git clone https://github.com/rust-lang/mdBook.git
```
0. Navigate into the newly created `mdBook` directory
0. Run `cargo build`
@@ -57,7 +57,7 @@ We love code quality and Rust has some excellent tools to assist you with contri
Before you make your Pull Request to the project, please run it through the `rustfmt` utility.
This will ensure we have good quality source code that is better for us all to maintain.
[rustfmt](https://github.com/rust-lang-nursery/rustfmt) has a lot more information on the project.
[rustfmt](https://github.com/rust-lang/rustfmt) has a lot more information on the project.
The quick guide is
1. Install it
@@ -74,7 +74,7 @@ The quick guide is
```
When run through `cargo` it will format all bin and lib files in the current crate.
For more information, such as running it from your favourite editor, please see the `rustfmt` project. [rustfmt](https://github.com/rust-lang-nursery/rustfmt)
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
@@ -83,7 +83,7 @@ Clippy is a code analyser/linter detecting mistakes, and therfore helps to impro
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-nursery/rust-clippy)
The best documentation can be found over at [rust-clippy](https://github.com/rust-lang/rust-clippy)
1. To install
```
@@ -94,7 +94,7 @@ 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-nursery.github.io/rust-clippy/master/index.html).
Clippy has an ever growing list of checks, that are managed in [lint files](https://rust-lang.github.io/rust-clippy/master/index.html).
### Making a pull-request
@@ -102,7 +102,7 @@ When you feel comfortable that your changes could be integrated into mdBook, you
One of the core maintainers will then approve the changes or request some changes before it gets merged.
If you want to make your pull-request even better, you might want to run [Clippy](https://github.com/Manishearth/rust-clippy)
and [rustfmt](https://github.com/rust-lang-nursery/rustfmt) on the code first.
and [rustfmt](https://github.com/rust-lang/rustfmt) on the code first.
This is not a requirement though and will never block a pull-request from being merged.
That's it, happy contributions! :tada: :tada: :tada:

1103
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,18 @@
[package]
name = "mdbook"
version = "0.3.0"
version = "0.3.6"
authors = [
"Mathieu David <mathieudavid@mathieudavid.org>",
"Michael-F-Bryan <michaelfbryan@gmail.com>",
"Matt Ickstadt <mattico8@gmail.com>"
]
documentation = "http://rust-lang-nursery.github.io/mdBook/index.html"
documentation = "http://rust-lang.github.io/mdBook/index.html"
edition = "2018"
exclude = ["/book-example/*"]
keywords = ["book", "gitbook", "rustbook", "markdown"]
license = "MPL-2.0"
readme = "README.md"
repository = "https://github.com/rust-lang-nursery/mdBook"
repository = "https://github.com/rust-lang/mdBook"
description = "Creates a book from markdown files"
[dependencies]
@@ -20,13 +20,13 @@ chrono = "0.4"
clap = "2.24"
env_logger = "0.6"
error-chain = "0.12"
handlebars = { version = "1.0", default-features = false, features = ["no_dir_source"] }
handlebars = "3.0"
itertools = "0.8"
lazy_static = "1.0"
log = "0.4"
memchr = "2.0"
open = "1.1"
pulldown-cmark = "0.5"
pulldown-cmark = "0.6.1"
regex = "1.0.0"
serde = "1.0"
serde_derive = "1.0"
@@ -38,15 +38,16 @@ toml-query = "0.9"
# Watch feature
notify = { version = "4.0", optional = true }
gitignore = { version = "1.0", optional = true }
# Serve feature
iron = { version = "0.6", optional = true }
staticfile = { version = "0.5", optional = true }
ws = { version = "0.8", optional = true}
ws = { version = "0.9", optional = true}
# Search feature
elasticlunr-rs = { version = "2.3", optional = true, default-features = false }
ammonia = { version = "2.1", optional = true }
ammonia = { version = "3", optional = true }
[dev-dependencies]
select = "0.4"
@@ -57,7 +58,7 @@ walkdir = "2.0"
default = ["output", "watch", "serve", "search"]
debug = []
output = []
watch = ["notify"]
watch = ["notify", "gitignore"]
serve = ["iron", "staticfile", "ws"]
search = ["elasticlunr-rs", "ammonia"]

View File

@@ -1,25 +1,8 @@
# mdBook
<table>
<tr>
<td><strong>Linux / OS X</strong></td>
<td>
<a href="https://travis-ci.com/rust-lang-nursery/mdBook"><img src="https://travis-ci.com/rust-lang-nursery/mdBook.svg?branch=master"></a>
</td>
</tr>
<tr>
<td><strong>Windows</strong></td>
<td>
<a href="https://ci.appveyor.com/project/rust-lang-libs/mdbook"><img src="https://ci.appveyor.com/api/projects/status/ysyke2rvo85sni55?svg=true"></a>
</td>
</tr>
<tr>
<td colspan="2">
<a href="https://crates.io/crates/mdbook"><img src="https://img.shields.io/crates/v/mdbook.svg"></a>
<a href="LICENSE"><img src="https://img.shields.io/github/license/rust-lang-nursery/mdBook.svg"></a>
</td>
</tr>
</table>
[![Build Status](https://github.com/rust-lang/mdBook/workflows/CI/badge.svg)](https://github.com/rust-lang/mdBook/actions?workflow=CI)
[![crates.io](https://img.shields.io/crates/v/mdbook.svg)](https://crates.io/crates/mdbook)
[![LICENSE](https://img.shields.io/github/license/rust-lang/mdBook.svg)](LICENSE)
mdBook is a utility to create modern online books from Markdown files.
@@ -41,7 +24,7 @@ There are multiple ways to install mdBook.
2. **From Crates.io**
This requires at least [Rust] 1.20 and Cargo to be installed. Once you have installed
This requires at least [Rust] 1.35 and Cargo to be installed. Once you have installed
Rust, type the following in the terminal:
```
@@ -59,10 +42,14 @@ There are multiple ways to install mdBook.
This will constrain the server to install the latest **non-breaking**
version of mdBook and will prevent your books from failing to build because
we released a new version. For example:
we released a new version.
You can also disable default features to speed up compile time.
Example:
```
cargo install mdbook --vers "^0.1.0"
cargo install mdbook --no-default-features --features output --vers "^0.1.0"
```
3. **From Git**
@@ -72,7 +59,7 @@ There are multiple ways to install mdBook.
the git version of mdBook yourself. Cargo makes this ***super easy***!
```
cargo install --git https://github.com/rust-lang-nursery/mdBook.git mdbook
cargo install --git https://github.com/rust-lang/mdBook.git mdbook
```
Again, make sure to add the Cargo bin directory to your `PATH`.
@@ -83,7 +70,7 @@ There are multiple ways to install mdBook.
your local machine:
```
git clone https://github.com/rust-lang-nursery/mdBook.git
git clone https://github.com/rust-lang/mdBook.git
```
`cd` into `mdBook/` and run
@@ -169,6 +156,9 @@ format, however there's nothing stopping a renderer from doing static analysis
of a book in order to validate links or run tests. Some existing renderers are:
- `html` - the built-in renderer which will generate a HTML version of the book
- `markdown` - the built-in renderer (disabled by default) which will run
preprocessors then output the resulting Markdown. Useful for debugging
preprocessors.
- [`linkcheck`] - a backend which will check that all links are valid
- [`epub`] - an experimental EPUB generator
@@ -214,7 +204,7 @@ Contributions are highly appreciated and encouraged! Don't hesitate to
participate to discussions in the issues, propose new features and ask for
help.
If you are just starting out with Rust, there are a series of issus that are
If you are just starting out with Rust, there are a series of issues that are
tagged [E-Easy] and **we will gladly mentor you** so that you can successfully
go through the process of fixing a bug or adding a new feature! Let us know if
you need any help.
@@ -231,14 +221,14 @@ available, for those hacking on `master`.
All the code in this repository is released under the ***Mozilla Public License v2.0***, for more information take a look at the [LICENSE] file.
[User Guide]: https://rust-lang-nursery.github.io/mdBook/
[User Guide]: https://rust-lang.github.io/mdBook/
[API docs]: https://docs.rs/mdbook/*/mdbook/
[E-Easy]: https://github.com/rust-lang-nursery/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AE-Easy
[contribution guide]: https://github.com/rust-lang-nursery/mdBook/blob/master/CONTRIBUTING.md
[LICENSE]: https://github.com/rust-lang-nursery/mdBook/blob/master/LICENSE
[releases]: https://github.com/rust-lang-nursery/mdBook/releases
[E-Easy]: https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AE-Easy
[contribution guide]: https://github.com/rust-lang/mdBook/blob/master/CONTRIBUTING.md
[LICENSE]: https://github.com/rust-lang/mdBook/blob/master/LICENSE
[releases]: https://github.com/rust-lang/mdBook/releases
[Rust]: https://www.rust-lang.org/
[CLI docs]: http://rust-lang-nursery.github.io/mdBook/cli/init.html
[master-docs]: http://rust-lang-nursery.github.io/mdBook/mdbook/
[CLI docs]: http://rust-lang.github.io/mdBook/cli/init.html
[master-docs]: http://rust-lang.github.io/mdBook/
[`linkcheck`]: https://crates.io/crates/mdbook-linkcheck
[`epub`]: https://crates.io/crates/mdbook-epub
[`epub`]: https://crates.io/crates/mdbook-epub

View File

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

View File

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

View File

@@ -8,9 +8,9 @@ What you are reading serves as an example of the output of mdBook and at the
same time as a high-level documentation.
mdBook is free and open source, you can find the source code on
[GitHub](https://github.com/rust-lang-nursery/mdBook). Issues and feature
[GitHub](https://github.com/rust-lang/mdBook). Issues and feature
requests can be posted on the [GitHub issue
tracker](https://github.com/rust-lang-nursery/mdBook/issues).
tracker](https://github.com/rust-lang/mdBook/issues).
## API docs

View File

@@ -7,7 +7,7 @@ capabilities first.
## Install From Binaries
Precompiled binaries are provided for major platforms on a best-effort basis.
Visit [the releases page](https://github.com/rust-lang-nursery/mdBook/releases)
Visit [the releases page](https://github.com/rust-lang/mdBook/releases)
to download the appropriate version for your platform.
## Install From Source
@@ -39,14 +39,14 @@ have installed mdBook!
### Install Git version
The **[git version](https://github.com/rust-lang-nursery/mdBook)** contains all
The **[git version](https://github.com/rust-lang/mdBook)** contains all
the latest bug-fixes and features, that will be released in the next version on
**Crates.io**, if you can't wait until the next release. You can build the git
version yourself. Open your terminal and navigate to the directory of you
choice. We need to clone the git repository and then build it with Cargo.
```bash
git clone --depth=1 https://github.com/rust-lang-nursery/mdBook.git
git clone --depth=1 https://github.com/rust-lang/mdBook.git
cd mdBook
cargo build --release
```

View File

@@ -19,7 +19,7 @@ book-test/
└── SUMMARY.md
```
- The `src` directory is were you write your book in markdown. It contains all
- The `src` directory is where you write your book in markdown. It contains all
the source files, configuration files, etc.
- The `book` directory is where your book is rendered. All the output is ready

View File

@@ -19,7 +19,7 @@ The two main ways a developer can hook into the book's build process is via,
The process of rendering a book project goes through several steps.
1. Load the book
1. Load the book
- Parse the `book.toml`, falling back to the default `Config` if it doesn't
exist
- Load the book chapters into memory
@@ -41,6 +41,6 @@ The easiest way to find out how to use the `mdbook` crate is by looking at the
explanation on the configuration system.
[`MDBook`]: http://rust-lang-nursery.github.io/mdBook/mdbook/book/struct.MDBook.html
[`MDBook`]: https://docs.rs/mdbook/*/mdbook/book/struct.MDBook.html
[API Docs]: https://docs.rs/mdbook/*/mdbook/
[config]: file:///home/michael/Documents/forks/mdBook/target/doc/mdbook/config/index.html
[config]: https://docs.rs/mdbook/*/mdbook/config/index.html

View File

@@ -93,7 +93,7 @@ Now we've got the basics running, we want to actually use it. First, install the
program.
```shell
$ cargo install
$ cargo install --path .
```
Then `cd` to the particular book you'd like to count the words of and update its
@@ -261,6 +261,10 @@ in [`RenderContext`].
> **Note:** There is no guarantee that the destination directory exists or is
> empty (`mdbook` may leave the previous contents to let backends do caching),
> so it's always a good idea to create it with `fs::create_dir_all()`.
>
> If the destination directory already exists, don't assume it will be empty.
> To allow backends to cache the results from previous runs, `mdbook` may leave
> old content in the directory.
There's always the possibility that an error will occur while processing a book
(just look at all the `unwrap()`'s we've written already), so `mdbook` will
@@ -304,7 +308,7 @@ like this:
Now, if we reinstall the backend and build a book,
```shell
$ cargo install --force
$ cargo install --path . --force
$ mdbook build /path/to/book
...
2018-01-16 21:21:39 [INFO] (mdbook::renderer): Invoking the "wordcount" renderer
@@ -342,10 +346,10 @@ the source code or ask questions.
[mdbook-epub]: https://github.com/Michael-F-Bryan/mdbook-epub
[mdbook-test]: https://github.com/Michael-F-Bryan/mdbook-test
[rust-skeptic]: https://github.com/budziq/rust-skeptic
[`RenderContext`]: http://rust-lang-nursery.github.io/mdBook/mdbook/renderer/struct.RenderContext.html
[`RenderContext::from_json()`]: http://rust-lang-nursery.github.io/mdBook/mdbook/renderer/struct.RenderContext.html#method.from_json
[`RenderContext`]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html
[`RenderContext::from_json()`]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html#method.from_json
[`semver`]: https://crates.io/crates/semver
[`Book`]: http://rust-lang-nursery.github.io/mdBook/mdbook/book/struct.Book.html
[`Book::iter()`]: http://rust-lang-nursery.github.io/mdBook/mdbook/book/struct.Book.html#method.iter
[`Config`]: http://rust-lang-nursery.github.io/mdBook/mdbook/config/struct.Config.html
[issue tracker]: https://github.com/rust-lang-nursery/mdBook/issues
[`Book`]: https://docs.rs/mdbook/*/mdbook/book/struct.Book.html
[`Book::iter()`]: https://docs.rs/mdbook/*/mdbook/book/struct.Book.html#method.iter
[`Config`]: https://docs.rs/mdbook/*/mdbook/config/struct.Config.html
[issue tracker]: https://github.com/rust-lang/mdBook/issues

View File

@@ -109,7 +109,7 @@ For everything else, have a look [at the complete example][example].
[preprocessor-docs]: https://docs.rs/mdbook/latest/mdbook/preprocess/trait.Preprocessor.html
[pc]: https://crates.io/crates/pulldown-cmark
[pctc]: https://crates.io/crates/pulldown-cmark-to-cmark
[example]: https://github.com/rust-lang-nursery/mdBook/blob/master/examples/nop-preprocessor.rs
[an example no-op preprocessor]: https://github.com/rust-lang-nursery/mdBook/blob/master/examples/nop-preprocessor.rs
[example]: https://github.com/rust-lang/mdBook/blob/master/examples/nop-preprocessor.rs
[an example no-op preprocessor]: https://github.com/rust-lang/mdBook/blob/master/examples/nop-preprocessor.rs
[`CmdPreprocessor::parse_input()`]: https://docs.rs/mdbook/latest/mdbook/preprocess/trait.Preprocessor.html#method.parse_input
[`Book::for_each_mut()`]: https://docs.rs/mdbook/latest/mdbook/book/struct.Book.html#method.for_each_mut

View File

@@ -27,7 +27,7 @@ limit-results = 15
## Supported configuration options
It is important to note that **any** relative path specified in the in the
It is important to note that **any** relative path specified in the
configuration will always be taken relative from the root of the book where the
configuration file is located.
@@ -42,6 +42,7 @@ This is general information about your book.
- **src:** By default, the source directory is found in the directory named
`src` directly under the root folder. But this is configurable with the `src`
key in the configuration file.
- **language:** The main language of the book, which is used as a language attribute `<html lang="en">` for example.
**book.toml**
```toml
@@ -50,6 +51,7 @@ title = "Example book"
authors = ["John Doe", "Jane Doe"]
description = "The example book covers examples."
src = "my-src" # the source files will be found in `root/my-src` instead of `root/src`
language = "en"
```
### Build options
@@ -79,7 +81,7 @@ This controls the build process of your book.
The following preprocessors are available and included by default:
- `links`: Expand the `{{ #playpen }}` and `{{ #include }}` handlebars
- `links`: Expand the `{{ #playpen }}`, `{{ #include }}`, and `{{ #rustdoc_include }}` handlebars
helpers in a chapter to include the contents of a file.
- `index`: Convert all chapter files named `README.md` into `index.md`. That is
to say, all `README.md` would be rendered to an index file `index.html` in the
@@ -148,6 +150,10 @@ The following configuration options are available:
files with the ones found in the specified folder.
- **default-theme:** The theme color scheme to select by default in the
'Change Theme' dropdown. Defaults to `light`.
- **preferred-dark-theme:** The default dark theme. This theme will be used if
the browser requests the dark version of the site via the
['prefers-color-scheme'](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)
CSS media query. Defaults to the same theme as `default-theme`.
- **curly-quotes:** Convert straight quotes to curly quotes, except for those
that occur in code blocks and code spans. Defaults to `false`.
- **mathjax-support:** Adds support for [MathJax](mathjax.md). Defaults to
@@ -164,6 +170,7 @@ The following configuration options are available:
- **no-section-label:** mdBook by defaults adds section label in table of
contents column. For example, "1.", "2.1". Set this option to true to disable
those labels. Defaults to `false`.
- **fold:** A subtable for configuring sidebar section-folding behavior.
- **playpen:** A subtable for configuring various playpen settings.
- **search:** A subtable for configuring the in-browser search functionality.
mdBook must be compiled with the `search` feature enabled (on by default).
@@ -171,12 +178,21 @@ The following configuration options are available:
an icon link will be output in the menu bar of the book.
- **git-repository-icon:** The FontAwesome icon class to use for the git
repository link. Defaults to `fa-github`.
Available configuration options for the `[output.html.fold]` table:
- **enable:** Enable section-folding. When off, all folds are open.
Defaults to `false`.
- **level:** The higher the more folded regions are open. When level is 0, all
folds are closed. Defaults to `0`.
Available configuration options for the `[output.html.playpen]` table:
- **editable:** Allow editing the source code. Defaults to `false`.
- **copyable:** Display the copy button on code snippets. Defaults to `true`.
- **copy-js:** Copy JavaScript files for the editor to the output directory.
Defaults to `true`.
- **line-numbers** Display line numbers on editable sections of code. Requires both `editable` and `copy-js` to be `true`. Defaults to `false`.
[Ace]: https://ace.c9.io/
@@ -187,7 +203,7 @@ Available configuration options for the `[output.html.search]` table:
- **teaser-word-count:** The number of words used for a search result teaser.
Defaults to `30`.
- **use-boolean-and:** Define the logical link between multiple search words. If
true, all search words must appear in each result. Defaults to `true`.
true, all search words must appear in each result. Defaults to `false`.
- **boost-title:** Boost factor for the search result score if a search word
appears in the header. Defaults to `2`.
- **boost-hierarchy:** Boost factor for the search result score if a search word
@@ -214,18 +230,24 @@ description = "The example book covers examples."
[output.html]
theme = "my-theme"
default-theme = "light"
preferred-dark-theme = "navy"
curly-quotes = true
mathjax-support = false
google-analytics = "123456"
additional-css = ["custom.css", "custom2.css"]
additional-js = ["custom.js"]
no-section-label = false
git-repository-url = "https://github.com/rust-lang-nursery/mdBook"
git-repository-url = "https://github.com/rust-lang/mdBook"
git-repository-icon = "fa-github"
[output.html.fold]
enable = false
level = 0
[output.html.playpen]
editable = false
copy-js = true
line-numbers = false
[output.html.search]
enable = true
@@ -240,6 +262,26 @@ heading-split-level = 3
copy-js = true
```
### Markdown Renderer
The Markdown renderer will run preprocessors and then output the resulting
Markdown. This is mostly useful for debugging preprocessors, especially in
conjunction with `mdbook test` to see the Markdown that `mdbook` is passing
to `rustdoc`.
The Markdown renderer is included with `mdbook` but disabled by default.
Enable it by adding an emtpy table to your `book.toml` as follows:
```toml
[output.markdown]
```
There are no configuration options for the Markdown renderer at this time;
only whether it is enabled or disabled.
See [the preprocessors documentation](#configuring-preprocessors) for how to
specify which preprocessors should run before the Markdown renderer.
### Custom Renderers
A custom renderer can be enabled by adding a `[output.foo]` table to your

View File

@@ -3,7 +3,9 @@
## Hiding code lines
There is a feature in mdBook that lets you hide code lines by prepending them
with a `#`.
with a `#` [in the same way that Rustdoc does][rustdoc-hide].
[rustdoc-hide]: https://doc.rust-lang.org/stable/rustdoc/documentation-tests.html#hiding-portions-of-the-example
```bash
# fn main() {
@@ -63,6 +65,114 @@ the file are omitted. The third command includes all lines from line 2, i.e. the
first line is omitted. The last command includes the excerpt of `file.rs`
consisting of lines 2 to 10.
To avoid breaking your book when modifying included files, you can also
include a specific section using anchors instead of line numbers.
An anchor is a pair of matching lines. The line beginning an anchor must
match the regex "ANCHOR:\s*[\w_-]+" and similarly the ending line must match
the regex "ANCHOR_END:\s*[\w_-]+". This allows you to put anchors in
any kind of commented line.
Consider the following file to include:
```rs
/* ANCHOR: all */
// ANCHOR: component
struct Paddle {
hello: f32,
}
// ANCHOR_END: component
////////// ANCHOR: system
impl System for MySystem { ... }
////////// ANCHOR_END: system
/* ANCHOR_END: all */
```
Then in the book, all you have to do is:
````hbs
Here is a component:
```rust,no_run,noplaypen
\{{#include file.rs:component}}
```
Here is a system:
```rust,no_run,noplaypen
\{{#include file.rs:system}}
```
This is the full file.
```rust,no_run,noplaypen
\{{#include file.rs:all}}
```
````
Lines containing anchor patterns inside the included anchor are ignored.
## Including a file but initially hiding all except specified lines
The `rustdoc_include` helper is for including code from external Rust files that contain complete
examples, but only initially showing particular lines specified with line numbers or anchors in the
same way as with `include`.
The lines not in the line number range or between the anchors will still be included, but they will
be prefaced with `#`. This way, a reader can expand the snippet to see the complete example, and
Rustdoc will use the complete example when you run `mdbook test`.
For example, consider a file named `file.rs` that contains this Rust program:
```rust
fn main() {
let x = add_one(2);
assert_eq!(x, 3);
}
fn add_one(num: i32) -> i32 {
num + 1
}
```
We can include a snippet that initially shows only line 2 by using this syntax:
````hbs
To call the `add_one` function, we pass it an `i32` and bind the returned value to `x`:
```rust
\{{#rustdoc_include file.rs:2}}
```
````
This would have the same effect as if we had manually inserted the code and hidden all but line 2
using `#`:
````hbs
To call the `add_one` function, we pass it an `i32` and bind the returned value to `x`:
```rust
# fn main() {
let x = add_one(2);
# assert_eq!(x, 3);
# }
#
# fn add_one(num: i32) -> i32 {
# num + 1
#}
```
````
That is, it looks like this (click the "expand" icon to see the rest of the file):
```rust
# fn main() {
let x = add_one(2);
# assert_eq!(x, 3);
# }
#
# fn add_one(num: i32) -> i32 {
# num + 1
#}
```
## Inserting runnable Rust files
With the following syntax, you can insert runnable Rust files into your book:

View File

@@ -17,9 +17,8 @@ handlebars template you can access this information by using
Here is a list of the properties that are exposed:
- ***language*** Language of the book in the form `en`. To use in <code
class="language-html">\<html lang="{{ language }}"></code> for example. At the
moment it is hardcoded.
- ***language*** Language of the book in the form `en`, as specified in `book.toml` (if not specified, defaults to `en`). To use in <code
class="language-html">\<html lang="{{ language }}"></code> for example.
- ***title*** Title of the book, as specified in `book.toml`
- ***chapter_title*** Title of the current chapter, as listed in `SUMMARY.md`
@@ -98,4 +97,4 @@ Of course the inner html can be changed to your liking.
------
*If you would like other properties or helpers exposed, please [create a new
issue](https://github.com/rust-lang-nursery/mdBook/issues)*
issue](https://github.com/rust-lang/mdBook/issues)*

View File

@@ -22,7 +22,7 @@ overridden with your own.
If you want to use another theme for `highlight.js` download it from their
website, or make it yourself, rename it to `highlight.css` and put it in
`src/theme` (or the equivalent if you changed your source folder)
the `theme` folder of your book.
Now your theme will be used instead of the default theme.
@@ -62,7 +62,7 @@ everyone can benefit from it.**
If you think the default theme doesn't look quite right for a specific language,
or could be improved. Feel free to [submit a new
issue](https://github.com/rust-lang-nursery/mdBook/issues) explaining what you
issue](https://github.com/rust-lang/mdBook/issues) explaining what you
have in mind and I will take a look at it.
You could also create a pull-request with the proposed improvements.

View File

@@ -1,32 +0,0 @@
# This script takes care of building your crate and packaging it for release
set -ex
main() {
local src=$(pwd) \
stage=
case $TRAVIS_OS_NAME in
linux)
stage=$(mktemp -d)
;;
osx)
stage=$(mktemp -d -t tmp)
;;
esac
# This will slow down the build, but is necessary to not run out of disk space
cargo clean
cargo rustc --bin mdbook --target $TARGET --release -- -C lto
cp target/$TARGET/release/mdbook $stage/
cd $stage
tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz *
cd $src
rm -rf $stage
}
main

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

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

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

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

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

@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Builds the release and creates an archive and optionally deploys to GitHub.
set -ex
if [[ -z "$GITHUB_REF" ]]
then
echo "GITHUB_REF must be set"
exit 1
fi
# Strip mdbook-refs/tags/ from the start of the ref.
TAG=${GITHUB_REF#*/tags/}
host=$(rustc -Vv | grep ^host: | sed -e "s/host: //g")
cargo rustc --bin mdbook --release -- -C lto
cd target/release
case $1 in
ubuntu* | macos*)
asset="mdbook-$TAG-$host.tar.gz"
tar czf ../../$asset mdbook
;;
windows*)
asset="mdbook-$TAG-$host.zip"
7z a ../../$asset mdbook.exe
;;
*)
echo "OS should be first parameter, was: $1"
;;
esac
cd ../..
if [[ -z "$GITHUB_TOKEN" ]]
then
echo "$GITHUB_TOKEN not set, skipping deploy."
else
hub release edit -m "" --attach $asset $TAG
fi

View File

@@ -24,7 +24,7 @@ use crate::errors::*;
use crate::preprocess::{
CmdPreprocessor, IndexPreprocessor, LinkPreprocessor, Preprocessor, PreprocessorContext,
};
use crate::renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer};
use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderContext, Renderer};
use crate::utils;
use crate::config::Config;
@@ -56,7 +56,7 @@ impl MDBook {
warn!("This format is no longer used, so you should migrate to the");
warn!("book.toml format.");
warn!("Check the user guide for migration information:");
warn!("\thttps://rust-lang-nursery.github.io/mdBook/format/config.html");
warn!("\thttps://rust-lang.github.io/mdBook/format/config.html");
}
let mut config = if config_location.exists() {
@@ -182,7 +182,7 @@ impl MDBook {
}
/// Run the entire build process for a particular `Renderer`.
fn execute_build_process(&self, renderer: &dyn Renderer) -> Result<()> {
pub fn execute_build_process(&self, renderer: &dyn Renderer) -> Result<()> {
let mut preprocessed_book = self.book.clone();
let preprocess_ctx = PreprocessorContext::new(
self.root.clone(),
@@ -190,19 +190,6 @@ impl MDBook {
renderer.name().to_string(),
);
let name = renderer.name();
let build_dir = self.build_dir_for(name);
if build_dir.exists() {
debug!(
"Cleaning build dir for the \"{}\" renderer ({})",
name,
build_dir.display()
);
utils::fs::remove_dir_content(&build_dir)
.chain_err(|| "Unable to clear output directory")?;
}
for preprocessor in &self.preprocessors {
if preprocessor_should_run(&**preprocessor, renderer, &self.config) {
debug!("Running the {} preprocessor.", preprocessor.name());
@@ -343,18 +330,18 @@ impl MDBook {
/// Look at the `Config` and try to figure out what renderers to use.
fn determine_renderers(config: &Config) -> Vec<Box<dyn Renderer>> {
let mut renderers: Vec<Box<dyn Renderer>> = Vec::new();
let mut renderers = Vec::new();
if let Some(output_table) = config.get("output").and_then(Value::as_table) {
for (key, table) in output_table.iter() {
// the "html" backend has its own Renderer
renderers.extend(output_table.iter().map(|(key, table)| {
if key == "html" {
renderers.push(Box::new(HtmlHandlebars::new()));
Box::new(HtmlHandlebars::new()) as Box<dyn Renderer>
} else if key == "markdown" {
Box::new(MarkdownRenderer::new()) as Box<dyn Renderer>
} else {
let renderer = interpret_custom_renderer(key, table);
renderers.push(renderer);
interpret_custom_renderer(key, table)
}
}
}));
}
// if we couldn't find anything, add the HTML renderer as a default

View File

@@ -153,7 +153,8 @@ impl From<Link> for SummaryItem {
/// > match the following regex: "[^<>\n[]]+".
struct SummaryParser<'a> {
src: &'a str,
stream: pulldown_cmark::Parser<'a>,
stream: pulldown_cmark::OffsetIter<'a>,
offset: usize,
}
/// Reads `Events` from the provided stream until the corresponding
@@ -174,7 +175,7 @@ macro_rules! collect_events {
let mut events = Vec::new();
loop {
let event = $stream.next();
let event = $stream.next().map(|(ev, _range)| ev);
trace!("Next event: {:?}", event);
match event {
@@ -196,23 +197,22 @@ macro_rules! collect_events {
impl<'a> SummaryParser<'a> {
fn new(text: &str) -> SummaryParser<'_> {
let pulldown_parser = pulldown_cmark::Parser::new(text);
let pulldown_parser = pulldown_cmark::Parser::new(text).into_offset_iter();
SummaryParser {
src: text,
stream: pulldown_parser,
offset: 0,
}
}
/// Get the current line and column to give the user more useful error
/// messages.
fn current_location(&self) -> (usize, usize) {
let byte_offset = self.stream.get_offset();
let previous_text = self.src[..byte_offset].as_bytes();
let previous_text = self.src[..self.offset].as_bytes();
let line = Memchr::new(b'\n', previous_text).count() + 1;
let start_of_line = memchr::memrchr(b'\n', previous_text).unwrap_or(0);
let col = self.src[start_of_line..byte_offset].chars().count();
let col = self.src[start_of_line..self.offset].chars().count();
(line, col)
}
@@ -263,7 +263,7 @@ impl<'a> SummaryParser<'a> {
let link = self.parse_link(href.to_string())?;
items.push(SummaryItem::Link(link));
}
Some(Event::Start(Tag::Rule)) => items.push(SummaryItem::Separator),
Some(Event::Rule) => items.push(SummaryItem::Separator),
Some(_) => {}
None => break,
}
@@ -319,9 +319,6 @@ impl<'a> SummaryParser<'a> {
break;
}
Some(Event::Start(other_tag)) => {
if other_tag == Tag::Rule {
items.push(SummaryItem::Separator);
}
trace!("Skipping contents of {:?}", other_tag);
// Skip over the contents of this tag
@@ -337,6 +334,14 @@ impl<'a> SummaryParser<'a> {
break;
}
}
Some(Event::Rule) => {
items.push(SummaryItem::Separator);
if let Some(Event::Start(Tag::List(..))) = self.next_event() {
continue;
} else {
break;
}
}
Some(_) => {
// something else... ignore
continue;
@@ -352,7 +357,10 @@ impl<'a> SummaryParser<'a> {
}
fn next_event(&mut self) -> Option<Event<'a>> {
let next = self.stream.next();
let next = self.stream.next().map(|(ev, range)| {
self.offset = range.start;
ev
});
trace!("Next event: {:?}", next);
next
@@ -369,6 +377,10 @@ impl<'a> SummaryParser<'a> {
items.push(item);
}
Some(Event::Start(Tag::List(..))) => {
// Skip this tag after comment bacause it is not nested.
if items.is_empty() {
continue;
}
// recurse to parse the nested list
let (_, last_item) = get_last_link(&mut items)?;
let last_item_number = last_item
@@ -431,10 +443,10 @@ impl<'a> SummaryParser<'a> {
/// Try to parse the title line.
fn parse_title(&mut self) -> Option<String> {
if let Some(Event::Start(Tag::Header(1))) = self.next_event() {
if let Some(Event::Start(Tag::Heading(1))) = self.next_event() {
debug!("Found a h1 in the SUMMARY");
let tags = collect_events!(self.stream, end Tag::Header(1));
let tags = collect_events!(self.stream, end Tag::Heading(1));
Some(stringify_events(tags))
} else {
None
@@ -629,7 +641,7 @@ mod tests {
let _ = parser.stream.next(); // skip past start of paragraph
let href = match parser.stream.next() {
Some(Event::Start(Tag::Link(_type, href, _title))) => href.to_string(),
Some((Event::Start(Tag::Link(_type, href, _title)), _range)) => href.to_string(),
other => panic!("Unreachable, {:?}", other),
};
@@ -688,6 +700,33 @@ mod tests {
assert_eq!(got, should_be);
}
#[test]
fn parse_numbered_chapters_separated_by_comment() {
let src = "- [First](./first.md)\n<!-- this is a comment -->\n- [Second](./second.md)";
let should_be = vec![
SummaryItem::Link(Link {
name: String::from("First"),
location: PathBuf::from("./first.md"),
number: Some(SectionNumber(vec![1])),
nested_items: Vec::new(),
}),
SummaryItem::Link(Link {
name: String::from("Second"),
location: PathBuf::from("./second.md"),
number: Some(SectionNumber(vec![2])),
nested_items: Vec::new(),
}),
];
let mut parser = SummaryParser::new(src);
let _ = parser.stream.next();
let got = parser.parse_numbered().unwrap();
assert_eq!(got, should_be);
}
/// This test ensures the book will continue to pass because it breaks the
/// `SUMMARY.md` up using level 2 headers ([example]).
///
@@ -728,7 +767,7 @@ mod tests {
assert!(got.is_err());
}
/// Regression test for https://github.com/rust-lang-nursery/mdBook/issues/779
/// Regression test for https://github.com/rust-lang/mdBook/issues/779
/// Ensure section numbers are correctly incremented after a horizontal separator.
#[test]
fn keep_numbering_after_separator() {

View File

@@ -29,7 +29,10 @@ pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> {
Some(dest_dir) => dest_dir.into(),
None => book.root.join(&book.config.build.build_dir),
};
fs::remove_dir_all(&dir_to_remove).chain_err(|| "Unable to remove the build directory")?;
if dir_to_remove.exists() {
fs::remove_dir_all(&dir_to_remove).chain_err(|| "Unable to remove the build directory")?;
}
Ok(())
}

View File

@@ -2,6 +2,7 @@
use super::watch;
use crate::{get_book_dir, open};
use clap::{App, Arg, ArgMatches, SubCommand};
use iron::headers;
use iron::{status, AfterMiddleware, Chain, Iron, IronError, IronResult, Request, Response, Set};
use mdbook::errors::*;
use mdbook::utils;
@@ -9,6 +10,8 @@ use mdbook::MDBook;
struct ErrorRecover;
struct NoCache;
// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("serve")
@@ -86,6 +89,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
book.build()?;
let mut chain = Chain::new(staticfile::Static::new(book.build_dir_for("html")));
chain.link_after(NoCache);
chain.link_after(ErrorRecover);
let _iron = Iron::new(chain)
.http(&*address)
@@ -133,6 +137,17 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
Ok(())
}
impl AfterMiddleware for NoCache {
fn after(&self, _: &mut Request, mut res: Response) -> IronResult<Response> {
res.headers.set(headers::CacheControl(vec![
headers::CacheDirective::NoStore,
headers::CacheDirective::MaxAge(0u32),
]));
Ok(res)
}
}
impl AfterMiddleware for ErrorRecover {
fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
match err.response.status {

View File

@@ -48,6 +48,53 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
Ok(())
}
fn remove_ignored_files(book_root: &PathBuf, paths: &[PathBuf]) -> Vec<PathBuf> {
if paths.is_empty() {
return vec![];
}
match find_gitignore(book_root) {
Some(gitignore_path) => {
match gitignore::File::new(gitignore_path.as_path()) {
Ok(exclusion_checker) => filter_ignored_files(exclusion_checker, paths),
Err(_) => {
// We're unable to read the .gitignore file, so we'll silently allow everything.
// Please see discussion: https://github.com/rust-lang/mdBook/pull/1051
paths.iter().map(|path| path.to_path_buf()).collect()
}
}
}
None => {
// There is no .gitignore file.
paths.iter().map(|path| path.to_path_buf()).collect()
}
}
}
fn find_gitignore(book_root: &PathBuf) -> Option<PathBuf> {
book_root
.ancestors()
.map(|p| p.join(".gitignore"))
.find(|p| p.exists())
}
fn filter_ignored_files(exclusion_checker: gitignore::File, paths: &[PathBuf]) -> Vec<PathBuf> {
paths
.iter()
.filter(|path| match exclusion_checker.is_excluded(path) {
Ok(exclude) => !exclude,
Err(error) => {
warn!(
"Unable to determine if {:?} is excluded: {:?}. Including it.",
&path, error
);
true
}
})
.map(|path| path.to_path_buf())
.collect()
}
/// Calls the closure when a book source file is changed, blocking indefinitely.
pub fn trigger_on_change<F>(book: &MDBook, closure: F)
where
@@ -96,8 +143,12 @@ where
_ => None,
}
})
.collect();
.collect::<Vec<_>>();
closure(paths, &book.root);
let paths = remove_ignored_files(&book.root, &paths[..]);
if !paths.is_empty() {
closure(paths, &book.root);
}
}
}

View File

@@ -40,8 +40,8 @@
//! cfg.set("output.html.theme", "./themes");
//!
//! // then load it again, automatically deserializing to a `PathBuf`.
//! let got: PathBuf = cfg.get_deserialized("output.html.theme")?;
//! assert_eq!(got, PathBuf::from("./themes"));
//! let got: Option<PathBuf> = cfg.get_deserialized_opt("output.html.theme")?;
//! assert_eq!(got, Some(PathBuf::from("./themes")));
//! # Ok(())
//! # }
//! # fn main() { run().unwrap() }
@@ -62,6 +62,7 @@ use toml_query::insert::TomlValueInsertExt;
use toml_query::read::TomlValueReadExt;
use crate::errors::*;
use crate::utils;
/// The overall configuration object for MDBook, essentially an in-memory
/// representation of `book.toml`.
@@ -131,10 +132,8 @@ impl Config {
pub fn update_from_env(&mut self) {
debug!("Updating the config from environment variables");
let overrides = env::vars().filter_map(|(key, value)| match parse_env(&key) {
Some(index) => Some((index, value)),
None => None,
});
let overrides =
env::vars().filter_map(|(key, value)| parse_env(&key).map(|index| (index, value)));
for (key, value) in overrides {
trace!("{} => {}", key, value);
@@ -151,14 +150,11 @@ impl Config {
/// `output.html.playpen` will fetch the "playpen" out of the html output
/// table).
pub fn get(&self, key: &str) -> Option<&Value> {
match self.rest.read(key) {
Ok(inner) => inner,
Err(_) => None,
}
self.rest.read(key).unwrap_or(None)
}
/// Fetch a value from the `Config` so you can mutate it.
pub fn get_mut<'a>(&'a mut self, key: &str) -> Option<&'a mut Value> {
pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
match self.rest.read_mut(key) {
Ok(inner) => inner,
Err(_) => None,
@@ -173,22 +169,41 @@ impl Config {
/// HTML renderer is refactored to be less coupled to `mdbook` internals.
#[doc(hidden)]
pub fn html_config(&self) -> Option<HtmlConfig> {
self.get_deserialized("output.html").ok()
match self.get_deserialized_opt("output.html") {
Ok(Some(config)) => Some(config),
Ok(None) => None,
Err(e) => {
utils::log_backtrace(&e.chain_err(|| "Parsing configuration [output.html]"));
None
}
}
}
/// Deprecated, use get_deserialized_opt instead.
#[deprecated = "use get_deserialized_opt instead"]
pub fn get_deserialized<'de, T: Deserialize<'de>, S: AsRef<str>>(&self, name: S) -> Result<T> {
let name = name.as_ref();
match self.get_deserialized_opt(name)? {
Some(value) => Ok(value),
None => bail!("Key not found, {:?}", name),
}
}
/// Convenience function to fetch a value from the config and deserialize it
/// into some arbitrary type.
pub fn get_deserialized<'de, T: Deserialize<'de>, S: AsRef<str>>(&self, name: S) -> Result<T> {
pub fn get_deserialized_opt<'de, T: Deserialize<'de>, S: AsRef<str>>(
&self,
name: S,
) -> Result<Option<T>> {
let name = name.as_ref();
if let Some(value) = self.get(name) {
value
.clone()
.try_into()
.chain_err(|| "Couldn't deserialize the value")
} else {
bail!("Key not found, {:?}", name)
}
self.get(name)
.map(|value| {
value
.clone()
.try_into()
.chain_err(|| "Couldn't deserialize the value")
})
.transpose()
}
/// Set a config key, clobbering any existing values along the way.
@@ -208,7 +223,7 @@ impl Config {
} else {
self.rest
.insert(index, value)
.map_err(|e| ErrorKind::TomlQueryError(e))?;
.map_err(ErrorKind::TomlQueryError)?;
}
Ok(())
@@ -281,7 +296,7 @@ impl<'de> Deserialize<'de> for Config {
warn!("`description` under a table called `[book]`, move the `destination` entry");
warn!("from `[output.html]`, renamed to `build-dir`, under a table called");
warn!("`[build]`, and it should all work.");
warn!("Documentation: http://rust-lang-nursery.github.io/mdBook/format/config.html");
warn!("Documentation: http://rust-lang.github.io/mdBook/format/config.html");
return Ok(Config::from_legacy(raw));
}
@@ -376,6 +391,8 @@ pub struct BookConfig {
pub src: PathBuf,
/// Does this book support more than one language?
pub multilingual: bool,
/// The main language of the book.
pub language: Option<String>,
}
impl Default for BookConfig {
@@ -386,6 +403,7 @@ impl Default for BookConfig {
description: None,
src: PathBuf::from("src"),
multilingual: false,
language: Some(String::from("en")),
}
}
}
@@ -396,7 +414,7 @@ impl Default for BookConfig {
pub struct BuildConfig {
/// Where to put built artefacts relative to the book's root directory.
pub build_dir: PathBuf,
/// Should non-existent markdown files specified in `SETTINGS.md` be created
/// Should non-existent markdown files specified in `SUMMARY.md` be created
/// if they don't exist?
pub create_missing: bool,
/// Should the default preprocessors always be used when they are
@@ -422,6 +440,9 @@ pub struct HtmlConfig {
pub theme: Option<PathBuf>,
/// The default theme to use, defaults to 'light'
pub default_theme: Option<String>,
/// The theme to use if the browser requests the dark version of the site.
/// Defaults to the same as 'default_theme'
pub preferred_dark_theme: Option<String>,
/// Use "smart quotes" instead of the usual `"` character.
pub curly_quotes: bool,
/// Should mathjax be enabled?
@@ -433,16 +454,10 @@ pub struct HtmlConfig {
/// Additional JS scripts to include at the bottom of the rendered page's
/// `<body>`.
pub additional_js: Vec<PathBuf>,
/// Fold settings.
pub fold: Fold,
/// Playpen settings.
pub playpen: Playpen,
/// This is used as a bit of a workaround for the `mdbook serve` command.
/// Basically, because you set the websocket port from the command line, the
/// `mdbook serve` command needs a way to let the HTML renderer know where
/// to point livereloading at, if it has been enabled.
///
/// This config item *should not be edited* by the end user.
#[doc(hidden)]
pub livereload_url: Option<String>,
/// Don't render section labels.
pub no_section_label: bool,
/// Search settings. If `None`, the default will be used.
@@ -452,6 +467,14 @@ pub struct HtmlConfig {
/// FontAwesome icon class to use for the Git repository link.
/// Defaults to `fa-github` if `None`.
pub git_repository_icon: Option<String>,
/// This is used as a bit of a workaround for the `mdbook serve` command.
/// Basically, because you set the websocket port from the command line, the
/// `mdbook serve` command needs a way to let the HTML renderer know where
/// to point livereloading at, if it has been enabled.
///
/// This config item *should not be edited* by the end user.
#[doc(hidden)]
pub livereload_url: Option<String>,
}
impl HtmlConfig {
@@ -465,22 +488,40 @@ impl HtmlConfig {
}
}
/// Configuration for how to fold chapters of sidebar.
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct Fold {
/// When off, all folds are open. Default: `false`.
pub enable: bool,
/// The higher the more folded regions are open. When level is 0, all folds
/// are closed.
/// Default: `0`.
pub level: u8,
}
/// Configuration for tweaking how the the HTML renderer handles the playpen.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct Playpen {
/// Should playpen snippets be editable? Default: `false`.
pub editable: bool,
/// Display the copy button. Default: `true`.
pub copyable: bool,
/// Copy JavaScript files for the editor to the output directory?
/// Default: `true`.
pub copy_js: bool,
/// Display line numbers on playpen snippets. Default: `false`.
pub line_numbers: bool,
}
impl Default for Playpen {
fn default() -> Playpen {
Playpen {
editable: false,
copyable: true,
copy_js: true,
line_numbers: false,
}
}
}
@@ -496,7 +537,7 @@ pub struct Search {
/// The number of words used for a search result teaser. Default: `30`.
pub teaser_word_count: u32,
/// Define the logical link between multiple search words.
/// If true, all search words must appear in each result. Default: `true`.
/// If true, all search words must appear in each result. Default: `false`.
pub use_boolean_and: bool,
/// Boost factor for the search result score if a search word appears in the header.
/// Default: `2`.
@@ -545,12 +586,10 @@ trait Updateable<'de>: Serialize + Deserialize<'de> {
fn update_value<S: Serialize>(&mut self, key: &str, value: S) {
let mut raw = Value::try_from(&self).expect("unreachable");
{
if let Ok(value) = Value::try_from(value) {
let _ = raw.insert(key, value);
} else {
return;
}
if let Ok(value) = Value::try_from(value) {
let _ = raw.insert(key, value);
} else {
return;
}
if let Ok(updated) = raw.try_into() {
@@ -572,6 +611,7 @@ mod tests {
description = "A completely useless book"
multilingual = true
src = "source"
language = "ja"
[build]
build-dir = "outputs"
@@ -606,6 +646,7 @@ mod tests {
description: Some(String::from("A completely useless book")),
multilingual: true,
src: PathBuf::from("source"),
language: Some(String::from("ja")),
};
let build_should_be = BuildConfig {
build_dir: PathBuf::from("outputs"),
@@ -614,7 +655,9 @@ mod tests {
};
let playpen_should_be = Playpen {
editable: true,
copyable: true,
copy_js: true,
line_numbers: false,
};
let html_should_be = HtmlConfig {
curly_quotes: true,
@@ -658,11 +701,14 @@ mod tests {
};
let cfg = Config::from_str(src).unwrap();
let got: RandomOutput = cfg.get_deserialized("output.random").unwrap();
let got: RandomOutput = cfg.get_deserialized_opt("output.random").unwrap().unwrap();
assert_eq!(got, should_be);
let got_baz: Vec<bool> = cfg.get_deserialized("output.random.baz").unwrap();
let got_baz: Vec<bool> = cfg
.get_deserialized_opt("output.random.baz")
.unwrap()
.unwrap();
let baz_should_be = vec![true, true, false];
assert_eq!(got_baz, baz_should_be);
@@ -742,7 +788,7 @@ mod tests {
assert!(cfg.get(key).is_none());
cfg.set(key, value).unwrap();
let got: String = cfg.get_deserialized(key).unwrap();
let got: String = cfg.get_deserialized_opt(key).unwrap().unwrap();
assert_eq!(got, value);
}
@@ -783,7 +829,10 @@ mod tests {
cfg.update_from_env();
assert_eq!(cfg.get_deserialized::<String, _>(key).unwrap(), value);
assert_eq!(
cfg.get_deserialized_opt::<String, _>(key).unwrap().unwrap(),
value
);
}
#[test]
@@ -802,7 +851,9 @@ mod tests {
cfg.update_from_env();
assert_eq!(
cfg.get_deserialized::<serde_json::Value, _>(key).unwrap(),
cfg.get_deserialized_opt::<serde_json::Value, _>(key)
.unwrap()
.unwrap(),
value
);
}

View File

@@ -75,9 +75,9 @@
//! directly, making deserializing the `RenderContext` easy and giving you
//! access to the various methods for working with the [`Config`].
//!
//! [user guide]: https://rust-lang-nursery.github.io/mdBook/
//! [user guide]: https://rust-lang.github.io/mdBook/
//! [`RenderContext`]: renderer/struct.RenderContext.html
//! [relevant chapter]: https://rust-lang-nursery.github.io/mdBook/for_developers/backends.html
//! [relevant chapter]: https://rust-lang.github.io/mdBook/for_developers/backends.html
//! [`Config`]: config/struct.Config.html
#![deny(missing_docs)]

View File

@@ -30,7 +30,7 @@ fn main() {
.setting(AppSettings::ColoredHelp)
.after_help(
"For more information about a specific command, try `mdbook <command> --help`\n\
The source code for mdBook is available at: https://github.com/rust-lang-nursery/mdBook",
The source code for mdBook is available at: https://github.com/rust-lang/mdBook",
)
.subcommand(cmd::init::make_subcommand())
.subcommand(cmd::build::make_subcommand())

View File

@@ -1,8 +1,11 @@
use crate::errors::*;
use crate::utils::fs::file_to_string;
use crate::utils::take_lines;
use crate::utils::{
take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines,
take_rustdoc_include_lines,
};
use regex::{CaptureMatches, Captures, Regex};
use std::ops::{Range, RangeFrom, RangeFull, RangeTo};
use std::fs;
use std::ops::{Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeTo};
use std::path::{Path, PathBuf};
use super::{Preprocessor, PreprocessorContext};
@@ -11,8 +14,15 @@ use crate::book::{Book, BookItem};
const ESCAPE_CHAR: char = '\\';
const MAX_LINK_NESTED_DEPTH: usize = 10;
/// A preprocessor for expanding the `{{# playpen}}` and `{{# include}}`
/// helpers in a chapter.
/// A preprocessor for expanding helpers in a chapter. Supported helpers are:
///
/// - `{{# include}}` - Insert an external file of any type. Include the whole file, only particular
///. lines, or only between the specified anchors.
/// - `{{# rustdoc_include}}` - Insert an external Rust file, showing the particular lines
///. specified or the lines between specified anchors, and include the rest of the file behind `#`.
/// This hides the lines from initial display but shows them when the reader expands the code
/// block and provides them to Rustdoc for testing.
/// - `{{# playpen}}` - Insert runnable Rust files
#[derive(Default)]
pub struct LinkPreprocessor;
@@ -63,13 +73,13 @@ where
let mut previous_end_index = 0;
let mut replaced = String::new();
for playpen in find_links(s) {
replaced.push_str(&s[previous_end_index..playpen.start_index]);
for link in find_links(s) {
replaced.push_str(&s[previous_end_index..link.start_index]);
match playpen.render_with_path(&path) {
match link.render_with_path(&path) {
Ok(new_content) => {
if depth < MAX_LINK_NESTED_DEPTH {
if let Some(rel_path) = playpen.link.relative_path(path) {
if let Some(rel_path) = link.link_type.relative_path(path) {
replaced.push_str(&replace_all(&new_content, rel_path, source, depth + 1));
} else {
replaced.push_str(&new_content);
@@ -80,17 +90,17 @@ where
source.display()
);
}
previous_end_index = playpen.end_index;
previous_end_index = link.end_index;
}
Err(e) => {
error!("Error updating \"{}\", {}", playpen.link_text, e);
error!("Error updating \"{}\", {}", link.link_text, e);
for cause in e.iter().skip(1) {
warn!("Caused By: {}", cause);
}
// This should make sure we include the raw `{{# ... }}` snippet
// in the page content if there are any errors.
previous_end_index = playpen.start_index;
previous_end_index = link.start_index;
}
}
}
@@ -102,11 +112,68 @@ where
#[derive(PartialEq, Debug, Clone)]
enum LinkType<'a> {
Escaped,
IncludeRange(PathBuf, Range<usize>),
IncludeRangeFrom(PathBuf, RangeFrom<usize>),
IncludeRangeTo(PathBuf, RangeTo<usize>),
IncludeRangeFull(PathBuf, RangeFull),
Include(PathBuf, RangeOrAnchor),
Playpen(PathBuf, Vec<&'a str>),
RustdocInclude(PathBuf, RangeOrAnchor),
}
#[derive(PartialEq, Debug, Clone)]
enum RangeOrAnchor {
Range(LineRange),
Anchor(String),
}
// A range of lines specified with some include directive.
#[derive(PartialEq, Debug, Clone)]
enum LineRange {
Range(Range<usize>),
RangeFrom(RangeFrom<usize>),
RangeTo(RangeTo<usize>),
RangeFull(RangeFull),
}
impl RangeBounds<usize> for LineRange {
fn start_bound(&self) -> Bound<&usize> {
match self {
LineRange::Range(r) => r.start_bound(),
LineRange::RangeFrom(r) => r.start_bound(),
LineRange::RangeTo(r) => r.start_bound(),
LineRange::RangeFull(r) => r.start_bound(),
}
}
fn end_bound(&self) -> Bound<&usize> {
match self {
LineRange::Range(r) => r.end_bound(),
LineRange::RangeFrom(r) => r.end_bound(),
LineRange::RangeTo(r) => r.end_bound(),
LineRange::RangeFull(r) => r.end_bound(),
}
}
}
impl From<Range<usize>> for LineRange {
fn from(r: Range<usize>) -> LineRange {
LineRange::Range(r)
}
}
impl From<RangeFrom<usize>> for LineRange {
fn from(r: RangeFrom<usize>) -> LineRange {
LineRange::RangeFrom(r)
}
}
impl From<RangeTo<usize>> for LineRange {
fn from(r: RangeTo<usize>) -> LineRange {
LineRange::RangeTo(r)
}
}
impl From<RangeFull> for LineRange {
fn from(r: RangeFull) -> LineRange {
LineRange::RangeFull(r)
}
}
impl<'a> LinkType<'a> {
@@ -114,11 +181,9 @@ impl<'a> LinkType<'a> {
let base = base.as_ref();
match self {
LinkType::Escaped => None,
LinkType::IncludeRange(p, _) => Some(return_relative_path(base, &p)),
LinkType::IncludeRangeFrom(p, _) => Some(return_relative_path(base, &p)),
LinkType::IncludeRangeTo(p, _) => Some(return_relative_path(base, &p)),
LinkType::IncludeRangeFull(p, _) => Some(return_relative_path(base, &p)),
LinkType::Include(p, _) => Some(return_relative_path(base, &p)),
LinkType::Playpen(p, _) => Some(return_relative_path(base, &p)),
LinkType::RustdocInclude(p, _) => Some(return_relative_path(base, &p)),
}
}
}
@@ -130,46 +195,59 @@ fn return_relative_path<P: AsRef<Path>>(base: P, relative: P) -> PathBuf {
.to_path_buf()
}
fn parse_include_path(path: &str) -> LinkType<'static> {
let mut parts = path.split(':');
let path = parts.next().unwrap().into();
// subtract 1 since line numbers usually begin with 1
let start = parts
.next()
.and_then(|s| s.parse::<usize>().ok())
.map(|val| val.saturating_sub(1));
fn parse_range_or_anchor(parts: Option<&str>) -> RangeOrAnchor {
let mut parts = parts.unwrap_or("").splitn(3, ':').fuse();
let next_element = parts.next();
let start = if let Some(value) = next_element.and_then(|s| s.parse::<usize>().ok()) {
// subtract 1 since line numbers usually begin with 1
Some(value.saturating_sub(1))
} else if let Some("") = next_element {
None
} else if let Some(anchor) = next_element {
return RangeOrAnchor::Anchor(String::from(anchor));
} else {
None
};
let end = parts.next();
let has_end = end.is_some();
let end = end.and_then(|s| s.parse::<usize>().ok());
match start {
Some(start) => match end {
Some(end) => LinkType::IncludeRange(path, Range { start, end }),
None => {
if has_end {
LinkType::IncludeRangeFrom(path, RangeFrom { start })
} else {
LinkType::IncludeRange(
path,
Range {
start,
end: start + 1,
},
)
}
}
},
None => match end {
Some(end) => LinkType::IncludeRangeTo(path, RangeTo { end }),
None => LinkType::IncludeRangeFull(path, RangeFull),
},
// If `end` is empty string or any other value that can't be parsed as a usize, treat this
// include as a range with only a start bound. However, if end isn't specified, include only
// the single line specified by `start`.
let end = end.map(|s| s.parse::<usize>());
match (start, end) {
(Some(start), Some(Ok(end))) => RangeOrAnchor::Range(LineRange::from(start..end)),
(Some(start), Some(Err(_))) => RangeOrAnchor::Range(LineRange::from(start..)),
(Some(start), None) => RangeOrAnchor::Range(LineRange::from(start..start + 1)),
(None, Some(Ok(end))) => RangeOrAnchor::Range(LineRange::from(..end)),
(None, None) | (None, Some(Err(_))) => RangeOrAnchor::Range(LineRange::from(RangeFull)),
}
}
fn parse_include_path(path: &str) -> LinkType<'static> {
let mut parts = path.splitn(2, ':');
let path = parts.next().unwrap().into();
let range_or_anchor = parse_range_or_anchor(parts.next());
LinkType::Include(path, range_or_anchor)
}
fn parse_rustdoc_include_path(path: &str) -> LinkType<'static> {
let mut parts = path.splitn(2, ':');
let path = parts.next().unwrap().into();
let range_or_anchor = parse_range_or_anchor(parts.next());
LinkType::RustdocInclude(path, range_or_anchor)
}
#[derive(PartialEq, Debug, Clone)]
struct Link<'a> {
start_index: usize,
end_index: usize,
link: LinkType<'a>,
link_type: LinkType<'a>,
link_text: &'a str,
}
@@ -184,6 +262,7 @@ impl<'a> Link<'a> {
match (typ.as_str(), file_arg) {
("include", Some(pth)) => Some(parse_include_path(pth)),
("playpen", Some(pth)) => Some(LinkType::Playpen(pth.into(), props)),
("rustdoc_include", Some(pth)) => Some(parse_rustdoc_include_path(pth)),
_ => None,
}
}
@@ -193,11 +272,11 @@ impl<'a> Link<'a> {
_ => None,
};
link_type.and_then(|lnk| {
link_type.and_then(|lnk_type| {
cap.get(0).map(|mat| Link {
start_index: mat.start(),
end_index: mat.end(),
link: lnk,
link_type: lnk_type,
link_text: mat.as_str(),
})
})
@@ -205,14 +284,17 @@ impl<'a> Link<'a> {
fn render_with_path<P: AsRef<Path>>(&self, base: P) -> Result<String> {
let base = base.as_ref();
match self.link {
match self.link_type {
// omit the escape char
LinkType::Escaped => Ok((&self.link_text[1..]).to_owned()),
LinkType::IncludeRange(ref pat, ref range) => {
LinkType::Include(ref pat, ref range_or_anchor) => {
let target = base.join(pat);
file_to_string(&target)
.map(|s| take_lines(&s, range.clone()))
fs::read_to_string(&target)
.map(|s| match range_or_anchor {
RangeOrAnchor::Range(range) => take_lines(&s, range.clone()),
RangeOrAnchor::Anchor(anchor) => take_anchored_lines(&s, anchor),
})
.chain_err(|| {
format!(
"Could not read file for link {} ({})",
@@ -221,11 +303,18 @@ impl<'a> Link<'a> {
)
})
}
LinkType::IncludeRangeFrom(ref pat, ref range) => {
LinkType::RustdocInclude(ref pat, ref range_or_anchor) => {
let target = base.join(pat);
file_to_string(&target)
.map(|s| take_lines(&s, range.clone()))
fs::read_to_string(&target)
.map(|s| match range_or_anchor {
RangeOrAnchor::Range(range) => {
take_rustdoc_include_lines(&s, range.clone())
}
RangeOrAnchor::Anchor(anchor) => {
take_rustdoc_include_anchored_lines(&s, anchor)
}
})
.chain_err(|| {
format!(
"Could not read file for link {} ({})",
@@ -234,34 +323,10 @@ impl<'a> Link<'a> {
)
})
}
LinkType::IncludeRangeTo(ref pat, ref range) => {
let target = base.join(pat);
file_to_string(&target)
.map(|s| take_lines(&s, *range))
.chain_err(|| {
format!(
"Could not read file for link {} ({})",
self.link_text,
target.display(),
)
})
}
LinkType::IncludeRangeFull(ref pat, _) => {
let target = base.join(pat);
file_to_string(&target).chain_err(|| {
format!(
"Could not read file for link {} ({})",
self.link_text,
target.display()
)
})
}
LinkType::Playpen(ref pat, ref attrs) => {
let target = base.join(pat);
let contents = file_to_string(&target).chain_err(|| {
let contents = fs::read_to_string(&target).chain_err(|| {
format!(
"Could not read file for link {} ({})",
self.link_text,
@@ -303,7 +368,7 @@ fn find_links(contents: &str) -> LinkIter<'_> {
\\\{\{\#.*\}\} # match escaped link
| # or
\{\{\s* # link opening parens and whitespace
\#([a-zA-Z0-9]+) # link type
\#([a-zA-Z0-9_]+) # link type
\s+ # separating whitespace
([a-zA-Z0-9\s_.\-:/\\]+) # link target path and space separated properties
\s*\}\} # whitespace and link closing parens"
@@ -373,13 +438,13 @@ mod tests {
Link {
start_index: 22,
end_index: 42,
link: LinkType::Playpen(PathBuf::from("file.rs"), vec![]),
link_type: LinkType::Playpen(PathBuf::from("file.rs"), vec![]),
link_text: "{{#playpen file.rs}}",
},
Link {
start_index: 47,
end_index: 68,
link: LinkType::Playpen(PathBuf::from("test.rs"), vec![]),
link_type: LinkType::Playpen(PathBuf::from("test.rs"), vec![]),
link_text: "{{#playpen test.rs }}",
},
]
@@ -396,7 +461,10 @@ mod tests {
vec![Link {
start_index: 22,
end_index: 48,
link: LinkType::IncludeRange(PathBuf::from("file.rs"), 9..20),
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(9..20))
),
link_text: "{{#include file.rs:10:20}}",
}]
);
@@ -412,7 +480,10 @@ mod tests {
vec![Link {
start_index: 22,
end_index: 45,
link: LinkType::IncludeRange(PathBuf::from("file.rs"), 9..10),
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(9..10))
),
link_text: "{{#include file.rs:10}}",
}]
);
@@ -428,7 +499,10 @@ mod tests {
vec![Link {
start_index: 22,
end_index: 46,
link: LinkType::IncludeRangeFrom(PathBuf::from("file.rs"), 9..),
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(9..))
),
link_text: "{{#include file.rs:10:}}",
}]
);
@@ -444,7 +518,10 @@ mod tests {
vec![Link {
start_index: 22,
end_index: 46,
link: LinkType::IncludeRangeTo(PathBuf::from("file.rs"), ..20),
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..20))
),
link_text: "{{#include file.rs::20}}",
}]
);
@@ -460,7 +537,10 @@ mod tests {
vec![Link {
start_index: 22,
end_index: 44,
link: LinkType::IncludeRangeFull(PathBuf::from("file.rs"), ..),
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..))
),
link_text: "{{#include file.rs::}}",
}]
);
@@ -476,12 +556,34 @@ mod tests {
vec![Link {
start_index: 22,
end_index: 42,
link: LinkType::IncludeRangeFull(PathBuf::from("file.rs"), ..),
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..))
),
link_text: "{{#include file.rs}}",
}]
);
}
#[test]
fn test_find_links_with_anchor() {
let s = "Some random text with {{#include file.rs:anchor}}...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
assert_eq!(
res,
vec![Link {
start_index: 22,
end_index: 49,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Anchor(String::from("anchor"))
),
link_text: "{{#include file.rs:anchor}}",
}]
);
}
#[test]
fn test_find_links_escaped_link() {
let s = "Some random text with escaped playpen \\{{#playpen file.rs editable}} ...";
@@ -494,7 +596,7 @@ mod tests {
vec![Link {
start_index: 38,
end_index: 68,
link: LinkType::Escaped,
link_type: LinkType::Escaped,
link_text: "\\{{#playpen file.rs editable}}",
}]
);
@@ -513,13 +615,13 @@ mod tests {
Link {
start_index: 38,
end_index: 68,
link: LinkType::Playpen(PathBuf::from("file.rs"), vec!["editable"]),
link_type: LinkType::Playpen(PathBuf::from("file.rs"), vec!["editable"]),
link_text: "{{#playpen file.rs editable }}",
},
Link {
start_index: 89,
end_index: 136,
link: LinkType::Playpen(
link_type: LinkType::Playpen(
PathBuf::from("my.rs"),
vec!["editable", "no_run", "should_panic"],
),
@@ -543,7 +645,10 @@ mod tests {
Link {
start_index: 38,
end_index: 58,
link: LinkType::IncludeRangeFull(PathBuf::from("file.rs"), ..),
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..))
),
link_text: "{{#include file.rs}}",
}
);
@@ -552,7 +657,7 @@ mod tests {
Link {
start_index: 63,
end_index: 112,
link: LinkType::Escaped,
link_type: LinkType::Escaped,
link_text: "\\{{#contents are insignifficant in escaped link}}",
}
);
@@ -561,7 +666,7 @@ mod tests {
Link {
start_index: 130,
end_index: 177,
link: LinkType::Playpen(
link_type: LinkType::Playpen(
PathBuf::from("my.rs"),
vec!["editable", "no_run", "should_panic"]
),
@@ -570,4 +675,183 @@ mod tests {
);
}
#[test]
fn parse_without_colon_includes_all() {
let link_type = parse_include_path("arbitrary");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(RangeFull))
)
);
}
#[test]
fn parse_with_nothing_after_colon_includes_all() {
let link_type = parse_include_path("arbitrary:");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(RangeFull))
)
);
}
#[test]
fn parse_with_two_colons_includes_all() {
let link_type = parse_include_path("arbitrary::");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(RangeFull))
)
);
}
#[test]
fn parse_with_garbage_after_two_colons_includes_all() {
let link_type = parse_include_path("arbitrary::NaN");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(RangeFull))
)
);
}
#[test]
fn parse_with_one_number_after_colon_only_that_line() {
let link_type = parse_include_path("arbitrary:5");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..5))
)
);
}
#[test]
fn parse_with_one_based_start_becomes_zero_based() {
let link_type = parse_include_path("arbitrary:1");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(0..1))
)
);
}
#[test]
fn parse_with_zero_based_start_stays_zero_based_but_is_probably_an_error() {
let link_type = parse_include_path("arbitrary:0");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(0..1))
)
);
}
#[test]
fn parse_start_only_range() {
let link_type = parse_include_path("arbitrary:5:");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..))
)
);
}
#[test]
fn parse_start_with_garbage_interpreted_as_start_only_range() {
let link_type = parse_include_path("arbitrary:5:NaN");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..))
)
);
}
#[test]
fn parse_end_only_range() {
let link_type = parse_include_path("arbitrary::5");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(..5))
)
);
}
#[test]
fn parse_start_and_end_range() {
let link_type = parse_include_path("arbitrary:5:10");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..10))
)
);
}
#[test]
fn parse_with_negative_interpreted_as_anchor() {
let link_type = parse_include_path("arbitrary:-5");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Anchor("-5".to_string())
)
);
}
#[test]
fn parse_with_floating_point_interpreted_as_anchor() {
let link_type = parse_include_path("arbitrary:-5.7");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Anchor("-5.7".to_string())
)
);
}
#[test]
fn parse_with_anchor_followed_by_colon() {
let link_type = parse_include_path("arbitrary:some-anchor:this-gets-ignored");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Anchor("some-anchor".to_string())
)
);
}
#[test]
fn parse_with_more_than_three_colons_ignores_everything_after_third_colon() {
let link_type = parse_include_path("arbitrary:5:10:17:anything:");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..10))
)
);
}
}

View File

@@ -6,6 +6,7 @@ use crate::renderer::{RenderContext, Renderer};
use crate::theme::{self, playpen_editor, Theme};
use crate::utils;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::fs;
@@ -33,12 +34,10 @@ impl HtmlHandlebars {
let content = ch.content.clone();
let content = utils::render_markdown(&content, ctx.html_config.curly_quotes);
let string_path = ch.path.parent().unwrap().display().to_string();
let fixed_content = utils::render_markdown_with_base(
let fixed_content = utils::render_markdown_with_path(
&ch.content,
ctx.html_config.curly_quotes,
&string_path,
Some(&ch.path),
);
print_content.push_str(&fixed_content);
@@ -62,7 +61,11 @@ impl HtmlHandlebars {
.get("book_title")
.and_then(serde_json::Value::as_str)
.unwrap_or("");
title = ch.name.clone() + " - " + book_title;
title = match book_title {
"" => ch.name.clone(),
_ => ch.name.clone() + " - " + book_title,
}
}
ctx.data.insert("path".to_owned(), json!(path));
@@ -73,6 +76,10 @@ impl HtmlHandlebars {
"path_to_root".to_owned(),
json!(utils::fs::path_to_root(&ch.path)),
);
if let Some(ref section) = ch.number {
ctx.data
.insert("section".to_owned(), json!(section.to_string()));
}
// Render the handlebars template with the data
debug!("Render template");
@@ -87,6 +94,7 @@ impl HtmlHandlebars {
if ctx.is_index {
ctx.data.insert("path".to_owned(), json!("index.md"));
ctx.data.insert("path_to_root".to_owned(), json!(""));
ctx.data.insert("is_index".to_owned(), json!("true"));
let rendered_index = ctx.handlebars.render("index", &ctx.data)?;
let rendered_index = self.post_process(rendered_index, &ctx.html_config.playpen);
debug!("Creating index.html from {}", path);
@@ -204,7 +212,7 @@ impl HtmlHandlebars {
);
}
fn register_hbs_helpers(&self, handlebars: &mut Handlebars, html_config: &HtmlConfig) {
fn register_hbs_helpers(&self, handlebars: &mut Handlebars<'_>, html_config: &HtmlConfig) {
handlebars.register_helper(
"toc",
Box::new(helpers::toc::RenderToc {
@@ -284,6 +292,11 @@ impl Renderer for HtmlHandlebars {
let destination = &ctx.destination;
let book = &ctx.book;
if destination.exists() {
utils::fs::remove_dir_content(destination)
.chain_err(|| "Unable to remove stale HTML output")?;
}
trace!("render");
let mut handlebars = Handlebars::new();
@@ -378,10 +391,12 @@ fn make_data(
html_config: &HtmlConfig,
) -> Result<serde_json::Map<String, serde_json::Value>> {
trace!("make_data");
let html = config.html_config().unwrap_or_default();
let mut data = serde_json::Map::new();
data.insert("language".to_owned(), json!("en"));
data.insert(
"language".to_owned(),
json!(config.book.language.clone().unwrap_or_default()),
);
data.insert(
"book_title".to_owned(),
json!(config.book.title.clone().unwrap_or_default()),
@@ -396,24 +411,33 @@ fn make_data(
}
let default_theme = match html_config.default_theme {
Some(ref theme) => theme,
None => "light",
Some(ref theme) => theme.to_lowercase(),
None => "light".to_string(),
};
data.insert("default_theme".to_owned(), json!(default_theme));
let preferred_dark_theme = match html_config.preferred_dark_theme {
Some(ref theme) => theme.to_lowercase(),
None => default_theme,
};
data.insert(
"preferred_dark_theme".to_owned(),
json!(preferred_dark_theme),
);
// Add google analytics tag
if let Some(ref ga) = config.html_config().and_then(|html| html.google_analytics) {
if let Some(ref ga) = html_config.google_analytics {
data.insert("google_analytics".to_owned(), json!(ga));
}
if html.mathjax_support {
if html_config.mathjax_support {
data.insert("mathjax_support".to_owned(), json!(true));
}
// Add check to see if there is an additional style
if !html.additional_css.is_empty() {
if !html_config.additional_css.is_empty() {
let mut css = Vec::new();
for style in &html.additional_css {
for style in &html_config.additional_css {
match style.strip_prefix(root) {
Ok(p) => css.push(p.to_str().expect("Could not convert to str")),
Err(_) => css.push(style.to_str().expect("Could not convert to str")),
@@ -423,9 +447,9 @@ fn make_data(
}
// Add check to see if there is an additional script
if !html.additional_js.is_empty() {
if !html_config.additional_js.is_empty() {
let mut js = Vec::new();
for script in &html.additional_js {
for script in &html_config.additional_js {
match script.strip_prefix(root) {
Ok(p) => js.push(p.to_str().expect("Could not convert to str")),
Err(_) => js.push(script.to_str().expect("Could not convert to str")),
@@ -434,9 +458,18 @@ fn make_data(
data.insert("additional_js".to_owned(), json!(js));
}
if html.playpen.editable && html.playpen.copy_js {
if html_config.playpen.editable && html_config.playpen.copy_js {
data.insert("playpen_js".to_owned(), json!(true));
if html_config.playpen.line_numbers {
data.insert("playpen_line_numbers".to_owned(), json!(true));
}
}
if html_config.playpen.copyable {
data.insert("playpen_copyable".to_owned(), json!(true));
}
data.insert("fold_enable".to_owned(), json!((html_config.fold.enable)));
data.insert("fold_level".to_owned(), json!((html_config.fold.level)));
let search = html_config.search.clone();
if cfg!(feature = "search") {
@@ -457,6 +490,7 @@ fn make_data(
if let Some(ref git_repository_url) = html_config.git_repository_url {
data.insert("git_repository_url".to_owned(), json!(git_repository_url));
}
let git_repository_icon = match html_config.git_repository_icon {
Some(ref git_repository_icon) => git_repository_icon,
None => "fa-github",
@@ -475,6 +509,11 @@ fn make_data(
chapter.insert("section".to_owned(), json!(section.to_string()));
}
chapter.insert(
"has_sub_items".to_owned(),
json!((!ch.sub_items.is_empty()).to_string()),
);
chapter.insert("name".to_owned(), json!(ch.name));
let path = ch
.path
@@ -573,26 +612,36 @@ fn add_playpen_pre(html: &str, playpen_config: &Playpen) -> String {
let classes = &caps[2];
let code = &caps[3];
if (classes.contains("language-rust")
&& !classes.contains("ignore")
&& !classes.contains("noplaypen"))
|| classes.contains("mdbook-runnable")
{
// wrap the contents in an external pre block
if playpen_config.editable && classes.contains("editable")
|| text.contains("fn main")
|| text.contains("quick_main!")
if classes.contains("language-rust") {
if (!classes.contains("ignore") && !classes.contains("noplaypen"))
|| classes.contains("mdbook-runnable")
{
format!("<pre class=\"playpen\">{}</pre>", text)
} else {
// we need to inject our own main
let (attrs, code) = partition_source(code);
// wrap the contents in an external pre block
format!(
"<pre class=\"playpen\"><code class=\"{}\">\n# \
#![allow(unused_variables)]\n{}#fn main() {{\n{}#}}</code></pre>",
classes, attrs, code
"<pre class=\"playpen\"><code class=\"{}\">{}</code></pre>",
classes,
{
let content: Cow<'_, str> = if playpen_config.editable
&& classes.contains("editable")
|| text.contains("fn main")
|| text.contains("quick_main!")
{
code.into()
} else {
// we need to inject our own main
let (attrs, code) = partition_source(code);
format!(
"\n# #![allow(unused_variables)]\n{}#fn main() {{\n{}#}}",
attrs, code
)
.into()
};
hide_lines(&content)
}
)
} else {
format!("<code class=\"{}\">{}</code>", classes, hide_lines(code))
}
} else {
// not language-rust, so no-op
@@ -602,6 +651,38 @@ fn add_playpen_pre(html: &str, playpen_config: &Playpen) -> String {
.into_owned()
}
lazy_static! {
static ref BORING_LINES_REGEX: Regex = Regex::new(r"^(\s*)#(.?)(.*)$").unwrap();
}
fn hide_lines(content: &str) -> String {
let mut result = String::with_capacity(content.len());
for line in content.lines() {
if let Some(caps) = BORING_LINES_REGEX.captures(line) {
if &caps[2] == "#" {
result += &caps[1];
result += &caps[2];
result += &caps[3];
result += "\n";
continue;
} else if &caps[2] != "!" && &caps[2] != "[" {
result += "<span class=\"boring\">";
result += &caps[1];
if &caps[2] != " " {
result += &caps[2];
}
result += &caps[3];
result += "\n";
result += "</span>";
continue;
}
}
result += line;
result += "\n";
}
result
}
fn partition_source(s: &str) -> (String, String) {
let mut after_header = false;
let mut before = String::new();
@@ -624,7 +705,7 @@ fn partition_source(s: &str) -> (String, String) {
}
struct RenderItemContext<'a> {
handlebars: &'a Handlebars,
handlebars: &'a Handlebars<'a>,
destination: PathBuf,
data: serde_json::Map<String, serde_json::Value>,
is_index: bool,
@@ -669,4 +750,34 @@ mod tests {
assert_eq!(got, should_be);
}
}
#[test]
fn add_playpen() {
let inputs = [
("<code class=\"language-rust\">x()</code>",
"<pre class=\"playpen\"><code class=\"language-rust\">\n<span class=\"boring\">#![allow(unused_variables)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
("<code class=\"language-rust\">fn main() {}</code>",
"<pre class=\"playpen\"><code class=\"language-rust\">fn main() {}\n</code></pre>"),
("<code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code>",
"<pre class=\"playpen\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";\n</code></pre>"),
("<code class=\"language-rust editable\">let s = \"foo\n ## bar\n\";</code>",
"<pre class=\"playpen\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n\";\n</code></pre>"),
("<code class=\"language-rust editable\">let s = \"foo\n # bar\n#\n\";</code>",
"<pre class=\"playpen\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span><span class=\"boring\">\n</span>\";\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 editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code>",
"<pre class=\"playpen\"><code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]\n</code></pre>"),
];
for (src, should_be) in &inputs {
let got = add_playpen_pre(
src,
&Playpen {
editable: true,
..Playpen::default()
},
);
assert_eq!(&*got, *should_be);
}
}
}

View File

@@ -46,22 +46,44 @@ impl Target {
fn find_chapter(
ctx: &Context,
rc: &mut RenderContext<'_>,
rc: &mut RenderContext<'_, '_>,
target: Target,
) -> Result<Option<StringMap>, RenderError> {
debug!("Get data from context");
let chapters = rc.evaluate_absolute(ctx, "chapters", true).and_then(|c| {
serde_json::value::from_value::<Vec<StringMap>>(c.clone())
let chapters = rc.evaluate(ctx, "@root/chapters").and_then(|c| {
serde_json::value::from_value::<Vec<StringMap>>(c.as_json().clone())
.map_err(|_| RenderError::new("Could not decode the JSON data"))
})?;
let base_path = rc
.evaluate_absolute(ctx, "path", true)?
.evaluate(ctx, "@root/path")?
.as_json()
.as_str()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", "");
if !rc.evaluate(ctx, "@root/is_index")?.is_missing() {
// Special case for index.md which may be a synthetic page.
// Target::find won't match because there is no page with the path
// "index.md" (unless there really is an index.md in SUMMARY.md).
match target {
Target::Previous => return Ok(None),
Target::Next => match chapters
.iter()
.filter(|chapter| {
// Skip things like "spacer"
chapter.contains_key("path")
})
.skip(1)
.next()
{
Some(chapter) => return Ok(Some(chapter.clone())),
None => return Ok(None),
},
}
}
let mut previous: Option<StringMap> = None;
debug!("Search for chapter");
@@ -86,9 +108,9 @@ fn find_chapter(
fn render(
_h: &Helper<'_, '_>,
r: &Handlebars,
r: &Handlebars<'_>,
ctx: &Context,
rc: &mut RenderContext<'_>,
rc: &mut RenderContext<'_, '_>,
out: &mut dyn Output,
chapter: &StringMap,
) -> Result<(), RenderError> {
@@ -96,7 +118,8 @@ fn render(
let mut context = BTreeMap::new();
let base_path = rc
.evaluate_absolute(ctx, "path", false)?
.evaluate(ctx, "@root/path")?
.as_json()
.as_str()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", "");
@@ -127,7 +150,7 @@ fn render(
_h.template()
.ok_or_else(|| RenderError::new("Error with the handlebars template"))
.and_then(|t| {
let mut local_rc = rc.new_for_block();
let mut local_rc = rc.clone();
let local_ctx = Context::wraps(&context)?;
t.render(r, &local_ctx, &mut local_rc, out)
})?;
@@ -137,9 +160,9 @@ fn render(
pub fn previous(
_h: &Helper<'_, '_>,
r: &Handlebars,
r: &Handlebars<'_>,
ctx: &Context,
rc: &mut RenderContext<'_>,
rc: &mut RenderContext<'_, '_>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
trace!("previous (handlebars helper)");
@@ -153,9 +176,9 @@ pub fn previous(
pub fn next(
_h: &Helper<'_, '_>,
r: &Handlebars,
r: &Handlebars<'_>,
ctx: &Context,
rc: &mut RenderContext<'_>,
rc: &mut RenderContext<'_, '_>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
trace!("next (handlebars helper)");

View File

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

View File

@@ -16,24 +16,48 @@ impl HelperDef for RenderToc {
fn call<'reg: 'rc, 'rc>(
&self,
_h: &Helper<'reg, 'rc>,
_r: &'reg Handlebars,
_r: &'reg Handlebars<'_>,
ctx: &'rc Context,
rc: &mut RenderContext<'reg>,
rc: &mut RenderContext<'reg, 'rc>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
// get value from context data
// rc.get_path() is current json parent path, you should always use it like this
// param is the key of value you want to display
let chapters = rc.evaluate_absolute(ctx, "chapters", true).and_then(|c| {
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
let chapters = rc.evaluate(ctx, "@root/chapters").and_then(|c| {
serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.as_json().clone())
.map_err(|_| RenderError::new("Could not decode the JSON data"))
})?;
let current = rc
.evaluate_absolute(ctx, "path", true)?
let current_path = rc
.evaluate(ctx, "@root/path")?
.as_json()
.as_str()
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.ok_or(RenderError::new("Type error for `path`, string expected"))?
.replace("\"", "");
let current_section = rc
.evaluate(ctx, "@root/section")?
.as_json()
.as_str()
.map(str::to_owned)
.unwrap_or_default();
let fold_enable = rc
.evaluate(ctx, "@root/fold_enable")?
.as_json()
.as_bool()
.ok_or(RenderError::new(
"Type error for `fold_enable`, bool expected",
))?;
let fold_level = rc
.evaluate(ctx, "@root/fold_level")?
.as_json()
.as_u64()
.ok_or(RenderError::new(
"Type error for `fold_level`, u64 expected",
))?;
out.write("<ol class=\"chapter\">")?;
let mut current_level = 1;
@@ -45,10 +69,23 @@ impl HelperDef for RenderToc {
continue;
}
let level = if let Some(s) = item.get("section") {
s.matches('.').count()
let (section, level) = if let Some(s) = item.get("section") {
(s.as_str(), s.matches('.').count())
} else {
1
("", 1)
};
let is_expanded = {
if !fold_enable {
// Disable fold. Expand all chapters.
true
} else if !section.is_empty() && current_section.starts_with(section) {
// The section is ancestor or the current section itself.
true
} else {
// Levels that are larger than this would be folded.
level - 1 < fold_level as usize
}
};
if level > current_level {
@@ -57,20 +94,16 @@ impl HelperDef for RenderToc {
out.write("<ol class=\"section\">")?;
current_level += 1;
}
out.write("<li>")?;
write_li_open_tag(out, is_expanded, false)?;
} else if level < current_level {
while level < current_level {
out.write("</ol>")?;
out.write("</li>")?;
current_level -= 1;
}
out.write("<li>")?;
write_li_open_tag(out, is_expanded, false)?;
} else {
out.write("<li")?;
if item.get("section").is_none() {
out.write(" class=\"affix\"")?;
}
out.write(">")?;
write_li_open_tag(out, is_expanded, item.get("section").is_none())?;
}
// Link
@@ -86,11 +119,11 @@ impl HelperDef for RenderToc {
.replace("\\", "/");
// Add link
out.write(&utils::fs::path_to_root(&current))?;
out.write(&utils::fs::path_to_root(&current_path))?;
out.write(&tmp)?;
out.write("\"")?;
if path == &current {
if path == &current_path {
out.write(" class=\"active\"")?;
}
@@ -117,7 +150,7 @@ impl HelperDef for RenderToc {
// filter all events that are not inline code blocks
let parser = Parser::new(name).filter(|event| match *event {
Event::Code(_) | Event::InlineHtml(_) | Event::Text(_) => true,
Event::Code(_) | Event::Html(_) | Event::Text(_) => true,
_ => false,
});
@@ -133,6 +166,13 @@ impl HelperDef for RenderToc {
out.write("</a>")?;
}
// Render expand/collapse toggle
if let Some(flag) = item.get("has_sub_items") {
let has_sub_items = flag.parse::<bool>().unwrap_or_default();
if fold_enable && has_sub_items {
out.write("<a class=\"toggle\"><div>❱</div></a>")?;
}
}
out.write("</li>")?;
}
while current_level > 1 {
@@ -145,3 +185,19 @@ impl HelperDef for RenderToc {
Ok(())
}
}
fn write_li_open_tag(
out: &mut dyn Output,
is_expanded: bool,
is_affix: bool,
) -> Result<(), std::io::Error> {
let mut li = String::from("<li class=\"");
if is_expanded {
li.push_str("expanded ");
}
if is_affix {
li.push_str("affix ");
}
li.push_str("\">");
out.write(&li)
}

View File

@@ -81,22 +81,21 @@ fn render_item(
.chain_err(|| "Could not convert HTML path to str")?;
let anchor_base = utils::fs::normalize_path(filepath);
let p = utils::new_cmark_parser(&chapter.content);
let mut p = utils::new_cmark_parser(&chapter.content).peekable();
let mut in_header = false;
let max_section_depth = i32::from(search_config.heading_split_level);
let mut in_heading = false;
let max_section_depth = u32::from(search_config.heading_split_level);
let mut section_id = None;
let mut heading = String::new();
let mut body = String::new();
let mut html_block = String::new();
let mut breadcrumbs = chapter.parent_names.clone();
let mut footnote_numbers = HashMap::new();
for event in p {
while let Some(event) = p.next() {
match event {
Event::Start(Tag::Header(i)) if i <= max_section_depth => {
Event::Start(Tag::Heading(i)) if i <= max_section_depth => {
if !heading.is_empty() {
// Section finished, the next header is following now
// Section finished, the next heading is following now
// Write the data to the index, and clear it for the next section
add_doc(
index,
@@ -111,10 +110,10 @@ fn render_item(
breadcrumbs.pop();
}
in_header = true;
in_heading = true;
}
Event::End(Tag::Header(i)) if i <= max_section_depth => {
in_header = false;
Event::End(Tag::Heading(i)) if i <= max_section_depth => {
in_heading = false;
section_id = Some(utils::id_from_content(&heading));
breadcrumbs.push(heading.clone());
}
@@ -123,31 +122,34 @@ fn render_item(
footnote_numbers.entry(name).or_insert(number);
}
Event::Html(html) => {
html_block.push_str(&html);
}
Event::End(Tag::HtmlBlock) => {
let mut html_block = html.into_string();
// As of pulldown_cmark 0.6, html events are no longer contained
// in an HtmlBlock tag. We must collect consecutive Html events
// into a block ourselves.
while let Some(Event::Html(html)) = p.peek() {
html_block.push_str(&html);
p.next();
}
body.push_str(&clean_html(&html_block));
html_block.clear();
}
Event::Start(_) | Event::End(_) | Event::SoftBreak | Event::HardBreak => {
Event::Start(_) | Event::End(_) | Event::Rule | Event::SoftBreak | Event::HardBreak => {
// Insert spaces where HTML output would usually seperate text
// to ensure words don't get merged together
if in_header {
if in_heading {
heading.push(' ');
} else {
body.push(' ');
}
}
Event::Text(text) | Event::Code(text) => {
if in_header {
if in_heading {
heading.push_str(&text);
} else {
body.push_str(&text);
}
}
Event::InlineHtml(html) => {
body.push_str(&clean_html(&html));
}
Event::FootnoteReference(name) => {
let len = footnote_numbers.len() + 1;
let number = footnote_numbers.entry(name).or_insert(len);

View File

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

View File

@@ -8,12 +8,14 @@
//!
//! The definition for [RenderContext] may be useful though.
//!
//! [For Developers]: https://rust-lang-nursery.github.io/mdBook/for_developers/index.html
//! [For Developers]: https://rust-lang.github.io/mdBook/for_developers/index.html
//! [RenderContext]: struct.RenderContext.html
pub use self::html_handlebars::HtmlHandlebars;
pub use self::markdown_renderer::MarkdownRenderer;
mod html_handlebars;
mod markdown_renderer;
use shlex::Shlex;
use std::fs;

View File

@@ -12,8 +12,7 @@ Original by Dempfi (https://github.com/dempfi/ayu)
}
.hljs-comment,
.hljs-quote,
.hljs-meta {
.hljs-quote {
color: #5c6773;
font-style: italic;
}
@@ -30,6 +29,7 @@ Original by Dempfi (https://github.com/dempfi/ayu)
}
.hljs-number,
.hljs-meta,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,

View File

@@ -16,9 +16,6 @@ function playpen_text(playpen) {
}
(function codeSnippets() {
// Hide Rust code lines prepended with a specific character
var hiding_character = "#";
function fetch_with_timeout(url, options, timeout = 6000) {
return Promise.race([
fetch(url, options),
@@ -55,6 +52,15 @@ function playpen_text(playpen) {
editor.addEventListener("change", function (e) {
update_play_button(playpen_block, playground_crates);
});
// add Ctrl-Enter command to execute rust code
editor.commands.addCommand({
name: "run",
bindKey: {
win: "Ctrl-Enter",
mac: "Ctrl-Enter"
},
exec: _editor => run_rust_code(playpen_block)
});
}
}
}
@@ -161,95 +167,59 @@ function playpen_text(playpen) {
Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) {
var code_block = block;
var pre_block = block.parentNode;
// hide lines
var lines = code_block.innerHTML.split("\n");
var first_non_hidden_line = false;
var lines_hidden = false;
var trimmed_line = "";
for (var n = 0; n < lines.length; n++) {
trimmed_line = lines[n].trim();
if (trimmed_line[0] == hiding_character && trimmed_line[1] != hiding_character) {
if (first_non_hidden_line) {
lines[n] = "<span class=\"hidden\">" + "\n" + lines[n].replace(/(\s*)# ?/, "$1") + "</span>";
}
else {
lines[n] = "<span class=\"hidden\">" + lines[n].replace(/(\s*)# ?/, "$1") + "\n" + "</span>";
}
lines_hidden = true;
}
else if (first_non_hidden_line) {
lines[n] = "\n" + lines[n];
}
else {
first_non_hidden_line = true;
}
if (trimmed_line[0] == hiding_character && trimmed_line[1] == hiding_character) {
lines[n] = lines[n].replace("##", "#")
}
}
code_block.innerHTML = lines.join("");
var lines = Array.from(block.querySelectorAll('.boring'));
// If no lines were hidden, return
if (!lines_hidden) { return; }
if (!lines.length) { return; }
block.classList.add("hide-boring");
var buttons = document.createElement('div');
buttons.className = 'buttons';
buttons.innerHTML = "<button class=\"fa fa-expand\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>";
// add expand button
var pre_block = block.parentNode;
pre_block.insertBefore(buttons, pre_block.firstChild);
pre_block.querySelector('.buttons').addEventListener('click', function (e) {
if (e.target.classList.contains('fa-expand')) {
var lines = pre_block.querySelectorAll('span.hidden');
e.target.classList.remove('fa-expand');
e.target.classList.add('fa-compress');
e.target.title = 'Hide lines';
e.target.setAttribute('aria-label', e.target.title);
Array.from(lines).forEach(function (line) {
line.classList.remove('hidden');
line.classList.add('unhidden');
});
block.classList.remove('hide-boring');
} else if (e.target.classList.contains('fa-compress')) {
var lines = pre_block.querySelectorAll('span.unhidden');
e.target.classList.remove('fa-compress');
e.target.classList.add('fa-expand');
e.target.title = 'Show hidden lines';
e.target.setAttribute('aria-label', e.target.title);
Array.from(lines).forEach(function (line) {
line.classList.remove('unhidden');
line.classList.add('hidden');
});
block.classList.add('hide-boring');
}
});
});
Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
var pre_block = block.parentNode;
if (!pre_block.classList.contains('playpen')) {
var buttons = pre_block.querySelector(".buttons");
if (!buttons) {
buttons = document.createElement('div');
buttons.className = 'buttons';
pre_block.insertBefore(buttons, pre_block.firstChild);
if (window.playpen_copyable) {
Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
var pre_block = block.parentNode;
if (!pre_block.classList.contains('playpen')) {
var buttons = pre_block.querySelector(".buttons");
if (!buttons) {
buttons = document.createElement('div');
buttons.className = 'buttons';
pre_block.insertBefore(buttons, pre_block.firstChild);
}
var clipButton = document.createElement('button');
clipButton.className = 'fa fa-copy clip-button';
clipButton.title = 'Copy to clipboard';
clipButton.setAttribute('aria-label', clipButton.title);
clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
buttons.insertBefore(clipButton, buttons.firstChild);
}
var clipButton = document.createElement('button');
clipButton.className = 'fa fa-copy clip-button';
clipButton.title = 'Copy to clipboard';
clipButton.setAttribute('aria-label', clipButton.title);
clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
buttons.insertBefore(clipButton, buttons.firstChild);
}
});
});
}
// Process playpen code blocks
Array.from(document.querySelectorAll(".playpen")).forEach(function (pre_block) {
@@ -267,19 +237,21 @@ function playpen_text(playpen) {
runCodeButton.title = 'Run this code';
runCodeButton.setAttribute('aria-label', runCodeButton.title);
var copyCodeClipboardButton = document.createElement('button');
copyCodeClipboardButton.className = 'fa fa-copy clip-button';
copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
copyCodeClipboardButton.title = 'Copy to clipboard';
copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
buttons.insertBefore(runCodeButton, buttons.firstChild);
buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
runCodeButton.addEventListener('click', function (e) {
run_rust_code(pre_block);
});
if (window.playpen_copyable) {
var copyCodeClipboardButton = document.createElement('button');
copyCodeClipboardButton.className = 'fa fa-copy clip-button';
copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
copyCodeClipboardButton.title = 'Copy to clipboard';
copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
}
let code_block = pre_block.querySelector("code");
if (window.ace && code_block.classList.contains("editable")) {
var undoChangesButton = document.createElement('button');
@@ -321,7 +293,7 @@ function playpen_text(playpen) {
themeToggleButton.focus();
}
function set_theme(theme) {
function set_theme(theme, store = true) {
let ace_theme;
if (theme == 'coal' || theme == 'navy') {
@@ -356,9 +328,10 @@ function playpen_text(playpen) {
try { previousTheme = localStorage.getItem('mdbook-theme'); } catch (e) { }
if (previousTheme === null || previousTheme === undefined) { previousTheme = default_theme; }
try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
if (store) {
try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
}
document.body.className = theme;
html.classList.remove(previousTheme);
html.classList.add(theme);
}
@@ -368,7 +341,7 @@ function playpen_text(playpen) {
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
set_theme(theme);
set_theme(theme, false);
themeToggleButton.addEventListener('click', function () {
if (themePopup.style.display === 'block') {
@@ -390,7 +363,7 @@ function playpen_text(playpen) {
}
});
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang-nursery/mdBook/issues/628
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628
document.addEventListener('click', function(e) {
if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {
hideThemes();
@@ -435,6 +408,7 @@ function playpen_text(playpen) {
(function sidebar() {
var html = document.querySelector("html");
var sidebar = document.getElementById("sidebar");
var sidebarScrollBox = document.querySelector(".sidebar-scrollbox");
var sidebarLinks = document.querySelectorAll('#sidebar a');
var sidebarToggleButton = document.getElementById("sidebar-toggle");
var sidebarResizeHandle = document.getElementById("sidebar-resize-handle");
@@ -451,6 +425,17 @@ function playpen_text(playpen) {
try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }
}
var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle');
function toggleSection(ev) {
ev.currentTarget.parentElement.classList.toggle('expanded');
}
Array.from(sidebarAnchorToggles).forEach(function (el) {
el.addEventListener('click', toggleSection);
});
function hideSidebar() {
html.classList.remove('sidebar-visible')
html.classList.add('sidebar-hidden');
@@ -520,9 +505,9 @@ function playpen_text(playpen) {
}, { passive: true });
// Scroll sidebar to current active section
var activeSection = sidebar.querySelector(".active");
var activeSection = document.getElementById("sidebar").querySelector(".active");
if (activeSection) {
sidebar.scrollTop = activeSection.offsetTop;
sidebarScrollBox.scrollTop = activeSection.offsetTop;
}
})();
@@ -595,26 +580,60 @@ function playpen_text(playpen) {
});
})();
(function autoHideMenu() {
(function controllMenu() {
var menu = document.getElementById('menu-bar');
var previousScrollTop = document.scrollingElement.scrollTop;
document.addEventListener('scroll', function () {
if (menu.classList.contains('folded') && document.scrollingElement.scrollTop < previousScrollTop) {
menu.classList.remove('folded');
} else if (!menu.classList.contains('folded') && document.scrollingElement.scrollTop > previousScrollTop) {
menu.classList.add('folded');
}
if (!menu.classList.contains('bordered') && document.scrollingElement.scrollTop > 0) {
menu.classList.add('bordered');
}
if (menu.classList.contains('bordered') && document.scrollingElement.scrollTop === 0) {
menu.classList.remove('bordered');
}
previousScrollTop = document.scrollingElement.scrollTop;
}, { passive: true });
(function controllPosition() {
var scrollTop = document.scrollingElement.scrollTop;
var prevScrollTop = scrollTop;
var minMenuY = -menu.clientHeight - 50;
// When the script loads, the page can be at any scroll (e.g. if you reforesh it).
menu.style.top = scrollTop + 'px';
// Same as parseInt(menu.style.top.slice(0, -2), but faster
var topCache = menu.style.top.slice(0, -2);
menu.classList.remove('sticky');
var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster
document.addEventListener('scroll', function () {
scrollTop = Math.max(document.scrollingElement.scrollTop, 0);
// `null` means that it doesn't need to be updated
var nextSticky = null;
var nextTop = null;
var scrollDown = scrollTop > prevScrollTop;
var menuPosAbsoluteY = topCache - scrollTop;
if (scrollDown) {
nextSticky = false;
if (menuPosAbsoluteY > 0) {
nextTop = prevScrollTop;
}
} else {
if (menuPosAbsoluteY > 0) {
nextSticky = true;
} else if (menuPosAbsoluteY < minMenuY) {
nextTop = prevScrollTop + minMenuY;
}
}
if (nextSticky === true && stickyCache === false) {
menu.classList.add('sticky');
stickyCache = true;
} else if (nextSticky === false && stickyCache === true) {
menu.classList.remove('sticky');
stickyCache = false;
}
if (nextTop !== null) {
menu.style.top = nextTop + 'px';
topCache = nextTop;
}
prevScrollTop = scrollTop;
}, { passive: true });
})();
(function controllBorder() {
menu.classList.remove('bordered');
document.addEventListener('scroll', function () {
if (menu.offsetTop === 0) {
menu.classList.remove('bordered');
} else {
menu.classList.add('bordered');
}
}, { passive: true });
})();
})();

View File

@@ -8,7 +8,9 @@
::-webkit-scrollbar-thumb {
background: var(--scrollbar);
}
html {
scrollbar-color: var(--scrollbar) var(--bg);
}
#searchresults a,
.content a:link,
a:visited,
@@ -18,14 +20,13 @@ a > .hljs {
/* Menu Bar */
#menu-bar {
position: -webkit-sticky;
position: sticky;
top: 0;
#menu-bar,
#menu-bar-hover-placeholder {
z-index: 101;
margin: auto calc(0px - var(--page-padding));
}
#menu-bar > #menu-bar-sticky-container {
#menu-bar {
position: relative;
display: flex;
flex-wrap: wrap;
background-color: var(--bg);
@@ -33,17 +34,28 @@ a > .hljs {
border-bottom-width: 1px;
border-bottom-style: solid;
}
.js #menu-bar > #menu-bar-sticky-container {
transition: transform 0.3s;
#menu-bar.sticky,
.js #menu-bar-hover-placeholder:hover + #menu-bar,
.js #menu-bar:hover,
.js.sidebar-visible #menu-bar {
position: -webkit-sticky;
position: sticky;
top: 0 !important;
}
#menu-bar.bordered > #menu-bar-sticky-container {
#menu-bar-hover-placeholder {
position: sticky;
position: -webkit-sticky;
top: 0;
height: var(--menu-bar-height);
}
#menu-bar.bordered {
border-bottom-color: var(--table-border-color);
}
#menu-bar i, #menu-bar .icon-button {
position: relative;
padding: 0 8px;
z-index: 10;
line-height: 50px;
line-height: var(--menu-bar-height);
cursor: pointer;
transition: color 0.5s;
}
@@ -70,10 +82,6 @@ a > .hljs {
text-decoration: none;
}
html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-container {
transform: translateY(-60px);
}
.left-buttons {
display: flex;
margin: 0 5px;
@@ -85,8 +93,8 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
.menu-title {
display: inline-block;
font-weight: 200;
font-size: 20px;
line-height: 50px;
font-size: 2rem;
line-height: var(--menu-bar-height);
text-align: center;
margin: 0;
flex: 1;
@@ -124,7 +132,7 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
text-decoration: none;
position: fixed;
top: 50px; /* Height of menu-bar */
top: 0;
bottom: 0;
margin: 0;
max-width: 150px;
@@ -135,10 +143,14 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
align-content: center;
flex-direction: column;
transition: color 0.5s;
transition: color 0.5s, background-color 0.5s;
}
.nav-chapters:hover { text-decoration: none; }
.nav-chapters:hover {
text-decoration: none;
background-color: var(--theme-hover);
transition: background-color 0.15s, color 0.15s;
}
.nav-wrapper {
margin-top: 50px;
@@ -176,8 +188,7 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
/* Inline code */
:not(pre) > .hljs {
display: inline-block;
vertical-align: middle;
display: inline;
padding: 0.1em 0.3em;
border-radius: 3px;
}
@@ -370,7 +381,13 @@ ul#searchresults span.teaser em {
padding-left: 0;
line-height: 2.2em;
}
.chapter ol {
width: 100%;
}
.chapter li {
display: flex;
color: var(--sidebar-non-existant);
}
.chapter li a {
@@ -384,10 +401,37 @@ ul#searchresults span.teaser em {
color: var(--sidebar-active);
}
.chapter li .active {
.chapter li a.active {
color: var(--sidebar-active);
}
.chapter li > a.toggle {
cursor: pointer;
display: block;
margin-left: auto;
padding: 0 10px;
user-select: none;
opacity: 0.68;
}
.chapter li > a.toggle div {
transition: transform 0.5s;
}
/* collapse the section */
.chapter li:not(.expanded) + li > ol {
display: none;
}
.chapter li.expanded {
line-height: 1.5em;
margin-top: 0.6em;
}
.chapter li.expanded > a.toggle div {
transform: rotate(90deg);
}
.spacer {
width: 100%;
height: 3px;
@@ -413,7 +457,7 @@ ul#searchresults span.teaser em {
.theme-popup {
position: absolute;
left: 10px;
top: 50px;
top: var(--menu-bar-height);
z-index: 1000;
border-radius: 4px;
font-size: 0.7em;

View File

@@ -2,6 +2,11 @@
@import 'variables.css';
:root {
/* Browser default font-size is 16px, this way 1 rem = 10px */
font-size: 62.5%;
}
html {
font-family: "Open Sans", sans-serif;
color: var(--fg);
@@ -11,19 +16,20 @@ html {
body {
margin: 0;
font-size: 1rem;
font-size: 1.6rem;
overflow-x: hidden;
}
code {
font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace;
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 */
}
.left { float: left; }
.right { float: right; }
.boring { opacity: 0.6; }
.hide-boring .boring { display: none; }
.hidden { display: none; }
.play-button.hidden { display: none; }
h2, h3 { margin-top: 2.5em; }
h4, h5 { margin-top: 2em; }
@@ -44,9 +50,17 @@ h4 a.header:target::before {
width: 30px;
}
h1 a.header:target,
h2 a.header:target,
h3 a.header:target,
h4 a.header:target {
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 */
}
.page-wrapper {
box-sizing: border-box;
@@ -65,6 +79,9 @@ h4 a.header:target::before {
margin-right: auto;
max-width: var(--content-max-width);
}
.content p { line-height: 1.45em; }
.content ol { line-height: 1.45em; }
.content ul { line-height: 1.45em; }
.content a { text-decoration: none; }
.content a:hover { text-decoration: underline; }
.content img { max-width: 100%; }
@@ -92,6 +109,9 @@ table thead td {
font-weight: 700;
border: none;
}
table thead th {
padding: 3px 20px;
}
table thead tr {
border: 1px var(--table-header-bg) solid;
}

View File

@@ -5,6 +5,7 @@
--sidebar-width: 300px;
--page-padding: 15px;
--content-max-width: 750px;
--menu-bar-height: 50px;
}
/* Themes */
@@ -208,3 +209,45 @@
--searchresults-li-bg: #dec2a2;
--search-mark-bg: #e69f67;
}
@media (prefers-color-scheme: dark) {
.light.no-js {
--bg: hsl(200, 7%, 8%);
--fg: #98a3ad;
--sidebar-bg: #292c2f;
--sidebar-fg: #a1adb8;
--sidebar-non-existant: #505254;
--sidebar-active: #3473ad;
--sidebar-spacer: #393939;
--scrollbar: var(--sidebar-fg);
--icons: #43484d;
--icons-hover: #b3c0cc;
--links: #2b79a2;
--inline-code-color: #c5c8c6;;
--theme-popup-bg: #141617;
--theme-popup-border: #43484d;
--theme-hover: #1f2124;
--quote-bg: hsl(234, 21%, 18%);
--quote-border: hsl(234, 21%, 23%);
--table-border-color: hsl(200, 7%, 13%);
--table-header-bg: hsl(200, 7%, 28%);
--table-alternate-bg: hsl(200, 7%, 11%);
--searchbar-border-color: #aaa;
--searchbar-bg: #b7b7b7;
--searchbar-fg: #000;
--searchbar-shadow-color: #aaa;
--searchresults-header-fg: #666;
--searchresults-border-color: #98a3ad;
--searchresults-li-bg: #2b2b2f;
--search-mark-bg: #355c7d;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
<!DOCTYPE HTML>
<html lang="{{ language }}" class="sidebar-visible no-js">
<html lang="{{ language }}" class="sidebar-visible no-js {{ default_theme }}">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
@@ -39,11 +39,11 @@
<script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
{{/if}}
</head>
<body class="{{ default_theme }}">
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "{{ path_to_root }}";
var default_theme = "{{ default_theme }}";
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 -->
@@ -67,8 +67,11 @@
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
document.body.className = theme;
document.querySelector('html').className = theme + ' js';
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('{{ default_theme }}')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
@@ -84,7 +87,7 @@
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
{{#toc}}{{/toc}}
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
@@ -94,41 +97,40 @@
<div class="page">
{{> header}}
<div id="menu-bar" class="menu-bar">
<div id="menu-bar-sticky-container">
<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">
<i class="fa fa-bars"></i>
</button>
<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>
</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">
<i class="fa fa-search"></i>
</button>
{{/if}}
</div>
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky bordered">
<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">
<i class="fa fa-bars"></i>
</button>
<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>
</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">
<i class="fa fa-search"></i>
</button>
{{/if}}
</div>
<h1 class="menu-title">{{ book_title }}</h1>
<h1 class="menu-title">{{ book_title }}</h1>
<div class="right-buttons">
<a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
{{#if git_repository_url}}
<a href="{{git_repository_url}}" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
</a>
{{/if}}
</div>
<div class="right-buttons">
<a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
{{#if git_repository_url}}
<a href="{{git_repository_url}}" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
</a>
{{/if}}
</div>
</div>
@@ -180,13 +182,13 @@
<nav class="nav-wide-wrapper" aria-label="Page navigation">
{{#previous}}
<a href="{{ path_to_root }}{{link}}" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<a rel="prev" href="{{ path_to_root }}{{link}}" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
{{/previous}}
{{#next}}
<a href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next" 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}}
@@ -201,7 +203,7 @@
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload(true); // force reload from server (not from cache)
location.reload();
}
};
@@ -230,6 +232,18 @@
</script>
{{/if}}
{{#if playpen_line_numbers}}
<script type="text/javascript">
window.playpen_line_numbers = true;
</script>
{{/if}}
{{#if playpen_copyable}}
<script type="text/javascript">
window.playpen_copyable = true;
</script>
{{/if}}
{{#if playpen_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>

View File

@@ -6,12 +6,14 @@ window.editors = [];
}
Array.from(document.querySelectorAll('.editable')).forEach(function(editable) {
let display_line_numbers = window.playpen_line_numbers || false;
let editor = ace.edit(editable);
editor.setOptions({
highlightActiveLine: false,
showPrintMargin: false,
showLineNumbers: false,
showGutter: false,
showLineNumbers: display_line_numbers,
showGutter: display_line_numbers,
maxLines: Infinity,
fontSize: "0.875em" // please adjust the font size of the code in general.css
});

View File

@@ -1,22 +1,9 @@
use crate::errors::*;
use std::convert::Into;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::io::Write;
use std::path::{Component, Path, PathBuf};
/// Takes a path to a file and try to read the file into a String
pub fn file_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
let path = path.as_ref();
let mut content = String::new();
File::open(path)
.chain_err(|| "Unable to open the file")?
.read_to_string(&mut content)
.chain_err(|| "Unable to read the file")?;
Ok(content)
}
/// Naively replaces any path seperator with a forward-slash '/'
pub fn normalize_path(path: &str) -> String {
use std::path::is_separator;
@@ -50,8 +37,8 @@ pub fn write_file<P: AsRef<Path>>(build_dir: &Path, filename: P, content: &[u8])
///
/// **note:** it's not very fool-proof, if you find a situation where
/// it doesn't return the correct path.
/// Consider [submitting a new issue](https://github.com/rust-lang-nursery/mdBook/issues)
/// or a [pull-request](https://github.com/rust-lang-nursery/mdBook/pulls) to improve it.
/// 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
@@ -122,6 +109,15 @@ pub fn copy_files_except_ext(
return Ok(());
}
// Is the destination inside of the source?
if to.canonicalize()?.starts_with(from.canonicalize()?) {
return Err(Error::from(format!(
"Destination directory cannot be contained in source directory: '{}' is in '{}'",
to.display(),
from.display()
)));
}
for entry in fs::read_dir(from)? {
let entry = entry?;
let metadata = entry.metadata()?;
@@ -190,63 +186,81 @@ mod tests {
use std::fs;
#[test]
fn copy_files_except_ext_test() {
fn it_fails_when_destination_is_in_source() {
let tmp = match tempfile::TempDir::new() {
Ok(t) => t,
Err(e) => panic!("Could not create a temp dir: {}", e),
};
let dst = tmp.path().join("destination");
fs::create_dir(&dst).unwrap();
assert!(
format!("{:?}", copy_files_except_ext(tmp.path(), &dst, true, &[]))
.contains("Destination directory cannot be contained in source directory: ")
);
}
#[test]
fn copy_files_except_ext_test() {
let src = match tempfile::TempDir::new() {
Ok(t) => t,
Err(e) => panic!("Could not create a temp dir: {}", e),
};
// Create a couple of files
if let Err(err) = fs::File::create(&tmp.path().join("file.txt")) {
if let Err(err) = fs::File::create(&src.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(&src.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(&src.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(&src.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(&src.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(&src.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(&src.path().join("sub_dir_exists/file.txt")) {
panic!("Could not create sub_dir_exists/file.txt: {}", err);
}
// Create output dir
if let Err(err) = fs::create_dir(&tmp.path().join("output")) {
let dst = match tempfile::TempDir::new() {
Ok(t) => t,
Err(e) => panic!("Could not create a temp dir: {}", e),
};
if let Err(err) = fs::create_dir(&dst.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(&dst.path().join("output/sub_dir_exists")) {
panic!("Could not create output/sub_dir_exists: {}", err);
}
if let Err(e) =
copy_files_except_ext(&tmp.path(), &tmp.path().join("output"), true, &["md"])
copy_files_except_ext(&src.path(), &dst.path().join("output"), true, &["md"])
{
panic!("Error while executing the function:\n{:?}", e);
}
// Check if the correct files where created
if !(&tmp.path().join("output/file.txt")).exists() {
if !(&dst.path().join("output/file.txt")).exists() {
panic!("output/file.txt should exist")
}
if (&tmp.path().join("output/file.md")).exists() {
if (&dst.path().join("output/file.md")).exists() {
panic!("output/file.md should not exist")
}
if !(&tmp.path().join("output/file.png")).exists() {
if !(&dst.path().join("output/file.png")).exists() {
panic!("output/file.png should exist")
}
if !(&tmp.path().join("output/sub_dir/file.png")).exists() {
if !(&dst.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 !(&dst.path().join("output/sub_dir_exists/file.txt")).exists() {
panic!("output/sub_dir/file.png should exist")
}
}

View File

@@ -8,8 +8,13 @@ use regex::Regex;
use pulldown_cmark::{html, CowStr, Event, Options, Parser, Tag};
use std::borrow::Cow;
use std::fmt::Write;
use std::path::Path;
pub use self::string::{take_lines, RangeArgument};
pub use self::string::{
take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines,
take_rustdoc_include_lines,
};
/// Replaces multiple consecutive whitespace characters with a single space character.
pub fn collapse_whitespace(text: &str) -> Cow<'_, str> {
@@ -65,20 +70,47 @@ pub fn id_from_content(content: &str) -> String {
normalize_id(trimmed)
}
fn adjust_links<'a>(event: Event<'a>, with_base: &str) -> Event<'a> {
/// Fix links to the correct location.
///
/// This adjusts links, such as turning `.md` extensions to `.html`.
///
/// `path` is the path to the page being rendered relative to the root of the
/// book. This is used for the `print.html` page so that links on the print
/// page go to the original location. Normal page rendering sets `path` to
/// 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();
}
fn fix<'a>(dest: CowStr<'a>, base: &str) -> CowStr<'a> {
fn fix<'a>(dest: CowStr<'a>, path: Option<&Path>) -> CowStr<'a> {
if dest.starts_with('#') {
// Fragment-only link.
if let Some(path) = path {
let mut base = path.display().to_string();
if base.ends_with(".md") {
base.replace_range(base.len() - 3.., ".html");
}
return format!("{}{}", base, dest).into();
} else {
return dest;
}
}
// Don't modify links with schemes like `https`.
if !SCHEME_LINK.is_match(&dest) {
// This is a relative link, adjust it as necessary.
let mut fixed_link = String::new();
if !base.is_empty() {
fixed_link.push_str(base);
fixed_link.push_str("/");
if let Some(path) = path {
let base = path
.parent()
.expect("path can't be empty")
.to_str()
.expect("utf-8 paths only");
if !base.is_empty() {
write!(fixed_link, "{}/", base).unwrap();
}
}
if let Some(caps) = MD_LINK.captures(&dest) {
@@ -95,20 +127,44 @@ fn adjust_links<'a>(event: Event<'a>, with_base: &str) -> Event<'a> {
dest
}
fn fix_html<'a>(html: CowStr<'a>, path: Option<&Path>) -> CowStr<'a> {
// This is a terrible hack, but should be reasonably reliable. Nobody
// should ever parse a tag with a regex. However, there isn't anything
// in Rust that I know of that is suitable for handling partial html
// fragments like those generated by pulldown_cmark.
//
// 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();
}
HTML_LINK
.replace_all(&html, |caps: &regex::Captures<'_>| {
let fixed = fix(caps[2].into(), path);
format!("{}{}\"", &caps[1], fixed)
})
.into_owned()
.into()
}
match event {
Event::Start(Tag::Link(link_type, dest, title)) => {
Event::Start(Tag::Link(link_type, fix(dest, with_base), title))
Event::Start(Tag::Link(link_type, fix(dest, path), title))
}
Event::Start(Tag::Image(link_type, dest, title)) => {
Event::Start(Tag::Image(link_type, fix(dest, with_base), title))
Event::Start(Tag::Image(link_type, fix(dest, path), title))
}
Event::Html(html) => Event::Html(fix_html(html, path)),
_ => event,
}
}
/// Wrapper around the pulldown-cmark parser for rendering markdown to HTML.
pub fn render_markdown(text: &str, curly_quotes: bool) -> String {
render_markdown_with_base(text, curly_quotes, "")
render_markdown_with_path(text, curly_quotes, None)
}
pub fn new_cmark_parser(text: &str) -> Parser<'_> {
@@ -120,13 +176,13 @@ pub fn new_cmark_parser(text: &str) -> Parser<'_> {
Parser::new_ext(text, opts)
}
pub fn render_markdown_with_base(text: &str, curly_quotes: bool, base: &str) -> String {
pub fn render_markdown_with_path(text: &str, curly_quotes: bool, path: Option<&Path>) -> String {
let mut s = String::with_capacity(text.len() * 3 / 2);
let p = new_cmark_parser(text);
let mut converter = EventQuoteConverter::new(curly_quotes);
let events = p
.map(clean_codeblock_headers)
.map(|event| adjust_links(event, base))
.map(|event| adjust_links(event, path))
.map(|event| converter.convert(event));
html::push_html(&mut s, events);
@@ -302,8 +358,7 @@ more text with spaces
```
"#;
let expected =
r#"<pre><code class="language-rust,no_run,should_panic,property_3"></code></pre>
let expected = r#"<pre><code class="language-rust,no_run,should_panic,property_3"></code></pre>
"#;
assert_eq!(render_markdown(input, false), expected);
assert_eq!(render_markdown(input, true), expected);
@@ -316,8 +371,7 @@ more text with spaces
```
"#;
let expected =
r#"<pre><code class="language-rust,no_run,,,should_panic,,property_3"></code></pre>
let expected = r#"<pre><code class="language-rust,no_run,,,should_panic,,property_3"></code></pre>
"#;
assert_eq!(render_markdown(input, false), expected);
assert_eq!(render_markdown(input, true), expected);
@@ -394,18 +448,12 @@ more text with spaces
#[test]
fn it_converts_single_quotes() {
assert_eq!(
convert_quotes_to_curly("'one', 'two'"),
"one, two"
);
assert_eq!(convert_quotes_to_curly("'one', 'two'"), "one, two");
}
#[test]
fn it_converts_double_quotes() {
assert_eq!(
convert_quotes_to_curly(r#""one", "two""#),
"“one”, “two”"
);
assert_eq!(convert_quotes_to_curly(r#""one", "two""#), "“one”, “two”");
}
#[test]

View File

@@ -1,63 +1,124 @@
use itertools::Itertools;
use std::ops::{Range, RangeFrom, RangeFull, RangeTo};
// This trait is already contained in the standard lib, however it is unstable.
// TODO: Remove when the `collections_range` feature stabilises
// (https://github.com/rust-lang/rust/issues/30877)
pub trait RangeArgument<T: ?Sized> {
fn start(&self) -> Option<&T>;
fn end(&self) -> Option<&T>;
}
impl<T: ?Sized> RangeArgument<T> for RangeFull {
fn start(&self) -> Option<&T> {
None
}
fn end(&self) -> Option<&T> {
None
}
}
impl<T> RangeArgument<T> for RangeFrom<T> {
fn start(&self) -> Option<&T> {
Some(&self.start)
}
fn end(&self) -> Option<&T> {
None
}
}
impl<T> RangeArgument<T> for RangeTo<T> {
fn start(&self) -> Option<&T> {
None
}
fn end(&self) -> Option<&T> {
Some(&self.end)
}
}
impl<T> RangeArgument<T> for Range<T> {
fn start(&self) -> Option<&T> {
Some(&self.start)
}
fn end(&self) -> Option<&T> {
Some(&self.end)
}
}
use regex::Regex;
use std::ops::Bound::{Excluded, Included, Unbounded};
use std::ops::RangeBounds;
/// Take a range of lines from a string.
pub fn take_lines<R: RangeArgument<usize>>(s: &str, range: R) -> String {
let start = *range.start().unwrap_or(&0);
pub fn take_lines<R: RangeBounds<usize>>(s: &str, range: R) -> String {
let start = match range.start_bound() {
Excluded(&n) => n + 1,
Included(&n) => n,
Unbounded => 0,
};
let mut lines = s.lines().skip(start);
match range.end() {
Some(&end) => lines.take(end.saturating_sub(start)).join("\n"),
None => lines.join("\n"),
match range.end_bound() {
Excluded(end) => lines.take(end.saturating_sub(start)).join("\n"),
Included(end) => lines.take((end + 1).saturating_sub(start)).join("\n"),
Unbounded => lines.join("\n"),
}
}
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();
}
/// Take anchored lines from a string.
/// Lines containing anchor are ignored.
pub fn take_anchored_lines(s: &str, anchor: &str) -> String {
let mut retained = Vec::<&str>::new();
let mut anchor_found = false;
for l in s.lines() {
if anchor_found {
match ANCHOR_END.captures(l) {
Some(cap) => {
if &cap["anchor_name"] == anchor {
break;
}
}
None => {
if !ANCHOR_START.is_match(l) {
retained.push(l);
}
}
}
} else {
if let Some(cap) = ANCHOR_START.captures(l) {
if &cap["anchor_name"] == anchor {
anchor_found = true;
}
}
}
}
retained.join("\n")
}
/// Keep lines contained within the range specified as-is.
/// For any lines not in the range, include them but use `#` at the beginning. This will hide the
/// lines from initial display but include them when expanding the code snippet or testing with
/// rustdoc.
pub fn take_rustdoc_include_lines<R: RangeBounds<usize>>(s: &str, range: R) -> String {
let mut output = String::with_capacity(s.len());
for (index, line) in s.lines().enumerate() {
if !range.contains(&index) {
output.push_str("# ");
}
output.push_str(line);
output.push_str("\n");
}
output.pop();
output
}
/// Keep lines between the anchor comments specified as-is.
/// For any lines not between the anchors, include them but use `#` at the beginning. This will
/// hide the lines from initial display but include them when expanding the code snippet or testing
/// with rustdoc.
pub fn take_rustdoc_include_anchored_lines(s: &str, anchor: &str) -> String {
let mut output = String::with_capacity(s.len());
let mut within_anchored_section = false;
for l in s.lines() {
if within_anchored_section {
match ANCHOR_END.captures(l) {
Some(cap) => {
if &cap["anchor_name"] == anchor {
within_anchored_section = false;
}
}
None => {
if !ANCHOR_START.is_match(l) {
output.push_str(l);
output.push_str("\n");
}
}
}
} else {
if let Some(cap) = ANCHOR_START.captures(l) {
if &cap["anchor_name"] == anchor {
within_anchored_section = true;
}
} else if !ANCHOR_END.is_match(l) {
output.push_str("# ");
output.push_str(l);
output.push_str("\n");
}
}
}
output.pop();
output
}
#[cfg(test)]
mod tests {
use super::take_lines;
use super::{
take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines,
take_rustdoc_include_lines,
};
#[test]
fn take_lines_test() {
@@ -70,4 +131,122 @@ mod tests {
assert_eq!(take_lines(s, 4..3), "");
assert_eq!(take_lines(s, ..100), s);
}
#[test]
fn take_anchored_lines_test() {
let s = "Lorem\nipsum\ndolor\nsit\namet";
assert_eq!(take_anchored_lines(s, "test"), "");
let s = "Lorem\nipsum\ndolor\nANCHOR_END: test\nsit\namet";
assert_eq!(take_anchored_lines(s, "test"), "");
let s = "Lorem\nipsum\nANCHOR: test\ndolor\nsit\namet";
assert_eq!(take_anchored_lines(s, "test"), "dolor\nsit\namet");
assert_eq!(take_anchored_lines(s, "something"), "");
let s = "Lorem\nipsum\nANCHOR: test\ndolor\nsit\namet\nANCHOR_END: test\nlorem\nipsum";
assert_eq!(take_anchored_lines(s, "test"), "dolor\nsit\namet");
assert_eq!(take_anchored_lines(s, "something"), "");
let s = "Lorem\nANCHOR: test\nipsum\nANCHOR: test\ndolor\nsit\namet\nANCHOR_END: test\nlorem\nipsum";
assert_eq!(take_anchored_lines(s, "test"), "ipsum\ndolor\nsit\namet");
assert_eq!(take_anchored_lines(s, "something"), "");
let s = "Lorem\nANCHOR: test2\nipsum\nANCHOR: test\ndolor\nsit\namet\nANCHOR_END: test\nlorem\nANCHOR_END:test2\nipsum";
assert_eq!(
take_anchored_lines(s, "test2"),
"ipsum\ndolor\nsit\namet\nlorem"
);
assert_eq!(take_anchored_lines(s, "test"), "dolor\nsit\namet");
assert_eq!(take_anchored_lines(s, "something"), "");
}
#[test]
fn take_rustdoc_include_lines_test() {
let s = "Lorem\nipsum\ndolor\nsit\namet";
assert_eq!(
take_rustdoc_include_lines(s, 1..3),
"# Lorem\nipsum\ndolor\n# sit\n# amet"
);
assert_eq!(
take_rustdoc_include_lines(s, 3..),
"# Lorem\n# ipsum\n# dolor\nsit\namet"
);
assert_eq!(
take_rustdoc_include_lines(s, ..3),
"Lorem\nipsum\ndolor\n# sit\n# amet"
);
assert_eq!(take_rustdoc_include_lines(s, ..), s);
// corner cases
assert_eq!(
take_rustdoc_include_lines(s, 4..3),
"# Lorem\n# ipsum\n# dolor\n# sit\n# amet"
);
assert_eq!(take_rustdoc_include_lines(s, ..100), s);
}
#[test]
fn take_rustdoc_include_anchored_lines_test() {
let s = "Lorem\nipsum\ndolor\nsit\namet";
assert_eq!(
take_rustdoc_include_anchored_lines(s, "test"),
"# Lorem\n# ipsum\n# dolor\n# sit\n# amet"
);
let s = "Lorem\nipsum\ndolor\nANCHOR_END: test\nsit\namet";
assert_eq!(
take_rustdoc_include_anchored_lines(s, "test"),
"# Lorem\n# ipsum\n# dolor\n# sit\n# amet"
);
let s = "Lorem\nipsum\nANCHOR: test\ndolor\nsit\namet";
assert_eq!(
take_rustdoc_include_anchored_lines(s, "test"),
"# Lorem\n# ipsum\ndolor\nsit\namet"
);
assert_eq!(
take_rustdoc_include_anchored_lines(s, "something"),
"# Lorem\n# ipsum\n# dolor\n# sit\n# amet"
);
let s = "Lorem\nipsum\nANCHOR: test\ndolor\nsit\namet\nANCHOR_END: test\nlorem\nipsum";
assert_eq!(
take_rustdoc_include_anchored_lines(s, "test"),
"# Lorem\n# ipsum\ndolor\nsit\namet\n# lorem\n# ipsum"
);
assert_eq!(
take_rustdoc_include_anchored_lines(s, "something"),
"# Lorem\n# ipsum\n# dolor\n# sit\n# amet\n# lorem\n# ipsum"
);
let s = "Lorem\nANCHOR: test\nipsum\nANCHOR: test\ndolor\nsit\namet\nANCHOR_END: test\nlorem\nipsum";
assert_eq!(
take_rustdoc_include_anchored_lines(s, "test"),
"# Lorem\nipsum\ndolor\nsit\namet\n# lorem\n# ipsum"
);
assert_eq!(
take_rustdoc_include_anchored_lines(s, "something"),
"# Lorem\n# ipsum\n# dolor\n# sit\n# amet\n# lorem\n# ipsum"
);
let s = "Lorem\nANCHOR: test2\nipsum\nANCHOR: test\ndolor\nsit\namet\nANCHOR_END: test\nlorem\nANCHOR_END:test2\nipsum";
assert_eq!(
take_rustdoc_include_anchored_lines(s, "test2"),
"# Lorem\nipsum\ndolor\nsit\namet\nlorem\n# ipsum"
);
assert_eq!(
take_rustdoc_include_anchored_lines(s, "test"),
"# Lorem\n# ipsum\ndolor\nsit\namet\n# lorem\n# ipsum"
);
assert_eq!(
take_rustdoc_include_anchored_lines(s, "something"),
"# Lorem\n# ipsum\n# dolor\n# sit\n# amet\n# lorem\n# ipsum"
);
let s = "Lorem\nANCHOR: test\nipsum\nANCHOR_END: test\ndolor\nANCHOR: test\nsit\nANCHOR_END: test\namet";
assert_eq!(
take_rustdoc_include_anchored_lines(s, "test"),
"# Lorem\nipsum\n# dolor\nsit\n# amet"
);
}
}

View File

@@ -5,7 +5,6 @@
#![allow(dead_code, unused_variables, unused_imports, unused_extern_crates)]
use mdbook::errors::*;
use mdbook::utils::fs::file_to_string;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::Path;
@@ -53,7 +52,13 @@ impl DummyBook {
})?;
let sub_pattern = if self.passing_test { "true" } else { "false" };
let files_containing_tests = ["src/first/nested.md", "src/first/nested-test.rs"];
let files_containing_tests = [
"src/first/nested.md",
"src/first/nested-test.rs",
"src/first/nested-test-with-anchors.rs",
"src/first/partially-included-test.rs",
"src/first/partially-included-test-with-anchors.rs",
];
for file in &files_containing_tests {
let path_containing_tests = temp.path().join(file);
replace_pattern_in_file(&path_containing_tests, "$TEST_STATUS", sub_pattern)?;
@@ -64,7 +69,7 @@ impl DummyBook {
}
fn replace_pattern_in_file(filename: &Path, from: &str, to: &str) -> Result<()> {
let contents = file_to_string(filename)?;
let contents = fs::read_to_string(filename)?;
File::create(filename)?.write_all(contents.replace(from, to).as_bytes())?;
Ok(())
@@ -74,7 +79,7 @@ fn replace_pattern_in_file(filename: &Path, from: &str, to: &str) -> Result<()>
/// the list of strings asserting that the file contains all of them.
pub fn assert_contains_strings<P: AsRef<Path>>(filename: P, strings: &[&str]) {
let filename = filename.as_ref();
let content = file_to_string(filename).expect("Couldn't read the file's contents");
let content = fs::read_to_string(filename).expect("Couldn't read the file's contents");
for s in strings {
assert!(
@@ -89,7 +94,7 @@ pub fn assert_contains_strings<P: AsRef<Path>>(filename: P, strings: &[&str]) {
pub fn assert_doesnt_contain_strings<P: AsRef<Path>>(filename: P, strings: &[&str]) {
let filename = filename.as_ref();
let content = file_to_string(filename).expect("Couldn't read the file's contents");
let content = fs::read_to_string(filename).expect("Couldn't read the file's contents");
for s in strings {
assert!(

View File

@@ -1,6 +1,9 @@
# Summary
[Dummy Book](README.md)
---
[Introduction](intro.md)
- [First Chapter](first/index.md)
@@ -8,6 +11,7 @@
- [Includes](first/includes.md)
- [Recursive](first/recursive.md)
- [Markdown](first/markdown.md)
- [Unicode](first/unicode.md)
- [Second Chapter](second.md)
- [Nested Chapter](second/nested.md)

View File

@@ -0,0 +1,11 @@
// The next line will cause a `testing` test to fail if the anchor feature is broken in such a way
// that the whole file gets mistakenly included.
assert!(!$TEST_STATUS);
// ANCHOR: myanchor
// ANCHOR: unendinganchor
// The next line will cause a `rendered_output` test to fail if the anchor feature is broken in
// such a way that the content between anchors isn't included.
// unique-string-for-anchor-test
assert!($TEST_STATUS);
// ANCHOR_END: myanchor

View File

@@ -11,3 +11,21 @@ assert!($TEST_STATUS);
```rust
{{#include nested-test.rs}}
```
## Anchors include the part of a file between special comments
```rust
{{#include nested-test-with-anchors.rs:myanchor}}
```
## Rustdoc include adds the rest of the file as hidden
```rust
{{#rustdoc_include partially-included-test.rs:5:7}}
```
## Rustdoc include works with anchors too
```rust
{{#rustdoc_include partially-included-test-with-anchors.rs:rustdoc-include-anchor}}
```

View File

@@ -0,0 +1,11 @@
fn some_other_function() {
// ANCHOR: unused-anchor-that-should-be-stripped
assert!($TEST_STATUS);
// ANCHOR_END: unused-anchor-that-should-be-stripped
}
// ANCHOR: rustdoc-include-anchor
fn main() {
some_other_function();
}
// ANCHOR_END: rustdoc-include-anchor

View File

@@ -0,0 +1,7 @@
fn some_function() {
assert!($TEST_STATUS);
}
fn main() {
some_function();
}

View File

@@ -0,0 +1,21 @@
# Unicode stress tests
Please be careful editing, this contains carefully crafted characters.
Two byte character: spatiëring
Combining character: spatiëring
Three byte character: 书こんにちは
Four byte character: 𐌀‮𐌁‮𐌂‮𐌃‮𐌄‮𐌅‮𐌆‮𐌇‮𐌈‬
Right-to-left: مرحبا
Emoticons: 🔊 😍 💜 1
right-to-left mark: hello באמת!
Zalgo: ǫ̛̖̱̗̝͈̋͒͋̏ͥͫ̒̆ͩ̏͌̾͊͐ͪ̾̚

View File

@@ -3,6 +3,14 @@
When we link to [the first section](../first/nested.md), it should work on
both the print page and the non-print page.
A [fragment link](#some-section) should work.
Link [outside](../../std/foo/bar.html).
![Some image](../images/picture.png)
<a href="../first/markdown.md">HTML Link</a>
<img src="../images/picture.png" alt="raw html">
## Some section

View File

@@ -7,7 +7,7 @@ use crate::dummy_book::{assert_contains_strings, assert_doesnt_contain_strings,
use mdbook::config::Config;
use mdbook::errors::*;
use mdbook::utils::fs::{file_to_string, write_file};
use mdbook::utils::fs::write_file;
use mdbook::MDBook;
use select::document::Document;
use select::predicate::{Class, Name, Predicate};
@@ -31,6 +31,7 @@ const TOC_SECOND_LEVEL: &[&str] = &[
"1.2. Includes",
"1.3. Recursive",
"1.4. Markdown",
"1.5. Unicode",
"2.1. Nested Chapter",
];
@@ -124,6 +125,9 @@ fn check_correct_relative_links_in_print_page() {
r##"<a href="second/../first/nested.html">the first section</a>,"##,
r##"<a href="second/../../std/foo/bar.html">outside</a>"##,
r##"<img src="second/../images/picture.png" alt="Some image" />"##,
r##"<a href="second/nested.html#some-section">fragment link</a>"##,
r##"<a href="second/../first/markdown.html">HTML Link</a>"##,
r##"<img src="second/../images/picture.png" alt="raw html">"##,
],
);
}
@@ -143,6 +147,35 @@ fn rendered_code_has_playpen_stuff() {
assert_contains_strings(book_js, &[".playpen"]);
}
#[test]
fn anchors_include_text_between_but_not_anchor_comments() {
let temp = DummyBook::new().build().unwrap();
let md = MDBook::load(temp.path()).unwrap();
md.build().unwrap();
let nested = temp.path().join("book/first/nested.html");
let text_between_anchors = vec!["unique-string-for-anchor-test"];
let anchor_text = vec!["ANCHOR"];
assert_contains_strings(nested.clone(), &text_between_anchors);
assert_doesnt_contain_strings(nested, &anchor_text);
}
#[test]
fn rustdoc_include_hides_the_unspecified_part_of_the_file() {
let temp = DummyBook::new().build().unwrap();
let md = MDBook::load(temp.path()).unwrap();
md.build().unwrap();
let nested = temp.path().join("book/first/nested.html");
let text = vec![
"<span class=\"boring\">fn some_function() {",
"<span class=\"boring\">fn some_other_function() {",
];
assert_contains_strings(nested, &text);
}
#[test]
fn chapter_content_appears_in_rendered_document() {
let content = vec![
@@ -220,7 +253,7 @@ fn root_index_html() -> Result<Document> {
.chain_err(|| "Book building failed")?;
let index_page = temp.path().join("book").join("index.html");
let html = file_to_string(&index_page).chain_err(|| "Unable to read index.html")?;
let html = fs::read_to_string(&index_page).chain_err(|| "Unable to read index.html")?;
Ok(Document::from(html.as_str()))
}
@@ -231,7 +264,12 @@ fn check_second_toc_level() {
let mut should_be = Vec::from(TOC_SECOND_LEVEL);
should_be.sort();
let pred = descendants!(Class("chapter"), Name("li"), Name("li"), Name("a"));
let pred = descendants!(
Class("chapter"),
Name("li"),
Name("li"),
Name("a").and(Class("toggle").not())
);
let mut children_of_children: Vec<_> = doc
.find(pred)
@@ -250,7 +288,11 @@ fn check_first_toc_level() {
should_be.extend(TOC_SECOND_LEVEL);
should_be.sort();
let pred = descendants!(Class("chapter"), Name("li"), Name("a"));
let pred = descendants!(
Class("chapter"),
Name("li"),
Name("a").and(Class("toggle").not())
);
let mut children: Vec<_> = doc
.find(pred)
@@ -264,7 +306,7 @@ fn check_first_toc_level() {
#[test]
fn check_spacers() {
let doc = root_index_html().unwrap();
let should_be = 1;
let should_be = 2;
let num_spacers = doc
.find(Class("chapter").descendant(Name("li").and(Class("spacer"))))
@@ -448,12 +490,15 @@ fn markdown_options() {
"<td>bim</td>",
],
);
assert_contains_strings(&path, &[
r##"<sup class="footnote-reference"><a href="#1">1</a></sup>"##,
r##"<sup class="footnote-reference"><a href="#word">2</a></sup>"##,
r##"<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>"##,
r##"<div class="footnote-definition" id="word"><sup class="footnote-definition-label">2</sup>"##,
]);
assert_contains_strings(
&path,
&[
r##"<sup class="footnote-reference"><a href="#1">1</a></sup>"##,
r##"<sup class="footnote-reference"><a href="#word">2</a></sup>"##,
r##"<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>"##,
r##"<div class="footnote-definition" id="word"><sup class="footnote-definition-label">2</sup>"##,
],
);
assert_contains_strings(&path, &["<del>strikethrough example</del>"]);
assert_contains_strings(
&path,
@@ -468,14 +513,13 @@ fn markdown_options() {
#[cfg(feature = "search")]
mod search {
use crate::dummy_book::DummyBook;
use mdbook::utils::fs::file_to_string;
use mdbook::MDBook;
use std::fs::File;
use std::fs::{self, File};
use std::path::Path;
fn read_book_index(root: &Path) -> serde_json::Value {
let index = root.join("book/searchindex.js");
let index = file_to_string(index).unwrap();
let index = fs::read_to_string(index).unwrap();
let index = index.trim_start_matches("Object.assign(window.search, ");
let index = index.trim_end_matches(");");
serde_json::from_str(&index).unwrap()
@@ -511,7 +555,7 @@ mod search {
assert_eq!(docs[&some_section]["body"], "");
assert_eq!(
docs[&summary]["body"],
"Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Second Chapter Nested Chapter Conclusion"
"Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode Second Chapter Nested Chapter Conclusion"
);
assert_eq!(docs[&summary]["breadcrumbs"], "First Chapter » Summary");
assert_eq!(docs[&conclusion]["body"], "I put &lt;HTML&gt; in here!");

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,12 @@ fn mdbook_can_correctly_test_a_passing_book() {
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
let mut md = MDBook::load(temp.path()).unwrap();
assert!(md.test(vec![]).is_ok());
let result = md.test(vec![]);
assert!(
result.is_ok(),
"Tests failed with {}",
result.err().unwrap()
);
}
#[test]