Merge branch 'rust-lang:master' into master
This commit is contained in:
commit
8c662b750f
|
@ -32,9 +32,9 @@ jobs:
|
|||
- build: msrv
|
||||
os: ubuntu-20.04
|
||||
# sync MSRV with docs: guide/src/guide/installation.md and Cargo.toml
|
||||
rust: 1.60.0
|
||||
rust: 1.65.0
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust
|
||||
run: bash ci/install-rust.sh ${{ matrix.rust }}
|
||||
- name: Build and run tests
|
||||
|
@ -46,7 +46,7 @@ jobs:
|
|||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust
|
||||
run: rustup update stable && rustup default stable && rustup component add rustfmt
|
||||
- run: cargo fmt --check
|
||||
|
|
48
CHANGELOG.md
48
CHANGELOG.md
|
@ -1,5 +1,53 @@
|
|||
# Changelog
|
||||
|
||||
## mdBook 0.4.31
|
||||
[v0.4.30...v0.4.31](https://github.com/rust-lang/mdBook/compare/v0.4.30...v0.4.31)
|
||||
|
||||
### Fixed
|
||||
- Fixed menu border render flash during page navigation.
|
||||
[#2101](https://github.com/rust-lang/mdBook/pull/2101)
|
||||
- Fixed flicker setting sidebar scroll position.
|
||||
[#2104](https://github.com/rust-lang/mdBook/pull/2104)
|
||||
- Fixed compile error with proc-macro2 on latest Rust nightly.
|
||||
[#2109](https://github.com/rust-lang/mdBook/pull/2109)
|
||||
|
||||
## mdBook 0.4.30
|
||||
[v0.4.29...v0.4.30](https://github.com/rust-lang/mdBook/compare/v0.4.29...v0.4.30)
|
||||
|
||||
### Added
|
||||
- Added support for heading attributes.
|
||||
Attributes are specified in curly braces just after the heading text.
|
||||
An HTML ID can be specified with `#` and classes with `.`.
|
||||
For example: `## My heading {#custom-id .class1 .class2}`
|
||||
[#2013](https://github.com/rust-lang/mdBook/pull/2013)
|
||||
- Added support for hidden code lines for languages other than Rust.
|
||||
The `output.html.code.hidelines` table allows you to define the prefix character that will be used to hide code lines based on the language.
|
||||
[#2093](https://github.com/rust-lang/mdBook/pull/2093)
|
||||
|
||||
### Fixed
|
||||
- Fixed a few minor markdown rendering issues.
|
||||
[#2092](https://github.com/rust-lang/mdBook/pull/2092)
|
||||
|
||||
## mdBook 0.4.29
|
||||
[v0.4.28...v0.4.29](https://github.com/rust-lang/mdBook/compare/v0.4.28...v0.4.29)
|
||||
|
||||
### Changed
|
||||
- Built-in fonts are no longer copied when `fonts/fonts.css` is overridden in the theme directory.
|
||||
Additionally, the warning about `copy-fonts` has been removed if `fonts/fonts.css` is specified.
|
||||
[#2080](https://github.com/rust-lang/mdBook/pull/2080)
|
||||
- `mdbook init --force` now skips all interactive prompts as intended.
|
||||
[#2057](https://github.com/rust-lang/mdBook/pull/2057)
|
||||
- Updated dependencies
|
||||
[#2063](https://github.com/rust-lang/mdBook/pull/2063)
|
||||
[#2086](https://github.com/rust-lang/mdBook/pull/2086)
|
||||
[#2082](https://github.com/rust-lang/mdBook/pull/2082)
|
||||
[#2084](https://github.com/rust-lang/mdBook/pull/2084)
|
||||
[#2085](https://github.com/rust-lang/mdBook/pull/2085)
|
||||
|
||||
### Fixed
|
||||
- Switched from the `gitignore` library to `ignore`. This should bring some improvements with gitignore handling.
|
||||
[#2076](https://github.com/rust-lang/mdBook/pull/2076)
|
||||
|
||||
## mdBook 0.4.28
|
||||
[v0.4.27...v0.4.28](https://github.com/rust-lang/mdBook/compare/v0.4.27...v0.4.28)
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
66
Cargo.toml
66
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "mdbook"
|
||||
version = "0.4.28"
|
||||
version = "0.4.31"
|
||||
authors = [
|
||||
"Mathieu David <mathieudavid@mathieudavid.org>",
|
||||
"Michael-F-Bryan <michaelfbryan@gmail.com>",
|
||||
|
@ -14,55 +14,55 @@ license = "MPL-2.0"
|
|||
readme = "README.md"
|
||||
repository = "https://github.com/rust-lang/mdBook"
|
||||
description = "Creates a book from markdown files"
|
||||
rust-version = "1.60"
|
||||
rust-version = "1.65"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.28"
|
||||
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"
|
||||
anyhow = "1.0.71"
|
||||
chrono = { version = "0.4.24", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.2.7", features = ["cargo", "wrap_help"] }
|
||||
clap_complete = "4.2.3"
|
||||
once_cell = "1.17.1"
|
||||
env_logger = "0.10.0"
|
||||
handlebars = "4.0"
|
||||
log = "0.4"
|
||||
memchr = "2.0"
|
||||
opener = "0.5"
|
||||
pulldown-cmark = { version = "0.9.1", default-features = false }
|
||||
regex = "1.5.5"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
shlex = "1"
|
||||
tempfile = "3.0"
|
||||
toml = "0.5.1"
|
||||
handlebars = "4.3.7"
|
||||
log = "0.4.17"
|
||||
memchr = "2.5.0"
|
||||
opener = "0.5.2"
|
||||
pulldown-cmark = { version = "0.9.3", default-features = false }
|
||||
regex = "1.8.1"
|
||||
serde = { version = "1.0.163", features = ["derive"] }
|
||||
serde_json = "1.0.96"
|
||||
shlex = "1.1.0"
|
||||
tempfile = "3.4.0"
|
||||
toml = "0.5.11"
|
||||
topological-sort = "0.2.2"
|
||||
|
||||
# Watch feature
|
||||
notify = { version = "5.0.0", optional = true }
|
||||
notify = { version = "5.1.0", optional = true }
|
||||
notify-debouncer-mini = { version = "0.2.1", optional = true }
|
||||
gitignore = { version = "1.0", optional = true }
|
||||
ignore = { version = "0.4.20", optional = true }
|
||||
|
||||
# Serve feature
|
||||
futures-util = { version = "0.3.4", optional = true }
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"], optional = true }
|
||||
warp = { version = "0.3.2", default-features = false, features = ["websocket"], optional = true }
|
||||
futures-util = { version = "0.3.28", optional = true }
|
||||
tokio = { version = "1.28.1", features = ["macros", "rt-multi-thread"], optional = true }
|
||||
warp = { version = "0.3.5", default-features = false, features = ["websocket"], optional = true }
|
||||
|
||||
# Search feature
|
||||
elasticlunr-rs = { version = "3.0.0", optional = true }
|
||||
ammonia = { version = "3", optional = true }
|
||||
elasticlunr-rs = { version = "3.0.2", optional = true }
|
||||
ammonia = { version = "3.3.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0.7"
|
||||
predicates = "2"
|
||||
assert_cmd = "2.0.11"
|
||||
predicates = "2.1.5"
|
||||
select = "0.6.0"
|
||||
semver = "1.0"
|
||||
pretty_assertions = "1.2.1"
|
||||
walkdir = "2.0"
|
||||
semver = "1.0.17"
|
||||
pretty_assertions = "1.3.0"
|
||||
walkdir = "2.3.3"
|
||||
|
||||
[features]
|
||||
default = ["watch", "serve", "search"]
|
||||
watch = ["notify", "notify-debouncer-mini", "gitignore"]
|
||||
serve = ["futures-util", "tokio", "warp"]
|
||||
search = ["elasticlunr-rs", "ammonia"]
|
||||
watch = ["dep:notify", "dep:notify-debouncer-mini", "dep:ignore"]
|
||||
serve = ["dep:futures-util", "dep:tokio", "dep:warp"]
|
||||
search = ["dep:elasticlunr-rs", "dep:ammonia"]
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
|
|
|
@ -17,6 +17,9 @@ edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path
|
|||
editable = true
|
||||
line-numbers = true
|
||||
|
||||
[output.html.code.hidelines]
|
||||
python = "~"
|
||||
|
||||
[output.html.search]
|
||||
limit-results = 20
|
||||
use-boolean-and = true
|
||||
|
|
|
@ -67,4 +67,16 @@ mdbook init --title="my amazing book"
|
|||
Create a `.gitignore` file configured to ignore the `book` directory created when [building] a book.
|
||||
If not supplied, an interactive prompt will ask whether it should be created.
|
||||
|
||||
```bash
|
||||
mdbook init --ignore=none
|
||||
```
|
||||
|
||||
```bash
|
||||
mdbook init --ignore=git
|
||||
```
|
||||
|
||||
[building]: build.md
|
||||
|
||||
#### --force
|
||||
|
||||
Skip the prompts to create a `.gitignore` and for the title for the book.
|
||||
|
|
|
@ -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.28/mdbook-v0.4.28-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
|
||||
curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.31/mdbook-v0.4.31-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
|
||||
bin/mdbook build
|
||||
```
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ For example, if you have a preprocessor called `mdbook-example`, then you can in
|
|||
With this table, mdBook will execute the `mdbook-example` preprocessor.
|
||||
|
||||
This table can include additional key-value pairs that are specific to the preprocessor.
|
||||
For example, if our example prepocessor needed some extra configuration options:
|
||||
For example, if our example preprocessor needed some extra configuration options:
|
||||
|
||||
```toml
|
||||
[preprocessor.example]
|
||||
|
|
|
@ -150,9 +150,9 @@ The following configuration options are available:
|
|||
- **edit-url-template:** Edit url template, when provided shows a
|
||||
"Suggest an edit" button (which looks like <i class="fa fa-edit"></i>) for directly jumping to editing the currently
|
||||
viewed page. For e.g. GitHub projects set this to
|
||||
`https://github.com/<owner>/<repo>/edit/master/{path}` or for
|
||||
`https://github.com/<owner>/<repo>/edit/<branch>/{path}` or for
|
||||
Bitbucket projects set it to
|
||||
`https://bitbucket.org/<owner>/<repo>/src/master/{path}?mode=edit`
|
||||
`https://bitbucket.org/<owner>/<repo>/src/<branch>/{path}?mode=edit`
|
||||
where {path} will be replaced with the full path of the file in the
|
||||
repository.
|
||||
- **input-404:** The name of the markdown file used for missing files.
|
||||
|
@ -182,7 +182,7 @@ page-break = true # insert page-break after each chapter
|
|||
|
||||
- **enable:** Enable print support. When `false`, all print support will not be
|
||||
rendered. Defaults to `true`.
|
||||
- **page-break** Insert page breaks between chapters. Defaults to `true`.
|
||||
- **page-break:** Insert page breaks between chapters. Defaults to `true`.
|
||||
|
||||
### `[output.html.fold]`
|
||||
|
||||
|
@ -218,11 +218,25 @@ runnable = true # displays a run button for rust code
|
|||
- **copyable:** Display the copy button on code snippets. Defaults to `true`.
|
||||
- **copy-js:** Copy JavaScript files for the editor to the output directory.
|
||||
Defaults to `true`.
|
||||
- **line-numbers** Display line numbers on editable sections of code. Requires both `editable` and `copy-js` to be `true`. Defaults to `false`.
|
||||
- **runnable** Displays a run button for rust code snippets. Changing this to `false` will disable the run in playground feature globally. Defaults to `true`.
|
||||
- **line-numbers:** Display line numbers on editable sections of code. Requires both `editable` and `copy-js` to be `true`. Defaults to `false`.
|
||||
- **runnable:** Displays a run button for rust code snippets. Changing this to `false` will disable the run in playground feature globally. Defaults to `true`.
|
||||
|
||||
[Ace]: https://ace.c9.io/
|
||||
|
||||
### `[output.html.code]`
|
||||
|
||||
The `[output.html.code]` table provides options for controlling code blocks.
|
||||
|
||||
```toml
|
||||
[output.html.code]
|
||||
# A prefix string per language (one or more chars).
|
||||
# Any line starting with whitespace+prefix is hidden.
|
||||
hidelines = { python = "~" }
|
||||
```
|
||||
|
||||
- **hidelines:** A table that defines how [hidden code lines](../mdbook.md#hiding-code-lines) work for each language.
|
||||
The key is the language and the value is a string that will cause code lines starting with that prefix to be hidden.
|
||||
|
||||
### `[output.html.search]`
|
||||
|
||||
The `[output.html.search]` table provides options for controlling the built-in text [search].
|
||||
|
|
|
@ -124,7 +124,7 @@ mdBook has several extensions beyond the standard CommonMark specification.
|
|||
### Strikethrough
|
||||
|
||||
Text may be rendered with a horizontal line through the center by wrapping the
|
||||
text with two tilde characters on each side:
|
||||
text with one or two tilde characters on each side:
|
||||
|
||||
```text
|
||||
An example of ~~strikethrough text~~.
|
||||
|
@ -220,3 +220,16 @@ To enable it, see the [`output.html.curly-quotes`] config option.
|
|||
[tables]: https://github.github.com/gfm/#tables-extension-
|
||||
[task list extension]: https://github.github.com/gfm/#task-list-items-extension-
|
||||
[`output.html.curly-quotes`]: configuration/renderers.md#html-renderer-options
|
||||
|
||||
### Heading attributes
|
||||
|
||||
Headings can have a custom HTML ID and classes. This lets you maintain the same ID even if you change the heading's text, it also lets you add multiple classes in the heading.
|
||||
|
||||
Example:
|
||||
```md
|
||||
# Example heading { #first .class1 .class2 }
|
||||
```
|
||||
|
||||
This makes the level 1 heading with the content `Example heading`, ID `first`, and classes `class1` and `class2`. Note that the attributes should be space-separated.
|
||||
|
||||
More information can be found in the [heading attrs spec page](https://github.com/raphlinus/pulldown-cmark/blob/master/specs/heading_attrs.txt).
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
## Hiding code lines
|
||||
|
||||
There is a feature in mdBook that lets you hide code lines by prepending them
|
||||
with a `#` [like you would with Rustdoc][rustdoc-hide].
|
||||
This currently only works with Rust language code blocks.
|
||||
There is a feature in mdBook that lets you hide code lines by prepending them with a specific prefix.
|
||||
|
||||
[rustdoc-hide]: https://doc.rust-lang.org/stable/rustdoc/documentation-tests.html#hiding-portions-of-the-example
|
||||
For the Rust language, you can use the `#` character as a prefix which will hide lines [like you would with Rustdoc][rustdoc-hide].
|
||||
|
||||
[rustdoc-hide]: https://doc.rust-lang.org/stable/rustdoc/write-documentation/documentation-tests.html#hiding-portions-of-the-example
|
||||
|
||||
```bash
|
||||
# fn main() {
|
||||
|
@ -28,7 +28,47 @@ Will render as
|
|||
# }
|
||||
```
|
||||
|
||||
The code block has an eyeball icon (<i class="fa fa-eye"></i>) which will toggle the visibility of the hidden lines.
|
||||
When you tap or hover the mouse over the code block, there will be an eyeball icon (<i class="fa fa-eye"></i>) which will toggle the visibility of the hidden lines.
|
||||
|
||||
By default, this only works for code examples that are annotated with `rust`.
|
||||
However, you can define custom prefixes for other languages by adding a new line-hiding prefix in your `book.toml` with the language name and prefix character(s):
|
||||
|
||||
```toml
|
||||
[output.html.code.hidelines]
|
||||
python = "~"
|
||||
```
|
||||
|
||||
The prefix will hide any lines that begin with the given prefix. With the python prefix shown above, this:
|
||||
|
||||
```bash
|
||||
~hidden()
|
||||
nothidden():
|
||||
~ hidden()
|
||||
~hidden()
|
||||
nothidden()
|
||||
```
|
||||
|
||||
will render as
|
||||
|
||||
```python
|
||||
~hidden()
|
||||
nothidden():
|
||||
~ hidden()
|
||||
~hidden()
|
||||
nothidden()
|
||||
```
|
||||
|
||||
This behavior can be overridden locally with a different prefix. This has the same effect as above:
|
||||
|
||||
~~~markdown
|
||||
```python,hidelines=!!!
|
||||
!!!hidden()
|
||||
nothidden():
|
||||
!!! hidden()
|
||||
!!!hidden()
|
||||
nothidden()
|
||||
```
|
||||
~~~
|
||||
|
||||
## Rust Playground
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Theme
|
||||
|
||||
The default renderer uses a [handlebars](http://handlebarsjs.com/) template to
|
||||
The default renderer uses a [handlebars](https://handlebarsjs.com) template to
|
||||
render your markdown files and comes with a default theme included in the mdBook
|
||||
binary.
|
||||
|
||||
|
|
|
@ -77,38 +77,6 @@ the `theme` folder of your book.
|
|||
|
||||
Now your theme will be used instead of the default theme.
|
||||
|
||||
## Hiding code lines
|
||||
|
||||
There is a feature in mdBook that lets you hide code lines by prepending them
|
||||
with a `#`.
|
||||
|
||||
|
||||
```bash
|
||||
# fn main() {
|
||||
let x = 5;
|
||||
let y = 6;
|
||||
|
||||
println!("{}", x + y);
|
||||
# }
|
||||
```
|
||||
|
||||
Will render as
|
||||
|
||||
```rust
|
||||
# fn main() {
|
||||
let x = 5;
|
||||
let y = 7;
|
||||
|
||||
println!("{}", x + y);
|
||||
# }
|
||||
```
|
||||
|
||||
**At the moment, this only works for code examples that are annotated with
|
||||
`rust`. Because it would collide with semantics of some programming languages.
|
||||
In the future, we want to make this configurable through the `book.toml` so that
|
||||
everyone can benefit from it.**
|
||||
|
||||
|
||||
## Improve default theme
|
||||
|
||||
If you think the default theme doesn't look quite right for a specific language,
|
||||
|
|
|
@ -20,7 +20,7 @@ To make it easier to run, put the path to the binary into your `PATH`.
|
|||
|
||||
To build the `mdbook` executable from source, you will first need to install Rust and Cargo.
|
||||
Follow the instructions on the [Rust installation page].
|
||||
mdBook currently requires at least Rust version 1.60.
|
||||
mdBook currently requires at least Rust version 1.65.
|
||||
|
||||
Once you have installed Rust, the following command can be used to build and install mdBook:
|
||||
|
||||
|
|
|
@ -39,9 +39,7 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
|
|||
.chain(summary.suffix_chapters.iter())
|
||||
.collect();
|
||||
|
||||
while !items.is_empty() {
|
||||
let next = items.pop().expect("already checked");
|
||||
|
||||
while let Some(next) = items.pop() {
|
||||
if let SummaryItem::Link(ref link) = *next {
|
||||
if let Some(ref location) = link.location {
|
||||
let filename = src_dir.join(location);
|
||||
|
@ -277,7 +275,7 @@ fn load_chapter<P: AsRef<Path>>(
|
|||
}
|
||||
|
||||
let stripped = location
|
||||
.strip_prefix(&src_dir)
|
||||
.strip_prefix(src_dir)
|
||||
.expect("Chapters are always inside a book");
|
||||
|
||||
Chapter::new(&link.name, content, stripped, parent_names.clone())
|
||||
|
@ -317,7 +315,7 @@ impl<'a> Iterator for BookItems<'a> {
|
|||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let item = self.items.pop_front();
|
||||
|
||||
if let Some(&BookItem::Chapter(ref ch)) = item {
|
||||
if let Some(BookItem::Chapter(ch)) = item {
|
||||
// if we wanted a breadth-first iterator we'd `extend()` here
|
||||
for sub_item in ch.sub_items.iter().rev() {
|
||||
self.items.push_front(sub_item);
|
||||
|
|
|
@ -198,8 +198,7 @@ impl BookBuilder {
|
|||
writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
|
||||
|
||||
let chapter_1 = src_dir.join("chapter_1.md");
|
||||
let mut f =
|
||||
File::create(&chapter_1).with_context(|| "Unable to create chapter_1.md")?;
|
||||
let mut f = File::create(chapter_1).with_context(|| "Unable to create chapter_1.md")?;
|
||||
writeln!(f, "# Chapter 1")?;
|
||||
} else {
|
||||
trace!("Existing summary found, no need to create stub files.");
|
||||
|
@ -212,10 +211,10 @@ impl BookBuilder {
|
|||
fs::create_dir_all(&self.root)?;
|
||||
|
||||
let src = self.root.join(&self.config.book.src);
|
||||
fs::create_dir_all(&src)?;
|
||||
fs::create_dir_all(src)?;
|
||||
|
||||
let build = self.root.join(&self.config.build.build_dir);
|
||||
fs::create_dir_all(&build)?;
|
||||
fs::create_dir_all(build)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ impl MDBook {
|
|||
let root = book_root.into();
|
||||
|
||||
let src_dir = root.join(&config.book.src);
|
||||
let book = book::load_book(&src_dir, &config.build)?;
|
||||
let book = book::load_book(src_dir, &config.build)?;
|
||||
|
||||
let renderers = determine_renderers(&config);
|
||||
let preprocessors = determine_preprocessors(&config)?;
|
||||
|
@ -122,7 +122,7 @@ impl MDBook {
|
|||
let root = book_root.into();
|
||||
|
||||
let src_dir = root.join(&config.book.src);
|
||||
let book = book::load_book_from_disk(&summary, &src_dir)?;
|
||||
let book = book::load_book_from_disk(&summary, src_dir)?;
|
||||
|
||||
let renderers = determine_renderers(&config);
|
||||
let preprocessors = determine_preprocessors(&config)?;
|
||||
|
@ -309,7 +309,7 @@ impl MDBook {
|
|||
info!("Testing chapter '{}': {:?}", ch.name, chapter_path);
|
||||
|
||||
// write preprocessed file to tempdir
|
||||
let path = temp_dir.path().join(&chapter_path);
|
||||
let path = temp_dir.path().join(chapter_path);
|
||||
let mut tmpf = utils::fs::create_file(&path)?;
|
||||
tmpf.write_all(ch.content.as_bytes())?;
|
||||
|
||||
|
@ -319,13 +319,13 @@ impl MDBook {
|
|||
if let Some(edition) = self.config.rust.edition {
|
||||
match edition {
|
||||
RustEdition::E2015 => {
|
||||
cmd.args(&["--edition", "2015"]);
|
||||
cmd.args(["--edition", "2015"]);
|
||||
}
|
||||
RustEdition::E2018 => {
|
||||
cmd.args(&["--edition", "2018"]);
|
||||
cmd.args(["--edition", "2018"]);
|
||||
}
|
||||
RustEdition::E2021 => {
|
||||
cmd.args(&["--edition", "2021"]);
|
||||
cmd.args(["--edition", "2021"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ pub fn make_subcommand() -> Command {
|
|||
// Build command implementation
|
||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
let mut book = MDBook::load(book_dir)?;
|
||||
|
||||
if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") {
|
||||
book.config.build.build_dir = dest_dir.into();
|
||||
|
|
|
@ -16,7 +16,7 @@ pub fn make_subcommand() -> Command {
|
|||
// Clean command implementation
|
||||
pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let book = MDBook::load(&book_dir)?;
|
||||
let book = MDBook::load(book_dir)?;
|
||||
|
||||
let dir_to_remove = match args.get_one::<PathBuf>("dest-dir") {
|
||||
Some(dest_dir) => dest_dir.into(),
|
||||
|
|
|
@ -56,7 +56,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
|||
"git" => builder.create_gitignore(true),
|
||||
_ => builder.create_gitignore(false),
|
||||
};
|
||||
} else {
|
||||
} else if !args.get_flag("force") {
|
||||
println!("\nDo you want a .gitignore to be created? (y/n)");
|
||||
if confirm() {
|
||||
builder.create_gitignore(true);
|
||||
|
@ -65,6 +65,8 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
|||
|
||||
config.book.title = if args.contains_id("title") {
|
||||
args.get_one::<String>("title").map(String::from)
|
||||
} else if args.get_flag("force") {
|
||||
None
|
||||
} else {
|
||||
request_book_title()
|
||||
};
|
||||
|
@ -84,7 +86,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
|||
/// Obtains author name from git config file by running the `git config` command.
|
||||
fn get_author_name() -> Option<String> {
|
||||
let output = Command::new("git")
|
||||
.args(&["config", "--get", "user.name"])
|
||||
.args(["config", "--get", "user.name"])
|
||||
.output()
|
||||
.ok()?;
|
||||
|
||||
|
@ -114,5 +116,5 @@ fn confirm() -> bool {
|
|||
io::stdout().flush().unwrap();
|
||||
let mut s = String::new();
|
||||
io::stdin().read_line(&mut s).ok();
|
||||
matches!(&*s.trim(), "Y" | "y" | "yes" | "Yes")
|
||||
matches!(s.trim(), "Y" | "y" | "yes" | "Yes")
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ pub fn make_subcommand() -> Command {
|
|||
// Serve command implementation
|
||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
let mut book = MDBook::load(book_dir)?;
|
||||
|
||||
let port = args.get_one::<String>("port").unwrap();
|
||||
let hostname = args.get_one::<String>("hostname").unwrap();
|
||||
|
@ -102,7 +102,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
|||
info!("Building book...");
|
||||
|
||||
// FIXME: This area is really ugly because we need to re-set livereload :(
|
||||
let result = MDBook::load(&book_dir).and_then(|mut b| {
|
||||
let result = MDBook::load(book_dir).and_then(|mut b| {
|
||||
update_config(&mut b);
|
||||
b.build()
|
||||
});
|
||||
|
|
|
@ -44,7 +44,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
|||
let chapter: Option<&str> = args.get_one::<String>("chapter").map(|s| s.as_str());
|
||||
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
let mut book = MDBook::load(book_dir)?;
|
||||
|
||||
if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") {
|
||||
book.config.build.build_dir = dest_dir.to_path_buf();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use super::command_prelude::*;
|
||||
use crate::{get_book_dir, open};
|
||||
use ignore::gitignore::Gitignore;
|
||||
use mdbook::errors::Result;
|
||||
use mdbook::utils;
|
||||
use mdbook::MDBook;
|
||||
|
@ -20,7 +21,7 @@ pub fn make_subcommand() -> Command {
|
|||
// Watch command implementation
|
||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
let mut book = MDBook::load(book_dir)?;
|
||||
|
||||
let update_config = |book: &mut MDBook| {
|
||||
if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") {
|
||||
|
@ -41,7 +42,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
|||
|
||||
trigger_on_change(&book, |paths, book_dir| {
|
||||
info!("Files changed: {:?}\nBuilding book...\n", paths);
|
||||
let result = MDBook::load(&book_dir).and_then(|mut b| {
|
||||
let result = MDBook::load(book_dir).and_then(|mut b| {
|
||||
update_config(&mut b);
|
||||
b.build()
|
||||
});
|
||||
|
@ -62,14 +63,14 @@ fn remove_ignored_files(book_root: &Path, paths: &[PathBuf]) -> Vec<PathBuf> {
|
|||
|
||||
match find_gitignore(book_root) {
|
||||
Some(gitignore_path) => {
|
||||
match gitignore::File::new(gitignore_path.as_path()) {
|
||||
Ok(exclusion_checker) => filter_ignored_files(exclusion_checker, paths),
|
||||
Err(_) => {
|
||||
// We're unable to read the .gitignore file, so we'll silently allow everything.
|
||||
// Please see discussion: https://github.com/rust-lang/mdBook/pull/1051
|
||||
paths.iter().map(|path| path.to_path_buf()).collect()
|
||||
}
|
||||
let (ignore, err) = Gitignore::new(&gitignore_path);
|
||||
if let Some(err) = err {
|
||||
warn!(
|
||||
"error reading gitignore `{}`: {err}",
|
||||
gitignore_path.display()
|
||||
);
|
||||
}
|
||||
filter_ignored_files(ignore, paths)
|
||||
}
|
||||
None => {
|
||||
// There is no .gitignore file.
|
||||
|
@ -85,18 +86,13 @@ fn find_gitignore(book_root: &Path) -> Option<PathBuf> {
|
|||
.find(|p| p.exists())
|
||||
}
|
||||
|
||||
fn filter_ignored_files(exclusion_checker: gitignore::File, paths: &[PathBuf]) -> Vec<PathBuf> {
|
||||
fn filter_ignored_files(ignore: Gitignore, paths: &[PathBuf]) -> Vec<PathBuf> {
|
||||
paths
|
||||
.iter()
|
||||
.filter(|path| match exclusion_checker.is_excluded(path) {
|
||||
Ok(exclude) => !exclude,
|
||||
Err(error) => {
|
||||
warn!(
|
||||
"Unable to determine if {:?} is excluded: {:?}. Including it.",
|
||||
&path, error
|
||||
);
|
||||
true
|
||||
}
|
||||
.filter(|path| {
|
||||
!ignore
|
||||
.matched_path_or_any_parents(path, path.is_dir())
|
||||
.is_ignore()
|
||||
})
|
||||
.map(|path| path.to_path_buf())
|
||||
.collect()
|
||||
|
|
|
@ -308,7 +308,7 @@ impl<'de> serde::Deserialize<'de> for Config {
|
|||
warn!("`description` under a table called `[book]`, move the `destination` entry");
|
||||
warn!("from `[output.html]`, renamed to `build-dir`, under a table called");
|
||||
warn!("`[build]`, and it should all work.");
|
||||
warn!("Documentation: http://rust-lang.github.io/mdBook/format/config.html");
|
||||
warn!("Documentation: https://rust-lang.github.io/mdBook/format/config.html");
|
||||
return Ok(Config::from_legacy(raw));
|
||||
}
|
||||
|
||||
|
@ -504,6 +504,8 @@ pub struct HtmlConfig {
|
|||
/// Playground settings.
|
||||
#[serde(alias = "playpen")]
|
||||
pub playground: Playground,
|
||||
/// Code settings.
|
||||
pub code: Code,
|
||||
/// Print settings.
|
||||
pub print: Print,
|
||||
/// Don't render section labels.
|
||||
|
@ -556,6 +558,7 @@ impl Default for HtmlConfig {
|
|||
additional_js: Vec::new(),
|
||||
fold: Fold::default(),
|
||||
playground: Playground::default(),
|
||||
code: Code::default(),
|
||||
print: Print::default(),
|
||||
no_section_label: false,
|
||||
search: None,
|
||||
|
@ -642,6 +645,22 @@ impl Default for Playground {
|
|||
}
|
||||
}
|
||||
|
||||
/// Configuration for tweaking how the the HTML renderer handles code blocks.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default, rename_all = "kebab-case")]
|
||||
pub struct Code {
|
||||
/// A prefix string to hide lines per language (one or more chars).
|
||||
pub hidelines: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Default for Code {
|
||||
fn default() -> Code {
|
||||
Code {
|
||||
hidelines: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration of the search functionality of the HTML renderer.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default, rename_all = "kebab-case")]
|
||||
|
@ -703,7 +722,7 @@ trait Updateable<'de>: Serialize + Deserialize<'de> {
|
|||
let mut raw = Value::try_from(&self).expect("unreachable");
|
||||
|
||||
if let Ok(value) = Value::try_from(value) {
|
||||
let _ = raw.insert(key, value);
|
||||
raw.insert(key, value);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ where
|
|||
for link in find_links(s) {
|
||||
replaced.push_str(&s[previous_end_index..link.start_index]);
|
||||
|
||||
match link.render_with_path(&path, chapter_title) {
|
||||
match link.render_with_path(path, chapter_title) {
|
||||
Ok(new_content) => {
|
||||
if depth < MAX_LINK_NESTED_DEPTH {
|
||||
if let Some(rel_path) = link.link_type.relative_path(path) {
|
||||
|
@ -327,7 +327,7 @@ impl<'a> Link<'a> {
|
|||
let base = base.as_ref();
|
||||
match self.link_type {
|
||||
// omit the escape char
|
||||
LinkType::Escaped => Ok((&self.link_text[1..]).to_owned()),
|
||||
LinkType::Escaped => Ok(self.link_text[1..].to_owned()),
|
||||
LinkType::Include(ref pat, ref range_or_anchor) => {
|
||||
let target = base.join(pat);
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::book::{Book, BookItem};
|
||||
use crate::config::{BookConfig, Config, HtmlConfig, Playground, RustEdition};
|
||||
use crate::config::{BookConfig, Code, Config, HtmlConfig, Playground, RustEdition};
|
||||
use crate::errors::*;
|
||||
use crate::renderer::html_handlebars::helpers;
|
||||
use crate::renderer::{RenderContext, Renderer};
|
||||
|
@ -99,7 +99,7 @@ impl HtmlHandlebars {
|
|||
ctx.data.insert("title".to_owned(), json!(title));
|
||||
ctx.data.insert(
|
||||
"path_to_root".to_owned(),
|
||||
json!(utils::fs::path_to_root(&path)),
|
||||
json!(utils::fs::path_to_root(path)),
|
||||
);
|
||||
if let Some(ref section) = ch.number {
|
||||
ctx.data
|
||||
|
@ -110,7 +110,12 @@ impl HtmlHandlebars {
|
|||
debug!("Render template");
|
||||
let rendered = ctx.handlebars.render("index", &ctx.data)?;
|
||||
|
||||
let rendered = self.post_process(rendered, &ctx.html_config.playground, ctx.edition);
|
||||
let rendered = self.post_process(
|
||||
rendered,
|
||||
&ctx.html_config.playground,
|
||||
&ctx.html_config.code,
|
||||
ctx.edition,
|
||||
);
|
||||
|
||||
// Write to file
|
||||
debug!("Creating {}", filepath.display());
|
||||
|
@ -121,8 +126,12 @@ impl HtmlHandlebars {
|
|||
ctx.data.insert("path_to_root".to_owned(), json!(""));
|
||||
ctx.data.insert("is_index".to_owned(), json!(true));
|
||||
let rendered_index = ctx.handlebars.render("index", &ctx.data)?;
|
||||
let rendered_index =
|
||||
self.post_process(rendered_index, &ctx.html_config.playground, ctx.edition);
|
||||
let rendered_index = self.post_process(
|
||||
rendered_index,
|
||||
&ctx.html_config.playground,
|
||||
&ctx.html_config.code,
|
||||
ctx.edition,
|
||||
);
|
||||
debug!("Creating index.html from {}", ctx_path);
|
||||
utils::fs::write_file(&ctx.destination, "index.html", rendered_index.as_bytes())?;
|
||||
}
|
||||
|
@ -182,8 +191,12 @@ impl HtmlHandlebars {
|
|||
data_404.insert("title".to_owned(), json!(title));
|
||||
let rendered = handlebars.render("index", &data_404)?;
|
||||
|
||||
let rendered =
|
||||
self.post_process(rendered, &html_config.playground, ctx.config.rust.edition);
|
||||
let rendered = self.post_process(
|
||||
rendered,
|
||||
&html_config.playground,
|
||||
&html_config.code,
|
||||
ctx.config.rust.edition,
|
||||
);
|
||||
let output_file = get_404_output_file(&html_config.input_404);
|
||||
utils::fs::write_file(destination, output_file, rendered.as_bytes())?;
|
||||
debug!("Creating 404.html ✓");
|
||||
|
@ -195,11 +208,13 @@ impl HtmlHandlebars {
|
|||
&self,
|
||||
rendered: String,
|
||||
playground_config: &Playground,
|
||||
code_config: &Code,
|
||||
edition: Option<RustEdition>,
|
||||
) -> String {
|
||||
let rendered = build_header_links(&rendered);
|
||||
let rendered = fix_code_blocks(&rendered);
|
||||
let rendered = add_playground_pre(&rendered, playground_config, edition);
|
||||
let rendered = hide_lines(&rendered, code_config);
|
||||
|
||||
rendered
|
||||
}
|
||||
|
@ -275,7 +290,8 @@ impl HtmlHandlebars {
|
|||
"FontAwesome/fonts/FontAwesome.ttf",
|
||||
theme::FONT_AWESOME_TTF,
|
||||
)?;
|
||||
if html_config.copy_fonts {
|
||||
// Don't copy the stock fonts if the user has specified their own fonts to use.
|
||||
if html_config.copy_fonts && theme.fonts_css.is_none() {
|
||||
write_file(destination, "fonts/fonts.css", theme::fonts::CSS)?;
|
||||
for (file_name, contents) in theme::fonts::LICENSES.iter() {
|
||||
write_file(destination, file_name, contents)?;
|
||||
|
@ -291,20 +307,13 @@ impl HtmlHandlebars {
|
|||
}
|
||||
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)?;
|
||||
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\
|
||||
"output.html.copy-fonts is deprecated.\n\
|
||||
This book appears to have copy-fonts=false in book.toml without a fonts.css file.\n\
|
||||
Add an empty `theme/fonts/fonts.css` file to squelch this warning."
|
||||
);
|
||||
}
|
||||
|
@ -553,7 +562,7 @@ impl Renderer for HtmlHandlebars {
|
|||
// Print version
|
||||
let mut print_content = String::new();
|
||||
|
||||
fs::create_dir_all(&destination)
|
||||
fs::create_dir_all(destination)
|
||||
.with_context(|| "Unexpected error when constructing destination path")?;
|
||||
|
||||
let mut is_index = true;
|
||||
|
@ -589,8 +598,12 @@ impl Renderer for HtmlHandlebars {
|
|||
debug!("Render template");
|
||||
let rendered = handlebars.render("index", &data)?;
|
||||
|
||||
let rendered =
|
||||
self.post_process(rendered, &html_config.playground, ctx.config.rust.edition);
|
||||
let rendered = self.post_process(
|
||||
rendered,
|
||||
&html_config.playground,
|
||||
&html_config.code,
|
||||
ctx.config.rust.edition,
|
||||
);
|
||||
|
||||
utils::fs::write_file(destination, "print.html", rendered.as_bytes())?;
|
||||
debug!("Creating print.html ✓");
|
||||
|
@ -795,8 +808,10 @@ fn make_data(
|
|||
/// Goes through the rendered HTML, making sure all header tags have
|
||||
/// an anchor respectively so people can link to sections directly.
|
||||
fn build_header_links(html: &str) -> String {
|
||||
static BUILD_HEADER_LINKS: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap());
|
||||
static BUILD_HEADER_LINKS: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r#"<h(\d)(?: id="([^"]+)")?(?: class="([^"]+)")?>(.*?)</h\d>"#).unwrap()
|
||||
});
|
||||
static IGNORE_CLASS: &[&str] = &["menu-title"];
|
||||
|
||||
let mut id_counter = HashMap::new();
|
||||
|
||||
|
@ -806,7 +821,22 @@ fn build_header_links(html: &str) -> String {
|
|||
.parse()
|
||||
.expect("Regex should ensure we only ever get numbers here");
|
||||
|
||||
insert_link_into_header(level, &caps[2], &mut id_counter)
|
||||
// Ignore .menu-title because now it's getting detected by the regex.
|
||||
if let Some(classes) = caps.get(3) {
|
||||
for class in classes.as_str().split(" ") {
|
||||
if IGNORE_CLASS.contains(&class) {
|
||||
return caps[0].to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
insert_link_into_header(
|
||||
level,
|
||||
&caps[4],
|
||||
caps.get(2).map(|x| x.as_str().to_string()),
|
||||
caps.get(3).map(|x| x.as_str().to_string()),
|
||||
&mut id_counter,
|
||||
)
|
||||
})
|
||||
.into_owned()
|
||||
}
|
||||
|
@ -816,15 +846,21 @@ fn build_header_links(html: &str) -> String {
|
|||
fn insert_link_into_header(
|
||||
level: usize,
|
||||
content: &str,
|
||||
id: Option<String>,
|
||||
classes: Option<String>,
|
||||
id_counter: &mut HashMap<String, usize>,
|
||||
) -> String {
|
||||
let id = utils::unique_id_from_content(content, id_counter);
|
||||
let id = id.unwrap_or_else(|| utils::unique_id_from_content(content, id_counter));
|
||||
let classes = classes
|
||||
.map(|s| format!(" class=\"{s}\""))
|
||||
.unwrap_or_default();
|
||||
|
||||
format!(
|
||||
r##"<h{level} id="{id}"><a class="header" href="#{id}">{text}</a></h{level}>"##,
|
||||
r##"<h{level} id="{id}"{classes}><a class="header" href="#{id}">{text}</a></h{level}>"##,
|
||||
level = level,
|
||||
id = id,
|
||||
text = content
|
||||
text = content,
|
||||
classes = classes
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -856,26 +892,26 @@ fn fix_code_blocks(html: &str) -> String {
|
|||
.into_owned()
|
||||
}
|
||||
|
||||
static CODE_BLOCK_RE: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap());
|
||||
|
||||
fn add_playground_pre(
|
||||
html: &str,
|
||||
playground_config: &Playground,
|
||||
edition: Option<RustEdition>,
|
||||
) -> String {
|
||||
static ADD_PLAYGROUND_PRE: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap());
|
||||
|
||||
ADD_PLAYGROUND_PRE
|
||||
CODE_BLOCK_RE
|
||||
.replace_all(html, |caps: &Captures<'_>| {
|
||||
let text = &caps[1];
|
||||
let classes = &caps[2];
|
||||
let code = &caps[3];
|
||||
|
||||
if classes.contains("language-rust") {
|
||||
if (!classes.contains("ignore")
|
||||
if classes.contains("language-rust")
|
||||
&& ((!classes.contains("ignore")
|
||||
&& !classes.contains("noplayground")
|
||||
&& !classes.contains("noplaypen")
|
||||
&& playground_config.runnable)
|
||||
|| classes.contains("mdbook-runnable")
|
||||
|| classes.contains("mdbook-runnable"))
|
||||
{
|
||||
let contains_e2015 = classes.contains("edition2015");
|
||||
let contains_e2018 = classes.contains("edition2018");
|
||||
|
@ -911,12 +947,9 @@ fn add_playground_pre(
|
|||
format!("# #![allow(unused)]\n{}#fn main() {{\n{}#}}", attrs, code)
|
||||
.into()
|
||||
};
|
||||
hide_lines(&content)
|
||||
content
|
||||
}
|
||||
)
|
||||
} else {
|
||||
format!("<code class=\"{}\">{}</code>", classes, hide_lines(code))
|
||||
}
|
||||
} else {
|
||||
// not language-rust, so no-op
|
||||
text.to_owned()
|
||||
|
@ -925,7 +958,50 @@ fn add_playground_pre(
|
|||
.into_owned()
|
||||
}
|
||||
|
||||
fn hide_lines(content: &str) -> String {
|
||||
/// Modifies all `<code>` blocks to convert "hidden" lines and to wrap them in
|
||||
/// a `<span class="boring">`.
|
||||
fn hide_lines(html: &str, code_config: &Code) -> String {
|
||||
let language_regex = Regex::new(r"\blanguage-(\w+)\b").unwrap();
|
||||
let hidelines_regex = Regex::new(r"\bhidelines=(\S+)").unwrap();
|
||||
CODE_BLOCK_RE
|
||||
.replace_all(html, |caps: &Captures<'_>| {
|
||||
let text = &caps[1];
|
||||
let classes = &caps[2];
|
||||
let code = &caps[3];
|
||||
|
||||
if classes.contains("language-rust") {
|
||||
format!(
|
||||
"<code class=\"{}\">{}</code>",
|
||||
classes,
|
||||
hide_lines_rust(code)
|
||||
)
|
||||
} else {
|
||||
// First try to get the prefix from the code block
|
||||
let hidelines_capture = hidelines_regex.captures(classes);
|
||||
let hidelines_prefix = match &hidelines_capture {
|
||||
Some(capture) => Some(&capture[1]),
|
||||
None => {
|
||||
// Then look up the prefix by language
|
||||
language_regex.captures(classes).and_then(|capture| {
|
||||
code_config.hidelines.get(&capture[1]).map(|p| p.as_str())
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
match hidelines_prefix {
|
||||
Some(prefix) => format!(
|
||||
"<code class=\"{}\">{}</code>",
|
||||
classes,
|
||||
hide_lines_with_prefix(code, prefix)
|
||||
),
|
||||
None => text.to_owned(),
|
||||
}
|
||||
}
|
||||
})
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
fn hide_lines_rust(content: &str) -> String {
|
||||
static BORING_LINES_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(\s*)#(.?)(.*)$").unwrap());
|
||||
|
||||
let mut result = String::with_capacity(content.len());
|
||||
|
@ -958,6 +1034,26 @@ fn hide_lines(content: &str) -> String {
|
|||
result
|
||||
}
|
||||
|
||||
fn hide_lines_with_prefix(content: &str, prefix: &str) -> String {
|
||||
let mut result = String::with_capacity(content.len());
|
||||
for line in content.lines() {
|
||||
if line.trim_start().starts_with(prefix) {
|
||||
let pos = line.find(prefix).unwrap();
|
||||
let (ws, rest) = (&line[..pos], &line[pos + prefix.len()..]);
|
||||
|
||||
result += "<span class=\"boring\">";
|
||||
result += ws;
|
||||
result += rest;
|
||||
result += "\n";
|
||||
result += "</span>";
|
||||
continue;
|
||||
}
|
||||
result += line;
|
||||
result += "\n";
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn partition_source(s: &str) -> (String, String) {
|
||||
let mut after_header = false;
|
||||
let mut before = String::new();
|
||||
|
@ -993,6 +1089,7 @@ struct RenderItemContext<'a> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn original_build_header_links() {
|
||||
|
@ -1021,6 +1118,21 @@ mod tests {
|
|||
"<h1>Foo</h1><h3>Foo</h3>",
|
||||
r##"<h1 id="foo"><a class="header" href="#foo">Foo</a></h1><h3 id="foo-1"><a class="header" href="#foo-1">Foo</a></h3>"##,
|
||||
),
|
||||
// id only
|
||||
(
|
||||
r##"<h1 id="foobar">Foo</h1>"##,
|
||||
r##"<h1 id="foobar"><a class="header" href="#foobar">Foo</a></h1>"##,
|
||||
),
|
||||
// class only
|
||||
(
|
||||
r##"<h1 class="class1 class2">Foo</h1>"##,
|
||||
r##"<h1 id="foo" class="class1 class2"><a class="header" href="#foo">Foo</a></h1>"##,
|
||||
),
|
||||
// both id and class
|
||||
(
|
||||
r##"<h1 id="foobar" class="class1 class2">Foo</h1>"##,
|
||||
r##"<h1 id="foobar" class="class1 class2"><a class="header" href="#foobar">Foo</a></h1>"##,
|
||||
),
|
||||
];
|
||||
|
||||
for (src, should_be) in inputs {
|
||||
|
@ -1033,17 +1145,17 @@ mod tests {
|
|||
fn add_playground() {
|
||||
let inputs = [
|
||||
("<code class=\"language-rust\">x()</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</span></code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust\"># #![allow(unused)]\n#fn main() {\nx()\n#}</code></pre>"),
|
||||
("<code class=\"language-rust\">fn main() {}</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust\">fn main() {}</code></pre>"),
|
||||
("<code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";</code></pre>"),
|
||||
("<code class=\"language-rust editable\">let s = \"foo\n ## bar\n\";</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code></pre>"),
|
||||
("<code class=\"language-rust editable\">let s = \"foo\n ## bar\n\";</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n ## bar\n\";</code></pre>"),
|
||||
("<code class=\"language-rust editable\">let s = \"foo\n # bar\n#\n\";</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span><span class=\"boring\">\n</span>\";</code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n#\n\";</code></pre>"),
|
||||
("<code class=\"language-rust ignore\">let s = \"foo\n # bar\n\";</code>",
|
||||
"<code class=\"language-rust ignore\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";</code>"),
|
||||
"<code class=\"language-rust ignore\">let s = \"foo\n # bar\n\";</code>"),
|
||||
("<code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code></pre>"),
|
||||
];
|
||||
|
@ -1063,7 +1175,7 @@ mod tests {
|
|||
fn add_playground_edition2015() {
|
||||
let inputs = [
|
||||
("<code class=\"language-rust\">x()</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</span></code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\"># #![allow(unused)]\n#fn main() {\nx()\n#}</code></pre>"),
|
||||
("<code class=\"language-rust\">fn main() {}</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}</code></pre>"),
|
||||
("<code class=\"language-rust edition2015\">fn main() {}</code>",
|
||||
|
@ -1087,7 +1199,7 @@ mod tests {
|
|||
fn add_playground_edition2018() {
|
||||
let inputs = [
|
||||
("<code class=\"language-rust\">x()</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</span></code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\"># #![allow(unused)]\n#fn main() {\nx()\n#}</code></pre>"),
|
||||
("<code class=\"language-rust\">fn main() {}</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}</code></pre>"),
|
||||
("<code class=\"language-rust edition2015\">fn main() {}</code>",
|
||||
|
@ -1111,7 +1223,7 @@ mod tests {
|
|||
fn add_playground_edition2021() {
|
||||
let inputs = [
|
||||
("<code class=\"language-rust\">x()</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2021\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</span></code></pre>"),
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2021\"># #![allow(unused)]\n#fn main() {\nx()\n#}</code></pre>"),
|
||||
("<code class=\"language-rust\">fn main() {}</code>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust edition2021\">fn main() {}</code></pre>"),
|
||||
("<code class=\"language-rust edition2015\">fn main() {}</code>",
|
||||
|
@ -1131,4 +1243,60 @@ mod tests {
|
|||
assert_eq!(&*got, *should_be);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hide_lines_language_rust() {
|
||||
let inputs = [
|
||||
(
|
||||
"<pre class=\"playground\"><code class=\"language-rust\">\n# #![allow(unused)]\n#fn main() {\nx()\n#}</code></pre>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust\">\n<span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}</span></code></pre>",),
|
||||
(
|
||||
"<pre class=\"playground\"><code class=\"language-rust\">fn main() {}</code></pre>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust\">fn main() {}</code></pre>",),
|
||||
(
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code></pre>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";</code></pre>",),
|
||||
(
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n ## bar\n\";</code></pre>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code></pre>",),
|
||||
(
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n#\n\";</code></pre>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span><span class=\"boring\">\n</span>\";</code></pre>",),
|
||||
(
|
||||
"<code class=\"language-rust ignore\">let s = \"foo\n # bar\n\";</code>",
|
||||
"<code class=\"language-rust ignore\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";</code>",),
|
||||
(
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code></pre>",
|
||||
"<pre class=\"playground\"><code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code></pre>",),
|
||||
];
|
||||
for (src, should_be) in &inputs {
|
||||
let got = hide_lines(src, &Code::default());
|
||||
assert_eq!(&*got, *should_be);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hide_lines_language_other() {
|
||||
let inputs = [
|
||||
(
|
||||
"<code class=\"language-python\">~hidden()\nnothidden():\n~ hidden()\n ~hidden()\n nothidden()</code>",
|
||||
"<code class=\"language-python\"><span class=\"boring\">hidden()\n</span>nothidden():\n<span class=\"boring\"> hidden()\n</span><span class=\"boring\"> hidden()\n</span> nothidden()\n</code>",),
|
||||
(
|
||||
"<code class=\"language-python hidelines=!!!\">!!!hidden()\nnothidden():\n!!! hidden()\n !!!hidden()\n nothidden()</code>",
|
||||
"<code class=\"language-python hidelines=!!!\"><span class=\"boring\">hidden()\n</span>nothidden():\n<span class=\"boring\"> hidden()\n</span><span class=\"boring\"> hidden()\n</span> nothidden()\n</code>",),
|
||||
];
|
||||
for (src, should_be) in &inputs {
|
||||
let got = hide_lines(
|
||||
src,
|
||||
&Code {
|
||||
hidelines: {
|
||||
let mut map = HashMap::new();
|
||||
map.insert("python".to_string(), "~".to_string());
|
||||
map
|
||||
},
|
||||
},
|
||||
);
|
||||
assert_eq!(&*got, *should_be);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,7 +127,7 @@ fn render(
|
|||
|
||||
context.insert(
|
||||
"path_to_root".to_owned(),
|
||||
json!(utils::fs::path_to_root(&base_path)),
|
||||
json!(utils::fs::path_to_root(base_path)),
|
||||
);
|
||||
|
||||
chapter
|
||||
|
|
|
@ -138,9 +138,11 @@ fn render_item(
|
|||
|
||||
in_heading = true;
|
||||
}
|
||||
Event::End(Tag::Heading(i, ..)) if i as u32 <= max_section_depth => {
|
||||
Event::End(Tag::Heading(i, id, _classes)) if i as u32 <= max_section_depth => {
|
||||
in_heading = false;
|
||||
section_id = Some(utils::unique_id_from_content(&heading, &mut id_counter));
|
||||
section_id = id
|
||||
.map(|id| id.to_string())
|
||||
.or_else(|| Some(utils::unique_id_from_content(&heading, &mut id_counter)));
|
||||
breadcrumbs.push(heading.clone());
|
||||
}
|
||||
Event::Start(Tag::FootnoteDefinition(name)) => {
|
||||
|
|
|
@ -37,14 +37,14 @@ impl Renderer for MarkdownRenderer {
|
|||
if !ch.is_draft_chapter() {
|
||||
utils::fs::write_file(
|
||||
&ctx.destination,
|
||||
&ch.path.as_ref().expect("Checked path exists before"),
|
||||
ch.path.as_ref().expect("Checked path exists before"),
|
||||
ch.content.as_bytes(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs::create_dir_all(&destination)
|
||||
fs::create_dir_all(destination)
|
||||
.with_context(|| "Unexpected error when constructing destination path")?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -68,7 +68,7 @@ function playground_text(playground, hidden = true) {
|
|||
}
|
||||
|
||||
// updates the visibility of play button based on `no_run` class and
|
||||
// used crates vs ones available on http://play.rust-lang.org
|
||||
// used crates vs ones available on https://play.rust-lang.org
|
||||
function update_play_button(pre_block, playground_crates) {
|
||||
var play_button = pre_block.querySelector(".play-button");
|
||||
|
||||
|
@ -179,7 +179,7 @@ function playground_text(playground, hidden = true) {
|
|||
// even if highlighting doesn't apply
|
||||
code_nodes.forEach(function (block) { block.classList.add('hljs'); });
|
||||
|
||||
Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) {
|
||||
Array.from(document.querySelectorAll("code.hljs")).forEach(function (block) {
|
||||
|
||||
var lines = Array.from(block.querySelectorAll('.boring'));
|
||||
// If no lines were hidden, return
|
||||
|
@ -551,13 +551,6 @@ function playground_text(playground, hidden = true) {
|
|||
firstContact = null;
|
||||
}
|
||||
}, { passive: true });
|
||||
|
||||
// Scroll sidebar to current active section
|
||||
var activeSection = document.getElementById("sidebar").querySelector(".active");
|
||||
if (activeSection) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
|
||||
activeSection.scrollIntoView({ block: 'center' });
|
||||
}
|
||||
})();
|
||||
|
||||
(function chapterNavigation() {
|
||||
|
@ -676,13 +669,14 @@ function playground_text(playground, hidden = true) {
|
|||
}, { passive: true });
|
||||
})();
|
||||
(function controllBorder() {
|
||||
menu.classList.remove('bordered');
|
||||
document.addEventListener('scroll', function () {
|
||||
function updateBorder() {
|
||||
if (menu.offsetTop === 0) {
|
||||
menu.classList.remove('bordered');
|
||||
} else {
|
||||
menu.classList.add('bordered');
|
||||
}
|
||||
}, { passive: true });
|
||||
}
|
||||
updateBorder();
|
||||
document.addEventListener('scroll', updateBorder, { passive: true });
|
||||
})();
|
||||
})();
|
||||
|
|
|
@ -110,12 +110,34 @@
|
|||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
|
||||
<!-- Track and set sidebar scroll position -->
|
||||
<script>
|
||||
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
|
||||
sidebarScrollbox.addEventListener('click', function(e) {
|
||||
if (e.target.tagName === 'A') {
|
||||
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
|
||||
}
|
||||
}, { passive: true });
|
||||
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
|
||||
sessionStorage.removeItem('sidebar-scroll');
|
||||
if (sidebarScrollTop) {
|
||||
// preserve sidebar scroll position when navigating via links within sidebar
|
||||
sidebarScrollbox.scrollTop = sidebarScrollTop;
|
||||
} else {
|
||||
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
|
||||
var activeSection = document.querySelector('#sidebar .active');
|
||||
if (activeSection) {
|
||||
activeSection.scrollIntoView({ block: 'center' });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
{{> header}}
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky bordered">
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div class="left-buttons">
|
||||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* Tomorrow Night Theme */
|
||||
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
|
||||
/* https://github.com/jmblog/color-themes-for-highlightjs */
|
||||
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
|
||||
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
|
||||
/* https://github.com/jmblog/color-themes-for-highlightjs */
|
||||
|
||||
/* Tomorrow Comment */
|
||||
.hljs-comment {
|
||||
|
|
|
@ -210,39 +210,36 @@ mod tests {
|
|||
};
|
||||
|
||||
// Create a couple of files
|
||||
if let Err(err) = fs::File::create(&tmp.path().join("file.txt")) {
|
||||
if let Err(err) = fs::File::create(tmp.path().join("file.txt")) {
|
||||
panic!("Could not create file.txt: {}", err);
|
||||
}
|
||||
if let Err(err) = fs::File::create(&tmp.path().join("file.md")) {
|
||||
if let Err(err) = fs::File::create(tmp.path().join("file.md")) {
|
||||
panic!("Could not create file.md: {}", err);
|
||||
}
|
||||
if let Err(err) = fs::File::create(&tmp.path().join("file.png")) {
|
||||
if let Err(err) = fs::File::create(tmp.path().join("file.png")) {
|
||||
panic!("Could not create file.png: {}", err);
|
||||
}
|
||||
if let Err(err) = fs::create_dir(&tmp.path().join("sub_dir")) {
|
||||
if let Err(err) = fs::create_dir(tmp.path().join("sub_dir")) {
|
||||
panic!("Could not create sub_dir: {}", err);
|
||||
}
|
||||
if let Err(err) = fs::File::create(&tmp.path().join("sub_dir/file.png")) {
|
||||
if let Err(err) = fs::File::create(tmp.path().join("sub_dir/file.png")) {
|
||||
panic!("Could not create sub_dir/file.png: {}", err);
|
||||
}
|
||||
if let Err(err) = fs::create_dir(&tmp.path().join("sub_dir_exists")) {
|
||||
if let Err(err) = fs::create_dir(tmp.path().join("sub_dir_exists")) {
|
||||
panic!("Could not create sub_dir_exists: {}", err);
|
||||
}
|
||||
if let Err(err) = fs::File::create(&tmp.path().join("sub_dir_exists/file.txt")) {
|
||||
if let Err(err) = fs::File::create(tmp.path().join("sub_dir_exists/file.txt")) {
|
||||
panic!("Could not create sub_dir_exists/file.txt: {}", err);
|
||||
}
|
||||
if let Err(err) = symlink(
|
||||
&tmp.path().join("file.png"),
|
||||
&tmp.path().join("symlink.png"),
|
||||
) {
|
||||
if let Err(err) = symlink(tmp.path().join("file.png"), tmp.path().join("symlink.png")) {
|
||||
panic!("Could not symlink file.png: {}", err);
|
||||
}
|
||||
|
||||
// Create output dir
|
||||
if let Err(err) = fs::create_dir(&tmp.path().join("output")) {
|
||||
if let Err(err) = fs::create_dir(tmp.path().join("output")) {
|
||||
panic!("Could not create output: {}", err);
|
||||
}
|
||||
if let Err(err) = fs::create_dir(&tmp.path().join("output/sub_dir_exists")) {
|
||||
if let Err(err) = fs::create_dir(tmp.path().join("output/sub_dir_exists")) {
|
||||
panic!("Could not create output/sub_dir_exists: {}", err);
|
||||
}
|
||||
|
||||
|
@ -253,22 +250,22 @@ mod tests {
|
|||
}
|
||||
|
||||
// Check if the correct files where created
|
||||
if !(&tmp.path().join("output/file.txt")).exists() {
|
||||
if !tmp.path().join("output/file.txt").exists() {
|
||||
panic!("output/file.txt should exist")
|
||||
}
|
||||
if (&tmp.path().join("output/file.md")).exists() {
|
||||
if tmp.path().join("output/file.md").exists() {
|
||||
panic!("output/file.md should not exist")
|
||||
}
|
||||
if !(&tmp.path().join("output/file.png")).exists() {
|
||||
if !tmp.path().join("output/file.png").exists() {
|
||||
panic!("output/file.png should exist")
|
||||
}
|
||||
if !(&tmp.path().join("output/sub_dir/file.png")).exists() {
|
||||
if !tmp.path().join("output/sub_dir/file.png").exists() {
|
||||
panic!("output/sub_dir/file.png should exist")
|
||||
}
|
||||
if !(&tmp.path().join("output/sub_dir_exists/file.txt")).exists() {
|
||||
if !tmp.path().join("output/sub_dir_exists/file.txt").exists() {
|
||||
panic!("output/sub_dir/file.png should exist")
|
||||
}
|
||||
if !(&tmp.path().join("output/symlink.png")).exists() {
|
||||
if !tmp.path().join("output/symlink.png").exists() {
|
||||
panic!("output/symlink.png should exist")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -183,6 +183,7 @@ pub fn new_cmark_parser(text: &str, curly_quotes: bool) -> Parser<'_, '_> {
|
|||
opts.insert(Options::ENABLE_FOOTNOTES);
|
||||
opts.insert(Options::ENABLE_STRIKETHROUGH);
|
||||
opts.insert(Options::ENABLE_TASKLISTS);
|
||||
opts.insert(Options::ENABLE_HEADING_ATTRIBUTES);
|
||||
if curly_quotes {
|
||||
opts.insert(Options::ENABLE_SMART_PUNCTUATION);
|
||||
}
|
||||
|
|
|
@ -13,3 +13,9 @@
|
|||
##### Really Small Heading
|
||||
|
||||
###### Is it even a heading anymore - heading
|
||||
|
||||
## Custom id {#example-id}
|
||||
|
||||
## Custom class {.class1 .class2}
|
||||
|
||||
## Both id and class {#example-id2 .class1 .class2}
|
||||
|
|
|
@ -4,19 +4,19 @@ For copyright and trademark information on these images, please check [rust-artw
|
|||
|
||||
## A 16x16 image
|
||||
|
||||
![16x16 rust-lang logo](http://rust-lang.org/logos/rust-logo-16x16.png)
|
||||
![16x16 rust-lang logo](https://rust-lang.org/logos/rust-logo-16x16.png)
|
||||
|
||||
## A 32x32 image
|
||||
|
||||
![32x32 rust-lang logo](http://rust-lang.org/logos/rust-logo-32x32-blk.png)
|
||||
![32x32 rust-lang logo](https://rust-lang.org/logos/rust-logo-32x32-blk.png)
|
||||
|
||||
## A 256x256 image
|
||||
|
||||
![256x256 rust-lang logo](http://rust-lang.org/logos/rust-logo-256x256.png)
|
||||
![256x256 rust-lang logo](https://rust-lang.org/logos/rust-logo-256x256.png)
|
||||
|
||||
## A 512x512 image
|
||||
|
||||
![512x512 rust-lang logo](http://rust-lang.org/logos/rust-logo-512x512-blk.png)
|
||||
![512x512 rust-lang logo](https://rust-lang.org/logos/rust-logo-512x512-blk.png)
|
||||
|
||||
## A large image
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ fn main(){
|
|||
|
||||
A random image sprinkled in between
|
||||
|
||||
![16x16 rust-lang logo](http://rust-lang.org/logos/rust-logo-16x16.png)
|
||||
![16x16 rust-lang logo](https://rust-lang.org/logos/rust-logo-16x16.png)
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Strikethrough
|
||||
|
||||
~Single strike~
|
||||
|
||||
~~This is Striked~~
|
||||
|
||||
~~This is **strong**, _italic_ , **_both_** and striked~~
|
||||
|
|
|
@ -57,7 +57,7 @@ _start:
|
|||
|
||||
## bash
|
||||
|
||||
```
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
###### CONFIG
|
||||
|
|
|
@ -90,7 +90,7 @@ fn relative_command_path() {
|
|||
.set("output.html", toml::value::Table::new())
|
||||
.unwrap();
|
||||
config.set("output.myrenderer.command", cmd_path).unwrap();
|
||||
let md = MDBook::init(&temp.path())
|
||||
let md = MDBook::init(temp.path())
|
||||
.with_config(config)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
use crate::cli::cmd::mdbook_cmd;
|
||||
use crate::dummy_book::DummyBook;
|
||||
|
||||
use mdbook::config::Config;
|
||||
|
||||
/// Run `mdbook init` with `--force` to skip the confirmation prompts
|
||||
#[test]
|
||||
fn base_mdbook_init_can_skip_confirmation_prompts() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
|
||||
// doesn't exist before
|
||||
assert!(!temp.path().join("book").exists());
|
||||
|
||||
let mut cmd = mdbook_cmd();
|
||||
cmd.args(["init", "--force"]).current_dir(temp.path());
|
||||
cmd.assert()
|
||||
.success()
|
||||
.stdout(predicates::str::contains("\nAll done, no errors...\n"));
|
||||
|
||||
let config = Config::from_disk(temp.path().join("book.toml")).unwrap();
|
||||
assert_eq!(config.book.title, None);
|
||||
|
||||
assert!(!temp.path().join(".gitignore").exists());
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
mod build;
|
||||
mod cmd;
|
||||
mod init;
|
||||
mod test;
|
||||
|
|
|
@ -112,12 +112,12 @@ fn recursive_copy<A: AsRef<Path>, B: AsRef<Path>>(from: A, to: B) -> Result<()>
|
|||
let from = from.as_ref();
|
||||
let to = to.as_ref();
|
||||
|
||||
for entry in WalkDir::new(&from) {
|
||||
for entry in WalkDir::new(from) {
|
||||
let entry = entry.with_context(|| "Unable to inspect directory entry")?;
|
||||
|
||||
let original_location = entry.path();
|
||||
let relative = original_location
|
||||
.strip_prefix(&from)
|
||||
.strip_prefix(from)
|
||||
.expect("`original_location` is inside the `from` directory");
|
||||
let new_location = to.join(relative);
|
||||
|
||||
|
@ -126,7 +126,7 @@ fn recursive_copy<A: AsRef<Path>, B: AsRef<Path>>(from: A, to: B) -> Result<()>
|
|||
fs::create_dir_all(parent).with_context(|| "Couldn't create directory")?;
|
||||
}
|
||||
|
||||
fs::copy(&original_location, &new_location)
|
||||
fs::copy(original_location, &new_location)
|
||||
.with_context(|| "Unable to copy file contents")?;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
- [Unicode](first/unicode.md)
|
||||
- [No Headers](first/no-headers.md)
|
||||
- [Duplicate Headers](first/duplicate-headers.md)
|
||||
- [Heading Attributes](first/heading-attributes.md)
|
||||
- [Second Chapter](second.md)
|
||||
- [Nested Chapter](second/nested.md)
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# Heading Attributes {#attrs}
|
||||
|
||||
## Heading with classes {.class1 .class2}
|
||||
|
||||
## Heading with id and classes {#both .class1 .class2}
|
|
@ -35,6 +35,7 @@ const TOC_SECOND_LEVEL: &[&str] = &[
|
|||
"1.5. Unicode",
|
||||
"1.6. No Headers",
|
||||
"1.7. Duplicate Headers",
|
||||
"1.8. Heading Attributes",
|
||||
"2.1. Nested Chapter",
|
||||
];
|
||||
|
||||
|
@ -275,7 +276,7 @@ fn root_index_html() -> Result<Document> {
|
|||
.with_context(|| "Book building failed")?;
|
||||
|
||||
let index_page = temp.path().join("book").join("index.html");
|
||||
let html = fs::read_to_string(&index_page).with_context(|| "Unable to read index.html")?;
|
||||
let html = fs::read_to_string(index_page).with_context(|| "Unable to read index.html")?;
|
||||
|
||||
Ok(Document::from(html.as_str()))
|
||||
}
|
||||
|
@ -412,7 +413,7 @@ fn recursive_includes_are_capped() {
|
|||
let content = &["Around the world, around the world
|
||||
Around the world, around the world
|
||||
Around the world, around the world"];
|
||||
assert_contains_strings(&recursive, content);
|
||||
assert_contains_strings(recursive, content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -462,7 +463,7 @@ fn by_default_mdbook_use_index_preprocessor_to_convert_readme_to_index() {
|
|||
|
||||
let second_index = temp.path().join("book").join("second").join("index.html");
|
||||
let unexpected_strings = vec!["Second README"];
|
||||
assert_doesnt_contain_strings(&second_index, &unexpected_strings);
|
||||
assert_doesnt_contain_strings(second_index, &unexpected_strings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -628,10 +629,8 @@ fn edit_url_has_configured_src_dir_edit_url() {
|
|||
}
|
||||
|
||||
fn remove_absolute_components(path: &Path) -> impl Iterator<Item = Component> + '_ {
|
||||
path.components().skip_while(|c| match c {
|
||||
Component::Prefix(_) | Component::RootDir => true,
|
||||
_ => false,
|
||||
})
|
||||
path.components()
|
||||
.skip_while(|c| matches!(c, Component::Prefix(_) | Component::RootDir))
|
||||
}
|
||||
|
||||
/// Checks formatting of summary names with inline elements.
|
||||
|
@ -756,6 +755,7 @@ mod search {
|
|||
let no_headers = get_doc_ref("first/no-headers.html");
|
||||
let duplicate_headers_1 = get_doc_ref("first/duplicate-headers.html#header-text-1");
|
||||
let conclusion = get_doc_ref("conclusion.html#conclusion");
|
||||
let heading_attrs = get_doc_ref("first/heading-attributes.html#both");
|
||||
|
||||
let bodyidx = &index["index"]["index"]["body"]["root"];
|
||||
let textidx = &bodyidx["t"]["e"]["x"]["t"];
|
||||
|
@ -768,7 +768,7 @@ mod search {
|
|||
assert_eq!(docs[&some_section]["body"], "");
|
||||
assert_eq!(
|
||||
docs[&summary]["body"],
|
||||
"Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode No Headers Duplicate Headers Second Chapter Nested Chapter Conclusion"
|
||||
"Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode No Headers Duplicate Headers Heading Attributes Second Chapter Nested Chapter Conclusion"
|
||||
);
|
||||
assert_eq!(
|
||||
docs[&summary]["breadcrumbs"],
|
||||
|
@ -787,6 +787,10 @@ mod search {
|
|||
docs[&no_headers]["body"],
|
||||
"Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex."
|
||||
);
|
||||
assert_eq!(
|
||||
docs[&heading_attrs]["breadcrumbs"],
|
||||
"First Chapter » Heading Attributes » Heading with id and classes"
|
||||
);
|
||||
}
|
||||
|
||||
// Setting this to `true` may cause issues with `cargo watch`,
|
||||
|
@ -803,7 +807,7 @@ mod search {
|
|||
let src = read_book_index(temp.path());
|
||||
|
||||
let dest = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/searchindex_fixture.json");
|
||||
let dest = File::create(&dest).unwrap();
|
||||
let dest = File::create(dest).unwrap();
|
||||
serde_json::to_writer_pretty(dest, &src).unwrap();
|
||||
|
||||
src
|
||||
|
@ -891,8 +895,8 @@ fn custom_fonts() {
|
|||
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.
|
||||
// Mixed with copy-fonts=true
|
||||
// Should ignore the copy-fonts setting since the user has provided their own fonts.css.
|
||||
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||
let p = temp.path();
|
||||
MDBook::init(p).build().unwrap();
|
||||
|
@ -900,10 +904,10 @@ fn custom_fonts() {
|
|||
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());
|
||||
assert_eq!(
|
||||
actual_files(&p.join("book/fonts")),
|
||||
["fonts.css", "myfont.woff"]
|
||||
);
|
||||
|
||||
// copy-fonts=false, no theme
|
||||
// This should generate a deprecation warning.
|
||||
|
@ -948,3 +952,19 @@ fn custom_fonts() {
|
|||
&["fonts.css", "myfont.woff"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_header_attributes() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let contents = temp.path().join("book/first/heading-attributes.html");
|
||||
|
||||
let summary_strings = &[
|
||||
r##"<h1 id="attrs"><a class="header" href="#attrs">Heading Attributes</a></h1>"##,
|
||||
r##"<h2 id="heading-with-classes" class="class1 class2"><a class="header" href="#heading-with-classes">Heading with classes</a></h2>"##,
|
||||
r##"<h2 id="both" class="class1 class2"><a class="header" href="#both">Heading with id and classes</a></h2>"##,
|
||||
];
|
||||
assert_contains_strings(&contents, summary_strings);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue