Compare commits

..

63 Commits

Author SHA1 Message Date
Eric Huss
94b922d27a Merge pull request #2389 from ehuss/bump-version
Update to 0.4.40
2024-05-17 01:35:51 +00:00
Eric Huss
0a8f9a3f6b Update to 0.4.40 2024-05-16 18:29:30 -07:00
Eric Huss
a5d4d1e0ad Merge pull request #2386 from ehuss/revert-pulldown-cmark
Revert update to pulldown-cmark 0.11
2024-05-17 01:19:17 +00:00
Eric Huss
2bdb5866a7 Add comment not to update pulldown-cmark. 2024-05-16 18:10:56 -07:00
Eric Huss
65932289f7 Revert "Merge pull request #2381 from ehuss/update-pulldown-cmark"
This reverts commit 8884008b4d,
(https://github.com/rust-lang/mdBook/pull/2381) reversing
changes made to 3d6caa504f.

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-05 16:16:31 +00:00
Eric Huss
bd323fb930 Merge pull request #2339 from goodmost/master
chore: remove repetitive word
2024-03-19 15:46:26 +00:00
goodmost
aff1070f43 chore: remove repetitive word
Signed-off-by: goodmost <zhaohaiyang@outlook.com>
2024-03-19 22:22:16 +08:00
Eric Huss
b6742e90b1 Merge pull request #2338 from max-heller/patch-2
Fix typo in docs
2024-03-18 22:59:48 +00:00
Max Heller
95b6ed7965 Fix typo in docs 2024-03-18 18:38:55 -04:00
Eric Huss
5a35144d4f Merge pull request #2328 from ehuss/clarify-src-path
Clarify Chapter path and source_path.
2024-02-25 23:26:20 +00:00
Eric Huss
5f5f9d6fd5 Clarify Chapter path and source_path. 2024-02-25 15:20:19 -08:00
Eric Huss
c602a2fcd6 Merge pull request #2070 from expikr/testbook-add-mathajx-tests
Added missing tests for MathJax to the test book
2024-02-25 23:00:13 +00:00
_
821d3c423c Add MathJax tests. 2024-02-25 14:53:10 -08:00
Eric Huss
6b89f5dad8 Merge pull request #2327 from ehuss/smart-punctuation
Rename curly-quotes to smart-punctuation.
2024-02-25 22:30:25 +00:00
Eric Huss
d28cf53009 Rename curly-quotes to smart-punctuation. 2024-02-25 13:42:44 -08:00
Eric Huss
504900d7bd Merge pull request #2324 from ehuss/redundant-imports
Fix redundant imports.
2024-02-24 20:19:28 +00:00
Eric Huss
0cc439eee3 Fix redundant imports. 2024-02-24 12:04:57 -08:00
Eric Huss
e8b8f34f2b Merge pull request #2322 from wilwade/patch-1
Fix incorrect theme documentation: Next/Previous should use `title`
2024-02-21 21:25:18 +00:00
Wil Wade
58a23e06a1 Fix incorrect theme documentation
The theme documentation for next and previous used name instead of title
2024-02-20 15:29:30 -05:00
KFears
4b6813ecee fix: remove double imports of css 2023-12-06 22:15:24 +04:00
Stephen Checkoway
32687e64fe Color test output and shorten chapter paths
Currently, the output from `rustdoc --test` is not colored because
`rustdoc`'s stdout is not a tty. The output of a failed `rustdoc` run is
sent to `mdbook`'s stderr via the `error!()` macro. This commit checks
if stderr is a tty using the standard `.is_terminal()` and if so, passes
`--color always` to `rustdoc`.

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

error: aborting due to previous error
```

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

error: aborting due to previous error
```
(with colors, of course).
2023-12-06 12:09:07 -05:00
47 changed files with 1383 additions and 675 deletions

View File

@@ -12,16 +12,9 @@ permissions:
jobs:
release:
name: Deploy Release
runs-on: ${{ matrix.os }}
strategy:
matrix:
target:
- aarch64-unknown-linux-musl
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
- x86_64-apple-darwin
- x86_64-pc-windows-msvc
include:
- target: aarch64-unknown-linux-musl
os: ubuntu-20.04
@@ -33,8 +26,9 @@ jobs:
os: macos-latest
- target: x86_64-pc-windows-msvc
os: windows-latest
name: Deploy ${{ matrix.target }}
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v4
- name: Install Rust
run: ci/install-rust.sh stable ${{ matrix.target }}
- name: Build asset
@@ -47,7 +41,7 @@ jobs:
name: GitHub Pages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v4
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable
- name: Build book
@@ -64,7 +58,7 @@ jobs:
name: Publish to crates.io
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v4
- name: Install Rust (rustup)
run: rustup update stable --no-self-update && rustup default stable
- name: Publish

View File

@@ -5,45 +5,67 @@ on:
jobs:
test:
name: Test
runs-on: ${{ matrix.os }}
strategy:
matrix:
build: [stable, beta, nightly, macos, windows, msrv]
include:
- build: stable
- name: stable linux
os: ubuntu-latest
rust: stable
- build: beta
target: x86_64-unknown-linux-gnu
- name: beta linux
os: ubuntu-latest
rust: beta
- build: nightly
target: x86_64-unknown-linux-gnu
- name: nightly linux
os: ubuntu-latest
rust: nightly
- build: macos
target: x86_64-unknown-linux-gnu
- name: stable x86_64-unknown-linux-musl
os: ubuntu-20.04
rust: stable
target: x86_64-unknown-linux-musl
- name: stable x86_64 macos
os: macos-latest
rust: stable
- build: windows
target: x86_64-apple-darwin
- name: stable aarch64 macos
os: macos-latest
rust: stable
target: aarch64-apple-darwin
- name: stable windows-msvc
os: windows-latest
rust: stable
- build: msrv
target: x86_64-pc-windows-msvc
- name: msrv
os: ubuntu-20.04
# sync MSRV with docs: guide/src/guide/installation.md and Cargo.toml
rust: 1.71.0
rust: 1.74.0
target: x86_64-unknown-linux-gnu
name: ${{ matrix.name }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install Rust
run: bash ci/install-rust.sh ${{ matrix.rust }}
run: bash ci/install-rust.sh ${{ matrix.rust }} ${{ matrix.target }}
- name: Build and run tests
run: cargo test --locked
run: cargo test --locked --target ${{ matrix.target }}
- name: Test no default
run: cargo test --no-default-features
run: cargo test --no-default-features --target ${{ matrix.target }}
aarch64-cross-builds:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: bash ci/install-rust.sh stable aarch64-unknown-linux-musl
- name: Build
run: cargo build --locked --target aarch64-unknown-linux-musl
rustfmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install Rust
run: rustup update stable && rustup default stable && rustup component add rustfmt
- run: cargo fmt --check

View File

@@ -1,5 +1,52 @@
# Changelog
## mdBook 0.4.40
[v0.4.39...v0.4.40](https://github.com/rust-lang/mdBook/compare/v0.4.39...v0.4.40)
### Fixed
- Reverted the update to pulldown-cmark which broke the semver API.
[#2388](https://github.com/rust-lang/mdBook/pull/2388)
## mdBook 0.4.39
[v0.4.38...v0.4.39](https://github.com/rust-lang/mdBook/compare/v0.4.38...v0.4.39)
### Fixed
- Fixed the automatic deploy broken in the previous release.
[#2383](https://github.com/rust-lang/mdBook/pull/2383)
## mdBook 0.4.38
[v0.4.37...v0.4.38](https://github.com/rust-lang/mdBook/compare/v0.4.37...v0.4.38)
### Added
- Added `nix` to the default set of languages supported for syntax highlighting.
[#2262](https://github.com/rust-lang/mdBook/pull/2262)
### Changed
- The `output.html.curly-quotes` option has been renamed to `output.html.smart-punctuation` to better reflect what it does. The old option `curly-quotes` is kept for compatibility, but may be removed in the future.
[#2327](https://github.com/rust-lang/mdBook/pull/2327)
- The file-watcher used in `mdbook serve` and `mdbook watch` now uses a poll-based watcher instead of the native operating system notifications. This should fix issues on various systems and environments, and more accurately detect when files change. The native watcher can still be used with the `--watcher native` CLI option.
[#2325](https://github.com/rust-lang/mdBook/pull/2325)
- `mdbook test` output now includes color, and shows relative paths to the source.
[#2259](https://github.com/rust-lang/mdBook/pull/2259)
- Updated dependencies, MSRV raised to 1.74
[#2350](https://github.com/rust-lang/mdBook/pull/2350)
[#2351](https://github.com/rust-lang/mdBook/pull/2351)
[#2378](https://github.com/rust-lang/mdBook/pull/2378)
[#2381](https://github.com/rust-lang/mdBook/pull/2381)
### Fixed
- Reduced memory allocation when copying files.
[#2355](https://github.com/rust-lang/mdBook/pull/2355)
- Fixed the horizontal divider in `SUMMARY.md` from being indented into the previous nested section.
[#2364](https://github.com/rust-lang/mdBook/pull/2364)
- Removed unnecessary `@import` in the CSS.
[#2260](https://github.com/rust-lang/mdBook/pull/2260)
## mdBook 0.4.37
[v0.4.36...v0.4.37](https://github.com/rust-lang/mdBook/compare/v0.4.36...v0.4.37)

View File

@@ -148,7 +148,7 @@ The following are instructions for updating [highlight.js](https://highlightjs.o
1. Clone the repository at <https://github.com/highlightjs/highlight.js>
1. Check out a tagged release (like `10.1.1`).
1. Run `npm install`
1. Run `node tools/build.js :common apache armasm coffeescript d handlebars haskell http julia nginx nim properties r scala x86asm yaml`
1. Run `node tools/build.js :common apache armasm coffeescript d handlebars haskell http julia nginx nim nix properties r scala x86asm yaml`
1. Compare the language list that it spits out to the one in [`syntax-highlighting.md`](https://github.com/camelid/mdBook/blob/master/guide/src/format/theme/syntax-highlighting.md). If any are missing, add them to the list and rebuild (and update these docs). If any are added to the common set, add them to `syntax-highlighting.md`.
1. Copy `build/highlight.min.js` to mdbook's directory [`highlight.js`](https://github.com/rust-lang/mdBook/blob/master/src/theme/highlight.js).
1. Be sure to check the highlight.js [CHANGES](https://github.com/highlightjs/highlight.js/blob/main/CHANGES.md) for any breaking changes. Breaking changes that would affect users will need to wait until the next major release.

648
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "mdbook"
version = "0.4.37"
version = "0.4.40"
authors = [
"Mathieu David <mathieudavid@mathieudavid.org>",
"Michael-F-Bryan <michaelfbryan@gmail.com>",
@@ -14,7 +14,7 @@ license = "MPL-2.0"
readme = "README.md"
repository = "https://github.com/rust-lang/mdBook"
description = "Creates a book from markdown files"
rust-version = "1.71"
rust-version = "1.74"
[dependencies]
anyhow = "1.0.71"
@@ -26,8 +26,8 @@ env_logger = "0.11.1"
handlebars = "5.0"
log = "0.4.17"
memchr = "2.5.0"
opener = "0.6.1"
pulldown-cmark = { version = "0.10.0", default-features = false, features = ["html"] }
opener = "0.7.0"
pulldown-cmark = { version = "0.10.0", default-features = false, features = ["html"] } # Do not update, part of the public api.
regex = "1.8.1"
serde = { version = "1.0.163", features = ["derive"] }
serde_json = "1.0.96"
@@ -41,6 +41,7 @@ notify = { version = "6.1.1", optional = true }
notify-debouncer-mini = { version = "0.4.1", optional = true }
ignore = { version = "0.4.20", optional = true }
pathdiff = { version = "0.2.1", optional = true }
walkdir = { version = "2.3.3", optional = true }
# Serve feature
futures-util = { version = "0.3.28", optional = true }
@@ -49,7 +50,7 @@ warp = { version = "0.3.6", default-features = false, features = ["websocket"],
# Search feature
elasticlunr-rs = { version = "3.0.2", optional = true }
ammonia = { version = "3.3.0", optional = true }
ammonia = { version = "4.0.0", optional = true }
[dev-dependencies]
assert_cmd = "2.0.11"
@@ -61,7 +62,7 @@ walkdir = "2.3.3"
[features]
default = ["watch", "serve", "search"]
watch = ["dep:notify", "dep:notify-debouncer-mini", "dep:ignore", "dep:pathdiff"]
watch = ["dep:notify", "dep:notify-debouncer-mini", "dep:ignore", "dep:pathdiff", "dep:walkdir"]
serve = ["dep:futures-util", "dep:tokio", "dep:warp"]
search = ["dep:elasticlunr-rs", "dep:ammonia"]

View File

@@ -22,6 +22,19 @@ then
rustup component add llvm-tools-preview --toolchain=$TOOLCHAIN
rustup component add rust-std-$TARGET --toolchain=$TOOLCHAIN
fi
if [[ $TARGET == *"musl" ]]
then
# This is needed by libdbus-sys.
sudo apt update -y && sudo apt install musl-dev musl-tools -y
fi
if [[ $TARGET == "aarch64-unknown-linux-musl" ]]
then
echo CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=rust-lld >> $GITHUB_ENV
# This `CC` is some nonsense needed for libdbus-sys (via opener).
# I don't know if this is really the right thing to do, but it seems to work.
sudo apt install gcc-aarch64-linux-gnu -y
echo CC=aarch64-linux-gnu-gcc >> $GITHUB_ENV
fi
fi
rustup default $TOOLCHAIN

View File

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

View File

@@ -8,6 +8,7 @@ language = "en"
edition = "2018"
[output.html]
smart-punctuation = true
mathjax-support = true
site-url = "/mdBook/"
git-repository-url = "https://github.com/rust-lang/mdBook/tree/master/guide"

View File

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

View File

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

View File

@@ -22,12 +22,12 @@ root instead of the current working directory.
mdbook build path/to/book
```
#### --open
#### `--open`
When you use the `--open` (`-o`) flag, mdbook will open the rendered book in
your default web browser after building it.
#### --dest-dir
#### `--dest-dir`
The `--dest-dir` (`-d`) option allows you to change the output directory for the
book. Relative paths are interpreted relative to the book's root directory. If

View File

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

View File

@@ -45,7 +45,7 @@ instead of the current working directory.
mdbook init path/to/book
```
#### --theme
#### `--theme`
When you use the `--theme` flag, the default theme will be copied into a
directory called `theme` in your source directory so that you can modify it.
@@ -53,7 +53,7 @@ directory called `theme` in your source directory so that you can modify it.
The theme is selectively overwritten, this means that if you don't want to
overwrite a specific file, just delete it and the default file will be used.
#### --title
#### `--title`
Specify a title for the book. If not supplied, an interactive prompt will ask for
a title.
@@ -62,7 +62,7 @@ a title.
mdbook init --title="my amazing book"
```
#### --ignore
#### `--ignore`
Create a `.gitignore` file configured to ignore the `book` directory created when [building] a book.
If not supplied, an interactive prompt will ask whether it should be created.
@@ -77,6 +77,6 @@ mdbook init --ignore=git
[building]: build.md
#### --force
#### `--force`
Skip the prompts to create a `.gitignore` and for the title for the book.

View File

@@ -32,18 +32,20 @@ The `serve` hostname defaults to `localhost`, and the port defaults to `3000`. E
mdbook serve path/to/book -p 8000 -n 127.0.0.1
```
#### --open
#### `--open`
When you use the `--open` (`-o`) flag, mdbook will open the book in your
default web browser after starting the server.
#### --dest-dir
#### `--dest-dir`
The `--dest-dir` (`-d`) option allows you to change the output directory for the
book. Relative paths are interpreted relative to the book's root directory. If
not specified it will default to the value of the `build.build-dir` key in
`book.toml`, or to `./book`.
{{#include arg-watcher.md}}
#### Specify exclude patterns
The `serve` command will not automatically trigger a build for files listed in

View File

@@ -37,7 +37,7 @@ instead of the current working directory.
mdbook test path/to/book
```
#### --library-path
#### `--library-path`
The `--library-path` (`-L`) option allows you to add directories to the library
search path used by `rustdoc` when it builds and tests the examples. Multiple
@@ -54,14 +54,14 @@ mdbook test my-book -L target/debug/deps/
See the `rustdoc` command-line [documentation](https://doc.rust-lang.org/rustdoc/command-line-arguments.html#-l--library-path-where-to-look-for-dependencies)
for more information.
#### --dest-dir
#### `--dest-dir`
The `--dest-dir` (`-d`) option allows you to change the output directory for the
book. Relative paths are interpreted relative to the book's root directory. If
not specified it will default to the value of the `build.build-dir` key in
`book.toml`, or to `./book`.
#### --chapter
#### `--chapter`
The `--chapter` (`-c`) option allows you to test a specific chapter of the
book using the chapter name or the relative path to the chapter.

View File

@@ -15,18 +15,19 @@ root instead of the current working directory.
mdbook watch path/to/book
```
#### --open
#### `--open`
When you use the `--open` (`-o`) option, mdbook will open the rendered book in
your default web browser.
#### --dest-dir
#### `--dest-dir`
The `--dest-dir` (`-d`) option allows you to change the output directory for the
book. Relative paths are interpreted relative to the book's root directory. If
not specified it will default to the value of the `build.build-dir` key in
`book.toml`, or to `./book`.
{{#include arg-watcher.md}}
#### Specify exclude patterns

View File

@@ -21,7 +21,7 @@ A simple approach would be to use the popular `curl` CLI tool to download the ex
```sh
mkdir bin
curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.37/mdbook-v0.4.37-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.40/mdbook-v0.4.40-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
bin/mdbook build
```
@@ -51,13 +51,13 @@ cargo install mdbook --no-default-features --features search --vers "^0.4" --loc
This includes several recommended options:
* `--no-default-features` Disables features like the HTTP server used by `mdbook serve` that is likely not needed on CI.
* `--no-default-features` --- Disables features like the HTTP server used by `mdbook serve` that is likely not needed on CI.
This will speed up the build time significantly.
* `--features search` Disabling default features means you should then manually enable features that you want, such as the built-in [search] capability.
* `--vers "^0.4"` This will install the most recent version of the `0.4` series.
* `--features search` --- Disabling default features means you should then manually enable features that you want, such as the built-in [search] capability.
* `--vers "^0.4"` --- This will install the most recent version of the `0.4` series.
However, versions after like `0.5.0` won't be installed, as they may break your build.
Cargo will automatically upgrade mdBook if you have an older version already installed.
* `--locked` This will use the dependencies that were used when mdBook was released.
* `--locked` --- This will use the dependencies that were used when mdBook was released.
Without `--locked`, it will use the latest version of all dependencies, which may include some fixes since the last release, but may also (rarely) cause build problems.
You will likely want to investigate caching options, as building mdBook can be somewhat slow.

View File

@@ -72,7 +72,7 @@ edition = "2015" # the default edition for code blocks
```
- **edition**: Rust edition to use by default for the code snippets. Default
is "2015". Individual code blocks can be controlled with the `edition2015`,
is `"2015"`. Individual code blocks can be controlled with the `edition2015`,
`edition2018` or `edition2021` annotations, such as:
~~~text

View File

@@ -4,9 +4,9 @@ Renderers (also called "backends") are responsible for creating the output of th
The following backends are built-in:
* [`html`](#html-renderer-options) This renders the book to HTML.
* [`html`](#html-renderer-options) --- This renders the book to HTML.
This is enabled by default if no other `[output]` tables are defined in `book.toml`.
* [`markdown`](#markdown-renderer) This outputs the book as markdown after running the preprocessors.
* [`markdown`](#markdown-renderer) --- This outputs the book as markdown after running the preprocessors.
This is useful for debugging preprocessors.
The community has developed several backends.
@@ -97,7 +97,7 @@ description = "The example book covers examples."
theme = "my-theme"
default-theme = "light"
preferred-dark-theme = "navy"
curly-quotes = true
smart-punctuation = true
mathjax-support = false
copy-fonts = true
additional-css = ["custom.css", "custom2.css"]
@@ -120,10 +120,12 @@ The following configuration options are available:
'Change Theme' dropdown. Defaults to `light`.
- **preferred-dark-theme:** The default dark theme. This theme will be used if
the browser requests the dark version of the site via the
['prefers-color-scheme'](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)
[`prefers-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)
CSS media query. Defaults to `navy`.
- **curly-quotes:** Convert straight quotes to curly quotes, except for those
that occur in code blocks and code spans. Defaults to `false`.
- **smart-punctuation:** Converts quotes to curly quotes, `...` to `…`, `--` to en-dash, and `---` to em-dash.
See [Smart Punctuation].
Defaults to `false`.
- **curly-quotes:** Deprecated alias for `smart-punctuation`.
- **mathjax-support:** Adds support for [MathJax](../mathjax.md). Defaults to
`false`.
- **copy-fonts:** (**Deprecated**) If `true` (the default), mdBook uses its built-in fonts which are copied to the output directory.

View File

@@ -214,12 +214,12 @@ characters:
So, no need to manually enter those Unicode characters!
This feature is disabled by default.
To enable it, see the [`output.html.curly-quotes`] config option.
To enable it, see the [`output.html.smart-punctuation`] config option.
[strikethrough]: https://github.github.com/gfm/#strikethrough-extension-
[tables]: https://github.github.com/gfm/#tables-extension-
[task list extension]: https://github.github.com/gfm/#task-list-items-extension-
[`output.html.curly-quotes`]: configuration/renderers.md#html-renderer-options
[`output.html.smart-punctuation`]: configuration/renderers.md#html-renderer-options
### Heading attributes

View File

@@ -112,16 +112,16 @@ panic!("oops!");
These are particularly important when using [`mdbook test`] to test Rust examples.
These use the same attributes as [rustdoc attributes], with a few additions:
* `editable` Enables the [editor].
* `noplayground` Removes the play button, but will still be tested.
* `mdbook-runnable` Forces the play button to be displayed.
* `editable` --- Enables the [editor].
* `noplayground` --- Removes the play button, but will still be tested.
* `mdbook-runnable` --- Forces the play button to be displayed.
This is intended to be combined with the `ignore` attribute for examples that should not be tested, but you want to allow the reader to run.
* `ignore` Will not be tested and no play button is shown, but it is still highlighted as Rust syntax.
* `should_panic` When executed, it should produce a panic.
* `no_run` The code is compiled when tested, but it is not run.
* `ignore` --- Will not be tested and no play button is shown, but it is still highlighted as Rust syntax.
* `should_panic` --- When executed, it should produce a panic.
* `no_run` --- The code is compiled when tested, but it is not run.
The play button is also not shown.
* `compile_fail` The code should fail to compile.
* `edition2015`, `edition2018`, `edition2021` Forces the use of a specific Rust edition.
* `compile_fail` --- The code should fail to compile.
* `edition2015`, `edition2018`, `edition2021` --- Forces the use of a specific Rust edition.
See [`rust.edition`] to set this globally.
[`mdbook test`]: ../cli/test.md

View File

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

View File

@@ -45,6 +45,7 @@ your own `highlight.js` file:
- markdown
- nginx
- nim
- nix
- objectivec
- perl
- php

View File

@@ -20,7 +20,7 @@ To make it easier to run, put the path to the binary into your `PATH`.
To build the `mdbook` executable from source, you will first need to install Rust and Cargo.
Follow the instructions on the [Rust installation page].
mdBook currently requires at least Rust version 1.71.
mdBook currently requires at least Rust version 1.74.
Once you have installed Rust, the following command can be used to build and install mdBook:

View File

@@ -160,8 +160,20 @@ pub struct Chapter {
/// Nested items.
pub sub_items: Vec<BookItem>,
/// The chapter's location, relative to the `SUMMARY.md` file.
///
/// **Note**: After the index preprocessor runs, any README files will be
/// modified to be `index.md`. If you need access to the actual filename
/// on disk, use [`Chapter::source_path`] instead.
///
/// This is `None` for a draft chapter.
pub path: Option<PathBuf>,
/// The chapter's source file, relative to the `SUMMARY.md` file.
///
/// **Note**: Beware that README files will internally be treated as
/// `index.md` via the [`Chapter::path`] field. The `source_path` field
/// exists if you need access to the true file path.
///
/// This is `None` for a draft chapter.
pub source_path: Option<PathBuf>,
/// An ordered list of the names of each chapter above this one in the hierarchy.
pub parent_names: Vec<String>,
@@ -339,7 +351,6 @@ impl Display for Chapter {
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::{Builder as TempFileBuilder, TempDir};
const DUMMY_SRC: &str = "

View File

@@ -15,10 +15,10 @@ pub use self::init::BookBuilder;
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
use log::{debug, error, info, log_enabled, trace, warn};
use std::io::Write;
use std::path::PathBuf;
use std::ffi::OsString;
use std::io::{IsTerminal, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::string::ToString;
use tempfile::Builder as TempFileBuilder;
use toml::Value;
use topological_sort::TopologicalSort;
@@ -71,18 +71,24 @@ impl MDBook {
config.update_from_env();
if config
.html_config()
.map_or(false, |html| html.google_analytics.is_some())
{
warn!(
"The output.html.google-analytics field has been deprecated; \
it will be removed in a future release.\n\
Consider placing the appropriate site tag code into the \
theme/head.hbs file instead.\n\
The tracking code may be found in the Google Analytics Admin page.\n\
"
);
if let Some(html_config) = config.html_config() {
if html_config.google_analytics.is_some() {
warn!(
"The output.html.google-analytics field has been deprecated; \
it will be removed in a future release.\n\
Consider placing the appropriate site tag code into the \
theme/head.hbs file instead.\n\
The tracking code may be found in the Google Analytics Admin page.\n\
"
);
}
if html_config.curly_quotes {
warn!(
"The output.html.curly-quotes field has been renamed to \
output.html.smart-punctuation.\n\
Use the new name in book.toml to remove this warning."
);
}
}
if log_enabled!(log::Level::Trace) {
@@ -259,10 +265,18 @@ impl MDBook {
/// Run `rustdoc` tests on a specific chapter of the book, linking against the provided libraries.
/// If `chapter` is `None`, all tests will be run.
pub fn test_chapter(&mut self, library_paths: Vec<&str>, chapter: Option<&str>) -> Result<()> {
let library_args: Vec<&str> = (0..library_paths.len())
.map(|_| "-L")
.zip(library_paths.into_iter())
.flat_map(|x| vec![x.0, x.1])
let cwd = std::env::current_dir()?;
let library_args: Vec<OsString> = library_paths
.into_iter()
.flat_map(|path| {
let path = Path::new(path);
let path = if path.is_relative() {
cwd.join(path).into_os_string()
} else {
path.to_path_buf().into_os_string()
};
[OsString::from("-L"), path]
})
.collect();
let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
@@ -289,6 +303,7 @@ impl MDBook {
.collect();
let (book, _) = self.preprocess_book(&TestRenderer)?;
let color_output = std::io::stderr().is_terminal();
let mut failed = false;
for item in book.iter() {
if let BookItem::Chapter(ref ch) = *item {
@@ -314,7 +329,10 @@ impl MDBook {
tmpf.write_all(ch.content.as_bytes())?;
let mut cmd = Command::new("rustdoc");
cmd.arg(&path).arg("--test").args(&library_args);
cmd.current_dir(temp_dir.path())
.arg(chapter_path)
.arg("--test")
.args(&library_args);
if let Some(edition) = self.config.rust.edition {
match edition {
@@ -330,6 +348,10 @@ impl MDBook {
}
}
if color_output {
cmd.args(["--color", "always"]);
}
debug!("running {:?}", cmd);
let output = cmd.output()?;
@@ -605,7 +627,7 @@ fn preprocessor_should_run(
mod tests {
use super::*;
use std::str::FromStr;
use toml::value::{Table, Value};
use toml::value::Table;
#[test]
fn config_defaults_to_html_renderer_if_empty() {

View File

@@ -4,7 +4,6 @@ use memchr::Memchr;
use pulldown_cmark::{DefaultBrokenLinkCallback, Event, HeadingLevel, Tag, TagEnd};
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
use std::iter::FromIterator;
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
@@ -584,11 +583,13 @@ fn get_last_link(links: &mut [SummaryItem]) -> Result<(usize, &mut Link)> {
.iter_mut()
.enumerate()
.filter_map(|(i, item)| item.maybe_link_mut().map(|l| (i, l)))
.rev()
.next()
.ok_or_else(||
anyhow::anyhow!("Unable to get last link because the list of SummaryItems doesn't contain any Links")
.next_back()
.ok_or_else(|| {
anyhow::anyhow!(
"Unable to get last link because the list of SummaryItems \
doesn't contain any Links"
)
})
}
/// Removes the styling from a list of Markdown events and returns just the

View File

@@ -36,6 +36,20 @@ pub trait CommandExt: Sized {
fn arg_open(self) -> Self {
self._arg(arg!(-o --open "Opens the compiled book in a web browser"))
}
#[cfg(any(feature = "watch", feature = "serve"))]
fn arg_watcher(self) -> Self {
#[cfg(feature = "watch")]
return self._arg(
Arg::new("watcher")
.long("watcher")
.value_parser(["poll", "native"])
.default_value("poll")
.help("The filesystem watching technique"),
);
#[cfg(not(feature = "watch"))]
return self;
}
}
impl CommandExt for Command {

View File

@@ -6,7 +6,6 @@ use clap::builder::NonEmptyStringValueParser;
use futures_util::sink::SinkExt;
use futures_util::StreamExt;
use mdbook::errors::*;
use mdbook::utils;
use mdbook::utils::fs::get_404_output_file;
use mdbook::MDBook;
use std::net::{SocketAddr, ToSocketAddrs};
@@ -43,12 +42,13 @@ pub fn make_subcommand() -> Command {
.help("Port to use for HTTP connections"),
)
.arg_open()
.arg_watcher()
}
// Serve command implementation
pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args);
let mut book = MDBook::load(book_dir)?;
let mut book = MDBook::load(&book_dir)?;
let port = args.get_one::<String>("port").unwrap();
let hostname = args.get_one::<String>("hostname").unwrap();
@@ -97,23 +97,12 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
}
#[cfg(feature = "watch")]
watch::trigger_on_change(&book, move |paths, book_dir| {
info!("Files changed: {:?}", paths);
info!("Building book...");
// FIXME: This area is really ugly because we need to re-set livereload :(
let result = MDBook::load(book_dir).and_then(|mut b| {
update_config(&mut b);
b.build()
});
if let Err(e) = result {
error!("Unable to load the book");
utils::log_backtrace(&e);
} else {
{
let watcher = watch::WatcherKind::from_str(args.get_one::<String>("watcher").unwrap());
watch::rebuild_on_change(watcher, &book_dir, &update_config, &move || {
let _ = tx.send(Message::text("reload"));
}
});
});
}
let _ = thread_handle.join();

View File

@@ -1,7 +1,7 @@
use super::command_prelude::*;
use crate::get_book_dir;
use clap::builder::NonEmptyStringValueParser;
use clap::{Arg, ArgAction, ArgMatches, Command};
use clap::ArgAction;
use mdbook::errors::Result;
use mdbook::MDBook;
use std::path::PathBuf;

View File

@@ -1,14 +1,11 @@
use super::command_prelude::*;
use crate::{get_book_dir, open};
use ignore::gitignore::Gitignore;
use mdbook::errors::Result;
use mdbook::utils;
use mdbook::MDBook;
use pathdiff::diff_paths;
use std::path::{Path, PathBuf};
use std::sync::mpsc::channel;
use std::thread::sleep;
use std::time::Duration;
mod native;
mod poller;
// Create clap subcommand arguments
pub fn make_subcommand() -> Command {
@@ -17,12 +14,28 @@ pub fn make_subcommand() -> Command {
.arg_dest_dir()
.arg_root_dir()
.arg_open()
.arg_watcher()
}
pub enum WatcherKind {
Poll,
Native,
}
impl WatcherKind {
pub fn from_str(s: &str) -> WatcherKind {
match s {
"poll" => WatcherKind::Poll,
"native" => WatcherKind::Native,
_ => panic!("unsupported watcher {s}"),
}
}
}
// Watch command implementation
pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args);
let mut book = MDBook::load(book_dir)?;
let mut book = MDBook::load(&book_dir)?;
let update_config = |book: &mut MDBook| {
if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") {
@@ -41,42 +54,21 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
open(path);
}
trigger_on_change(&book, |paths, book_dir| {
info!("Files changed: {:?}\nBuilding book...\n", paths);
let result = MDBook::load(book_dir).and_then(|mut b| {
update_config(&mut b);
b.build()
});
if let Err(e) = result {
error!("Unable to build the book");
utils::log_backtrace(&e);
}
});
let watcher = WatcherKind::from_str(args.get_one::<String>("watcher").unwrap());
rebuild_on_change(watcher, &book_dir, &update_config, &|| {});
Ok(())
}
fn remove_ignored_files(book_root: &Path, paths: &[PathBuf]) -> Vec<PathBuf> {
if paths.is_empty() {
return vec![];
}
match find_gitignore(book_root) {
Some(gitignore_path) => {
let (ignore, err) = Gitignore::new(&gitignore_path);
if let Some(err) = err {
warn!(
"error reading gitignore `{}`: {err}",
gitignore_path.display()
);
}
filter_ignored_files(ignore, paths)
}
None => {
// There is no .gitignore file.
paths.iter().map(|path| path.to_path_buf()).collect()
}
pub fn rebuild_on_change(
kind: WatcherKind,
book_dir: &Path,
update_config: &dyn Fn(&mut MDBook),
post_build: &dyn Fn(),
) {
match kind {
WatcherKind::Poll => self::poller::rebuild_on_change(book_dir, update_config, post_build),
WatcherKind::Native => self::native::rebuild_on_change(book_dir, update_config, post_build),
}
}
@@ -86,144 +78,3 @@ fn find_gitignore(book_root: &Path) -> Option<PathBuf> {
.map(|p| p.join(".gitignore"))
.find(|p| p.exists())
}
// Note: The usage of `canonicalize` may encounter occasional failures on the Windows platform, presenting a potential risk.
// For more details, refer to [Pull Request #2229](https://github.com/rust-lang/mdBook/pull/2229#discussion_r1408665981).
fn filter_ignored_files(ignore: Gitignore, paths: &[PathBuf]) -> Vec<PathBuf> {
let ignore_root = ignore
.path()
.canonicalize()
.expect("ignore root canonicalize error");
paths
.iter()
.filter(|path| {
let relative_path =
diff_paths(&path, &ignore_root).expect("One of the paths should be an absolute");
!ignore
.matched_path_or_any_parents(&relative_path, relative_path.is_dir())
.is_ignore()
})
.map(|path| path.to_path_buf())
.collect()
}
/// Calls the closure when a book source file is changed, blocking indefinitely.
pub fn trigger_on_change<F>(book: &MDBook, closure: F)
where
F: Fn(Vec<PathBuf>, &Path),
{
use notify::RecursiveMode::*;
// Create a channel to receive the events.
let (tx, rx) = channel();
let mut debouncer = match notify_debouncer_mini::new_debouncer(Duration::from_secs(1), tx) {
Ok(d) => d,
Err(e) => {
error!("Error while trying to watch the files:\n\n\t{:?}", e);
std::process::exit(1)
}
};
let watcher = debouncer.watcher();
// Add the source directory to the watcher
if let Err(e) = watcher.watch(&book.source_dir(), Recursive) {
error!("Error while watching {:?}:\n {:?}", book.source_dir(), e);
std::process::exit(1);
};
let _ = watcher.watch(&book.theme_dir(), Recursive);
// Add the book.toml file to the watcher if it exists
let _ = watcher.watch(&book.root.join("book.toml"), NonRecursive);
for dir in &book.config.build.extra_watch_dirs {
let path = book.root.join(dir);
let canonical_path = path.canonicalize().unwrap_or_else(|e| {
error!("Error while watching extra directory {path:?}:\n {e}");
std::process::exit(1);
});
if let Err(e) = watcher.watch(&canonical_path, Recursive) {
error!(
"Error while watching extra directory {:?}:\n {:?}",
canonical_path, e
);
std::process::exit(1);
}
}
info!("Listening for changes...");
loop {
let first_event = rx.recv().unwrap();
sleep(Duration::from_millis(50));
let other_events = rx.try_iter();
let all_events = std::iter::once(first_event).chain(other_events);
let paths: Vec<_> = all_events
.filter_map(|event| match event {
Ok(events) => Some(events),
Err(error) => {
log::warn!("error while watching for changes: {error}");
None
}
})
.flatten()
.map(|event| event.path)
.collect();
// If we are watching files outside the current repository (via extra-watch-dirs), then they are definitionally
// ignored by gitignore. So we handle this case by including such files into the watched paths list.
let any_external_paths = paths.iter().filter(|p| !p.starts_with(&book.root)).cloned();
let mut paths = remove_ignored_files(&book.root, &paths[..]);
paths.extend(any_external_paths);
if !paths.is_empty() {
closure(paths, &book.root);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ignore::gitignore::GitignoreBuilder;
use std::env;
#[test]
fn test_filter_ignored_files() {
let current_dir = env::current_dir().unwrap();
let ignore = GitignoreBuilder::new(&current_dir)
.add_line(None, "*.html")
.unwrap()
.build()
.unwrap();
let should_remain = current_dir.join("record.text");
let should_filter = current_dir.join("index.html");
let remain = filter_ignored_files(ignore, &[should_remain.clone(), should_filter]);
assert_eq!(remain, vec![should_remain])
}
#[test]
fn filter_ignored_files_should_handle_parent_dir() {
let current_dir = env::current_dir().unwrap();
let ignore = GitignoreBuilder::new(&current_dir)
.add_line(None, "*.html")
.unwrap()
.build()
.unwrap();
let parent_dir = current_dir.join("..");
let should_remain = parent_dir.join("record.text");
let should_filter = parent_dir.join("index.html");
let remain = filter_ignored_files(ignore, &[should_remain.clone(), should_filter]);
assert_eq!(remain, vec![should_remain])
}
}

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

@@ -0,0 +1,189 @@
//! A filesystem watcher using native operating system facilities.
use ignore::gitignore::Gitignore;
use mdbook::MDBook;
use std::path::{Path, PathBuf};
use std::sync::mpsc::channel;
use std::thread::sleep;
use std::time::Duration;
pub fn rebuild_on_change(
book_dir: &Path,
update_config: &dyn Fn(&mut MDBook),
post_build: &dyn Fn(),
) {
use notify::RecursiveMode::*;
let mut book = MDBook::load(book_dir).unwrap_or_else(|e| {
error!("failed to load book: {e}");
std::process::exit(1);
});
// Create a channel to receive the events.
let (tx, rx) = channel();
let mut debouncer = match notify_debouncer_mini::new_debouncer(Duration::from_secs(1), tx) {
Ok(d) => d,
Err(e) => {
error!("Error while trying to watch the files:\n\n\t{:?}", e);
std::process::exit(1)
}
};
let watcher = debouncer.watcher();
// Add the source directory to the watcher
if let Err(e) = watcher.watch(&book.source_dir(), Recursive) {
error!("Error while watching {:?}:\n {:?}", book.source_dir(), e);
std::process::exit(1);
};
let _ = watcher.watch(&book.theme_dir(), Recursive);
// Add the book.toml file to the watcher if it exists
let _ = watcher.watch(&book.root.join("book.toml"), NonRecursive);
for dir in &book.config.build.extra_watch_dirs {
let path = book.root.join(dir);
let canonical_path = path.canonicalize().unwrap_or_else(|e| {
error!("Error while watching extra directory {path:?}:\n {e}");
std::process::exit(1);
});
if let Err(e) = watcher.watch(&canonical_path, Recursive) {
error!(
"Error while watching extra directory {:?}:\n {:?}",
canonical_path, e
);
std::process::exit(1);
}
}
info!("Listening for changes...");
loop {
let first_event = rx.recv().unwrap();
sleep(Duration::from_millis(50));
let other_events = rx.try_iter();
let all_events = std::iter::once(first_event).chain(other_events);
let paths: Vec<_> = all_events
.filter_map(|event| match event {
Ok(events) => Some(events),
Err(error) => {
log::warn!("error while watching for changes: {error}");
None
}
})
.flatten()
.map(|event| event.path)
.collect();
// If we are watching files outside the current repository (via extra-watch-dirs), then they are definitionally
// ignored by gitignore. So we handle this case by including such files into the watched paths list.
let any_external_paths = paths.iter().filter(|p| !p.starts_with(&book.root)).cloned();
let mut paths = remove_ignored_files(&book.root, &paths[..]);
paths.extend(any_external_paths);
if !paths.is_empty() {
info!("Files changed: {paths:?}");
match MDBook::load(book_dir) {
Ok(mut b) => {
update_config(&mut b);
if let Err(e) = b.build() {
error!("failed to build the book: {e:?}");
} else {
post_build();
}
book = b;
}
Err(e) => error!("failed to load book config: {e:?}"),
}
}
}
}
fn remove_ignored_files(book_root: &Path, paths: &[PathBuf]) -> Vec<PathBuf> {
if paths.is_empty() {
return vec![];
}
match super::find_gitignore(book_root) {
Some(gitignore_path) => {
let (ignore, err) = Gitignore::new(&gitignore_path);
if let Some(err) = err {
warn!(
"error reading gitignore `{}`: {err}",
gitignore_path.display()
);
}
filter_ignored_files(ignore, paths)
}
None => {
// There is no .gitignore file.
paths.iter().map(|path| path.to_path_buf()).collect()
}
}
}
// Note: The usage of `canonicalize` may encounter occasional failures on the Windows platform, presenting a potential risk.
// For more details, refer to [Pull Request #2229](https://github.com/rust-lang/mdBook/pull/2229#discussion_r1408665981).
fn filter_ignored_files(ignore: Gitignore, paths: &[PathBuf]) -> Vec<PathBuf> {
let ignore_root = ignore
.path()
.canonicalize()
.expect("ignore root canonicalize error");
paths
.iter()
.filter(|path| {
let relative_path = pathdiff::diff_paths(&path, &ignore_root)
.expect("One of the paths should be an absolute");
!ignore
.matched_path_or_any_parents(&relative_path, relative_path.is_dir())
.is_ignore()
})
.map(|path| path.to_path_buf())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use ignore::gitignore::GitignoreBuilder;
use std::env;
#[test]
fn test_filter_ignored_files() {
let current_dir = env::current_dir().unwrap();
let ignore = GitignoreBuilder::new(&current_dir)
.add_line(None, "*.html")
.unwrap()
.build()
.unwrap();
let should_remain = current_dir.join("record.text");
let should_filter = current_dir.join("index.html");
let remain = filter_ignored_files(ignore, &[should_remain.clone(), should_filter]);
assert_eq!(remain, vec![should_remain])
}
#[test]
fn filter_ignored_files_should_handle_parent_dir() {
let current_dir = env::current_dir().unwrap();
let ignore = GitignoreBuilder::new(&current_dir)
.add_line(None, "*.html")
.unwrap()
.build()
.unwrap();
let parent_dir = current_dir.join("..");
let should_remain = parent_dir.join("record.text");
let should_filter = parent_dir.join("index.html");
let remain = filter_ignored_files(ignore, &[should_remain.clone(), should_filter]);
assert_eq!(remain, vec![should_remain])
}
}

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

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

View File

@@ -58,7 +58,7 @@ use std::io::Read;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use toml::value::Table;
use toml::{self, Value};
use toml::Value;
use crate::errors::*;
use crate::utils::{self, toml_ext::TomlExt};
@@ -526,7 +526,9 @@ pub struct HtmlConfig {
/// The theme to use if the browser requests the dark version of the site.
/// Defaults to 'navy'.
pub preferred_dark_theme: Option<String>,
/// Use "smart quotes" instead of the usual `"` character.
/// Supports smart quotes, apostrophes, ellipsis, en-dash, and em-dash.
pub smart_punctuation: bool,
/// Deprecated alias for `smart_punctuation`.
pub curly_quotes: bool,
/// Should mathjax be enabled?
pub mathjax_support: bool,
@@ -590,6 +592,7 @@ impl Default for HtmlConfig {
theme: None,
default_theme: None,
preferred_dark_theme: None,
smart_punctuation: false,
curly_quotes: false,
mathjax_support: false,
copy_fonts: true,
@@ -623,6 +626,11 @@ impl HtmlConfig {
None => root.join("theme"),
}
}
/// Returns `true` if smart punctuation is enabled.
pub fn smart_punctuation(&self) -> bool {
self.smart_punctuation || self.curly_quotes
}
}
/// Configuration for how to render the print icon, print.html, and print.css.
@@ -656,7 +664,7 @@ pub struct Fold {
pub level: u8,
}
/// Configuration for tweaking how the the HTML renderer handles the playground.
/// Configuration for tweaking how the HTML renderer handles the playground.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct Playground {
@@ -685,22 +693,14 @@ impl Default for Playground {
}
}
/// Configuration for tweaking how the the HTML renderer handles code blocks.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
/// Configuration for tweaking how the HTML renderer handles code blocks.
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct Code {
/// A prefix string to hide lines per language (one or more chars).
pub hidelines: HashMap<String, String>,
}
impl Default for Code {
fn default() -> Code {
Code {
hidelines: HashMap::new(),
}
}
}
/// Configuration of the search functionality of the HTML renderer.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
@@ -798,7 +798,7 @@ mod tests {
[output.html]
theme = "./themedir"
default-theme = "rust"
curly-quotes = true
smart-punctuation = true
google-analytics = "123456"
additional-css = ["./foo/bar/baz.css"]
git-repository-url = "https://foo.com/"
@@ -845,7 +845,7 @@ mod tests {
runnable: true,
};
let html_should_be = HtmlConfig {
curly_quotes: true,
smart_punctuation: true,
google_analytics: Some(String::from("123456")),
additional_css: vec![PathBuf::from("./foo/bar/baz.css")],
theme: Some(PathBuf::from("./themedir")),
@@ -1025,7 +1025,7 @@ mod tests {
[output.html]
destination = "my-book" # the output files will be generated in `root/my-book` instead of `root/book`
theme = "my-theme"
curly-quotes = true
smart-punctuation = true
google-analytics = "123456"
additional-css = ["custom.css", "custom2.css"]
additional-js = ["custom.js"]
@@ -1050,7 +1050,7 @@ mod tests {
let html_should_be = HtmlConfig {
theme: Some(PathBuf::from("my-theme")),
curly_quotes: true,
smart_punctuation: true,
google_analytics: Some(String::from("123456")),
additional_css: vec![PathBuf::from("custom.css"), PathBuf::from("custom2.css")],
additional_js: vec![PathBuf::from("custom.js")],
@@ -1320,4 +1320,37 @@ mod tests {
assert!(html_config.print.enable);
assert!(!html_config.print.page_break);
}
#[test]
fn curly_quotes_or_smart_punctuation() {
let src = r#"
[book]
title = "mdBook Documentation"
[output.html]
smart-punctuation = true
"#;
let config = Config::from_str(src).unwrap();
assert_eq!(config.html_config().unwrap().smart_punctuation(), true);
let src = r#"
[book]
title = "mdBook Documentation"
[output.html]
curly-quotes = true
"#;
let config = Config::from_str(src).unwrap();
assert_eq!(config.html_config().unwrap().smart_punctuation(), true);
let src = r#"
[book]
title = "mdBook Documentation"
"#;
let config = Config::from_str(src).unwrap();
assert_eq!(
config.html_config().unwrap_or_default().smart_punctuation(),
false
);
}
}

View File

@@ -54,10 +54,13 @@ impl HtmlHandlebars {
.insert("git_repository_edit_url".to_owned(), json!(edit_url));
}
let content = utils::render_markdown(&ch.content, ctx.html_config.curly_quotes);
let content = utils::render_markdown(&ch.content, ctx.html_config.smart_punctuation());
let fixed_content =
utils::render_markdown_with_path(&ch.content, ctx.html_config.curly_quotes, Some(path));
let fixed_content = utils::render_markdown_with_path(
&ch.content,
ctx.html_config.smart_punctuation(),
Some(path),
);
if !ctx.is_index && ctx.html_config.print.page_break {
// Add page break between chapters
// See https://developer.mozilla.org/en-US/docs/Web/CSS/break-before and https://developer.mozilla.org/en-US/docs/Web/CSS/page-break-before
@@ -164,7 +167,8 @@ impl HtmlHandlebars {
.to_string()
}
};
let html_content_404 = utils::render_markdown(&content_404, html_config.curly_quotes);
let html_content_404 =
utils::render_markdown(&content_404, html_config.smart_punctuation());
let mut data_404 = data.clone();
let base_url = if let Some(site_url) = &html_config.site_url {
@@ -202,7 +206,7 @@ impl HtmlHandlebars {
Ok(())
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::let_and_return))]
#[allow(clippy::let_and_return)]
fn post_process(
&self,
rendered: String,

View File

@@ -74,12 +74,6 @@ impl HelperDef for RenderToc {
let mut is_first_chapter = ctx.data().get("is_index").is_some();
for item in chapters {
// Spacer
if item.get("spacer").is_some() {
out.write("<li class=\"spacer\"></li>")?;
continue;
}
let (section, level) = if let Some(s) = item.get("section") {
(s.as_str(), s.matches('.').count())
} else {
@@ -114,10 +108,16 @@ impl HelperDef for RenderToc {
write_li_open_tag(out, is_expanded, false)?;
}
Ordering::Equal => {
write_li_open_tag(out, is_expanded, item.get("section").is_none())?;
write_li_open_tag(out, is_expanded, !item.contains_key("section"))?;
}
}
// Spacer
if item.contains_key("spacer") {
out.write("<li class=\"spacer\"></li>")?;
continue;
}
// Part title
if let Some(title) = item.get("part") {
out.write("<li class=\"part-title\">")?;

View File

@@ -1,7 +1,5 @@
/* CSS for UI elements (a.k.a. chrome) */
@import 'variables.css';
html {
scrollbar-color: var(--scrollbar) var(--bg);
}

View File

@@ -1,7 +1,5 @@
/* Base styles and content styles */
@import 'variables.css';
:root {
/* Browser default font-size is 16px, this way 1 rem = 10px */
font-size: 62.5%;

File diff suppressed because one or more lines are too long

View File

@@ -212,7 +212,6 @@ fn load_file_contents<P: AsRef<Path>>(filename: P, dest: &mut Vec<u8>) -> Result
mod tests {
use super::*;
use std::fs;
use std::path::PathBuf;
use tempfile::Builder as TempFileBuilder;
#[test]

View File

@@ -1,6 +1,5 @@
use crate::errors::*;
use log::{debug, trace};
use std::convert::Into;
use std::fs::{self, File};
use std::io::Write;
use std::path::{Component, Path, PathBuf};
@@ -73,14 +72,12 @@ pub fn create_file(path: &Path) -> Result<File> {
/// Removes all the content of a directory but not the directory itself
pub fn remove_dir_content(dir: &Path) -> Result<()> {
for item in fs::read_dir(dir)? {
if let Ok(item) = item {
let item = item.path();
if item.is_dir() {
fs::remove_dir_all(item)?;
} else {
fs::remove_file(item)?;
}
for item in fs::read_dir(dir)?.flatten() {
let item = item.path();
if item.is_dir() {
fs::remove_dir_all(item)?;
} else {
fs::remove_file(item)?;
}
}
Ok(())
@@ -109,72 +106,41 @@ pub fn copy_files_except_ext(
}
for entry in fs::read_dir(from)? {
let entry = entry?;
let entry = entry?.path();
let metadata = entry
.path()
.metadata()
.with_context(|| format!("Failed to read {:?}", entry.path()))?;
.with_context(|| format!("Failed to read {entry:?}"))?;
let entry_file_name = entry.file_name().unwrap();
let target_file_path = to.join(entry_file_name);
// If the entry is a dir and the recursive option is enabled, call itself
if metadata.is_dir() && recursive {
if entry.path() == to.to_path_buf() {
if entry == to.as_os_str() {
continue;
}
if let Some(avoid) = avoid_dir {
if entry.path() == *avoid {
if entry == *avoid {
continue;
}
}
// check if output dir already exists
if !to.join(entry.file_name()).exists() {
fs::create_dir(&to.join(entry.file_name()))?;
if !target_file_path.exists() {
fs::create_dir(&target_file_path)?;
}
copy_files_except_ext(
&from.join(entry.file_name()),
&to.join(entry.file_name()),
true,
avoid_dir,
ext_blacklist,
)?;
copy_files_except_ext(&entry, &target_file_path, true, avoid_dir, ext_blacklist)?;
} else if metadata.is_file() {
// Check if it is in the blacklist
if let Some(ext) = entry.path().extension() {
if let Some(ext) = entry.extension() {
if ext_blacklist.contains(&ext.to_str().unwrap()) {
continue;
}
}
debug!(
"creating path for file: {:?}",
&to.join(
entry
.path()
.file_name()
.expect("a file should have a file name...")
)
);
debug!(
"Copying {:?} to {:?}",
entry.path(),
&to.join(
entry
.path()
.file_name()
.expect("a file should have a file name...")
)
);
copy(
entry.path(),
&to.join(
entry
.path()
.file_name()
.expect("a file should have a file name..."),
),
)?;
debug!("Copying {entry:?} to {target_file_path:?}");
copy(&entry, &target_file_path)?;
}
}
Ok(())

View File

@@ -190,26 +190,30 @@ fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
}
/// Wrapper around the pulldown-cmark parser for rendering markdown to HTML.
pub fn render_markdown(text: &str, curly_quotes: bool) -> String {
render_markdown_with_path(text, curly_quotes, None)
pub fn render_markdown(text: &str, smart_punctuation: bool) -> String {
render_markdown_with_path(text, smart_punctuation, None)
}
pub fn new_cmark_parser(text: &str, curly_quotes: bool) -> Parser<'_> {
pub fn new_cmark_parser(text: &str, smart_punctuation: bool) -> Parser<'_> {
let mut opts = Options::empty();
opts.insert(Options::ENABLE_TABLES);
opts.insert(Options::ENABLE_FOOTNOTES);
opts.insert(Options::ENABLE_STRIKETHROUGH);
opts.insert(Options::ENABLE_TASKLISTS);
opts.insert(Options::ENABLE_HEADING_ATTRIBUTES);
if curly_quotes {
if smart_punctuation {
opts.insert(Options::ENABLE_SMART_PUNCTUATION);
}
Parser::new_ext(text, opts)
}
pub fn render_markdown_with_path(text: &str, curly_quotes: bool, path: Option<&Path>) -> String {
pub fn render_markdown_with_path(
text: &str,
smart_punctuation: bool,
path: Option<&Path>,
) -> String {
let mut s = String::with_capacity(text.len() * 3 / 2);
let p = new_cmark_parser(text, curly_quotes);
let p = new_cmark_parser(text, smart_punctuation);
let events = p
.map(clean_codeblock_headers)
.map(|event| adjust_links(event, path))

View File

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

View File

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

View File

@@ -28,6 +28,7 @@ This Currently contains following languages
- markdown
- nginx
- nim
- nix
- objectivec
- perl
- php

View File

@@ -563,6 +563,15 @@ int main(int argc, const char * argv[]) {
"Hello " + world
```
## perl
```perl
print "Hello World!\n";
```
## php
```php
<?php
echo "Hello World!";
?>