mirror of
https://github.com/rust-lang/mdBook.git
synced 2025-12-28 18:32:41 -05:00
Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39664985ba | ||
|
|
ab1e9694bc | ||
|
|
2c710d3b7d | ||
|
|
581ab2c945 | ||
|
|
274b48c82f | ||
|
|
e352e4f59c | ||
|
|
734936d819 | ||
|
|
0e1384b4d2 | ||
|
|
2160613c6a | ||
|
|
69bb5c7fba | ||
|
|
f32e1a7773 | ||
|
|
703c2f214b | ||
|
|
6de831778a | ||
|
|
ca46086e79 | ||
|
|
0079184c16 | ||
|
|
dcc9efea0a | ||
|
|
a3b508fab9 | ||
|
|
5359b487f2 | ||
|
|
c2d973997a | ||
|
|
41a6f0d43e | ||
|
|
9764f8886b | ||
|
|
1ba2c063e0 | ||
|
|
c640294dbf | ||
|
|
dec487c62b | ||
|
|
1ba74a30fc | ||
|
|
fcf0cebf6c | ||
|
|
e14d38194f | ||
|
|
294aad092e | ||
|
|
8767ebf835 | ||
|
|
cd907f2edf | ||
|
|
eb77083d23 | ||
|
|
219362318c | ||
|
|
68a75dae48 | ||
|
|
87a381e0a7 | ||
|
|
0b2520f84a | ||
|
|
21ab85cd03 | ||
|
|
486bf32ac7 | ||
|
|
4f6610716a | ||
|
|
6db4ca71da | ||
|
|
d5319e2b4f | ||
|
|
cda44480b7 | ||
|
|
fb0af12433 | ||
|
|
b5f858da4e | ||
|
|
59bd5db556 | ||
|
|
cf1557e454 | ||
|
|
36e1f01091 | ||
|
|
e3c484af01 | ||
|
|
4deb5c7cee | ||
|
|
21fb329d56 | ||
|
|
678b469835 | ||
|
|
ded48ddac7 | ||
|
|
8a02fc755f | ||
|
|
4844f72b96 | ||
|
|
f32bd6f945 | ||
|
|
f64fcbc07d | ||
|
|
c34c3bf730 | ||
|
|
de4c551363 | ||
|
|
d45f02d38c | ||
|
|
666975a1ef | ||
|
|
144a1e4009 | ||
|
|
8b486dfc71 | ||
|
|
db092a404e | ||
|
|
edda3d1b51 | ||
|
|
27a11e7b35 | ||
|
|
cfd4c93d88 | ||
|
|
b1ca805d2a | ||
|
|
852a882fab | ||
|
|
fb0cbc90e3 | ||
|
|
3a24f10d7c | ||
|
|
3fc036e01a | ||
|
|
056a46cc97 | ||
|
|
f8df8ed72d | ||
|
|
79c159d123 | ||
|
|
a8c37ceace | ||
|
|
cb01f11ad1 | ||
|
|
7aaa84853d | ||
|
|
75857fbf73 | ||
|
|
c8db0c8ec6 | ||
|
|
3958260353 | ||
|
|
8cdb8d0367 | ||
|
|
66bf85b14f | ||
|
|
1a0892745e | ||
|
|
76b0493fb0 | ||
|
|
74eb4059d6 | ||
|
|
13f53eb64f | ||
|
|
b3941526cb | ||
|
|
fff067b2a8 | ||
|
|
217546c2a0 | ||
|
|
40c06f5e77 | ||
|
|
bb09caa9a3 | ||
|
|
4ebefeb43a | ||
|
|
8f01d0234f | ||
|
|
13035baeae |
45
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
45
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: Bug Report
|
||||
description: Create a report to help us improve
|
||||
labels: ["C-bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Thanks for filing a 🐛 bug report 😄!
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Problem
|
||||
description: >
|
||||
Please provide a clear and concise description of what the bug is,
|
||||
including what currently happens and what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps
|
||||
description: Please list the steps to reproduce the bug.
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
- type: textarea
|
||||
id: possible-solutions
|
||||
attributes:
|
||||
label: Possible Solution(s)
|
||||
description: >
|
||||
Not obligatory, but suggest a fix/reason for the bug,
|
||||
or ideas how to implement the addition or change.
|
||||
- type: textarea
|
||||
id: notes
|
||||
attributes:
|
||||
label: Notes
|
||||
description: Provide any additional notes that might be helpful.
|
||||
- type: textarea
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: >
|
||||
Please paste the output of running `mdbook --version` or which version
|
||||
of the library you are using.
|
||||
render: text
|
||||
28
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
28
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Enhancement
|
||||
description: Suggest an idea for enhancing mdBook
|
||||
labels: ["C-enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for filing a 🙋 feature request 😄!
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Problem
|
||||
description: >
|
||||
Please provide a clear description of your use case and the problem
|
||||
this feature request is trying to solve.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Proposed Solution
|
||||
description: >
|
||||
Please provide a clear and concise description of what you want to happen.
|
||||
- type: textarea
|
||||
id: notes
|
||||
attributes:
|
||||
label: Notes
|
||||
description: Provide any additional context or information that might be helpful.
|
||||
24
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
24
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Question
|
||||
description: Have a question on how to use mdBook?
|
||||
labels: ["C-question"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Got a question on how to do something with mdBook?
|
||||
- type: textarea
|
||||
id: question
|
||||
attributes:
|
||||
label: Question
|
||||
description: >
|
||||
Enter your question here. Please try to provide as much detail as possible.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: >
|
||||
Please paste the output of running `mdbook --version` or which version
|
||||
of the library you are using.
|
||||
render: text
|
||||
22
.github/workflows/deploy.yml
vendored
22
.github/workflows/deploy.yml
vendored
@@ -9,19 +9,35 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
target:
|
||||
- aarch64-unknown-linux-musl
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
- x86_64-apple-darwin
|
||||
- x86_64-pc-windows-msvc
|
||||
include:
|
||||
- target: aarch64-unknown-linux-musl
|
||||
os: ubuntu-20.04
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
os: ubuntu-20.04
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: ubuntu-20.04
|
||||
- target: x86_64-apple-darwin
|
||||
os: macos-latest
|
||||
- target: x86_64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install hub
|
||||
run: ci/install-hub.sh ${{ matrix.os }}
|
||||
shell: bash
|
||||
- name: Install Rust
|
||||
run: ci/install-rust.sh stable
|
||||
run: ci/install-rust.sh stable ${{ matrix.target }}
|
||||
shell: bash
|
||||
- name: Build and deploy artifacts
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ci/make-release.sh ${{ matrix.os }}
|
||||
run: ci/make-release.sh ${{ matrix.os }} ${{ matrix.target }}
|
||||
shell: bash
|
||||
pages:
|
||||
name: GitHub Pages
|
||||
|
||||
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
@@ -30,9 +30,9 @@ jobs:
|
||||
os: windows-latest
|
||||
rust: stable
|
||||
- build: msrv
|
||||
os: ubuntu-latest
|
||||
# sync MSRV with docs: guide/src/guide/installation.md
|
||||
rust: 1.54.0
|
||||
os: ubuntu-20.04
|
||||
# sync MSRV with docs: guide/src/guide/installation.md and Cargo.toml
|
||||
rust: 1.60.0
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install Rust
|
||||
|
||||
86
CHANGELOG.md
86
CHANGELOG.md
@@ -1,5 +1,91 @@
|
||||
# Changelog
|
||||
|
||||
## mdBook 0.4.26
|
||||
[v0.4.25...v0.4.26](https://github.com/rust-lang/mdBook/compare/v0.4.25...v0.4.26)
|
||||
|
||||
### Changed
|
||||
- Removed custom scrollbars for webkit browsers
|
||||
[#1961](https://github.com/rust-lang/mdBook/pull/1961)
|
||||
- Updated some dependencies
|
||||
[#1998](https://github.com/rust-lang/mdBook/pull/1998)
|
||||
[#2009](https://github.com/rust-lang/mdBook/pull/2009)
|
||||
[#2011](https://github.com/rust-lang/mdBook/pull/2011)
|
||||
- Fonts are now part of the theme.
|
||||
The `output.html.copy-fonts` option has been deprecated.
|
||||
To define custom fonts, be sure to define `theme/fonts.css`.
|
||||
[#1987](https://github.com/rust-lang/mdBook/pull/1987)
|
||||
|
||||
### Fixed
|
||||
- Fixed overflow viewport issue with mobile Safari
|
||||
[#1994](https://github.com/rust-lang/mdBook/pull/1994)
|
||||
|
||||
## mdBook 0.4.25
|
||||
[e14d381...1ba74a3](https://github.com/rust-lang/mdBook/compare/e14d381...1ba74a3)
|
||||
|
||||
### Fixed
|
||||
- Fixed a regression where `mdbook test -L deps path-to-book` would not work.
|
||||
[#1959](https://github.com/rust-lang/mdBook/pull/1959)
|
||||
|
||||
## mdBook 0.4.24
|
||||
[eb77083...8767ebf](https://github.com/rust-lang/mdBook/compare/eb77083...8767ebf)
|
||||
|
||||
### Fixed
|
||||
- The precompiled linux-gnu mdbook binary available on [GitHub Releases](https://github.com/rust-lang/mdBook/releases) inadvertently switched to a newer version of glibc. This release goes back to an older version that should be more compatible on older versions of Linux.
|
||||
[#1955](https://github.com/rust-lang/mdBook/pull/1955)
|
||||
|
||||
## mdBook 0.4.23
|
||||
[678b469...68a75da](https://github.com/rust-lang/mdBook/compare/678b469...68a75da)
|
||||
|
||||
### Changed
|
||||
- Updated all dependencies
|
||||
[#1951](https://github.com/rust-lang/mdBook/pull/1951)
|
||||
[#1952](https://github.com/rust-lang/mdBook/pull/1952)
|
||||
[#1844](https://github.com/rust-lang/mdBook/pull/1844)
|
||||
- Updated minimum Rust version to 1.60.
|
||||
[#1951](https://github.com/rust-lang/mdBook/pull/1951)
|
||||
|
||||
### Fixed
|
||||
- Fixed a regression where playground code was missing hidden lines, preventing it from compiling correctly.
|
||||
[#1950](https://github.com/rust-lang/mdBook/pull/1950)
|
||||
|
||||
## mdBook 0.4.22
|
||||
[40c06f5...4844f72](https://github.com/rust-lang/mdBook/compare/40c06f5...4844f72)
|
||||
|
||||
### Added
|
||||
- Added a `--chapter` option to `mdbook test` to specify a specific chapter to test.
|
||||
[#1741](https://github.com/rust-lang/mdBook/pull/1741)
|
||||
- Added CSS styling for `<kbd>` tags.
|
||||
[#1906](https://github.com/rust-lang/mdBook/pull/1906)
|
||||
- Added pre-compiled binaries for `x86_64-unknown-linux-musl` and `aarch64-unknown-linux-musl` (see [Releases](https://github.com/rust-lang/mdBook/releases)).
|
||||
[#1862](https://github.com/rust-lang/mdBook/pull/1862)
|
||||
- Added `build.extra-watch-dirs` which is an array of additional directories to watch for changes when running `mdbook serve`.
|
||||
[#1884](https://github.com/rust-lang/mdBook/pull/1884)
|
||||
|
||||
### Changed
|
||||
- Removed the `type="text/javascript"` attribute from `<script>` tags.
|
||||
[#1881](https://github.com/rust-lang/mdBook/pull/1881)
|
||||
- Switched to building with Rust Edition 2021.
|
||||
This raises the minimum supported Rust version to 1.56.
|
||||
[#1887](https://github.com/rust-lang/mdBook/pull/1887)
|
||||
- When hidden code is hidden, the hidden parts are no longer copied to the clipboard via the copy button.
|
||||
[#1911](https://github.com/rust-lang/mdBook/pull/1911)
|
||||
- Various HTML changes and fixes to be more compliant with HTML5.
|
||||
[#1924](https://github.com/rust-lang/mdBook/pull/1924)
|
||||
- The theme picker now shows which theme is currently selected.
|
||||
[#1935](https://github.com/rust-lang/mdBook/pull/1935)
|
||||
|
||||
### Fixed
|
||||
- Avoid blank line at the end of an ACE code block
|
||||
[#1836](https://github.com/rust-lang/mdBook/pull/1836)
|
||||
|
||||
|
||||
## mdBook 0.4.21
|
||||
[92afe9b...8f01d02](https://github.com/rust-lang/mdBook/compare/92afe9b...8f01d02)
|
||||
|
||||
### Fixed
|
||||
- Fixed an issue where mdBook would fail to compile with Rust nightly-2022-07-22.
|
||||
[#1861](https://github.com/rust-lang/mdBook/pull/1861)
|
||||
|
||||
## mdBook 0.4.20
|
||||
[53055e0...da166e0](https://github.com/rust-lang/mdBook/compare/53055e0...da166e0)
|
||||
|
||||
|
||||
@@ -7,13 +7,22 @@ If you have come here to learn how to contribute to mdBook, we have some tips fo
|
||||
First of all, don't hesitate to ask questions!
|
||||
Use the [issue tracker](https://github.com/rust-lang/mdBook/issues), no question is too simple.
|
||||
|
||||
### Issue assignment
|
||||
|
||||
**:warning: Important :warning:**
|
||||
|
||||
Before working on pull request, please ping us on the corresponding issue.
|
||||
The current PR backlog is beyond what we can process at this time.
|
||||
Only issues that have an [`E-Help-wanted`](https://github.com/rust-lang/mdBook/labels/E-Help-wanted) or [`Feature accepted`](https://github.com/rust-lang/mdBook/labels/Feature%20accepted) label will likely receive reviews.
|
||||
If there isn't already an open issue for what you want to work on, please open one first to see if it is something we would be available to review.
|
||||
|
||||
### Issues to work on
|
||||
|
||||
Any issue is up for the grabbing, but if you are starting out, you might be interested in the
|
||||
If you are starting out, you might be interested in the
|
||||
[E-Easy issues](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AE-Easy).
|
||||
Those are issues that are considered more straightforward for beginners to Rust or the codebase itself.
|
||||
These issues can be a good launching pad for more involved issues. Easy tasks for a first time contribution
|
||||
include documentation improvements, new tests, examples, updating dependencies, etc.
|
||||
These issues can be a good launching pad for more involved issues.
|
||||
Easy tasks for a first time contribution include documentation improvements, new tests, examples, updating dependencies, etc.
|
||||
|
||||
If you come from a web development background, you might be interested in issues related to web technologies tagged
|
||||
[A-JavaScript](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-JavaScript),
|
||||
@@ -21,16 +30,16 @@ If you come from a web development background, you might be interested in issues
|
||||
[A-HTML](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-HTML) or
|
||||
[A-Mobile](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-Mobile).
|
||||
|
||||
When you decide you want to work on a specific issue, ping us on that issue so that we can assign it to you.
|
||||
When you decide you want to work on a specific issue, and it isn't already assigned to someone else, assign the issue to yourself by leaving a comment with the text `@rustbot claim`.
|
||||
Again, do not hesitate to ask questions. We will gladly mentor anyone that want to tackle an issue.
|
||||
|
||||
Issues on the issue tracker are categorized with the following labels:
|
||||
|
||||
- **A**-prefixed labels state which area of the project an issue relates to.
|
||||
- **E**-prefixed labels show an estimate of the experience necessary to fix the issue.
|
||||
- **M**-prefixed labels are meta-issues used for questions, discussions, or tracking issues
|
||||
- **M**-prefixed labels are meta-issues regarding the management of the mdBook project itself
|
||||
- **S**-prefixed labels show the status of the issue
|
||||
- **T**-prefixed labels show the type of issue
|
||||
- **C**-prefixed labels show the category of issue
|
||||
|
||||
### Building mdBook
|
||||
|
||||
@@ -59,7 +68,7 @@ This will ensure we have good quality source code that is better for us all to m
|
||||
[rustfmt](https://github.com/rust-lang/rustfmt) has a lot more information on the project.
|
||||
The quick guide is
|
||||
|
||||
1. Install it
|
||||
1. Install it (`rustfmt` is usually installed by default via [rustup](https://rustup.rs/)):
|
||||
```
|
||||
rustup component add rustfmt
|
||||
```
|
||||
@@ -71,18 +80,15 @@ The quick guide is
|
||||
```
|
||||
cargo fmt
|
||||
```
|
||||
When run through `cargo` it will format all bin and lib files in the current crate.
|
||||
When run through `cargo` it will format all bin and lib files in the current package.
|
||||
|
||||
For more information, such as running it from your favourite editor, please see the `rustfmt` project. [rustfmt](https://github.com/rust-lang/rustfmt)
|
||||
|
||||
|
||||
#### Finding Issues with Clippy
|
||||
|
||||
Clippy is a code analyser/linter detecting mistakes, and therefore helps to improve your code.
|
||||
Like formatting your code with `rustfmt`, running clippy regularly and before your Pull Request will
|
||||
help us maintain awesome code.
|
||||
|
||||
The best documentation can be found over at [rust-clippy](https://github.com/rust-lang/rust-clippy)
|
||||
[Clippy](https://doc.rust-lang.org/clippy/) is a code analyser/linter detecting mistakes, and therefore helps to improve your code.
|
||||
Like formatting your code with `rustfmt`, running clippy regularly and before your Pull Request will help us maintain awesome code.
|
||||
|
||||
1. To install
|
||||
```
|
||||
@@ -93,17 +99,36 @@ The best documentation can be found over at [rust-clippy](https://github.com/rus
|
||||
cargo clippy
|
||||
```
|
||||
|
||||
Clippy has an ever growing list of checks, that are managed in [lint files](https://rust-lang.github.io/rust-clippy/master/index.html).
|
||||
### Change requirements
|
||||
|
||||
Please consider the following when making a change:
|
||||
|
||||
* Almost all changes that modify the Rust code must be accompanied with a test.
|
||||
|
||||
* Almost all features and changes must update the documentation.
|
||||
mdBook has the [mdBook Guide](https://rust-lang.github.io/mdBook/) whose source is at <https://github.com/rust-lang/mdBook/tree/master/guide>.
|
||||
|
||||
* Almost all Rust items should be documented with doc comments.
|
||||
See the [Rustdoc Book](https://doc.rust-lang.org/rustdoc/) for more information on writing doc comments.
|
||||
|
||||
* Breaking the API can only be done in major SemVer releases.
|
||||
These are done very infrequently, so it is preferred to avoid these when possible.
|
||||
See [SemVer Compatibility](https://doc.rust-lang.org/cargo/reference/semver.html) for more information on what a SemVer breaking change is.
|
||||
|
||||
(Note: At this time, some SemVer breaking changes are inevitable due to the current code structure.
|
||||
An example is adding new fields to the config structures.
|
||||
These are intended to be fixed in the next major release.)
|
||||
|
||||
* Similarly, the CLI interface is considered to be stable.
|
||||
Care should be taken to avoid breaking existing workflows.
|
||||
|
||||
* Check out the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/) for guidelines on designing the API.
|
||||
|
||||
### Making a pull-request
|
||||
|
||||
When you feel comfortable that your changes could be integrated into mdBook, you can create a pull-request on GitHub.
|
||||
One of the core maintainers will then approve the changes or request some changes before it gets merged.
|
||||
|
||||
If you want to make your pull-request even better, you might want to run [Clippy](https://github.com/Manishearth/rust-clippy)
|
||||
and [rustfmt](https://github.com/rust-lang/rustfmt) on the code first.
|
||||
This is not a requirement though and will never block a pull-request from being merged.
|
||||
|
||||
That's it, happy contributions! :tada: :tada: :tada:
|
||||
|
||||
## Browser compatibility and testing
|
||||
@@ -116,7 +141,7 @@ If possible, do your best to avoid breaking older browser releases.
|
||||
Any change to the HTML or styling is encouraged to manually check on as many browsers and platforms that you can.
|
||||
Unfortunately at this time we don't have any automated UI or browser testing, so your assistance in testing is appreciated.
|
||||
|
||||
## Updating higlight.js
|
||||
## Updating highlight.js
|
||||
|
||||
The following are instructions for updating [highlight.js](https://highlightjs.org/).
|
||||
|
||||
|
||||
1479
Cargo.lock
generated
1479
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
34
Cargo.toml
34
Cargo.toml
@@ -1,28 +1,29 @@
|
||||
[package]
|
||||
name = "mdbook"
|
||||
version = "0.4.20"
|
||||
version = "0.4.26"
|
||||
authors = [
|
||||
"Mathieu David <mathieudavid@mathieudavid.org>",
|
||||
"Michael-F-Bryan <michaelfbryan@gmail.com>",
|
||||
"Matt Ickstadt <mattico8@gmail.com>"
|
||||
]
|
||||
documentation = "http://rust-lang.github.io/mdBook/index.html"
|
||||
edition = "2018"
|
||||
documentation = "https://rust-lang.github.io/mdBook/index.html"
|
||||
edition = "2021"
|
||||
exclude = ["/guide/*"]
|
||||
keywords = ["book", "gitbook", "rustbook", "markdown"]
|
||||
license = "MPL-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/rust-lang/mdBook"
|
||||
description = "Creates a book from markdown files"
|
||||
rust-version = "1.60"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.28"
|
||||
chrono = "0.4"
|
||||
clap = { version = "3.0", features = ["cargo"] }
|
||||
clap_complete = "3.0"
|
||||
env_logger = "0.9.0"
|
||||
chrono = { version = "0.4", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.0.29", features = ["cargo", "wrap_help"] }
|
||||
clap_complete = "4.0.6"
|
||||
once_cell = "1"
|
||||
env_logger = "0.10.0"
|
||||
handlebars = "4.0"
|
||||
lazy_static = "1.0"
|
||||
log = "0.4"
|
||||
memchr = "2.0"
|
||||
opener = "0.5"
|
||||
@@ -32,11 +33,12 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
shlex = "1"
|
||||
tempfile = "3.0"
|
||||
toml = "0.5.1"
|
||||
topological-sort = "0.1.0"
|
||||
toml = "0.7.2"
|
||||
topological-sort = "0.2.2"
|
||||
|
||||
# Watch feature
|
||||
notify = { version = "4.0", optional = true }
|
||||
notify = { version = "5.0.0", optional = true }
|
||||
notify-debouncer-mini = { version = "0.2.1", optional = true }
|
||||
gitignore = { version = "1.0", optional = true }
|
||||
|
||||
# Serve feature
|
||||
@@ -49,19 +51,23 @@ elasticlunr-rs = { version = "3.0.0", optional = true }
|
||||
ammonia = { version = "3", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "1"
|
||||
assert_cmd = "2.0.7"
|
||||
predicates = "2"
|
||||
select = "0.5"
|
||||
select = "0.6.0"
|
||||
semver = "1.0"
|
||||
pretty_assertions = "1.2.1"
|
||||
walkdir = "2.0"
|
||||
|
||||
[features]
|
||||
default = ["watch", "serve", "search"]
|
||||
watch = ["notify", "gitignore"]
|
||||
watch = ["notify", "notify-debouncer-mini", "gitignore"]
|
||||
serve = ["futures-util", "tokio", "warp"]
|
||||
search = ["elasticlunr-rs", "ammonia"]
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "mdbook"
|
||||
|
||||
[[example]]
|
||||
name = "nop-preprocessor"
|
||||
test = true
|
||||
|
||||
@@ -13,6 +13,17 @@ TOOLCHAIN="$1"
|
||||
rustup set profile minimal
|
||||
rustup component remove --toolchain=$TOOLCHAIN rust-docs || echo "already removed"
|
||||
rustup update --no-self-update $TOOLCHAIN
|
||||
if [ -n "$2" ]
|
||||
then
|
||||
TARGET="$2"
|
||||
HOST=$(rustc -Vv | grep ^host: | sed -e "s/host: //g")
|
||||
if [ "$HOST" != "$TARGET" ]
|
||||
then
|
||||
rustup component add llvm-tools-preview --toolchain=$TOOLCHAIN
|
||||
rustup component add rust-std-$TARGET --toolchain=$TOOLCHAIN
|
||||
fi
|
||||
fi
|
||||
|
||||
rustup default $TOOLCHAIN
|
||||
rustup -V
|
||||
rustc -Vv
|
||||
|
||||
@@ -11,16 +11,21 @@ fi
|
||||
TAG=${GITHUB_REF#*/tags/}
|
||||
|
||||
host=$(rustc -Vv | grep ^host: | sed -e "s/host: //g")
|
||||
target=$2
|
||||
if [ "$host" != "$target" ]
|
||||
then
|
||||
export "CARGO_TARGET_$(echo $target | tr a-z- A-Z_)_LINKER"=rust-lld
|
||||
fi
|
||||
export CARGO_PROFILE_RELEASE_LTO=true
|
||||
cargo build --bin mdbook --release
|
||||
cd target/release
|
||||
cargo build --bin mdbook --release --target $target
|
||||
cd target/$target/release
|
||||
case $1 in
|
||||
ubuntu*)
|
||||
asset="mdbook-$TAG-$host.tar.gz"
|
||||
asset="mdbook-$TAG-$target.tar.gz"
|
||||
tar czf ../../$asset mdbook
|
||||
;;
|
||||
macos*)
|
||||
asset="mdbook-$TAG-$host.tar.gz"
|
||||
asset="mdbook-$TAG-$target.tar.gz"
|
||||
# There is a bug with BSD tar on macOS where the first 8MB of the file are
|
||||
# sometimes all NUL bytes. See https://github.com/actions/cache/issues/403
|
||||
# and https://github.com/rust-lang/cargo/issues/8603 for some more
|
||||
@@ -30,7 +35,7 @@ case $1 in
|
||||
tar czf ../../$asset mdbook
|
||||
;;
|
||||
windows*)
|
||||
asset="mdbook-$TAG-$host.zip"
|
||||
asset="mdbook-$TAG-$target.zip"
|
||||
7z a ../../$asset mdbook.exe
|
||||
;;
|
||||
*)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::nop_lib::Nop;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use clap::{Arg, ArgMatches, Command};
|
||||
use mdbook::book::Book;
|
||||
use mdbook::errors::Error;
|
||||
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
|
||||
@@ -7,11 +7,11 @@ use semver::{Version, VersionReq};
|
||||
use std::io;
|
||||
use std::process;
|
||||
|
||||
pub fn make_app() -> App<'static> {
|
||||
App::new("nop-preprocessor")
|
||||
pub fn make_app() -> Command {
|
||||
Command::new("nop-preprocessor")
|
||||
.about("A mdbook preprocessor which does precisely nothing")
|
||||
.subcommand(
|
||||
App::new("supports")
|
||||
Command::new("supports")
|
||||
.arg(Arg::new("renderer").required(true))
|
||||
.about("Check whether a renderer is supported by this preprocessor"),
|
||||
)
|
||||
@@ -54,7 +54,9 @@ fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
|
||||
}
|
||||
|
||||
fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! {
|
||||
let renderer = sub_args.value_of("renderer").expect("Required argument");
|
||||
let renderer = sub_args
|
||||
.get_one::<String>("renderer")
|
||||
.expect("Required argument");
|
||||
let supported = pre.supports_renderer(renderer);
|
||||
|
||||
// Signal whether the renderer is supported by exiting with 1 or 0.
|
||||
@@ -101,4 +103,58 @@ mod nop_lib {
|
||||
renderer != "not-supported"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn nop_preprocessor_run() {
|
||||
let input_json = r##"[
|
||||
{
|
||||
"root": "/path/to/book",
|
||||
"config": {
|
||||
"book": {
|
||||
"authors": ["AUTHOR"],
|
||||
"language": "en",
|
||||
"multilingual": false,
|
||||
"src": "src",
|
||||
"title": "TITLE"
|
||||
},
|
||||
"preprocessor": {
|
||||
"nop": {}
|
||||
}
|
||||
},
|
||||
"renderer": "html",
|
||||
"mdbook_version": "0.4.21"
|
||||
},
|
||||
{
|
||||
"sections": [
|
||||
{
|
||||
"Chapter": {
|
||||
"name": "Chapter 1",
|
||||
"content": "# Chapter 1\n",
|
||||
"number": [1],
|
||||
"sub_items": [],
|
||||
"path": "chapter_1.md",
|
||||
"source_path": "chapter_1.md",
|
||||
"parent_names": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"__non_exhaustive": null
|
||||
}
|
||||
]"##;
|
||||
let input_json = input_json.as_bytes();
|
||||
|
||||
let (ctx, book) = mdbook::preprocess::CmdPreprocessor::parse_input(input_json).unwrap();
|
||||
let expected_book = book.clone();
|
||||
let result = Nop::new().run(&ctx, book);
|
||||
assert!(result.is_ok());
|
||||
|
||||
// The nop-preprocessor should not have made any changes to the book content.
|
||||
let actual_book = result.unwrap();
|
||||
assert_eq!(actual_book, expected_book);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ mdbook test path/to/book
|
||||
The `--library-path` (`-L`) option allows you to add directories to the library
|
||||
search path used by `rustdoc` when it builds and tests the examples. Multiple
|
||||
directories can be specified with multiple options (`-L foo -L bar`) or with a
|
||||
comma-delimited list (`-L foo,bar`). The path should point to the Cargo
|
||||
comma-delimited list (`-L foo,bar`). The path should point to the Cargo
|
||||
[build cache](https://doc.rust-lang.org/cargo/guide/build-cache.html) `deps` directory that
|
||||
contains the build output of your project. For example, if your Rust project's book is in a directory
|
||||
named `my-book`, the following command would include the crate's dependencies when running `test`:
|
||||
@@ -61,3 +61,8 @@ The `--dest-dir` (`-d`) option allows you to change the output directory for the
|
||||
book. Relative paths are interpreted relative to the book's root directory. If
|
||||
not specified it will default to the value of the `build.build-dir` key in
|
||||
`book.toml`, or to `./book`.
|
||||
|
||||
#### --chapter
|
||||
|
||||
The `--chapter` (`-c`) option allows you to test a specific chapter of the
|
||||
book using the chapter name or the relative path to the chapter.
|
||||
@@ -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.20/mdbook-v0.4.20-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
|
||||
curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.26/mdbook-v0.4.26-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
|
||||
bin/mdbook build
|
||||
```
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ book's title without needing to touch your `book.toml`.
|
||||
> building the book with something like
|
||||
>
|
||||
> ```shell
|
||||
> $ export MDBOOK_BOOK="{'title': 'My Awesome Book', authors: ['Michael-F-Bryan']}"
|
||||
> $ export MDBOOK_BOOK='{"title": "My Awesome Book", "authors": ["Michael-F-Bryan"]}'
|
||||
> $ mdbook build
|
||||
> ```
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ This controls the build process of your book.
|
||||
build-dir = "book" # the directory where the output is placed
|
||||
create-missing = true # whether or not to create missing pages
|
||||
use-default-preprocessors = true # use the default preprocessors
|
||||
extra-watch-dirs = [] # directories to watch for triggering builds
|
||||
```
|
||||
|
||||
- **build-dir:** The directory to put the rendered book in. By default this is
|
||||
@@ -108,3 +109,6 @@ use-default-preprocessors = true # use the default preprocessors
|
||||
default preprocessors from running.
|
||||
- Adding `[preprocessor.links]`, for example, will ensure, regardless of
|
||||
`use-default-preprocessors` that `links` it will run.
|
||||
- **extra-watch-dirs**: A list of paths to directories that will be watched in
|
||||
the `watch` and `serve` commands. Changes to files under these directories will
|
||||
trigger rebuilds. Useful if your book depends on files outside its `src` directory.
|
||||
@@ -126,7 +126,10 @@ The following configuration options are available:
|
||||
that occur in code blocks and code spans. Defaults to `false`.
|
||||
- **mathjax-support:** Adds support for [MathJax](../mathjax.md). Defaults to
|
||||
`false`.
|
||||
- **copy-fonts:** Copies fonts.css and respective font files to the output directory and use them in the default theme. Defaults to `true`.
|
||||
- **copy-fonts:** (**Deprecated**) If `true` (the default), mdBook uses its built-in fonts which are copied to the output directory.
|
||||
If `false`, the built-in fonts will not be used.
|
||||
This option is deprecated. If you want to define your own custom fonts,
|
||||
create a `theme/fonts/fonts.css` file and store the fonts in the `theme/fonts/` directory.
|
||||
- **google-analytics:** This field has been deprecated and will be removed in a future release.
|
||||
Use the `theme/head.hbs` file to add the appropriate Google Analytics code instead.
|
||||
- **additional-css:** If you need to slightly change the appearance of your book
|
||||
@@ -157,7 +160,8 @@ The following configuration options are available:
|
||||
Defaults to `404.md`.
|
||||
- **site-url:** The url where the book will be hosted. This is required to ensure
|
||||
navigation links and script/css imports in the 404 file work correctly, even when accessing
|
||||
urls in subdirectories. Defaults to `/`.
|
||||
urls in subdirectories. Defaults to `/`. If `site-url` is set,
|
||||
make sure to use document relative links for your assets, meaning they should not start with `/`.
|
||||
- **cname:** The DNS subdomain or apex domain at which your book will be hosted.
|
||||
This string will be written to a file named CNAME in the root of your site, as
|
||||
required by GitHub Pages (see [*Managing a custom domain for your GitHub Pages
|
||||
|
||||
@@ -26,6 +26,8 @@ Here are the files you can override:
|
||||
- **_highlight.css_** is the theme used for the code highlighting.
|
||||
- **_favicon.svg_** and **_favicon.png_** the favicon that will be used. The SVG
|
||||
version is used by [newer browsers].
|
||||
- **fonts/fonts.css** contains the definition of which fonts to load.
|
||||
Custom fonts can be included in the `fonts` directory.
|
||||
|
||||
Generally, when you want to tweak the theme, you don't need to override all the
|
||||
files. If you only need changes in the stylesheet, there is no point in
|
||||
|
||||
@@ -20,7 +20,7 @@ To make it easier to run, put the path to the binary into your `PATH`.
|
||||
|
||||
To build the `mdbook` executable from source, you will first need to install Rust and Cargo.
|
||||
Follow the instructions on the [Rust installation page].
|
||||
mdBook currently requires at least Rust version 1.54.
|
||||
mdBook currently requires at least Rust version 1.60.
|
||||
|
||||
Once you have installed Rust, the following command can be used to build and install mdBook:
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
|
||||
use crate::config::BuildConfig;
|
||||
use crate::errors::*;
|
||||
use crate::utils::bracket_escape;
|
||||
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Load a book into memory from its `src/` directory.
|
||||
|
||||
@@ -6,6 +6,8 @@ use super::MDBook;
|
||||
use crate::config::Config;
|
||||
use crate::errors::*;
|
||||
use crate::theme;
|
||||
use crate::utils::fs::write_file;
|
||||
use log::{debug, error, info, trace};
|
||||
|
||||
/// A helper for setting up a new book and its directory structure.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -97,7 +99,9 @@ impl BookBuilder {
|
||||
fn write_book_toml(&self) -> Result<()> {
|
||||
debug!("Writing book.toml");
|
||||
let book_toml = self.root.join("book.toml");
|
||||
let cfg = toml::to_vec(&self.config).with_context(|| "Unable to serialize the config")?;
|
||||
let cfg = toml::to_string(&self.config)
|
||||
.with_context(|| "Unable to serialize the config")?
|
||||
.into_bytes();
|
||||
|
||||
File::create(book_toml)
|
||||
.with_context(|| "Couldn't create book.toml")?
|
||||
@@ -157,6 +161,19 @@ impl BookBuilder {
|
||||
let mut highlight_js = File::create(themedir.join("highlight.js"))?;
|
||||
highlight_js.write_all(theme::HIGHLIGHT_JS)?;
|
||||
|
||||
write_file(&themedir.join("fonts"), "fonts.css", theme::fonts::CSS)?;
|
||||
for (file_name, contents) in theme::fonts::LICENSES {
|
||||
write_file(&themedir, file_name, contents)?;
|
||||
}
|
||||
for (file_name, contents) in theme::fonts::OPEN_SANS.iter() {
|
||||
write_file(&themedir, file_name, contents)?;
|
||||
}
|
||||
write_file(
|
||||
&themedir,
|
||||
theme::fonts::SOURCE_CODE_PRO.0,
|
||||
theme::fonts::SOURCE_CODE_PRO.1,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ pub use self::book::{load_book, Book, BookItem, BookItems, Chapter};
|
||||
pub use self::init::BookBuilder;
|
||||
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
|
||||
|
||||
use log::{debug, error, info, log_enabled, trace, warn};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
@@ -246,6 +247,13 @@ impl MDBook {
|
||||
|
||||
/// Run `rustdoc` tests on the book, linking against the provided libraries.
|
||||
pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
|
||||
// test_chapter with chapter:None will run all tests.
|
||||
self.test_chapter(library_paths, None)
|
||||
}
|
||||
|
||||
/// Run `rustdoc` tests on a specific chapter of the book, linking against the provided libraries.
|
||||
/// If `chapter` is `None`, all tests will be run.
|
||||
pub fn test_chapter(&mut self, library_paths: Vec<&str>, chapter: Option<&str>) -> Result<()> {
|
||||
let library_args: Vec<&str> = (0..library_paths.len())
|
||||
.map(|_| "-L")
|
||||
.zip(library_paths.into_iter())
|
||||
@@ -254,6 +262,8 @@ impl MDBook {
|
||||
|
||||
let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
|
||||
|
||||
let mut chapter_found = false;
|
||||
|
||||
// FIXME: Is "test" the proper renderer name to use here?
|
||||
let preprocess_context =
|
||||
PreprocessorContext::new(self.root.clone(), self.config.clone(), "test".to_string());
|
||||
@@ -270,8 +280,16 @@ impl MDBook {
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let path = self.source_dir().join(&chapter_path);
|
||||
info!("Testing file: {:?}", path);
|
||||
if let Some(chapter) = chapter {
|
||||
if ch.name != chapter && chapter_path.to_str() != Some(chapter) {
|
||||
if chapter == "?" {
|
||||
info!("Skipping chapter '{}'...", ch.name);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
chapter_found = true;
|
||||
info!("Testing chapter '{}': {:?}", ch.name, chapter_path);
|
||||
|
||||
// write preprocessed file to tempdir
|
||||
let path = temp_dir.path().join(&chapter_path);
|
||||
@@ -295,6 +313,7 @@ impl MDBook {
|
||||
}
|
||||
}
|
||||
|
||||
debug!("running {:?}", cmd);
|
||||
let output = cmd.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
@@ -311,6 +330,11 @@ impl MDBook {
|
||||
if failed {
|
||||
bail!("One or more tests failed");
|
||||
}
|
||||
if let Some(chapter) = chapter {
|
||||
if !chapter_found {
|
||||
bail!("Chapter not found: {}", chapter);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::errors::*;
|
||||
use log::{debug, trace, warn};
|
||||
use memchr::{self, Memchr};
|
||||
use pulldown_cmark::{self, Event, HeadingLevel, Tag};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -453,7 +454,7 @@ impl<'a> SummaryParser<'a> {
|
||||
items.push(item);
|
||||
}
|
||||
Some(Event::Start(Tag::List(..))) => {
|
||||
// Skip this tag after comment bacause it is not nested.
|
||||
// Skip this tag after comment because it is not nested.
|
||||
if items.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,28 +1,16 @@
|
||||
use super::command_prelude::*;
|
||||
use crate::{get_book_dir, open};
|
||||
use clap::{arg, App, Arg, ArgMatches};
|
||||
use mdbook::errors::Result;
|
||||
use mdbook::MDBook;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'help>() -> App<'help> {
|
||||
App::new("build")
|
||||
pub fn make_subcommand() -> Command {
|
||||
Command::new("build")
|
||||
.about("Builds a book from its markdown files")
|
||||
.arg(
|
||||
Arg::new("dest-dir")
|
||||
.short('d')
|
||||
.long("dest-dir")
|
||||
.value_name("dest-dir")
|
||||
.help(
|
||||
"Output directory for the book{n}\
|
||||
Relative paths are interpreted relative to the book's root directory.{n}\
|
||||
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
|
||||
),
|
||||
)
|
||||
.arg(arg!([dir]
|
||||
"Root directory for the book{n}\
|
||||
(Defaults to the Current Directory when omitted)"
|
||||
))
|
||||
.arg(arg!(-o --open "Opens the compiled book in a web browser"))
|
||||
.arg_dest_dir()
|
||||
.arg_root_dir()
|
||||
.arg_open()
|
||||
}
|
||||
|
||||
// Build command implementation
|
||||
@@ -30,13 +18,13 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
|
||||
if let Some(dest_dir) = args.value_of("dest-dir") {
|
||||
if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") {
|
||||
book.config.build.build_dir = dest_dir.into();
|
||||
}
|
||||
|
||||
book.build()?;
|
||||
|
||||
if args.is_present("open") {
|
||||
if args.get_flag("open") {
|
||||
// FIXME: What's the right behaviour if we don't use the HTML renderer?
|
||||
let path = book.build_dir_for("html").join("index.html");
|
||||
if !path.exists() {
|
||||
|
||||
@@ -1,28 +1,16 @@
|
||||
use super::command_prelude::*;
|
||||
use crate::get_book_dir;
|
||||
use anyhow::Context;
|
||||
use clap::{arg, App, Arg, ArgMatches};
|
||||
use mdbook::MDBook;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'help>() -> App<'help> {
|
||||
App::new("clean")
|
||||
pub fn make_subcommand() -> Command {
|
||||
Command::new("clean")
|
||||
.about("Deletes a built book")
|
||||
.arg(
|
||||
Arg::new("dest-dir")
|
||||
.short('d')
|
||||
.long("dest-dir")
|
||||
.value_name("dest-dir")
|
||||
.help(
|
||||
"Output directory for the book{n}\
|
||||
Relative paths are interpreted relative to the book's root directory.{n}\
|
||||
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
|
||||
),
|
||||
)
|
||||
.arg(arg!([dir]
|
||||
"Root directory for the book{n}\
|
||||
(Defaults to the Current Directory when omitted)"
|
||||
))
|
||||
.arg_dest_dir()
|
||||
.arg_root_dir()
|
||||
}
|
||||
|
||||
// Clean command implementation
|
||||
@@ -30,7 +18,7 @@ pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let book = MDBook::load(&book_dir)?;
|
||||
|
||||
let dir_to_remove = match args.value_of("dest-dir") {
|
||||
let dir_to_remove = match args.get_one::<PathBuf>("dest-dir") {
|
||||
Some(dest_dir) => dest_dir.into(),
|
||||
None => book.root.join(&book.config.build.build_dir),
|
||||
};
|
||||
|
||||
45
src/cmd/command_prelude.rs
Normal file
45
src/cmd/command_prelude.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
//! Helpers for building the command-line arguments for commands.
|
||||
|
||||
pub use clap::{arg, Arg, ArgMatches, Command};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub trait CommandExt: Sized {
|
||||
fn _arg(self, arg: Arg) -> Self;
|
||||
|
||||
fn arg_dest_dir(self) -> Self {
|
||||
self._arg(
|
||||
Arg::new("dest-dir")
|
||||
.short('d')
|
||||
.long("dest-dir")
|
||||
.value_name("dest-dir")
|
||||
.value_parser(clap::value_parser!(PathBuf))
|
||||
.help(
|
||||
"Output directory for the book\n\
|
||||
Relative paths are interpreted relative to the book's root directory.\n\
|
||||
If omitted, mdBook uses build.build-dir from book.toml \
|
||||
or defaults to `./book`.",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn arg_root_dir(self) -> Self {
|
||||
self._arg(
|
||||
Arg::new("dir")
|
||||
.help(
|
||||
"Root directory for the book\n\
|
||||
(Defaults to the current directory when omitted)",
|
||||
)
|
||||
.value_parser(clap::value_parser!(PathBuf)),
|
||||
)
|
||||
}
|
||||
|
||||
fn arg_open(self) -> Self {
|
||||
self._arg(arg!(-o --open "Opens the compiled book in a web browser"))
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandExt for Command {
|
||||
fn _arg(self, arg: Arg) -> Self {
|
||||
self.arg(arg)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::get_book_dir;
|
||||
use clap::{arg, App, Arg, ArgMatches};
|
||||
use clap::{arg, ArgMatches, Command as ClapCommand};
|
||||
use mdbook::config;
|
||||
use mdbook::errors::Result;
|
||||
use mdbook::MDBook;
|
||||
@@ -8,30 +8,22 @@ use std::io::Write;
|
||||
use std::process::Command;
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'help>() -> App<'help> {
|
||||
App::new("init")
|
||||
pub fn make_subcommand() -> ClapCommand {
|
||||
ClapCommand::new("init")
|
||||
.about("Creates the boilerplate structure and files for a new book")
|
||||
// the {n} denotes a newline which will properly aligned in all help messages
|
||||
.arg(arg!([dir]
|
||||
"Directory to create the book in{n}\
|
||||
(Defaults to the Current Directory when omitted)"
|
||||
))
|
||||
.arg(
|
||||
arg!([dir]
|
||||
"Directory to create the book in\n\
|
||||
(Defaults to the current directory when omitted)"
|
||||
)
|
||||
.value_parser(clap::value_parser!(std::path::PathBuf)),
|
||||
)
|
||||
.arg(arg!(--theme "Copies the default theme into your source folder"))
|
||||
.arg(arg!(--force "Skips confirmation prompts"))
|
||||
.arg(arg!(--title <title> "Sets the book title"))
|
||||
.arg(
|
||||
Arg::new("title")
|
||||
.long("title")
|
||||
.takes_value(true)
|
||||
.help("Sets the book title")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("ignore")
|
||||
.long("ignore")
|
||||
.takes_value(true)
|
||||
.possible_values(&["none", "git"])
|
||||
.help("Creates a VCS ignore file (i.e. .gitignore)")
|
||||
.required(false),
|
||||
arg!(--ignore <ignore> "Creates a VCS ignore file (i.e. .gitignore)")
|
||||
.value_parser(["none", "git"]),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -41,12 +33,12 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let mut builder = MDBook::init(&book_dir);
|
||||
let mut config = config::Config::default();
|
||||
// If flag `--theme` is present, copy theme to src
|
||||
if args.is_present("theme") {
|
||||
if args.get_flag("theme") {
|
||||
let theme_dir = book_dir.join("theme");
|
||||
println!();
|
||||
println!("Copying the default theme to {}", theme_dir.display());
|
||||
// Skip this if `--force` is present
|
||||
if !args.is_present("force") && theme_dir.exists() {
|
||||
if !args.get_flag("force") && theme_dir.exists() {
|
||||
println!("This could potentially overwrite files already present in that directory.");
|
||||
print!("\nAre you sure you want to continue? (y/n) ");
|
||||
|
||||
@@ -59,7 +51,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ignore) = args.value_of("ignore") {
|
||||
if let Some(ignore) = args.get_one::<String>("ignore").map(|s| s.as_str()) {
|
||||
match ignore {
|
||||
"git" => builder.create_gitignore(true),
|
||||
_ => builder.create_gitignore(false),
|
||||
@@ -71,8 +63,8 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
config.book.title = if args.is_present("title") {
|
||||
args.value_of("title").map(String::from)
|
||||
config.book.title = if args.contains_id("title") {
|
||||
args.get_one::<String>("title").map(String::from)
|
||||
} else {
|
||||
request_book_title()
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
pub mod build;
|
||||
pub mod clean;
|
||||
pub mod command_prelude;
|
||||
pub mod init;
|
||||
#[cfg(feature = "serve")]
|
||||
pub mod serve;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use super::command_prelude::*;
|
||||
#[cfg(feature = "watch")]
|
||||
use super::watch;
|
||||
use crate::{get_book_dir, open};
|
||||
use clap::{arg, App, Arg, ArgMatches};
|
||||
use clap::builder::NonEmptyStringValueParser;
|
||||
use futures_util::sink::SinkExt;
|
||||
use futures_util::StreamExt;
|
||||
use mdbook::errors::*;
|
||||
@@ -18,43 +19,30 @@ use warp::Filter;
|
||||
const LIVE_RELOAD_ENDPOINT: &str = "__livereload";
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'help>() -> App<'help> {
|
||||
App::new("serve")
|
||||
pub fn make_subcommand() -> Command {
|
||||
Command::new("serve")
|
||||
.about("Serves a book at http://localhost:3000, and rebuilds it on changes")
|
||||
.arg(
|
||||
Arg::new("dest-dir")
|
||||
.short('d')
|
||||
.long("dest-dir")
|
||||
.value_name("dest-dir")
|
||||
.help(
|
||||
"Output directory for the book{n}\
|
||||
Relative paths are interpreted relative to the book's root directory.{n}\
|
||||
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
|
||||
),
|
||||
)
|
||||
.arg(arg!([dir]
|
||||
"Root directory for the book{n}\
|
||||
(Defaults to the Current Directory when omitted)"
|
||||
))
|
||||
.arg_dest_dir()
|
||||
.arg_root_dir()
|
||||
.arg(
|
||||
Arg::new("hostname")
|
||||
.short('n')
|
||||
.long("hostname")
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.default_value("localhost")
|
||||
.forbid_empty_values(true)
|
||||
.value_parser(NonEmptyStringValueParser::new())
|
||||
.help("Hostname to listen on for HTTP connections"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("port")
|
||||
.short('p')
|
||||
.long("port")
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.default_value("3000")
|
||||
.forbid_empty_values(true)
|
||||
.value_parser(NonEmptyStringValueParser::new())
|
||||
.help("Port to use for HTTP connections"),
|
||||
)
|
||||
.arg(arg!(-o --open "Opens the compiled book in a web browser"))
|
||||
.arg_open()
|
||||
}
|
||||
|
||||
// Serve command implementation
|
||||
@@ -62,17 +50,17 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
|
||||
let port = args.value_of("port").unwrap();
|
||||
let hostname = args.value_of("hostname").unwrap();
|
||||
let open_browser = args.is_present("open");
|
||||
let port = args.get_one::<String>("port").unwrap();
|
||||
let hostname = args.get_one::<String>("hostname").unwrap();
|
||||
let open_browser = args.get_flag("open");
|
||||
|
||||
let address = format!("{}:{}", hostname, port);
|
||||
|
||||
let update_config = |book: &mut MDBook| {
|
||||
book.config
|
||||
.set("output.html.live-reload-endpoint", &LIVE_RELOAD_ENDPOINT)
|
||||
.set("output.html.live-reload-endpoint", LIVE_RELOAD_ENDPOINT)
|
||||
.expect("live-reload-endpoint update failed");
|
||||
if let Some(dest_dir) = args.value_of("dest-dir") {
|
||||
if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") {
|
||||
book.config.build.build_dir = dest_dir.into();
|
||||
}
|
||||
// Override site-url for local serving of the 404 file
|
||||
@@ -89,8 +77,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let input_404 = book
|
||||
.config
|
||||
.get("output.html.input-404")
|
||||
.map(toml::Value::as_str)
|
||||
.and_then(std::convert::identity) // flatten
|
||||
.and_then(toml::Value::as_str)
|
||||
.map(ToString::to_string);
|
||||
let file_404 = get_404_output_file(&input_404);
|
||||
|
||||
|
||||
@@ -1,54 +1,58 @@
|
||||
use super::command_prelude::*;
|
||||
use crate::get_book_dir;
|
||||
use clap::{arg, App, Arg, ArgMatches};
|
||||
use clap::builder::NonEmptyStringValueParser;
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use mdbook::errors::Result;
|
||||
use mdbook::MDBook;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'help>() -> App<'help> {
|
||||
App::new("test")
|
||||
pub fn make_subcommand() -> Command {
|
||||
Command::new("test")
|
||||
.about("Tests that a book's Rust code samples compile")
|
||||
// FIXME: --dest-dir is unused by the test command, it should be removed
|
||||
.arg_dest_dir()
|
||||
.arg_root_dir()
|
||||
.arg(
|
||||
Arg::new("dest-dir")
|
||||
.short('d')
|
||||
.long("dest-dir")
|
||||
.value_name("dest-dir")
|
||||
Arg::new("chapter")
|
||||
.short('c')
|
||||
.long("chapter")
|
||||
.value_name("chapter"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("library-path")
|
||||
.short('L')
|
||||
.long("library-path")
|
||||
.value_name("dir")
|
||||
.value_delimiter(',')
|
||||
.value_parser(NonEmptyStringValueParser::new())
|
||||
.action(ArgAction::Append)
|
||||
.help(
|
||||
"Output directory for the book{n}\
|
||||
Relative paths are interpreted relative to the book's root directory.{n}\
|
||||
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
|
||||
"A comma-separated list of directories to add to the crate \
|
||||
search path when building tests",
|
||||
),
|
||||
)
|
||||
.arg(arg!([dir]
|
||||
"Root directory for the book{n}\
|
||||
(Defaults to the Current Directory when omitted)"
|
||||
))
|
||||
.arg(Arg::new("library-path")
|
||||
.short('L')
|
||||
.long("library-path")
|
||||
.value_name("dir")
|
||||
.takes_value(true)
|
||||
.use_delimiter(true)
|
||||
.require_delimiter(true)
|
||||
.multiple_values(true)
|
||||
.multiple_occurrences(true)
|
||||
.forbid_empty_values(true)
|
||||
.help("A comma-separated list of directories to add to {n}the crate search path when building tests"))
|
||||
}
|
||||
|
||||
// test command implementation
|
||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let library_paths: Vec<&str> = args
|
||||
.values_of("library-path")
|
||||
.map(std::iter::Iterator::collect)
|
||||
.get_many("library-path")
|
||||
.map(|it| it.map(String::as_str).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let chapter: Option<&str> = args.get_one::<String>("chapter").map(|s| s.as_str());
|
||||
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
|
||||
if let Some(dest_dir) = args.value_of("dest-dir") {
|
||||
book.config.build.build_dir = dest_dir.into();
|
||||
if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") {
|
||||
book.config.build.build_dir = dest_dir.to_path_buf();
|
||||
}
|
||||
|
||||
book.test(library_paths)?;
|
||||
match chapter {
|
||||
Some(_) => book.test_chapter(library_paths, chapter),
|
||||
None => book.test(library_paths),
|
||||
}?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,34 +1,20 @@
|
||||
use super::command_prelude::*;
|
||||
use crate::{get_book_dir, open};
|
||||
use clap::{arg, App, Arg, ArgMatches};
|
||||
use mdbook::errors::Result;
|
||||
use mdbook::utils;
|
||||
use mdbook::MDBook;
|
||||
use notify::Watcher;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc::channel;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'help>() -> App<'help> {
|
||||
App::new("watch")
|
||||
pub fn make_subcommand() -> Command {
|
||||
Command::new("watch")
|
||||
.about("Watches a book's files and rebuilds it on changes")
|
||||
.arg(
|
||||
Arg::new("dest-dir")
|
||||
.short('d')
|
||||
.long("dest-dir")
|
||||
.value_name("dest-dir")
|
||||
.help(
|
||||
"Output directory for the book{n}\
|
||||
Relative paths are interpreted relative to the book's root directory.{n}\
|
||||
If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
|
||||
),
|
||||
)
|
||||
.arg(arg!([dir]
|
||||
"Root directory for the book{n}\
|
||||
(Defaults to the Current Directory when omitted)"
|
||||
))
|
||||
.arg(arg!(-o --open "Opens the compiled book in a web browser"))
|
||||
.arg_dest_dir()
|
||||
.arg_root_dir()
|
||||
.arg_open()
|
||||
}
|
||||
|
||||
// Watch command implementation
|
||||
@@ -37,13 +23,13 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
|
||||
let update_config = |book: &mut MDBook| {
|
||||
if let Some(dest_dir) = args.value_of("dest-dir") {
|
||||
if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") {
|
||||
book.config.build.build_dir = dest_dir.into();
|
||||
}
|
||||
};
|
||||
update_config(&mut book);
|
||||
|
||||
if args.is_present("open") {
|
||||
if args.get_flag("open") {
|
||||
book.build()?;
|
||||
let path = book.build_dir_for("html").join("index.html");
|
||||
if !path.exists() {
|
||||
@@ -121,30 +107,42 @@ pub fn trigger_on_change<F>(book: &MDBook, closure: F)
|
||||
where
|
||||
F: Fn(Vec<PathBuf>, &Path),
|
||||
{
|
||||
use notify::DebouncedEvent::*;
|
||||
use notify::RecursiveMode::*;
|
||||
|
||||
// Create a channel to receive the events.
|
||||
let (tx, rx) = channel();
|
||||
|
||||
let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) {
|
||||
Ok(w) => w,
|
||||
let mut debouncer = match notify_debouncer_mini::new_debouncer(Duration::from_secs(1), None, tx)
|
||||
{
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
error!("Error while trying to watch the files:\n\n\t{:?}", e);
|
||||
std::process::exit(1)
|
||||
}
|
||||
};
|
||||
let watcher = debouncer.watcher();
|
||||
|
||||
// Add the source directory to the watcher
|
||||
if let Err(e) = watcher.watch(book.source_dir(), Recursive) {
|
||||
if let Err(e) = watcher.watch(&book.source_dir(), Recursive) {
|
||||
error!("Error while watching {:?}:\n {:?}", book.source_dir(), e);
|
||||
std::process::exit(1);
|
||||
};
|
||||
|
||||
let _ = watcher.watch(book.theme_dir(), Recursive);
|
||||
let _ = watcher.watch(&book.theme_dir(), Recursive);
|
||||
|
||||
// Add the book.toml file to the watcher if it exists
|
||||
let _ = watcher.watch(book.root.join("book.toml"), NonRecursive);
|
||||
let _ = watcher.watch(&book.root.join("book.toml"), NonRecursive);
|
||||
|
||||
for dir in &book.config.build.extra_watch_dirs {
|
||||
let path = dir.canonicalize().unwrap();
|
||||
if let Err(e) = watcher.watch(&path, Recursive) {
|
||||
error!(
|
||||
"Error while watching extra directory {:?}:\n {:?}",
|
||||
path, e
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
info!("Listening for changes...");
|
||||
|
||||
@@ -155,18 +153,25 @@ where
|
||||
|
||||
let all_events = std::iter::once(first_event).chain(other_events);
|
||||
|
||||
let paths = all_events
|
||||
.filter_map(|event| {
|
||||
debug!("Received filesystem event: {:?}", event);
|
||||
|
||||
match event {
|
||||
Create(path) | Write(path) | Remove(path) | Rename(_, path) => Some(path),
|
||||
_ => None,
|
||||
let paths: Vec<_> = all_events
|
||||
.filter_map(|event| match event {
|
||||
Ok(events) => Some(events),
|
||||
Err(errors) => {
|
||||
for error in errors {
|
||||
log::warn!("error while watching for changes: {error}");
|
||||
}
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.flatten()
|
||||
.map(|event| event.path)
|
||||
.collect();
|
||||
|
||||
let paths = remove_ignored_files(&book.root, &paths[..]);
|
||||
// If we are watching files outside the current repository (via extra-watch-dirs), then they are definitionally
|
||||
// ignored by gitignore. So we handle this case by including such files into the watched paths list.
|
||||
let any_external_paths = paths.iter().filter(|p| !p.starts_with(&book.root)).cloned();
|
||||
let mut paths = remove_ignored_files(&book.root, &paths[..]);
|
||||
paths.extend(any_external_paths);
|
||||
|
||||
if !paths.is_empty() {
|
||||
closure(paths, &book.root);
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use log::{debug, trace, warn};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
@@ -295,7 +296,7 @@ impl Default for Config {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Config {
|
||||
impl<'de> serde::Deserialize<'de> for Config {
|
||||
fn deserialize<D: Deserializer<'de>>(de: D) -> std::result::Result<Self, D::Error> {
|
||||
let raw = Value::deserialize(de)?;
|
||||
|
||||
@@ -437,6 +438,8 @@ pub struct BuildConfig {
|
||||
/// Should the default preprocessors always be used when they are
|
||||
/// compatible with the renderer?
|
||||
pub use_default_preprocessors: bool,
|
||||
/// Extra directories to trigger rebuild when watching/serving
|
||||
pub extra_watch_dirs: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl Default for BuildConfig {
|
||||
@@ -445,6 +448,7 @@ impl Default for BuildConfig {
|
||||
build_dir: PathBuf::from("book"),
|
||||
create_missing: true,
|
||||
use_default_preprocessors: true,
|
||||
extra_watch_dirs: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -526,10 +530,9 @@ pub struct HtmlConfig {
|
||||
/// directly jumping to editing the currently viewed page.
|
||||
/// Contains {path} that is replaced with chapter source file path
|
||||
pub edit_url_template: Option<String>,
|
||||
/// Endpoint of websocket, for livereload usage. Value loaded from .toml file
|
||||
/// is ignored, because our code overrides this field with the value [`LIVE_RELOAD_ENDPOINT`]
|
||||
///
|
||||
/// [`LIVE_RELOAD_ENDPOINT`]: cmd::serve::LIVE_RELOAD_ENDPOINT
|
||||
/// Endpoint of websocket, for livereload usage. Value loaded from .toml
|
||||
/// file is ignored, because our code overrides this field with an
|
||||
/// internal value (`LIVE_RELOAD_ENDPOINT)
|
||||
///
|
||||
/// This config item *should not be edited* by the end user.
|
||||
#[doc(hidden)]
|
||||
@@ -717,6 +720,7 @@ impl<'de, T> Updateable<'de> for T where T: Serialize + Deserialize<'de> {}
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::utils::fs::get_404_output_file;
|
||||
use serde_json::json;
|
||||
|
||||
const COMPLEX_CONFIG: &str = r#"
|
||||
[book]
|
||||
@@ -770,6 +774,7 @@ mod tests {
|
||||
build_dir: PathBuf::from("outputs"),
|
||||
create_missing: false,
|
||||
use_default_preprocessors: true,
|
||||
extra_watch_dirs: Vec::new(),
|
||||
};
|
||||
let rust_should_be = RustConfig { edition: None };
|
||||
let playground_should_be = Playground {
|
||||
@@ -980,6 +985,7 @@ mod tests {
|
||||
build_dir: PathBuf::from("my-book"),
|
||||
create_missing: true,
|
||||
use_default_preprocessors: true,
|
||||
extra_watch_dirs: Vec::new(),
|
||||
};
|
||||
|
||||
let html_should_be = HtmlConfig {
|
||||
|
||||
11
src/lib.rs
11
src/lib.rs
@@ -83,17 +83,6 @@
|
||||
#![deny(missing_docs)]
|
||||
#![deny(rust_2018_idioms)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
|
||||
pub mod book;
|
||||
pub mod config;
|
||||
pub mod preprocess;
|
||||
|
||||
42
src/main.rs
42
src/main.rs
@@ -5,7 +5,7 @@ extern crate log;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use chrono::Local;
|
||||
use clap::{App, AppSettings, Arg, ArgMatches};
|
||||
use clap::{Arg, ArgMatches, Command};
|
||||
use clap_complete::Shell;
|
||||
use env_logger::Builder;
|
||||
use log::LevelFilter;
|
||||
@@ -13,7 +13,7 @@ use mdbook::utils;
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
|
||||
mod cmd;
|
||||
|
||||
@@ -22,10 +22,10 @@ const VERSION: &str = concat!("v", crate_version!());
|
||||
fn main() {
|
||||
init_logger();
|
||||
|
||||
let app = create_clap_app();
|
||||
let command = create_clap_command();
|
||||
|
||||
// Check which subcomamnd the user ran...
|
||||
let res = match app.get_matches().subcommand() {
|
||||
// Check which subcommand the user ran...
|
||||
let res = match command.get_matches().subcommand() {
|
||||
Some(("init", sub_matches)) => cmd::init::execute(sub_matches),
|
||||
Some(("build", sub_matches)) => cmd::build::execute(sub_matches),
|
||||
Some(("clean", sub_matches)) => cmd::clean::execute(sub_matches),
|
||||
@@ -35,15 +35,13 @@ fn main() {
|
||||
Some(("serve", sub_matches)) => cmd::serve::execute(sub_matches),
|
||||
Some(("test", sub_matches)) => cmd::test::execute(sub_matches),
|
||||
Some(("completions", sub_matches)) => (|| {
|
||||
let shell: Shell = sub_matches
|
||||
.value_of("shell")
|
||||
.ok_or_else(|| anyhow!("Shell name missing."))?
|
||||
.parse()
|
||||
.map_err(|s| anyhow!("Invalid shell: {}", s))?;
|
||||
let shell = sub_matches
|
||||
.get_one::<Shell>("shell")
|
||||
.ok_or_else(|| anyhow!("Shell name missing."))?;
|
||||
|
||||
let mut complete_app = create_clap_app();
|
||||
let mut complete_app = create_clap_command();
|
||||
clap_complete::generate(
|
||||
shell,
|
||||
*shell,
|
||||
&mut complete_app,
|
||||
"mdbook",
|
||||
&mut std::io::stdout().lock(),
|
||||
@@ -61,13 +59,13 @@ fn main() {
|
||||
}
|
||||
|
||||
/// Create a list of valid arguments and sub-commands
|
||||
fn create_clap_app() -> App<'static> {
|
||||
let app = App::new(crate_name!())
|
||||
fn create_clap_command() -> Command {
|
||||
let app = Command::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.author("Mathieu David <mathieudavid@mathieudavid.org>")
|
||||
.version(VERSION)
|
||||
.setting(AppSettings::PropagateVersion)
|
||||
.setting(AppSettings::ArgRequiredElseHelp)
|
||||
.propagate_version(true)
|
||||
.arg_required_else_help(true)
|
||||
.after_help(
|
||||
"For more information about a specific command, try `mdbook <command> --help`\n\
|
||||
The source code for mdBook is available at: https://github.com/rust-lang/mdBook",
|
||||
@@ -77,12 +75,11 @@ fn create_clap_app() -> App<'static> {
|
||||
.subcommand(cmd::test::make_subcommand())
|
||||
.subcommand(cmd::clean::make_subcommand())
|
||||
.subcommand(
|
||||
App::new("completions")
|
||||
Command::new("completions")
|
||||
.about("Generate shell completions for your shell to stdout")
|
||||
.arg(
|
||||
Arg::new("shell")
|
||||
.takes_value(true)
|
||||
.possible_values(Shell::possible_values())
|
||||
.value_parser(clap::value_parser!(Shell))
|
||||
.help("the shell to generate completions for")
|
||||
.value_name("SHELL")
|
||||
.required(true),
|
||||
@@ -124,11 +121,10 @@ fn init_logger() {
|
||||
}
|
||||
|
||||
fn get_book_dir(args: &ArgMatches) -> PathBuf {
|
||||
if let Some(dir) = args.value_of("dir") {
|
||||
if let Some(p) = args.get_one::<PathBuf>("dir") {
|
||||
// Check if path is relative from current dir, or absolute...
|
||||
let p = Path::new(dir);
|
||||
if p.is_relative() {
|
||||
env::current_dir().unwrap().join(dir)
|
||||
env::current_dir().unwrap().join(p)
|
||||
} else {
|
||||
p.to_path_buf()
|
||||
}
|
||||
@@ -146,5 +142,5 @@ fn open<P: AsRef<OsStr>>(path: P) {
|
||||
|
||||
#[test]
|
||||
fn verify_app() {
|
||||
create_clap_app().debug_assert();
|
||||
create_clap_command().debug_assert();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::{Preprocessor, PreprocessorContext};
|
||||
use crate::book::Book;
|
||||
use crate::errors::*;
|
||||
use log::{debug, trace, warn};
|
||||
use shlex::Shlex;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::process::{Child, Command, Stdio};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use regex::Regex;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::errors::*;
|
||||
|
||||
use super::{Preprocessor, PreprocessorContext};
|
||||
use crate::book::{Book, BookItem};
|
||||
use crate::errors::*;
|
||||
use log::warn;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
/// A preprocessor for converting file name `README.md` to `index.md` since
|
||||
/// `README.md` is the de facto index file in markdown-based documentation.
|
||||
@@ -67,9 +68,8 @@ fn warn_readme_name_conflict<P: AsRef<Path>>(readme_path: P, index_path: P) {
|
||||
}
|
||||
|
||||
fn is_readme_file<P: AsRef<Path>>(path: P) -> bool {
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r"(?i)^readme$").unwrap();
|
||||
}
|
||||
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)^readme$").unwrap());
|
||||
|
||||
RE.is_match(
|
||||
path.as_ref()
|
||||
.file_stem()
|
||||
|
||||
@@ -10,6 +10,8 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use super::{Preprocessor, PreprocessorContext};
|
||||
use crate::book::{Book, BookItem};
|
||||
use log::{error, warn};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
const ESCAPE_CHAR: char = '\\';
|
||||
const MAX_LINK_NESTED_DEPTH: usize = 10;
|
||||
@@ -408,19 +410,20 @@ impl<'a> Iterator for LinkIter<'a> {
|
||||
fn find_links(contents: &str) -> LinkIter<'_> {
|
||||
// lazily compute following regex
|
||||
// r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([^}]+)\}\}")?;
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(
|
||||
static RE: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(
|
||||
r"(?x) # insignificant whitespace mode
|
||||
\\\{\{\#.*\}\} # match escaped link
|
||||
| # or
|
||||
\{\{\s* # link opening parens and whitespace
|
||||
\#([a-zA-Z0-9_]+) # link type
|
||||
\s+ # separating whitespace
|
||||
([^}]+) # link target path and space separated properties
|
||||
\}\} # link closing parens"
|
||||
\\\{\{\#.*\}\} # match escaped link
|
||||
| # or
|
||||
\{\{\s* # link opening parens and whitespace
|
||||
\#([a-zA-Z0-9_]+) # link type
|
||||
\s+ # separating whitespace
|
||||
([^}]+) # link target path and space separated properties
|
||||
\}\} # link closing parens",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
LinkIter(RE.captures_iter(contents))
|
||||
}
|
||||
|
||||
|
||||
@@ -12,12 +12,11 @@ use crate::book::Book;
|
||||
use crate::config::Config;
|
||||
use crate::errors::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Extra information for a `Preprocessor` to give them more context when
|
||||
/// processing a book.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@@ -14,7 +14,10 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::utils::fs::get_404_output_file;
|
||||
use handlebars::Handlebars;
|
||||
use log::{debug, trace, warn};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::{Captures, Regex};
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HtmlHandlebars;
|
||||
@@ -286,6 +289,31 @@ impl HtmlHandlebars {
|
||||
theme::fonts::SOURCE_CODE_PRO.1,
|
||||
)?;
|
||||
}
|
||||
if let Some(fonts_css) = &theme.fonts_css {
|
||||
if !fonts_css.is_empty() {
|
||||
if html_config.copy_fonts {
|
||||
warn!(
|
||||
"output.html.copy_fonts is deprecated.\n\
|
||||
Set copy_fonts=false and ensure the fonts you want are in \
|
||||
the `theme/fonts/` directory."
|
||||
);
|
||||
}
|
||||
write_file(destination, "fonts/fonts.css", &fonts_css)?;
|
||||
}
|
||||
}
|
||||
if !html_config.copy_fonts && theme.fonts_css.is_none() {
|
||||
warn!(
|
||||
"output.html.copy_fonts is deprecated.\n\
|
||||
This book appears to have copy_fonts=false without a fonts.css file.\n\
|
||||
Add an empty `theme/fonts/fonts.css` file to squelch this warning."
|
||||
);
|
||||
}
|
||||
for font_file in &theme.font_files {
|
||||
let contents = fs::read(font_file)?;
|
||||
let filename = font_file.file_name().unwrap();
|
||||
let filename = Path::new("fonts").join(filename);
|
||||
write_file(destination, filename, &contents)?;
|
||||
}
|
||||
|
||||
let playground_config = &html_config.playground;
|
||||
|
||||
@@ -337,6 +365,7 @@ impl HtmlHandlebars {
|
||||
);
|
||||
handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
|
||||
handlebars.register_helper("next", Box::new(helpers::navigation::next));
|
||||
// TODO: remove theme_option in 0.5, it is not needed.
|
||||
handlebars.register_helper("theme_option", Box::new(helpers::theme::theme_option));
|
||||
}
|
||||
|
||||
@@ -627,6 +656,7 @@ fn make_data(
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: remove default_theme in 0.5, it is not needed.
|
||||
let default_theme = match html_config.default_theme {
|
||||
Some(ref theme) => theme.to_lowercase(),
|
||||
None => "light".to_string(),
|
||||
@@ -651,7 +681,8 @@ fn make_data(
|
||||
data.insert("mathjax_support".to_owned(), json!(true));
|
||||
}
|
||||
|
||||
if html_config.copy_fonts {
|
||||
// This `matches!` checks for a non-empty file.
|
||||
if html_config.copy_fonts || matches!(theme.fonts_css.as_deref(), Some([_, ..])) {
|
||||
data.insert("copy_fonts".to_owned(), json!(true));
|
||||
}
|
||||
|
||||
@@ -764,9 +795,8 @@ fn make_data(
|
||||
/// Goes through the rendered HTML, making sure all header tags have
|
||||
/// an anchor respectively so people can link to sections directly.
|
||||
fn build_header_links(html: &str) -> String {
|
||||
lazy_static! {
|
||||
static ref BUILD_HEADER_LINKS: Regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();
|
||||
}
|
||||
static BUILD_HEADER_LINKS: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap());
|
||||
|
||||
let mut id_counter = HashMap::new();
|
||||
|
||||
@@ -807,10 +837,8 @@ fn insert_link_into_header(
|
||||
// ```
|
||||
// This function replaces all commas by spaces in the code block classes
|
||||
fn fix_code_blocks(html: &str) -> String {
|
||||
lazy_static! {
|
||||
static ref FIX_CODE_BLOCKS: Regex =
|
||||
Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap();
|
||||
}
|
||||
static FIX_CODE_BLOCKS: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap());
|
||||
|
||||
FIX_CODE_BLOCKS
|
||||
.replace_all(html, |caps: &Captures<'_>| {
|
||||
@@ -833,10 +861,9 @@ fn add_playground_pre(
|
||||
playground_config: &Playground,
|
||||
edition: Option<RustEdition>,
|
||||
) -> String {
|
||||
lazy_static! {
|
||||
static ref ADD_PLAYGROUND_PRE: Regex =
|
||||
Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap();
|
||||
}
|
||||
static ADD_PLAYGROUND_PRE: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap());
|
||||
|
||||
ADD_PLAYGROUND_PRE
|
||||
.replace_all(html, |caps: &Captures<'_>| {
|
||||
let text = &caps[1];
|
||||
@@ -899,18 +926,19 @@ fn add_playground_pre(
|
||||
}
|
||||
|
||||
fn hide_lines(content: &str) -> String {
|
||||
lazy_static! {
|
||||
static ref BORING_LINES_REGEX: Regex = Regex::new(r"^(\s*)#(.?)(.*)$").unwrap();
|
||||
}
|
||||
static BORING_LINES_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(\s*)#(.?)(.*)$").unwrap());
|
||||
|
||||
let mut result = String::with_capacity(content.len());
|
||||
for line in content.lines() {
|
||||
let mut lines = content.lines().peekable();
|
||||
while let Some(line) = lines.next() {
|
||||
// Don't include newline on the last line.
|
||||
let newline = if lines.peek().is_none() { "" } else { "\n" };
|
||||
if let Some(caps) = BORING_LINES_REGEX.captures(line) {
|
||||
if &caps[2] == "#" {
|
||||
result += &caps[1];
|
||||
result += &caps[2];
|
||||
result += &caps[3];
|
||||
result += "\n";
|
||||
result += newline;
|
||||
continue;
|
||||
} else if &caps[2] != "!" && &caps[2] != "[" {
|
||||
result += "<span class=\"boring\">";
|
||||
@@ -919,13 +947,13 @@ fn hide_lines(content: &str) -> String {
|
||||
result += &caps[2];
|
||||
}
|
||||
result += &caps[3];
|
||||
result += "\n";
|
||||
result += newline;
|
||||
result += "</span>";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result += line;
|
||||
result += "\n";
|
||||
result += newline;
|
||||
}
|
||||
result
|
||||
}
|
||||
@@ -1005,19 +1033,19 @@ mod tests {
|
||||
fn add_playground() {
|
||||
let inputs = [
|
||||
("<code class=\"language-rust\">x()</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</span></code></pre>"),
|
||||
("<code class=\"language-rust\">fn main() {}</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust\">fn main() {}\n</code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust\">fn main() {}</code></pre>"),
|
||||
("<code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";\n</code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";</code></pre>"),
|
||||
("<code class=\"language-rust editable\">let s = \"foo\n ## bar\n\";</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n\";\n</code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code></pre>"),
|
||||
("<code class=\"language-rust editable\">let s = \"foo\n # bar\n#\n\";</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span><span class=\"boring\">\n</span>\";\n</code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span><span class=\"boring\">\n</span>\";</code></pre>"),
|
||||
("<code class=\"language-rust ignore\">let s = \"foo\n # bar\n\";</code>",
|
||||
"<code class=\"language-rust ignore\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";\n</code>"),
|
||||
"<code class=\"language-rust ignore\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";</code>"),
|
||||
("<code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]\n</code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code></pre>"),
|
||||
];
|
||||
for (src, should_be) in &inputs {
|
||||
let got = add_playground_pre(
|
||||
@@ -1035,13 +1063,13 @@ mod tests {
|
||||
fn add_playground_edition2015() {
|
||||
let inputs = [
|
||||
("<code class=\"language-rust\">x()</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</span></code></pre>"),
|
||||
("<code class=\"language-rust\">fn main() {}</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
|
||||
("<code class=\"language-rust edition2015\">fn main() {}</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
|
||||
("<code class=\"language-rust edition2018\">fn main() {}</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
|
||||
];
|
||||
for (src, should_be) in &inputs {
|
||||
let got = add_playground_pre(
|
||||
@@ -1059,13 +1087,13 @@ mod tests {
|
||||
fn add_playground_edition2018() {
|
||||
let inputs = [
|
||||
("<code class=\"language-rust\">x()</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</span></code></pre>"),
|
||||
("<code class=\"language-rust\">fn main() {}</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
|
||||
("<code class=\"language-rust edition2015\">fn main() {}</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
|
||||
("<code class=\"language-rust edition2018\">fn main() {}</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
|
||||
];
|
||||
for (src, should_be) in &inputs {
|
||||
let got = add_playground_pre(
|
||||
@@ -1083,13 +1111,13 @@ mod tests {
|
||||
fn add_playground_edition2021() {
|
||||
let inputs = [
|
||||
("<code class=\"language-rust\">x()</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2021\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2021\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</span></code></pre>"),
|
||||
("<code class=\"language-rust\">fn main() {}</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2021\">fn main() {}\n</code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2021\">fn main() {}</code></pre>"),
|
||||
("<code class=\"language-rust edition2015\">fn main() {}</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
|
||||
("<code class=\"language-rust edition2018\">fn main() {}</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
|
||||
];
|
||||
for (src, should_be) in &inputs {
|
||||
let got = add_playground_pre(
|
||||
|
||||
@@ -4,6 +4,8 @@ use std::path::Path;
|
||||
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError, Renderable};
|
||||
|
||||
use crate::utils;
|
||||
use log::{debug, trace};
|
||||
use serde_json::json;
|
||||
|
||||
type StringMap = BTreeMap<String, String>;
|
||||
|
||||
@@ -146,15 +148,12 @@ fn render(
|
||||
|
||||
trace!("Render template");
|
||||
|
||||
_h.template()
|
||||
.ok_or_else(|| RenderError::new("Error with the handlebars template"))
|
||||
.and_then(|t| {
|
||||
let mut local_rc = rc.clone();
|
||||
let local_ctx = Context::wraps(&context)?;
|
||||
t.render(r, &local_ctx, &mut local_rc, out)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
let t = _h
|
||||
.template()
|
||||
.ok_or_else(|| RenderError::new("Error with the handlebars template"))?;
|
||||
let local_ctx = Context::wraps(&context)?;
|
||||
let mut local_rc = rc.clone();
|
||||
t.render(r, &local_ctx, &mut local_rc, out)
|
||||
}
|
||||
|
||||
pub fn previous(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError};
|
||||
use log::trace;
|
||||
|
||||
pub fn theme_option(
|
||||
h: &Helper<'_, '_>,
|
||||
|
||||
@@ -117,35 +117,35 @@ impl HelperDef for RenderToc {
|
||||
}
|
||||
|
||||
// Link
|
||||
let path_exists = if let Some(path) =
|
||||
item.get("path")
|
||||
.and_then(|p| if p.is_empty() { None } else { Some(p) })
|
||||
{
|
||||
out.write("<a href=\"")?;
|
||||
let path_exists: bool;
|
||||
match item.get("path") {
|
||||
Some(path) if !path.is_empty() => {
|
||||
out.write("<a href=\"")?;
|
||||
let tmp = Path::new(path)
|
||||
.with_extension("html")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
// Hack for windows who tends to use `\` as separator instead of `/`
|
||||
.replace('\\', "/");
|
||||
|
||||
let tmp = Path::new(item.get("path").expect("Error: path should be Some(_)"))
|
||||
.with_extension("html")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
// Hack for windows who tends to use `\` as separator instead of `/`
|
||||
.replace('\\', "/");
|
||||
// Add link
|
||||
out.write(&utils::fs::path_to_root(¤t_path))?;
|
||||
out.write(&tmp)?;
|
||||
out.write("\"")?;
|
||||
|
||||
// Add link
|
||||
out.write(&utils::fs::path_to_root(¤t_path))?;
|
||||
out.write(&tmp)?;
|
||||
out.write("\"")?;
|
||||
if path == ¤t_path || is_first_chapter {
|
||||
is_first_chapter = false;
|
||||
out.write(" class=\"active\"")?;
|
||||
}
|
||||
|
||||
if path == ¤t_path || is_first_chapter {
|
||||
is_first_chapter = false;
|
||||
out.write(" class=\"active\"")?;
|
||||
out.write(">")?;
|
||||
path_exists = true;
|
||||
}
|
||||
|
||||
out.write(">")?;
|
||||
true
|
||||
} else {
|
||||
out.write("<div>")?;
|
||||
false
|
||||
};
|
||||
_ => {
|
||||
out.write("<div>")?;
|
||||
path_exists = false;
|
||||
}
|
||||
}
|
||||
|
||||
if !self.no_section_label {
|
||||
// Section does not necessarily exist
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet};
|
||||
use std::path::Path;
|
||||
|
||||
use elasticlunr::{Index, IndexBuilder};
|
||||
use once_cell::sync::Lazy;
|
||||
use pulldown_cmark::*;
|
||||
|
||||
use crate::book::{Book, BookItem};
|
||||
@@ -10,7 +11,7 @@ use crate::config::Search;
|
||||
use crate::errors::*;
|
||||
use crate::theme::searcher;
|
||||
use crate::utils;
|
||||
|
||||
use log::{debug, warn};
|
||||
use serde::Serialize;
|
||||
|
||||
const MAX_WORD_LENGTH_TO_INDEX: usize = 80;
|
||||
@@ -266,21 +267,19 @@ fn write_to_json(index: Index, search_config: &Search, doc_urls: Vec<String>) ->
|
||||
}
|
||||
|
||||
fn clean_html(html: &str) -> String {
|
||||
lazy_static! {
|
||||
static ref AMMONIA: ammonia::Builder<'static> = {
|
||||
let mut clean_content = HashSet::new();
|
||||
clean_content.insert("script");
|
||||
clean_content.insert("style");
|
||||
let mut builder = ammonia::Builder::new();
|
||||
builder
|
||||
.tags(HashSet::new())
|
||||
.tag_attributes(HashMap::new())
|
||||
.generic_attributes(HashSet::new())
|
||||
.link_rel(None)
|
||||
.allowed_classes(HashMap::new())
|
||||
.clean_content_tags(clean_content);
|
||||
builder
|
||||
};
|
||||
}
|
||||
static AMMONIA: Lazy<ammonia::Builder<'static>> = Lazy::new(|| {
|
||||
let mut clean_content = HashSet::new();
|
||||
clean_content.insert("script");
|
||||
clean_content.insert("style");
|
||||
let mut builder = ammonia::Builder::new();
|
||||
builder
|
||||
.tags(HashSet::new())
|
||||
.tag_attributes(HashMap::new())
|
||||
.generic_attributes(HashSet::new())
|
||||
.link_rel(None)
|
||||
.allowed_classes(HashMap::new())
|
||||
.clean_content_tags(clean_content);
|
||||
builder
|
||||
});
|
||||
AMMONIA.clean(html).to_string()
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::book::BookItem;
|
||||
use crate::errors::*;
|
||||
use crate::renderer::{RenderContext, Renderer};
|
||||
use crate::utils;
|
||||
|
||||
use log::trace;
|
||||
use std::fs;
|
||||
|
||||
#[derive(Default)]
|
||||
|
||||
@@ -27,6 +27,7 @@ use std::process::{Command, Stdio};
|
||||
use crate::book::Book;
|
||||
use crate::config::Config;
|
||||
use crate::errors::*;
|
||||
use log::{error, info, trace, warn};
|
||||
use toml::Value;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -4,14 +4,16 @@
|
||||
window.onunload = function () { };
|
||||
|
||||
// Global variable, shared between modules
|
||||
function playground_text(playground) {
|
||||
function playground_text(playground, hidden = true) {
|
||||
let code_block = playground.querySelector("code");
|
||||
|
||||
if (window.ace && code_block.classList.contains("editable")) {
|
||||
let editor = window.ace.edit(code_block);
|
||||
return editor.getValue();
|
||||
} else {
|
||||
} else if (hidden) {
|
||||
return code_block.textContent;
|
||||
} else {
|
||||
return code_block.innerText;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +168,6 @@ function playground_text(playground) {
|
||||
.filter(function (node) {return node.classList.contains("editable"); })
|
||||
.forEach(function (block) { block.classList.remove('language-rust'); });
|
||||
|
||||
Array
|
||||
code_nodes
|
||||
.filter(function (node) {return !node.classList.contains("editable"); })
|
||||
.forEach(function (block) { hljs.highlightBlock(block); });
|
||||
@@ -300,6 +301,13 @@ function playground_text(playground) {
|
||||
themePopup.querySelector("button#" + get_theme()).focus();
|
||||
}
|
||||
|
||||
function updateThemeSelected() {
|
||||
themePopup.querySelectorAll('.theme-selected').forEach(function (el) {
|
||||
el.classList.remove('theme-selected');
|
||||
});
|
||||
themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected');
|
||||
}
|
||||
|
||||
function hideThemes() {
|
||||
themePopup.style.display = 'none';
|
||||
themeToggleButton.setAttribute('aria-expanded', false);
|
||||
@@ -355,6 +363,7 @@ function playground_text(playground) {
|
||||
|
||||
html.classList.remove(previousTheme);
|
||||
html.classList.add(theme);
|
||||
updateThemeSelected();
|
||||
}
|
||||
|
||||
// Set theme
|
||||
@@ -592,7 +601,7 @@ function playground_text(playground) {
|
||||
text: function (trigger) {
|
||||
hideTooltip(trigger);
|
||||
let playground = trigger.closest("pre");
|
||||
return playground_text(playground);
|
||||
return playground_text(playground, false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
|
||||
@import 'variables.css';
|
||||
|
||||
::-webkit-scrollbar {
|
||||
background: var(--bg);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar);
|
||||
}
|
||||
html {
|
||||
scrollbar-color: var(--scrollbar) var(--bg);
|
||||
}
|
||||
@@ -18,6 +12,19 @@ a > .hljs {
|
||||
color: var(--links);
|
||||
}
|
||||
|
||||
/*
|
||||
body-container is necessary because mobile browsers don't seem to like
|
||||
overflow-x on the body tag when there is a <meta name="viewport"> tag.
|
||||
*/
|
||||
#body-container {
|
||||
/*
|
||||
This is used when the sidebar pushes the body content off the side of
|
||||
the screen on small screens. Without it, dragging on mobile Safari
|
||||
will want to reposition the viewport in a weird way.
|
||||
*/
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Menu Bar */
|
||||
|
||||
#menu-bar,
|
||||
@@ -507,6 +514,8 @@ ul#searchresults span.teaser em {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: none;
|
||||
/* Don't let the children's background extend past the rounded corners. */
|
||||
overflow: hidden;
|
||||
}
|
||||
.theme-popup .default {
|
||||
color: var(--icons);
|
||||
@@ -515,7 +524,7 @@ ul#searchresults span.teaser em {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 2px 10px;
|
||||
padding: 2px 20px;
|
||||
line-height: 25px;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
@@ -527,8 +536,10 @@ ul#searchresults span.teaser em {
|
||||
.theme-popup .theme:hover {
|
||||
background-color: var(--theme-hover);
|
||||
}
|
||||
.theme-popup .theme:hover:first-child,
|
||||
.theme-popup .theme:hover:last-child {
|
||||
border-top-left-radius: inherit;
|
||||
border-top-right-radius: inherit;
|
||||
|
||||
.theme-selected::before {
|
||||
display: inline-block;
|
||||
content: "✓";
|
||||
margin-left: -14px;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ body {
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important;
|
||||
font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */
|
||||
font-family: var(--mono-font) !important;
|
||||
font-size: var(--code-font-size);
|
||||
}
|
||||
|
||||
/* make long words/inline code not x overflow */
|
||||
@@ -148,6 +148,18 @@ blockquote {
|
||||
border-bottom: .1em solid var(--quote-border);
|
||||
}
|
||||
|
||||
kbd {
|
||||
background-color: var(--table-border-color);
|
||||
border-radius: 4px;
|
||||
border: solid 1px var(--theme-popup-border);
|
||||
box-shadow: inset 0 -1px 0 var(--theme-hover);
|
||||
display: inline-block;
|
||||
font-size: var(--code-font-size);
|
||||
font-family: var(--mono-font);
|
||||
line-height: 10px;
|
||||
padding: 4px 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
:not(.footnote-definition) + .footnote-definition,
|
||||
.footnote-definition + :not(.footnote-definition) {
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
--page-padding: 15px;
|
||||
--content-max-width: 750px;
|
||||
--menu-bar-height: 50px;
|
||||
--mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace;
|
||||
--code-font-size: 0.875em /* please adjust the ace font size accordingly in editor.js */
|
||||
}
|
||||
|
||||
/* Themes */
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<!-- Custom HTML head -->
|
||||
{{> head}}
|
||||
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="{{ description }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
@@ -51,18 +50,19 @@
|
||||
|
||||
{{#if mathjax_support}}
|
||||
<!-- MathJax -->
|
||||
<script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||||
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||||
{{/if}}
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-container">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
var path_to_root = "{{ path_to_root }}";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
@@ -78,7 +78,7 @@
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
@@ -90,7 +90,7 @@
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
var html = document.querySelector('html');
|
||||
var sidebar = 'hidden';
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
@@ -122,11 +122,11 @@
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">{{ theme_option "Light" }}</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">{{ theme_option "Rust" }}</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">{{ theme_option "Coal" }}</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">{{ theme_option "Navy" }}</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">{{ theme_option "Ayu" }}</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
{{#if search_enabled}}
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
@@ -171,7 +171,7 @@
|
||||
{{/if}}
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
@@ -221,7 +221,7 @@
|
||||
|
||||
{{#if live_reload_endpoint}}
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
|
||||
const socket = new WebSocket(wsAddress);
|
||||
@@ -240,7 +240,7 @@
|
||||
|
||||
{{#if google_analytics}}
|
||||
<!-- Google Analytics Tag -->
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
var localAddrs = ["localhost", "127.0.0.1", ""];
|
||||
|
||||
// make sure we don't activate google analytics if the developer is
|
||||
@@ -258,43 +258,43 @@
|
||||
{{/if}}
|
||||
|
||||
{{#if playground_line_numbers}}
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
window.playground_line_numbers = true;
|
||||
</script>
|
||||
{{/if}}
|
||||
|
||||
{{#if playground_copyable}}
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
{{/if}}
|
||||
|
||||
{{#if playground_js}}
|
||||
<script src="{{ path_to_root }}ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}theme-dawn.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}ace.js"></script>
|
||||
<script src="{{ path_to_root }}editor.js"></script>
|
||||
<script src="{{ path_to_root }}mode-rust.js"></script>
|
||||
<script src="{{ path_to_root }}theme-dawn.js"></script>
|
||||
<script src="{{ path_to_root }}theme-tomorrow_night.js"></script>
|
||||
{{/if}}
|
||||
|
||||
{{#if search_js}}
|
||||
<script src="{{ path_to_root }}elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}mark.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}searcher.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}elasticlunr.min.js"></script>
|
||||
<script src="{{ path_to_root }}mark.min.js"></script>
|
||||
<script src="{{ path_to_root }}searcher.js"></script>
|
||||
{{/if}}
|
||||
|
||||
<script src="{{ path_to_root }}clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}highlight.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}book.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}clipboard.min.js"></script>
|
||||
<script src="{{ path_to_root }}highlight.js"></script>
|
||||
<script src="{{ path_to_root }}book.js"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
{{#each additional_js}}
|
||||
<script type="text/javascript" src="{{ ../path_to_root }}{{this}}"></script>
|
||||
<script src="{{ ../path_to_root }}{{this}}"></script>
|
||||
{{/each}}
|
||||
|
||||
{{#if is_print}}
|
||||
{{#if mathjax_support}}
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
window.addEventListener('load', function() {
|
||||
MathJax.Hub.Register.StartupHook('End', function() {
|
||||
window.setTimeout(window.print, 100);
|
||||
@@ -302,7 +302,7 @@
|
||||
});
|
||||
</script>
|
||||
{{else}}
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
window.addEventListener('load', function() {
|
||||
window.setTimeout(window.print, 100);
|
||||
});
|
||||
@@ -310,5 +310,6 @@
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -9,10 +9,10 @@ pub mod searcher;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::errors::*;
|
||||
|
||||
use log::warn;
|
||||
pub static INDEX: &[u8] = include_bytes!("index.hbs");
|
||||
pub static HEAD: &[u8] = include_bytes!("head.hbs");
|
||||
pub static REDIRECT: &[u8] = include_bytes!("redirect.hbs");
|
||||
@@ -54,6 +54,8 @@ pub struct Theme {
|
||||
pub general_css: Vec<u8>,
|
||||
pub print_css: Vec<u8>,
|
||||
pub variables_css: Vec<u8>,
|
||||
pub fonts_css: Option<Vec<u8>>,
|
||||
pub font_files: Vec<PathBuf>,
|
||||
pub favicon_png: Option<Vec<u8>>,
|
||||
pub favicon_svg: Option<Vec<u8>>,
|
||||
pub js: Vec<u8>,
|
||||
@@ -104,7 +106,7 @@ impl Theme {
|
||||
),
|
||||
];
|
||||
|
||||
let load_with_warn = |filename: &Path, dest| {
|
||||
let load_with_warn = |filename: &Path, dest: &mut Vec<u8>| {
|
||||
if !filename.exists() {
|
||||
// Don't warn if the file doesn't exist.
|
||||
return false;
|
||||
@@ -121,6 +123,29 @@ impl Theme {
|
||||
load_with_warn(&filename, dest);
|
||||
}
|
||||
|
||||
let fonts_dir = theme_dir.join("fonts");
|
||||
if fonts_dir.exists() {
|
||||
let mut fonts_css = Vec::new();
|
||||
if load_with_warn(&fonts_dir.join("fonts.css"), &mut fonts_css) {
|
||||
theme.fonts_css.replace(fonts_css);
|
||||
}
|
||||
if let Ok(entries) = fonts_dir.read_dir() {
|
||||
theme.font_files = entries
|
||||
.filter_map(|entry| {
|
||||
let entry = entry.ok()?;
|
||||
if entry.file_name() == "fonts.css" {
|
||||
None
|
||||
} else if entry.file_type().ok()?.is_dir() {
|
||||
log::info!("skipping font directory {:?}", entry.path());
|
||||
None
|
||||
} else {
|
||||
Some(entry.path())
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
|
||||
// If the user overrides one favicon, but not the other, do not
|
||||
// copy the default for the other.
|
||||
let favicon_png = &mut theme.favicon_png.as_mut().unwrap();
|
||||
@@ -153,6 +178,8 @@ impl Default for Theme {
|
||||
general_css: GENERAL_CSS.to_owned(),
|
||||
print_css: PRINT_CSS.to_owned(),
|
||||
variables_css: VARIABLES_CSS.to_owned(),
|
||||
fonts_css: None,
|
||||
font_files: Vec::new(),
|
||||
favicon_png: Some(FAVICON_PNG.to_owned()),
|
||||
favicon_svg: Some(FAVICON_SVG.to_owned()),
|
||||
js: JS.to_owned(),
|
||||
@@ -209,10 +236,10 @@ mod tests {
|
||||
"favicon.png",
|
||||
"favicon.svg",
|
||||
"css/chrome.css",
|
||||
"css/fonts.css",
|
||||
"css/general.css",
|
||||
"css/print.css",
|
||||
"css/variables.css",
|
||||
"fonts/fonts.css",
|
||||
"book.js",
|
||||
"highlight.js",
|
||||
"tomorrow-night.css",
|
||||
@@ -223,6 +250,7 @@ mod tests {
|
||||
|
||||
let temp = TempFileBuilder::new().prefix("mdbook-").tempdir().unwrap();
|
||||
fs::create_dir(temp.path().join("css")).unwrap();
|
||||
fs::create_dir(temp.path().join("fonts")).unwrap();
|
||||
|
||||
// "touch" all of the special files so we have empty copies
|
||||
for file in &files {
|
||||
@@ -240,6 +268,8 @@ mod tests {
|
||||
general_css: Vec::new(),
|
||||
print_css: Vec::new(),
|
||||
variables_css: Vec::new(),
|
||||
fonts_css: Some(Vec::new()),
|
||||
font_files: Vec::new(),
|
||||
favicon_png: Some(Vec::new()),
|
||||
favicon_svg: Some(Vec::new()),
|
||||
js: Vec::new(),
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Redirecting...</title>
|
||||
<meta http-equiv="refresh" content="0;URL='{{url}}'">
|
||||
<meta rel="canonical" href="{{url}}">
|
||||
<meta http-equiv="refresh" content="0; URL={{url}}">
|
||||
<link rel="canonical" href="{{url}}">
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to... <a href="{{url}}">{{url}}</a>.</p>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::errors::*;
|
||||
use log::{debug, trace};
|
||||
use std::convert::Into;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
|
||||
@@ -4,9 +4,10 @@ pub mod fs;
|
||||
mod string;
|
||||
pub(crate) mod toml_ext;
|
||||
use crate::errors::Error;
|
||||
use regex::Regex;
|
||||
|
||||
use log::error;
|
||||
use once_cell::sync::Lazy;
|
||||
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag};
|
||||
use regex::Regex;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
@@ -20,9 +21,7 @@ pub use self::string::{
|
||||
|
||||
/// Replaces multiple consecutive whitespace characters with a single space character.
|
||||
pub fn collapse_whitespace(text: &str) -> Cow<'_, str> {
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r"\s\s+").unwrap();
|
||||
}
|
||||
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\s\s+").unwrap());
|
||||
RE.replace_all(text, " ")
|
||||
}
|
||||
|
||||
@@ -51,9 +50,7 @@ pub fn id_from_content(content: &str) -> String {
|
||||
let mut content = content.to_string();
|
||||
|
||||
// Skip any tags or html-encoded stuff
|
||||
lazy_static! {
|
||||
static ref HTML: Regex = Regex::new(r"(<.*?>)").unwrap();
|
||||
}
|
||||
static HTML: Lazy<Regex> = Lazy::new(|| Regex::new(r"(<.*?>)").unwrap());
|
||||
content = HTML.replace_all(&content, "").into();
|
||||
const REPL_SUB: &[&str] = &["<", ">", "&", "'", """];
|
||||
for sub in REPL_SUB {
|
||||
@@ -96,10 +93,9 @@ pub fn unique_id_from_content(content: &str, id_counter: &mut HashMap<String, us
|
||||
/// None. Ideally, print page links would link to anchors on the print page,
|
||||
/// but that is very difficult.
|
||||
fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
|
||||
lazy_static! {
|
||||
static ref SCHEME_LINK: Regex = Regex::new(r"^[a-z][a-z0-9+.-]*:").unwrap();
|
||||
static ref MD_LINK: Regex = Regex::new(r"(?P<link>.*)\.md(?P<anchor>#.*)?").unwrap();
|
||||
}
|
||||
static SCHEME_LINK: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[a-z][a-z0-9+.-]*:").unwrap());
|
||||
static MD_LINK: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"(?P<link>.*)\.md(?P<anchor>#.*)?").unwrap());
|
||||
|
||||
fn fix<'a>(dest: CowStr<'a>, path: Option<&Path>) -> CowStr<'a> {
|
||||
if dest.starts_with('#') {
|
||||
@@ -152,10 +148,8 @@ fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
|
||||
// There are dozens of HTML tags/attributes that contain paths, so
|
||||
// feel free to add more tags if desired; these are the only ones I
|
||||
// care about right now.
|
||||
lazy_static! {
|
||||
static ref HTML_LINK: Regex =
|
||||
Regex::new(r#"(<(?:a|img) [^>]*?(?:src|href)=")([^"]+?)""#).unwrap();
|
||||
}
|
||||
static HTML_LINK: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r#"(<(?:a|img) [^>]*?(?:src|href)=")([^"]+?)""#).unwrap());
|
||||
|
||||
HTML_LINK
|
||||
.replace_all(&html, |caps: ®ex::Captures<'_>| {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use std::ops::Bound::{Excluded, Included, Unbounded};
|
||||
use std::ops::RangeBounds;
|
||||
@@ -23,10 +24,10 @@ pub fn take_lines<R: RangeBounds<usize>>(s: &str, range: R) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref ANCHOR_START: Regex = Regex::new(r"ANCHOR:\s*(?P<anchor_name>[\w_-]+)").unwrap();
|
||||
static ref ANCHOR_END: Regex = Regex::new(r"ANCHOR_END:\s*(?P<anchor_name>[\w_-]+)").unwrap();
|
||||
}
|
||||
static ANCHOR_START: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"ANCHOR:\s*(?P<anchor_name>[\w_-]+)").unwrap());
|
||||
static ANCHOR_END: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"ANCHOR_END:\s*(?P<anchor_name>[\w_-]+)").unwrap());
|
||||
|
||||
/// Take anchored lines from a string.
|
||||
/// Lines containing anchor are ignored.
|
||||
|
||||
@@ -27,6 +27,8 @@ fn main(){
|
||||
}
|
||||
```
|
||||
|
||||
<kbd>Ctrl</kbd> + <kbd>S</kbd> saves a file.
|
||||
|
||||
A random image sprinkled in between
|
||||
|
||||

|
||||
@@ -41,6 +43,7 @@ A random image sprinkled in between
|
||||
2. be
|
||||
3. `put`
|
||||
4. here?
|
||||
5. **<kbd>Ctrl</kbd> + <kbd>S</kbd> saves a file.**
|
||||
|
||||
| col1 | col2 | col 3 | col 4 | col 5 | col 6 |
|
||||
| ---- | ---- | ----- | ----- | ----- | ----- |
|
||||
|
||||
@@ -10,11 +10,11 @@ fn mdbook_cli_can_correctly_test_a_passing_book() {
|
||||
let mut cmd = mdbook_cmd();
|
||||
cmd.arg("test").current_dir(temp.path());
|
||||
cmd.assert().success()
|
||||
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]README.md""##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]intro.md""##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]index.md""##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]nested.md""##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"rustdoc returned an error:\n\n"##).unwrap().not())
|
||||
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "README.md""##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "intro.md""##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]index.md""##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]nested.md""##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"returned an error:\n\n"##).unwrap().not())
|
||||
.stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap().not());
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ fn mdbook_cli_detects_book_with_failing_tests() {
|
||||
let mut cmd = mdbook_cmd();
|
||||
cmd.arg("test").current_dir(temp.path());
|
||||
cmd.assert().failure()
|
||||
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]README.md""##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]intro.md""##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]index.md""##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]nested.md""##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"rustdoc returned an error:\n\n"##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "README.md""##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "intro.md""##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]index.md""##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"Testing chapter [^:]*: "first[\\/]nested.md""##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"returned an error:\n\n"##).unwrap())
|
||||
.stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use mdbook::config::Config;
|
||||
use mdbook::MDBook;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
@@ -95,7 +96,7 @@ fn run_mdbook_init_with_custom_book_and_src_locations() {
|
||||
let contents = fs::read_to_string(temp.path().join("book.toml")).unwrap();
|
||||
assert_eq!(
|
||||
contents,
|
||||
"[book]\nauthors = []\nlanguage = \"en\"\nmultilingual = false\nsrc = \"in\"\n\n[build]\nbuild-dir = \"out\"\ncreate-missing = true\nuse-default-preprocessors = true\n"
|
||||
"[book]\nauthors = []\nlanguage = \"en\"\nmultilingual = false\nsrc = \"in\"\n\n[build]\nbuild-dir = \"out\"\ncreate-missing = true\nextra-watch-dirs = []\nuse-default-preprocessors = true\n"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -121,6 +122,20 @@ fn copy_theme() {
|
||||
"css/variables.css",
|
||||
"favicon.png",
|
||||
"favicon.svg",
|
||||
"fonts/OPEN-SANS-LICENSE.txt",
|
||||
"fonts/SOURCE-CODE-PRO-LICENSE.txt",
|
||||
"fonts/fonts.css",
|
||||
"fonts/open-sans-v17-all-charsets-300.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-300italic.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-600.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-600italic.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-700.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-700italic.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-800.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-800italic.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-italic.woff2",
|
||||
"fonts/open-sans-v17-all-charsets-regular.woff2",
|
||||
"fonts/source-code-pro-v11-all-charsets-500.woff2",
|
||||
"highlight.css",
|
||||
"highlight.js",
|
||||
"index.hbs",
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
|
||||
mod dummy_book;
|
||||
|
||||
use crate::dummy_book::{assert_contains_strings, assert_doesnt_contain_strings, DummyBook};
|
||||
@@ -10,6 +7,7 @@ use mdbook::config::Config;
|
||||
use mdbook::errors::*;
|
||||
use mdbook::utils::fs::write_file;
|
||||
use mdbook::MDBook;
|
||||
use pretty_assertions::assert_eq;
|
||||
use select::document::Document;
|
||||
use select::predicate::{Class, Name, Predicate};
|
||||
use std::collections::HashMap;
|
||||
@@ -842,3 +840,111 @@ mod search {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_fonts() {
|
||||
// Tests to ensure custom fonts are copied as expected.
|
||||
let builtin_fonts = [
|
||||
"OPEN-SANS-LICENSE.txt",
|
||||
"SOURCE-CODE-PRO-LICENSE.txt",
|
||||
"fonts.css",
|
||||
"open-sans-v17-all-charsets-300.woff2",
|
||||
"open-sans-v17-all-charsets-300italic.woff2",
|
||||
"open-sans-v17-all-charsets-600.woff2",
|
||||
"open-sans-v17-all-charsets-600italic.woff2",
|
||||
"open-sans-v17-all-charsets-700.woff2",
|
||||
"open-sans-v17-all-charsets-700italic.woff2",
|
||||
"open-sans-v17-all-charsets-800.woff2",
|
||||
"open-sans-v17-all-charsets-800italic.woff2",
|
||||
"open-sans-v17-all-charsets-italic.woff2",
|
||||
"open-sans-v17-all-charsets-regular.woff2",
|
||||
"source-code-pro-v11-all-charsets-500.woff2",
|
||||
];
|
||||
let actual_files = |path: &Path| -> Vec<String> {
|
||||
let mut actual: Vec<_> = path
|
||||
.read_dir()
|
||||
.unwrap()
|
||||
.map(|entry| entry.unwrap().file_name().into_string().unwrap())
|
||||
.collect();
|
||||
actual.sort();
|
||||
actual
|
||||
};
|
||||
let has_fonts_css = |path: &Path| -> bool {
|
||||
let contents = fs::read_to_string(path.join("book/index.html")).unwrap();
|
||||
contents.contains("fonts/fonts.css")
|
||||
};
|
||||
|
||||
// No theme:
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
let p = temp.path();
|
||||
MDBook::init(p).build().unwrap();
|
||||
MDBook::load(p).unwrap().build().unwrap();
|
||||
assert_eq!(actual_files(&p.join("book/fonts")), &builtin_fonts);
|
||||
assert!(has_fonts_css(p));
|
||||
|
||||
// Full theme.
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
let p = temp.path();
|
||||
MDBook::init(p).copy_theme(true).build().unwrap();
|
||||
assert_eq!(actual_files(&p.join("theme/fonts")), &builtin_fonts);
|
||||
MDBook::load(p).unwrap().build().unwrap();
|
||||
assert_eq!(actual_files(&p.join("book/fonts")), &builtin_fonts);
|
||||
assert!(has_fonts_css(p));
|
||||
|
||||
// Mixed with copy_fonts=true
|
||||
// This should generate a deprecation warning.
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
let p = temp.path();
|
||||
MDBook::init(p).build().unwrap();
|
||||
write_file(&p.join("theme/fonts"), "fonts.css", b"/*custom*/").unwrap();
|
||||
write_file(&p.join("theme/fonts"), "myfont.woff", b"").unwrap();
|
||||
MDBook::load(p).unwrap().build().unwrap();
|
||||
assert!(has_fonts_css(p));
|
||||
let mut expected = Vec::from(builtin_fonts);
|
||||
expected.push("myfont.woff");
|
||||
expected.sort();
|
||||
assert_eq!(actual_files(&p.join("book/fonts")), expected.as_slice());
|
||||
|
||||
// copy-fonts=false, no theme
|
||||
// This should generate a deprecation warning.
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
let p = temp.path();
|
||||
MDBook::init(p).build().unwrap();
|
||||
let config = Config::from_str("output.html.copy-fonts = false").unwrap();
|
||||
MDBook::load_with_config(p, config)
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
assert!(!has_fonts_css(p));
|
||||
assert!(!p.join("book/fonts").exists());
|
||||
|
||||
// copy-fonts=false with empty fonts.css
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
let p = temp.path();
|
||||
MDBook::init(p).build().unwrap();
|
||||
write_file(&p.join("theme/fonts"), "fonts.css", b"").unwrap();
|
||||
let config = Config::from_str("output.html.copy-fonts = false").unwrap();
|
||||
MDBook::load_with_config(p, config)
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
assert!(!has_fonts_css(p));
|
||||
assert!(!p.join("book/fonts").exists());
|
||||
|
||||
// copy-fonts=false with fonts theme
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
let p = temp.path();
|
||||
MDBook::init(p).build().unwrap();
|
||||
write_file(&p.join("theme/fonts"), "fonts.css", b"/*custom*/").unwrap();
|
||||
write_file(&p.join("theme/fonts"), "myfont.woff", b"").unwrap();
|
||||
let config = Config::from_str("output.html.copy-fonts = false").unwrap();
|
||||
MDBook::load_with_config(p, config)
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
assert!(has_fonts_css(p));
|
||||
assert_eq!(
|
||||
actual_files(&p.join("book/fonts")),
|
||||
&["fonts.css", "myfont.woff"]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,3 +24,24 @@ fn mdbook_detects_book_with_failing_tests() {
|
||||
|
||||
assert!(md.test(vec![]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mdbook_test_chapter() {
|
||||
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
|
||||
let mut md = MDBook::load(temp.path()).unwrap();
|
||||
|
||||
let result = md.test_chapter(vec![], Some("Introduction"));
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"test_chapter failed with {}",
|
||||
result.err().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mdbook_test_chapter_not_found() {
|
||||
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
|
||||
let mut md = MDBook::load(temp.path()).unwrap();
|
||||
|
||||
assert!(md.test_chapter(vec![], Some("Bogus Chapter Name")).is_err());
|
||||
}
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
# This will allow users to self assign, and/or drop assignment
|
||||
[assign]
|
||||
|
||||
# Allows @rustbot ready, review, author, or blocked
|
||||
[shortcut]
|
||||
|
||||
[relabel]
|
||||
allow-unauthenticated = [
|
||||
# For Issue areas
|
||||
# For Issue areas
|
||||
"A-*",
|
||||
"E-Help-Wanted",
|
||||
"Bug",
|
||||
"Feature-Request"
|
||||
]
|
||||
# Categories
|
||||
"C-*",
|
||||
# Commands
|
||||
"Command-*",
|
||||
# Status
|
||||
"S-*",
|
||||
"regression",
|
||||
"Breaking Change",
|
||||
"msrv-bump",
|
||||
]
|
||||
|
||||
[autolabel."S-waiting-on-review"]
|
||||
new_pr = true
|
||||
|
||||
Reference in New Issue
Block a user