mirror of
https://github.com/tommilligan/mdbook-admonish.git
synced 2025-12-28 11:24:09 -05:00
Compare commits
29 Commits
v1.4.1
...
better-err
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92b441a950 | ||
|
|
5530da074b | ||
|
|
0fe2ad52ed | ||
|
|
787744f1f1 | ||
|
|
603c83e2eb | ||
|
|
fbdffaa723 | ||
|
|
7c20d5b2d1 | ||
|
|
3507d6b5e0 | ||
|
|
f81d4d40dd | ||
|
|
ed019a92d9 | ||
|
|
9dd2ca128c | ||
|
|
51120acfd9 | ||
|
|
650123645b | ||
|
|
438c1dff5a | ||
|
|
78b7451e49 | ||
|
|
bb937dc2d2 | ||
|
|
db7101cb12 | ||
|
|
fd6c2d0bd0 | ||
|
|
72deb8421c | ||
|
|
7b5a13d6af | ||
|
|
28dfc5b6c3 | ||
|
|
beb640077f | ||
|
|
6b479255a6 | ||
|
|
7fe2fdc329 | ||
|
|
e61e6148b5 | ||
|
|
0e235cb9b5 | ||
|
|
c28f2b2fc9 | ||
|
|
2a78ccbc2a | ||
|
|
5b61637eb7 |
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: monthly
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: monthly
|
||||
14
.github/workflows/check.yml
vendored
14
.github/workflows/check.yml
vendored
@@ -9,9 +9,9 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Cache build files
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
@@ -35,9 +35,9 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Cache build files
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- 1.58.0
|
||||
- 1.60.0
|
||||
experimental:
|
||||
- false
|
||||
# Run a canary test on nightly that's allowed to fail
|
||||
@@ -87,9 +87,9 @@ jobs:
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Cache build files
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
|
||||
10
.github/workflows/deploy.yml
vendored
10
.github/workflows/deploy.yml
vendored
@@ -37,11 +37,11 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Cache files between builds
|
||||
- name: Setup | Cache Cargo
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
tar czvf ../../stage/${{ env.CRATE_NAME }}-${{ steps.extract_tag.outputs.tag }}-${{ matrix.name }} ${{ env.CRATE_NAME }}
|
||||
cd -
|
||||
- name: Post Setup | Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.CRATE_NAME }}-${{ steps.extract_tag.outputs.tag }}-${{ matrix.name }}
|
||||
path: target/stage/*
|
||||
@@ -102,12 +102,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup | Artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v3
|
||||
|
||||
- name: Setup | Release notes
|
||||
run: |
|
||||
|
||||
39
.github/workflows/docs.yml
vendored
Normal file
39
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: docs
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
~/.cargo/bin
|
||||
cargo_target
|
||||
# We reuse the cache from our detailed test environment, if available
|
||||
key: detailed-test-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
|
||||
- name: Install toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Install mdbook
|
||||
run: ./scripts/install-mdbook
|
||||
- name: Build book
|
||||
run: ./scripts/build-book
|
||||
- name: Push docs
|
||||
uses: JamesIves/github-pages-deploy-action@v4.4.1
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: book/book
|
||||
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@@ -9,8 +9,8 @@ jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
|
||||
48
CHANGELOG.md
48
CHANGELOG.md
@@ -1,5 +1,53 @@
|
||||
## Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 1.8.0
|
||||
|
||||
### Changed
|
||||
|
||||
- MSRV (minimum supported rust version) is now 1.60.0 for clap v4
|
||||
|
||||
## 1.7.0
|
||||
|
||||
### Changed
|
||||
|
||||
- Required styles version is now `^2.0.0` (release `1.7.0`). Run `mdbook-admonish install` to update.
|
||||
|
||||
### Added
|
||||
|
||||
- Support key/value configuration ([#24](https://github.com/tommilligan/mdbook-admonish/pull/24), thanks [@gggto](https://github.com/gggto) and [@schungx](https://github.com/schungx) for design input)
|
||||
- Support collapsiable admonition bodies ([#26](https://github.com/tommilligan/mdbook-admonish/pull/26), thanks [@gggto](https://github.com/gggto) for the suggestion and implementation!)
|
||||
- Make anchor links hoverable ([#27](https://github.com/tommilligan/mdbook-admonish/pull/27))
|
||||
- Better handling for misconfigured admonitions ([#25](https://github.com/tommilligan/mdbook-admonish/pull/25))
|
||||
- Nicer in-book error messages
|
||||
- Option to fail the build instead
|
||||
|
||||
## 1.6.0
|
||||
|
||||
**Please note:** If updating from an older version, this release requires `mdboook-admonish install` to be rerun after installation.
|
||||
|
||||
This behaviour is [documented in the readme here](https://github.com/tommilligan/mdbook-admonish#semantic-versioning), and may appear in any future minor version release.
|
||||
|
||||
### Changed
|
||||
|
||||
- Required styles version is now `^1.0.0` (release `1.6.0`). Run `mdbook-admonish install` to update.
|
||||
|
||||
### Added
|
||||
|
||||
- Enforce updating installed styles when required for new features ([#19](https://github.com/tommilligan/mdbook-admonish/pull/19)
|
||||
- Each admonition has a unique id. Click the header bar to navigate to the anchor link ([#19](https://github.com/tommilligan/mdbook-admonish/pull/19), thanks [@schungx](https://github.com/schungx) for the suggestion)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Header bar overflow at some zoom levels on Firefox ([#21](https://github.com/tommilligan/mdbook-admonish/pull/21), thanks to [@sgoudham](https://github.com/sgoudham) for the report)
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### Added
|
||||
|
||||
- Admonitions now have an autogenerated `id`, to support anchor links ([#16](https://github.com/tommilligan/mdbook-admonish/pull/16), thanks [@schungx](https://github.com/schungx) for the suggestion)
|
||||
|
||||
## 1.4.1
|
||||
|
||||
### Changed
|
||||
|
||||
1058
Cargo.lock
generated
1058
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
26
Cargo.toml
26
Cargo.toml
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "mdbook-admonish"
|
||||
version = "1.4.1"
|
||||
version = "1.8.0"
|
||||
edition = "2021"
|
||||
|
||||
authors = ["Tom Milligan <code@tommilligan.net>"]
|
||||
description = "A preprocessor for mdbook to add Material Design admonishments."
|
||||
repository = "https://github.com/tommilligan/mdbook-admonish"
|
||||
documentation = "https://docs.rs/mdbook-admonish"
|
||||
documentation = "https://tommilligan.github.io/mdbook-admonish/"
|
||||
|
||||
license = "MIT"
|
||||
keywords = ["mdbook", "markdown", "material", "design", "ui"]
|
||||
@@ -22,16 +22,22 @@ name = "mdbook_admonish"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "3.0.14", default_features = false, features = ["std", "cargo"], optional = true }
|
||||
env_logger = { version = "0.9.0", default_features = false, optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
mdbook = "0.4.15"
|
||||
pulldown-cmark = "0.9.1"
|
||||
serde_json = "1.0.79"
|
||||
toml_edit = { version = "0.13.4", optional = true }
|
||||
anyhow = "1.0.65"
|
||||
clap = { version = "4", default_features = false, features = ["std", "derive"], optional = true }
|
||||
env_logger = { version = "0.9.1", default_features = false, optional = true }
|
||||
log = { version = "0.4.17", optional = true }
|
||||
mdbook = "0.4.21"
|
||||
once_cell = "1.15.0"
|
||||
pulldown-cmark = "0.9.2"
|
||||
regex = "1.6.0"
|
||||
semver = "1.0.14"
|
||||
serde = { version = "1.0.145", features = ["derive"] }
|
||||
serde_json = "1.0.85"
|
||||
toml = "0.5.9"
|
||||
toml_edit = { version = "0.15.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.1.0"
|
||||
pretty_assertions = "1.3.0"
|
||||
|
||||
[features]
|
||||
default = ["cli", "cli-install"]
|
||||
|
||||
140
README.md
140
README.md
@@ -1,7 +1,7 @@
|
||||
# mdbook-admonish
|
||||
|
||||
[](https://crates.io/crates/mdbook-admonish)
|
||||
[](https://docs.rs/mdbook-admonish)
|
||||
[](https://tommilligan.github.io/mdbook-admonish/)
|
||||
|
||||
A preprocessor for [mdbook](https://github.com/rust-lang-nursery/mdBook) to add [Material Design](https://material.io/design) admonishments, based on the [mkdocs-material](https://squidfunk.github.io/mkdocs-material/reference/admonitions/) implementation.
|
||||
|
||||
@@ -19,7 +19,7 @@ into this:
|
||||
|
||||
## Examples
|
||||
|
||||
Read the usage and reference [here](https://tommilligan.github.io/mdbook-admonish/), to see the actual examples in action. You can see the source in the [`./book`](./book) subdirectory.
|
||||
Read the documentation [here](https://tommilligan.github.io/mdbook-admonish/), to see the actual examples in action. You can see the source in the [`./book`](./book) subdirectory.
|
||||
|
||||
Other projects using mdbook-admonish:
|
||||
|
||||
@@ -58,72 +58,11 @@ A plain note.
|
||||
|
||||
### Additional Options
|
||||
|
||||
#### Custom title
|
||||
See the [`mdbook-admonish` book](https://tommilligan.github.io/mdbook-admonish/) for additional options, such as:
|
||||
|
||||
A custom title can be provided, contained in a double quoted JSON string.
|
||||
Note that JSON escapes must be escaped again - for instance, write `\"` as `\\"`.
|
||||
|
||||
````
|
||||
```admonish warning "Data loss"
|
||||
The following steps can lead to irrecoverable data corruption.
|
||||
```
|
||||
````
|
||||
|
||||

|
||||
|
||||
You can also remove the title bar entirely, by specifying the empty string:
|
||||
|
||||
````
|
||||
```admonish success ""
|
||||
This will take a while, go and grab a drink of water.
|
||||
```
|
||||
````
|
||||
|
||||

|
||||
|
||||
#### Nested Markdown/HTML
|
||||
|
||||
Markdown and HTML can be used in the inner content, as you'd expect:
|
||||
|
||||
````
|
||||
```admonish tip "_Referencing_ and <i>dereferencing</i>"
|
||||
The opposite of *referencing* by using `&` is *dereferencing*, which is
|
||||
accomplished with the <span style="color: hotpink">dereference operator</span>, `*`.
|
||||
```
|
||||
````
|
||||
|
||||

|
||||
|
||||
If you have code blocks you want to include in the content, use [tildes for the outer code fence](https://spec.commonmark.org/0.30/#fenced-code-blocks):
|
||||
|
||||
````
|
||||
~~~admonish bug
|
||||
This syntax won't work in Python 3:
|
||||
```python
|
||||
print "Hello, world!"
|
||||
```
|
||||
~~~
|
||||
````
|
||||
|
||||

|
||||
|
||||
#### Custom styling
|
||||
|
||||
If you want to provide custom styling to a specific admonition, you can attach one or more custom classnames:
|
||||
|
||||
````
|
||||
```admonish note.custom-0.custom-1
|
||||
Styled with my custom CSS class.
|
||||
```
|
||||
````
|
||||
|
||||
Will yield something like the following HTML, which you can then apply styles to:
|
||||
|
||||
```html
|
||||
<div class="admonition note custom-0 custom-1"
|
||||
...
|
||||
</div>
|
||||
```
|
||||
- Custom titles
|
||||
- Custom styling
|
||||
- Collapsible blocks
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -136,6 +75,8 @@ cargo install mdbook-admonish
|
||||
Then let `mdbook-admonish` add the required files and configuration:
|
||||
|
||||
```bash
|
||||
# Note: this may need to be rerun for new minor versions of mdbook-admonish
|
||||
# see the 'Semantic Versioning' section below for details.
|
||||
mdbook-admonish install path/to/your/book
|
||||
|
||||
# optionally, specify a directory where CSS files live, relative to the book root
|
||||
@@ -164,6 +105,50 @@ mdbook path/to/book
|
||||
|
||||
**Please note**, when updating your version of `mdbook-admonish`, updated styles will not be applied unless you rerun `mdbook-admonish install` to update the additional CSS files in your book.
|
||||
|
||||
`mdbook` will fail the build if you require newer assets than you have installed:
|
||||
|
||||
```log
|
||||
2022-04-26 12:27:52 [INFO] (mdbook::book): Book building has started
|
||||
ERROR:
|
||||
Incompatible assets installed: required mdbook-admonish assets version '^2.0.0', but found '1.0.0'.
|
||||
Please run `mdbook-admonish install` to update installed assets.
|
||||
2022-04-26 12:27:52 [ERROR] (mdbook::utils): Error: The "admonish" preprocessor exited unsuccessfully with exit status: 1 status
|
||||
```
|
||||
|
||||
If you want to update across minor versions without breakage, you should always run `mdbook-admonish install`.
|
||||
|
||||
Alternatively, pin to a specific version for a reproducible installation:
|
||||
|
||||
```bash
|
||||
cargo install mdbook-admonish --vers "1.5.0" --locked
|
||||
```
|
||||
|
||||
### Bail on error
|
||||
|
||||
By default, if an adomnition is incorrectly configured, an error will be shown in the book.
|
||||
|
||||
You can force it to break the build instead, with the following configuration:
|
||||
|
||||
```toml
|
||||
[preprocessor.admonish]
|
||||
on_failure = "bail"
|
||||
```
|
||||
|
||||
This may be useful for non-interative workflows.
|
||||
|
||||
### Semantic Versioning
|
||||
|
||||
Guarantees provided are as follows:
|
||||
|
||||
- Major versions: Contain breaking changes to the user facing markdown API, or the public API of the crate itself.
|
||||
- Minor versions: Feature release. May contain changes to generated CSS/HTML requiring `mdbook-admonish install` to be rerun.
|
||||
- **Note:** updating acrosss minor versions without running `mdbook-admonish install` to reinstall assets may break your build.
|
||||
- This is due to limitations in the `mdbook` preprocessor architecture. Relevant issues that may alleviate this:
|
||||
- https://github.com/rust-lang/mdBook/issues/1222
|
||||
- https://github.com/rust-lang/mdBook/issues/1687
|
||||
- https://github.com/rust-lang/mdBook/issues/1689
|
||||
- Patch versions: Bug fixes only.
|
||||
|
||||
## Development
|
||||
|
||||
Project design
|
||||
@@ -172,6 +157,27 @@ Project design
|
||||
- `mdbook-admonish install` is responsible for delivering additional assets and configuration to a client book.
|
||||
- `mdbook-admonish` is responsible for preprocessing book data, adding HTML that references compiled classnames.
|
||||
|
||||
### Scripts to get started
|
||||
|
||||
- `./scripts/install` installs other toolchains required for development
|
||||
- `./scripts/check` runs a full CI check
|
||||
- `./scripts/rebuild-book` rebuilds the reference book under `./book`. This is useful for integration testing locally.
|
||||
|
||||
### Making breaking changes in CSS
|
||||
|
||||
To make a breaking change in CSS, you should:
|
||||
|
||||
- Update the assets version in `./src/bin/assets/VERSION`
|
||||
- Update the required assets version specifier in `./src/REQUIRED_ASSETS_VERSION`
|
||||
|
||||
You must make the next `mdbook-admonish` crate version at least a **minor** version bump.
|
||||
|
||||
### Releasing
|
||||
|
||||
Github workflows are setup such that pushing a `vX.Y.Z` tag will trigger a release to be cut.
|
||||
|
||||
Once the release is created, copy and paste the relevant section of `CHANGELOG.md` manually to update the description.
|
||||
|
||||
## Thanks
|
||||
|
||||
This utility is heavily drawn from and inspired by other projects, namely:
|
||||
|
||||
1
book/.gitignore
vendored
1
book/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/book
|
||||
/mdbook-admonish.css
|
||||
|
||||
@@ -9,6 +9,7 @@ title = "The mdbook-admonish book"
|
||||
|
||||
[preprocessor.admonish]
|
||||
command = "mdbook-admonish"
|
||||
assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
|
||||
|
||||
[output]
|
||||
|
||||
|
||||
@@ -1,321 +0,0 @@
|
||||
:root {
|
||||
--md-admonition-icon--note:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");
|
||||
--md-admonition-icon--abstract:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>");
|
||||
--md-admonition-icon--info:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");
|
||||
--md-admonition-icon--tip:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");
|
||||
--md-admonition-icon--success:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>");
|
||||
--md-admonition-icon--question:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");
|
||||
--md-admonition-icon--warning:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");
|
||||
--md-admonition-icon--failure:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>");
|
||||
--md-admonition-icon--danger:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>");
|
||||
--md-admonition-icon--bug:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");
|
||||
--md-admonition-icon--example:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");
|
||||
--md-admonition-icon--quote:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>");
|
||||
}
|
||||
|
||||
:is(.admonition) {
|
||||
display: flow-root;
|
||||
margin: 1.5625em 0;
|
||||
padding: 0 1.2rem;
|
||||
color: var(--fg);
|
||||
page-break-inside: avoid;
|
||||
background-color: var(--bg);
|
||||
border: 0 solid black;
|
||||
border-inline-start-width: 0.4rem;
|
||||
border-radius: 0.2rem;
|
||||
box-shadow: 0 0.2rem 1rem rgba(0, 0, 0, 0.05), 0 0 0.1rem rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
@media print {
|
||||
:is(.admonition) {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
:is(.admonition) > * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:is(.admonition) :is(.admonition) {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
:is(.admonition) > .tabbed-set:only-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
html :is(.admonition) > :last-child {
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
:is(.admonition-title, summary) {
|
||||
position: relative;
|
||||
margin-block: 0;
|
||||
margin-inline: -1.6rem -1.2rem;
|
||||
padding-block: 0.8rem;
|
||||
padding-inline: 4rem 1.2rem;
|
||||
font-weight: 700;
|
||||
background-color: rgba(68, 138, 255, 0.1);
|
||||
border: 0 solid #448aff;
|
||||
border-inline-start-width: 0.4rem;
|
||||
border-start-start-radius: 0.2rem;
|
||||
display: flex;
|
||||
}
|
||||
:is(.admonition-title, summary) > p {
|
||||
margin: 0;
|
||||
}
|
||||
html :is(.admonition-title, summary):last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
:is(.admonition-title, summary)::before {
|
||||
position: absolute;
|
||||
top: 0.625em;
|
||||
inset-inline-start: 1.2rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background-color: #448aff;
|
||||
mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>');
|
||||
-webkit-mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>');
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-size: contain;
|
||||
content: "";
|
||||
}
|
||||
|
||||
:is(.admonition):is(.note) {
|
||||
border-color: #448aff;
|
||||
}
|
||||
|
||||
:is(.note) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(68, 138, 255, 0.1);
|
||||
border-color: #448aff;
|
||||
}
|
||||
:is(.note) > :is(.admonition-title, summary)::before {
|
||||
background-color: #448aff;
|
||||
mask-image: var(--md-admonition-icon--note);
|
||||
-webkit-mask-image: var(--md-admonition-icon--note);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.abstract, .summary, .tldr) {
|
||||
border-color: #00b0ff;
|
||||
}
|
||||
|
||||
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(0, 176, 255, 0.1);
|
||||
border-color: #00b0ff;
|
||||
}
|
||||
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::before {
|
||||
background-color: #00b0ff;
|
||||
mask-image: var(--md-admonition-icon--abstract);
|
||||
-webkit-mask-image: var(--md-admonition-icon--abstract);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.info, .todo) {
|
||||
border-color: #00b8d4;
|
||||
}
|
||||
|
||||
:is(.info, .todo) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(0, 184, 212, 0.1);
|
||||
border-color: #00b8d4;
|
||||
}
|
||||
:is(.info, .todo) > :is(.admonition-title, summary)::before {
|
||||
background-color: #00b8d4;
|
||||
mask-image: var(--md-admonition-icon--info);
|
||||
-webkit-mask-image: var(--md-admonition-icon--info);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.tip, .hint, .important) {
|
||||
border-color: #00bfa5;
|
||||
}
|
||||
|
||||
:is(.tip, .hint, .important) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(0, 191, 165, 0.1);
|
||||
border-color: #00bfa5;
|
||||
}
|
||||
:is(.tip, .hint, .important) > :is(.admonition-title, summary)::before {
|
||||
background-color: #00bfa5;
|
||||
mask-image: var(--md-admonition-icon--tip);
|
||||
-webkit-mask-image: var(--md-admonition-icon--tip);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.success, .check, .done) {
|
||||
border-color: #00c853;
|
||||
}
|
||||
|
||||
:is(.success, .check, .done) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(0, 200, 83, 0.1);
|
||||
border-color: #00c853;
|
||||
}
|
||||
:is(.success, .check, .done) > :is(.admonition-title, summary)::before {
|
||||
background-color: #00c853;
|
||||
mask-image: var(--md-admonition-icon--success);
|
||||
-webkit-mask-image: var(--md-admonition-icon--success);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.question, .help, .faq) {
|
||||
border-color: #64dd17;
|
||||
}
|
||||
|
||||
:is(.question, .help, .faq) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(100, 221, 23, 0.1);
|
||||
border-color: #64dd17;
|
||||
}
|
||||
:is(.question, .help, .faq) > :is(.admonition-title, summary)::before {
|
||||
background-color: #64dd17;
|
||||
mask-image: var(--md-admonition-icon--question);
|
||||
-webkit-mask-image: var(--md-admonition-icon--question);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.warning, .caution, .attention) {
|
||||
border-color: #ff9100;
|
||||
}
|
||||
|
||||
:is(.warning, .caution, .attention) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(255, 145, 0, 0.1);
|
||||
border-color: #ff9100;
|
||||
}
|
||||
:is(.warning, .caution, .attention) > :is(.admonition-title, summary)::before {
|
||||
background-color: #ff9100;
|
||||
mask-image: var(--md-admonition-icon--warning);
|
||||
-webkit-mask-image: var(--md-admonition-icon--warning);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.failure, .fail, .missing) {
|
||||
border-color: #ff5252;
|
||||
}
|
||||
|
||||
:is(.failure, .fail, .missing) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(255, 82, 82, 0.1);
|
||||
border-color: #ff5252;
|
||||
}
|
||||
:is(.failure, .fail, .missing) > :is(.admonition-title, summary)::before {
|
||||
background-color: #ff5252;
|
||||
mask-image: var(--md-admonition-icon--failure);
|
||||
-webkit-mask-image: var(--md-admonition-icon--failure);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.danger, .error) {
|
||||
border-color: #ff1744;
|
||||
}
|
||||
|
||||
:is(.danger, .error) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(255, 23, 68, 0.1);
|
||||
border-color: #ff1744;
|
||||
}
|
||||
:is(.danger, .error) > :is(.admonition-title, summary)::before {
|
||||
background-color: #ff1744;
|
||||
mask-image: var(--md-admonition-icon--danger);
|
||||
-webkit-mask-image: var(--md-admonition-icon--danger);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.bug) {
|
||||
border-color: #f50057;
|
||||
}
|
||||
|
||||
:is(.bug) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(245, 0, 87, 0.1);
|
||||
border-color: #f50057;
|
||||
}
|
||||
:is(.bug) > :is(.admonition-title, summary)::before {
|
||||
background-color: #f50057;
|
||||
mask-image: var(--md-admonition-icon--bug);
|
||||
-webkit-mask-image: var(--md-admonition-icon--bug);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.example) {
|
||||
border-color: #7c4dff;
|
||||
}
|
||||
|
||||
:is(.example) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(124, 77, 255, 0.1);
|
||||
border-color: #7c4dff;
|
||||
}
|
||||
:is(.example) > :is(.admonition-title, summary)::before {
|
||||
background-color: #7c4dff;
|
||||
mask-image: var(--md-admonition-icon--example);
|
||||
-webkit-mask-image: var(--md-admonition-icon--example);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.quote, .cite) {
|
||||
border-color: #9e9e9e;
|
||||
}
|
||||
|
||||
:is(.quote, .cite) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(158, 158, 158, 0.1);
|
||||
border-color: #9e9e9e;
|
||||
}
|
||||
:is(.quote, .cite) > :is(.admonition-title, summary)::before {
|
||||
background-color: #9e9e9e;
|
||||
mask-image: var(--md-admonition-icon--quote);
|
||||
-webkit-mask-image: var(--md-admonition-icon--quote);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.navy :is(.admonition) {
|
||||
background-color: var(--sidebar-bg);
|
||||
}
|
||||
|
||||
.ayu :is(.admonition), .coal :is(.admonition) {
|
||||
background-color: var(--theme-hover);
|
||||
}
|
||||
|
||||
.rust :is(.admonition) {
|
||||
background-color: var(--sidebar-bg);
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
@@ -58,28 +58,28 @@ A plain note.
|
||||
|
||||
#### Custom title
|
||||
|
||||
A custom title can be provided, contained in a double quoted JSON string.
|
||||
Note that JSON escapes must be escaped again - for instance, write `\"` as `\\"`.
|
||||
A custom title can be provided, contained in a double quoted TOML string.
|
||||
Note that TOML escapes must be escaped again - for instance, write `\"` as `\\"`.
|
||||
|
||||
````
|
||||
```admonish warning "Data loss"
|
||||
```admonish warning title="Data loss"
|
||||
The following steps can lead to irrecoverable data corruption.
|
||||
```
|
||||
````
|
||||
|
||||
```admonish warning "Data loss"
|
||||
```admonish warning title="Data loss"
|
||||
The following steps can lead to irrecoverable data corruption.
|
||||
```
|
||||
|
||||
You can also remove the title bar entirely, by specifying the empty string:
|
||||
|
||||
````
|
||||
```admonish success ""
|
||||
```admonish success title=""
|
||||
This will take a while, go and grab a drink of water.
|
||||
```
|
||||
````
|
||||
|
||||
```admonish success ""
|
||||
```admonish success title=""
|
||||
This will take a while, go and grab a drink of water.
|
||||
```
|
||||
|
||||
@@ -88,13 +88,13 @@ This will take a while, go and grab a drink of water.
|
||||
Markdown and HTML can be used in the inner content, as you'd expect:
|
||||
|
||||
````
|
||||
```admonish tip "_Referencing_ and <i>dereferencing</i>"
|
||||
```admonish tip title="_Referencing_ and <i>dereferencing</i>"
|
||||
The opposite of *referencing* by using `&` is *dereferencing*, which is
|
||||
accomplished with the <span style="color: hotpink">dereference operator</span>, `*`.
|
||||
```
|
||||
````
|
||||
|
||||
```admonish tip "_Referencing_ and <i>dereferencing</i>"
|
||||
```admonish tip title="_Referencing_ and <i>dereferencing</i>"
|
||||
The opposite of *referencing* by using `&` is *dereferencing*, which is
|
||||
accomplished with the <span style="color: hotpink">dereference operator</span>, `*`.
|
||||
```
|
||||
@@ -122,7 +122,7 @@ print "Hello, world!"
|
||||
If you want to provide custom styling to a specific admonition, you can attach one or more custom classnames:
|
||||
|
||||
````
|
||||
```admonish note.custom-0.custom-1
|
||||
```admonish note class="custom-0 custom-1"
|
||||
Styled with my custom CSS class.
|
||||
```
|
||||
````
|
||||
@@ -134,3 +134,33 @@ Will yield something like the following HTML, which you can then apply styles to
|
||||
...
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Collapsible
|
||||
|
||||
For a block to be initially collapsible, and then be openable, set `collapsible=true`:
|
||||
|
||||
````
|
||||
```admonish collapsible=true
|
||||
Content will be hidden initially.
|
||||
```
|
||||
````
|
||||
|
||||
Will yield something like the following HTML, which you can then apply styles to:
|
||||
|
||||
```admonish collapsible=true
|
||||
Content will be hidden initially.
|
||||
```
|
||||
|
||||
#### Invalid blocks
|
||||
|
||||
If a rendering error occurs, an error will be rendered in the output:
|
||||
|
||||
````
|
||||
```admonish title="\j"
|
||||
This block will error
|
||||
```
|
||||
````
|
||||
|
||||
```admonish title="\j"
|
||||
This block will error
|
||||
```
|
||||
|
||||
@@ -65,6 +65,8 @@ $admonitions: (
|
||||
--md-admonition-icon--#{nth($names, 1)}:
|
||||
url("data:image/svg+xml;charset=utf-8,#{nth($props, 2)}");
|
||||
}
|
||||
--md-details-icon:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -114,23 +116,46 @@ $admonitions: (
|
||||
}
|
||||
}
|
||||
|
||||
// Anchor links
|
||||
a.admonition-anchor-link {
|
||||
// Don't display the link by default
|
||||
display: none;
|
||||
|
||||
// Position to the left of the element to link to
|
||||
position: absolute;
|
||||
left: -1.2rem;
|
||||
// Ensure we have enough padding, so that we can move the mouse to click on it
|
||||
padding-right: 1.0rem;
|
||||
|
||||
&:link, &:visited {
|
||||
// Don't make links colored (override to standard text color)
|
||||
// variable provided downstream by mdbook
|
||||
color: var(--fg);
|
||||
}
|
||||
&:link:hover, &:visited:hover {
|
||||
// No underline on hover
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '§';
|
||||
}
|
||||
}
|
||||
|
||||
// Admonition title
|
||||
:is(.admonition-title, summary) {
|
||||
position: relative;
|
||||
margin-block: 0;
|
||||
margin-inline: -1.6rem -1.2rem;
|
||||
padding-block: 0.8rem;
|
||||
padding-inline: 4rem 1.2rem;
|
||||
padding-inline: 4.4rem 1.2rem;
|
||||
font-weight: 700;
|
||||
background-color: color.adjust($clr-blue-a200, $alpha: -0.9);
|
||||
border: 0 solid $clr-blue-a200;
|
||||
border-inline-start-width: 0.4rem;
|
||||
border-start-start-radius: 0.2rem;
|
||||
// Compatilility with rendering markdown inside the content
|
||||
display: flex;
|
||||
|
||||
// Compatilility with rendering markdown inside the content
|
||||
& > p {
|
||||
& p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -143,7 +168,7 @@ $admonitions: (
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0.625em;
|
||||
inset-inline-start: 1.2rem;
|
||||
inset-inline-start: 1.6rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background-color: $clr-blue-a200;
|
||||
@@ -155,6 +180,36 @@ $admonitions: (
|
||||
-webkit-mask-size: contain;
|
||||
content: "";
|
||||
}
|
||||
|
||||
|
||||
// Show anchor link on hover over title
|
||||
&:hover a.admonition-anchor-link {
|
||||
display: initial
|
||||
}
|
||||
}
|
||||
|
||||
summary.admonition-title {
|
||||
details.admonition > &::after {
|
||||
position: absolute;
|
||||
top: .625em;
|
||||
inset-inline-end: 1.6rem;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
background-color: currentcolor;
|
||||
mask-image: var(--md-details-icon);
|
||||
-webkit-mask-image: var(--md-details-icon);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-size: contain;
|
||||
content: "";
|
||||
transform: rotate(0deg);
|
||||
transition: transform .25s;
|
||||
}
|
||||
|
||||
details[open].admonition > &::after {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -179,7 +234,6 @@ $admonitions: (
|
||||
// Admonition flavour title
|
||||
:is(#{$flavours}) > :is(.admonition-title, summary) {
|
||||
background-color: color.adjust($tint, $alpha: -0.9);
|
||||
border-color: $tint;
|
||||
|
||||
// Admonition icon
|
||||
&::before {
|
||||
@@ -217,4 +271,9 @@ $admonitions: (
|
||||
background-color: var(--sidebar-bg);
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
& .admonition-anchor-link {
|
||||
&:link, &:visited {
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
integration/.gitignore
vendored
1
integration/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/book
|
||||
/mdbook-admonish.css
|
||||
|
||||
@@ -9,6 +9,7 @@ title = "mdbook-admonish-integration"
|
||||
|
||||
[preprocessor.admonish]
|
||||
command = "mdbook-admonish"
|
||||
assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
|
||||
|
||||
[output]
|
||||
|
||||
|
||||
17
integration/expected/book.toml
Normal file
17
integration/expected/book.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[book]
|
||||
authors = ["Tom Milligan"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "mdbook-admonish-integration"
|
||||
|
||||
[preprocessor]
|
||||
|
||||
[preprocessor.admonish]
|
||||
command = "mdbook-admonish"
|
||||
assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
|
||||
|
||||
[output]
|
||||
|
||||
[output.html]
|
||||
additional-css = ["././mdbook-admonish.css"]
|
||||
@@ -1,24 +1,49 @@
|
||||
<h1 id="chapter-1"><a class="header" href="#chapter-1">Chapter 1</a></h1>
|
||||
<div class="admonition abstract">
|
||||
<div id="admonition-what-is-this" class="admonition abstract">
|
||||
<div class="admonition-title">
|
||||
<p>What <i>is</i> this?</p>
|
||||
<p><a class="admonition-anchor-link" href="#admonition-what-is-this"></a></p>
|
||||
</div>
|
||||
<div>
|
||||
<p>This book acts as an integration test for <code>mdbook-admonish</code>.</p>
|
||||
<p>It verifies that <code>mdbook</code> post-processes our generated HTML in the way we expect.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admonition note">
|
||||
<div id="admonition-note" class="admonition note">
|
||||
<div class="admonition-title">
|
||||
<p>Note</p>
|
||||
<p><a class="admonition-anchor-link" href="#admonition-note"></a></p>
|
||||
</div>
|
||||
<div>
|
||||
<p>Simples</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admonition warning">
|
||||
<div id="admonition-default" class="admonition warning">
|
||||
<div>
|
||||
<p>No title, only body</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="admonition-error-rendering-admonishment" class="admonition bug">
|
||||
<div class="admonition-title">
|
||||
<p>Error rendering admonishment</p>
|
||||
<p><a class="admonition-anchor-link" href="#admonition-error-rendering-admonishment"></a></p>
|
||||
</div>
|
||||
<div>
|
||||
<p>Failed with: TOML parsing error: unterminated string at line 1 column 7</p>
|
||||
<p>Original markdown input:</p>
|
||||
<pre><code>```admonish title="
|
||||
No title, only body
|
||||
```
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<details id="admonition-note-1" class="admonition note">
|
||||
<summary class="admonition-title">
|
||||
<p>Note</p>
|
||||
<p><a class="admonition-anchor-link" href="#admonition-note-1"></a></p>
|
||||
</summary>
|
||||
<div>
|
||||
<p>Hidden on load</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../src/bin/assets/mdbook-admonish.css
|
||||
@@ -8,9 +8,36 @@ function eprintln() {
|
||||
>&2 echo "$1"
|
||||
}
|
||||
|
||||
pushd ..
|
||||
eprintln "Installing mdbook-admonish (system)"
|
||||
cargo install --path . --force
|
||||
popd
|
||||
|
||||
eprintln "Installing mdbook-admonish (book)"
|
||||
mdbook-admonish install .
|
||||
|
||||
eprintln "Verifying generated book config"
|
||||
set +e
|
||||
diff -u \
|
||||
"expected/book.toml" \
|
||||
"./book.toml"
|
||||
DIFF_RESULT=$?
|
||||
set -e
|
||||
|
||||
if [ "$DIFF_RESULT" != 0 ]; then
|
||||
eprintln ""
|
||||
eprintln "error: generated book config was different than expected"
|
||||
eprintln ""
|
||||
eprintln "error: If you expected the output to change, run:"
|
||||
eprintln "cp ./integration/book.toml ./integration/expected/book.toml"
|
||||
eprintln "and commit the result"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
eprintln "Building mdbook"
|
||||
mdbook build
|
||||
|
||||
|
||||
eprintln "Verifying generated html"
|
||||
set +e
|
||||
diff -u \
|
||||
|
||||
@@ -13,3 +13,11 @@ Simples
|
||||
```admonish warning ""
|
||||
No title, only body
|
||||
```
|
||||
|
||||
```admonish title="
|
||||
No title, only body
|
||||
```
|
||||
|
||||
```admonish collapsible=true
|
||||
Hidden on load
|
||||
```
|
||||
|
||||
24
scripts/build-book
Executable file
24
scripts/build-book
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Build book, using styles present in the repository.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
function eprintln() {
|
||||
>&2 echo "$1"
|
||||
}
|
||||
|
||||
eprintln "Installing mdbook-admonish (to system)"
|
||||
cargo install --path . --force
|
||||
|
||||
pushd book
|
||||
eprintln "Installing mdbook-admonish (to book)"
|
||||
mdbook-admonish install .
|
||||
|
||||
eprintln "Building book"
|
||||
mdbook build
|
||||
popd
|
||||
|
||||
eprintln "Book generated at ./book/book/index.html"
|
||||
@@ -15,10 +15,12 @@ cargo fmt -- --check
|
||||
# - RUSTSEC-2020-0071 known unlikely segfault in `time`
|
||||
# - RUSTSEC-2020-0016 `net2` is unmaintained
|
||||
# - RUSTSEC-2020-0159 known unlikely segfault in `chrono`
|
||||
# - RUSTSEC-2021-0145 known unmaintained atty transitive dep
|
||||
eprintln "Auditing dependencies"
|
||||
cargo audit --deny warnings \
|
||||
--ignore RUSTSEC-2020-0071 \
|
||||
--ignore RUSTSEC-2020-0016 \
|
||||
--ignore RUSTSEC-2021-0145 \
|
||||
--ignore RUSTSEC-2020-0159
|
||||
|
||||
eprintln "Linting sources"
|
||||
@@ -34,7 +36,5 @@ cargo test --no-default-features --features cli
|
||||
eprintln "Building documentation"
|
||||
cargo doc --no-deps --lib
|
||||
|
||||
eprintln "Installing 'mdbook-admonish' binary"
|
||||
cargo install --path .
|
||||
eprintln "Running mdbook integration test"
|
||||
./integration/scripts/check
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Install everything for development/CI
|
||||
#
|
||||
# Does not include offline node development stack (i.e. yarn)
|
||||
|
||||
set -exuo pipefail
|
||||
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
rustup component add rustfmt clippy
|
||||
cargo install cargo-audit
|
||||
cargo install mdbook
|
||||
|
||||
if ! cargo audit --version; then
|
||||
cargo install cargo-audit --force
|
||||
fi
|
||||
|
||||
./scripts/install-mdbook
|
||||
|
||||
9
scripts/install-mdbook
Executable file
9
scripts/install-mdbook
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -exuo pipefail
|
||||
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
if ! mdbook --version; then
|
||||
cargo install mdbook --force
|
||||
fi
|
||||
18
scripts/rebuild-book
Executable file
18
scripts/rebuild-book
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Development only. Rebuilds the book, including recompiling styles.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
function eprintln() {
|
||||
>&2 echo "$1"
|
||||
}
|
||||
|
||||
eprintln "Generating styles"
|
||||
pushd compile_assets
|
||||
yarn run build
|
||||
popd
|
||||
|
||||
./scripts/build-book
|
||||
1
src/REQUIRED_ASSETS_VERSION
Normal file
1
src/REQUIRED_ASSETS_VERSION
Normal file
@@ -0,0 +1 @@
|
||||
^2.0.0
|
||||
1
src/bin/assets/VERSION
Normal file
1
src/bin/assets/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
2.0.0
|
||||
@@ -1,3 +1,4 @@
|
||||
@charset "UTF-8";
|
||||
:root {
|
||||
--md-admonition-icon--note:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");
|
||||
@@ -23,6 +24,8 @@
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");
|
||||
--md-admonition-icon--quote:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>");
|
||||
--md-details-icon:
|
||||
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
|
||||
}
|
||||
|
||||
:is(.admonition) {
|
||||
@@ -56,20 +59,33 @@ html :is(.admonition) > :last-child {
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
a.admonition-anchor-link {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: -1.2rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
a.admonition-anchor-link:link, a.admonition-anchor-link:visited {
|
||||
color: var(--fg);
|
||||
}
|
||||
a.admonition-anchor-link:link:hover, a.admonition-anchor-link:visited:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
a.admonition-anchor-link::before {
|
||||
content: "§";
|
||||
}
|
||||
|
||||
:is(.admonition-title, summary) {
|
||||
position: relative;
|
||||
margin-block: 0;
|
||||
margin-inline: -1.6rem -1.2rem;
|
||||
padding-block: 0.8rem;
|
||||
padding-inline: 4rem 1.2rem;
|
||||
padding-inline: 4.4rem 1.2rem;
|
||||
font-weight: 700;
|
||||
background-color: rgba(68, 138, 255, 0.1);
|
||||
border: 0 solid #448aff;
|
||||
border-inline-start-width: 0.4rem;
|
||||
border-start-start-radius: 0.2rem;
|
||||
display: flex;
|
||||
}
|
||||
:is(.admonition-title, summary) > p {
|
||||
:is(.admonition-title, summary) p {
|
||||
margin: 0;
|
||||
}
|
||||
html :is(.admonition-title, summary):last-child {
|
||||
@@ -78,7 +94,7 @@ html :is(.admonition-title, summary):last-child {
|
||||
:is(.admonition-title, summary)::before {
|
||||
position: absolute;
|
||||
top: 0.625em;
|
||||
inset-inline-start: 1.2rem;
|
||||
inset-inline-start: 1.6rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background-color: #448aff;
|
||||
@@ -90,6 +106,30 @@ html :is(.admonition-title, summary):last-child {
|
||||
-webkit-mask-size: contain;
|
||||
content: "";
|
||||
}
|
||||
:is(.admonition-title, summary):hover a.admonition-anchor-link {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
details.admonition > summary.admonition-title::after {
|
||||
position: absolute;
|
||||
top: 0.625em;
|
||||
inset-inline-end: 1.6rem;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
background-color: currentcolor;
|
||||
mask-image: var(--md-details-icon);
|
||||
-webkit-mask-image: var(--md-details-icon);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-size: contain;
|
||||
content: "";
|
||||
transform: rotate(0deg);
|
||||
transition: transform 0.25s;
|
||||
}
|
||||
details[open].admonition > summary.admonition-title::after {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
:is(.admonition):is(.note) {
|
||||
border-color: #448aff;
|
||||
@@ -97,7 +137,6 @@ html :is(.admonition-title, summary):last-child {
|
||||
|
||||
:is(.note) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(68, 138, 255, 0.1);
|
||||
border-color: #448aff;
|
||||
}
|
||||
:is(.note) > :is(.admonition-title, summary)::before {
|
||||
background-color: #448aff;
|
||||
@@ -115,7 +154,6 @@ html :is(.admonition-title, summary):last-child {
|
||||
|
||||
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(0, 176, 255, 0.1);
|
||||
border-color: #00b0ff;
|
||||
}
|
||||
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::before {
|
||||
background-color: #00b0ff;
|
||||
@@ -133,7 +171,6 @@ html :is(.admonition-title, summary):last-child {
|
||||
|
||||
:is(.info, .todo) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(0, 184, 212, 0.1);
|
||||
border-color: #00b8d4;
|
||||
}
|
||||
:is(.info, .todo) > :is(.admonition-title, summary)::before {
|
||||
background-color: #00b8d4;
|
||||
@@ -151,7 +188,6 @@ html :is(.admonition-title, summary):last-child {
|
||||
|
||||
:is(.tip, .hint, .important) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(0, 191, 165, 0.1);
|
||||
border-color: #00bfa5;
|
||||
}
|
||||
:is(.tip, .hint, .important) > :is(.admonition-title, summary)::before {
|
||||
background-color: #00bfa5;
|
||||
@@ -169,7 +205,6 @@ html :is(.admonition-title, summary):last-child {
|
||||
|
||||
:is(.success, .check, .done) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(0, 200, 83, 0.1);
|
||||
border-color: #00c853;
|
||||
}
|
||||
:is(.success, .check, .done) > :is(.admonition-title, summary)::before {
|
||||
background-color: #00c853;
|
||||
@@ -187,7 +222,6 @@ html :is(.admonition-title, summary):last-child {
|
||||
|
||||
:is(.question, .help, .faq) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(100, 221, 23, 0.1);
|
||||
border-color: #64dd17;
|
||||
}
|
||||
:is(.question, .help, .faq) > :is(.admonition-title, summary)::before {
|
||||
background-color: #64dd17;
|
||||
@@ -205,7 +239,6 @@ html :is(.admonition-title, summary):last-child {
|
||||
|
||||
:is(.warning, .caution, .attention) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(255, 145, 0, 0.1);
|
||||
border-color: #ff9100;
|
||||
}
|
||||
:is(.warning, .caution, .attention) > :is(.admonition-title, summary)::before {
|
||||
background-color: #ff9100;
|
||||
@@ -223,7 +256,6 @@ html :is(.admonition-title, summary):last-child {
|
||||
|
||||
:is(.failure, .fail, .missing) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(255, 82, 82, 0.1);
|
||||
border-color: #ff5252;
|
||||
}
|
||||
:is(.failure, .fail, .missing) > :is(.admonition-title, summary)::before {
|
||||
background-color: #ff5252;
|
||||
@@ -241,7 +273,6 @@ html :is(.admonition-title, summary):last-child {
|
||||
|
||||
:is(.danger, .error) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(255, 23, 68, 0.1);
|
||||
border-color: #ff1744;
|
||||
}
|
||||
:is(.danger, .error) > :is(.admonition-title, summary)::before {
|
||||
background-color: #ff1744;
|
||||
@@ -259,7 +290,6 @@ html :is(.admonition-title, summary):last-child {
|
||||
|
||||
:is(.bug) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(245, 0, 87, 0.1);
|
||||
border-color: #f50057;
|
||||
}
|
||||
:is(.bug) > :is(.admonition-title, summary)::before {
|
||||
background-color: #f50057;
|
||||
@@ -277,7 +307,6 @@ html :is(.admonition-title, summary):last-child {
|
||||
|
||||
:is(.example) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(124, 77, 255, 0.1);
|
||||
border-color: #7c4dff;
|
||||
}
|
||||
:is(.example) > :is(.admonition-title, summary)::before {
|
||||
background-color: #7c4dff;
|
||||
@@ -295,7 +324,6 @@ html :is(.admonition-title, summary):last-child {
|
||||
|
||||
:is(.quote, .cite) > :is(.admonition-title, summary) {
|
||||
background-color: rgba(158, 158, 158, 0.1);
|
||||
border-color: #9e9e9e;
|
||||
}
|
||||
:is(.quote, .cite) > :is(.admonition-title, summary)::before {
|
||||
background-color: #9e9e9e;
|
||||
@@ -319,3 +347,6 @@ html :is(.admonition-title, summary):last-child {
|
||||
background-color: var(--sidebar-bg);
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
.rust .admonition-anchor-link:link, .rust .admonition-anchor-link:visited {
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
|
||||
@@ -1,57 +1,70 @@
|
||||
use clap::{crate_version, Arg, ArgMatches, Command};
|
||||
use mdbook::errors::Error;
|
||||
use mdbook::preprocess::{CmdPreprocessor, Preprocessor};
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use mdbook::{
|
||||
errors::Error,
|
||||
preprocess::{CmdPreprocessor, Preprocessor},
|
||||
};
|
||||
use mdbook_admonish::Admonish;
|
||||
|
||||
#[cfg(feature = "cli-install")]
|
||||
use std::path::PathBuf;
|
||||
use std::{io, process};
|
||||
|
||||
pub fn make_app() -> Command<'static> {
|
||||
let mut command = Command::new("mdbook-admonish")
|
||||
.version(crate_version!())
|
||||
.about("mdbook preprocessor to add support for admonitions");
|
||||
command = command.subcommand(
|
||||
Command::new("supports")
|
||||
.arg(Arg::new("renderer").required(true))
|
||||
.about("Check whether a renderer is supported by this preprocessor"),
|
||||
);
|
||||
/// mdbook preprocessor to add support for admonitions
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Option<Commands>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Check whether a renderer is supported by this preprocessor
|
||||
Supports { renderer: String },
|
||||
|
||||
#[cfg(feature = "cli-install")]
|
||||
{
|
||||
command = command.subcommand(
|
||||
Command::new("install")
|
||||
.arg(Arg::new("css-dir").long("css-dir").default_value(".").help(
|
||||
"Relative directory for the css assets,\nfrom the book directory root",
|
||||
))
|
||||
.arg(Arg::new("dir").default_value(".").help(
|
||||
"Root directory for the book,\nshould contain the configuration file (`book.toml`)",
|
||||
))
|
||||
.about("Install the required assset files and include it in the config"));
|
||||
}
|
||||
command
|
||||
/// Install the required assset files and include it in the config
|
||||
Install {
|
||||
/// Root directory for the book, should contain the configuration file (`book.toml`)
|
||||
///
|
||||
/// If not set, defaults to the current directory.
|
||||
dir: Option<PathBuf>,
|
||||
|
||||
/// Relative directory for the css assets, from the book directory root
|
||||
///
|
||||
/// If not set, defaults to the current directory.
|
||||
#[arg(long)]
|
||||
css_dir: Option<PathBuf>,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
|
||||
|
||||
let matches = make_app().get_matches();
|
||||
|
||||
if let Some(sub_args) = matches.subcommand_matches("supports") {
|
||||
handle_supports(sub_args);
|
||||
} else if let Some(sub_args) = matches.subcommand_matches("install") {
|
||||
#[cfg(feature = "cli-install")]
|
||||
{
|
||||
install::handle_install(sub_args);
|
||||
let cli = Cli::parse();
|
||||
if let Err(error) = run(cli) {
|
||||
log::error!("Fatal error: {}", error);
|
||||
for error in error.chain() {
|
||||
log::error!(" - {}", error);
|
||||
}
|
||||
#[cfg(not(feature = "cli-install"))]
|
||||
{
|
||||
panic!("cli-install feature not enabled: {:?}", sub_args)
|
||||
}
|
||||
} else if let Err(e) = handle_preprocessing() {
|
||||
eprintln!("{}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn run(cli: Cli) -> Result<()> {
|
||||
match cli.command {
|
||||
None => handle_preprocessing(),
|
||||
Some(Commands::Supports { renderer }) => {
|
||||
handle_supports(renderer);
|
||||
}
|
||||
#[cfg(feature = "cli-install")]
|
||||
Some(Commands::Install { dir, css_dir }) => install::handle_install(
|
||||
dir.unwrap_or_else(|| PathBuf::from(".")),
|
||||
css_dir.unwrap_or_else(|| PathBuf::from(".")),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_preprocessing() -> Result<(), Error> {
|
||||
let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
|
||||
|
||||
@@ -70,9 +83,8 @@ fn handle_preprocessing() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_supports(sub_args: &ArgMatches) -> ! {
|
||||
let renderer = sub_args.value_of("renderer").expect("Required argument");
|
||||
let supported = Admonish.supports_renderer(renderer);
|
||||
fn handle_supports(renderer: String) -> ! {
|
||||
let supported = Admonish.supports_renderer(&renderer);
|
||||
|
||||
// Signal whether the renderer is supported by exiting with 1 or 0.
|
||||
if supported {
|
||||
@@ -84,12 +96,11 @@ fn handle_supports(sub_args: &ArgMatches) -> ! {
|
||||
|
||||
#[cfg(feature = "cli-install")]
|
||||
mod install {
|
||||
use clap::ArgMatches;
|
||||
use anyhow::{Context, Result};
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
process,
|
||||
};
|
||||
use toml_edit::{self, Array, Document, Item, Table, Value};
|
||||
|
||||
@@ -111,31 +122,30 @@ mod install {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_install(sub_args: &ArgMatches) {
|
||||
let dir = sub_args.value_of("dir").expect("Required argument");
|
||||
let css_dir = sub_args.value_of("css-dir").expect("Required argument");
|
||||
let proj_dir = PathBuf::from(dir);
|
||||
pub fn handle_install(proj_dir: PathBuf, css_dir: PathBuf) -> Result<()> {
|
||||
let config = proj_dir.join("book.toml");
|
||||
|
||||
if !config.exists() {
|
||||
log::error!("Configuration file '{}' missing", config.display());
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
log::info!("Reading configuration file '{}'", config.display());
|
||||
let toml = fs::read_to_string(&config).expect("can't read configuration file");
|
||||
let toml = fs::read_to_string(&config)
|
||||
.with_context(|| format!("can't read configuration file '{}'", config.display()))?;
|
||||
let mut doc = toml
|
||||
.parse::<Document>()
|
||||
.expect("configuration is not valid TOML");
|
||||
.context("configuration is not valid TOML")?;
|
||||
|
||||
if preprocessor(&mut doc).is_err() {
|
||||
if let Ok(preprocessor) = preprocessor(&mut doc) {
|
||||
const ASSETS_VERSION: &str = std::include_str!("./assets/VERSION");
|
||||
let value = toml_edit::value(
|
||||
toml_edit::Value::from(ASSETS_VERSION.trim())
|
||||
.decorated(" ", " # do not edit: managed by `mdbook-admonish install`"),
|
||||
);
|
||||
preprocessor["assets_version"] = value;
|
||||
} else {
|
||||
log::info!("Unexpected configuration, not updating prereprocessor configuration");
|
||||
};
|
||||
|
||||
let mut additional_css = additional_css(&mut doc);
|
||||
for (name, content) in ADMONISH_CSS_FILES {
|
||||
let filepath = proj_dir.join(css_dir).join(name);
|
||||
let filepath_str = filepath.to_str().expect("non-utf8 filepath");
|
||||
let filepath = proj_dir.join(&css_dir).join(name);
|
||||
let filepath_str = filepath.to_str().context("non-utf8 filepath")?;
|
||||
|
||||
if let Ok(ref mut additional_css) = additional_css {
|
||||
if !additional_css.contains_str(filepath_str) {
|
||||
@@ -150,18 +160,18 @@ mod install {
|
||||
"Copying '{name}' to '{filepath}'",
|
||||
filepath = filepath.display()
|
||||
);
|
||||
let mut file = File::create(filepath).expect("can't open file for writing");
|
||||
let mut file = File::create(&filepath).context("can't open file for writing")?;
|
||||
file.write_all(content)
|
||||
.expect("can't write content to file");
|
||||
.context("can't write content to file")?;
|
||||
}
|
||||
|
||||
let new_toml = doc.to_string();
|
||||
if new_toml != toml {
|
||||
log::info!("Saving changed configuration to '{}'", config.display());
|
||||
let mut file =
|
||||
File::create(config).expect("can't open configuration file for writing.");
|
||||
File::create(config).context("can't open configuration file for writing.")?;
|
||||
file.write_all(new_toml.as_bytes())
|
||||
.expect("can't write configuration");
|
||||
.context("can't write configuration")?;
|
||||
} else {
|
||||
log::info!("Configuration '{}' already up to date", config.display());
|
||||
}
|
||||
@@ -171,8 +181,7 @@ mod install {
|
||||
A beautifully styled message.
|
||||
```"#;
|
||||
log::info!("Add a code block like:\n{}", codeblock);
|
||||
|
||||
process::exit(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the `additional-css` field, initializing if required.
|
||||
|
||||
158
src/config/mod.rs
Normal file
158
src/config/mod.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
use crate::types::Directive;
|
||||
use std::str::FromStr;
|
||||
|
||||
mod v1;
|
||||
mod v2;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) struct AdmonitionInfoRaw {
|
||||
directive: String,
|
||||
title: Option<String>,
|
||||
additional_classnames: Vec<String>,
|
||||
collapsible: bool,
|
||||
}
|
||||
|
||||
/// Extract the remaining info string, if this is an admonition block.
|
||||
fn admonition_config_string(info_string: &str) -> Option<&str> {
|
||||
const ADMONISH_BLOCK_KEYWORD: &str = "admonish";
|
||||
|
||||
// Get the rest of the info string if this is an admonition
|
||||
if info_string == ADMONISH_BLOCK_KEYWORD {
|
||||
return Some("");
|
||||
}
|
||||
|
||||
match info_string.split_once(' ') {
|
||||
Some((keyword, rest)) if keyword == ADMONISH_BLOCK_KEYWORD => Some(rest),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl AdmonitionInfoRaw {
|
||||
/// Returns:
|
||||
/// - `None` if this is not an `admonish` block.
|
||||
/// - `Some(AdmonitionInfoRaw)` if this is an `admonish` block
|
||||
pub fn from_info_string(info_string: &str) -> Option<Result<Self, String>> {
|
||||
let config_string = admonition_config_string(info_string)?;
|
||||
|
||||
// If we succeed at parsing v2, return that. Otherwise hold onto the error
|
||||
let config_v2_error = match v2::from_config_string(config_string) {
|
||||
Ok(config) => return Some(Ok(config)),
|
||||
Err(config) => config,
|
||||
};
|
||||
|
||||
Some(
|
||||
if let Ok(info_raw) = v1::from_config_string(config_string) {
|
||||
// If we succeed at parsing v1, return that.
|
||||
Ok(info_raw)
|
||||
} else {
|
||||
// Otherwise return our v2 error.
|
||||
Err(config_v2_error)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) struct AdmonitionInfo {
|
||||
pub directive: Directive,
|
||||
pub title: Option<String>,
|
||||
pub additional_classnames: Vec<String>,
|
||||
pub collapsible: bool,
|
||||
}
|
||||
|
||||
impl AdmonitionInfo {
|
||||
pub fn from_info_string(info_string: &str) -> Option<Result<Self, String>> {
|
||||
AdmonitionInfoRaw::from_info_string(info_string).map(|result| result.map(Into::into))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AdmonitionInfoRaw> for AdmonitionInfo {
|
||||
fn from(other: AdmonitionInfoRaw) -> Self {
|
||||
let AdmonitionInfoRaw {
|
||||
directive: raw_directive,
|
||||
title,
|
||||
additional_classnames,
|
||||
collapsible,
|
||||
} = other;
|
||||
let (directive, title) = match (Directive::from_str(&raw_directive), title) {
|
||||
(Ok(directive), None) => (directive, ucfirst(&raw_directive)),
|
||||
(Err(_), None) => (Directive::Note, "Note".to_owned()),
|
||||
(Ok(directive), Some(title)) => (directive, title),
|
||||
(Err(_), Some(title)) => (Directive::Note, title),
|
||||
};
|
||||
// If the user explicitly gave no title, then disable the title bar
|
||||
let title = if title.is_empty() { None } else { Some(title) };
|
||||
Self {
|
||||
directive,
|
||||
title,
|
||||
additional_classnames,
|
||||
collapsible,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Make the first letter of `input` upppercase.
|
||||
///
|
||||
/// source: https://stackoverflow.com/a/38406885
|
||||
fn ucfirst(input: &str) -> String {
|
||||
let mut chars = input.chars();
|
||||
match chars.next() {
|
||||
None => String::new(),
|
||||
Some(f) => f.to_uppercase().collect::<String>() + chars.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_from_info_string() {
|
||||
// Not admonition blocks
|
||||
assert_eq!(AdmonitionInfoRaw::from_info_string(""), None);
|
||||
assert_eq!(AdmonitionInfoRaw::from_info_string("adm"), None);
|
||||
// v1 syntax is supported back compatibly
|
||||
assert_eq!(
|
||||
AdmonitionInfoRaw::from_info_string("admonish note.additional-classname")
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
AdmonitionInfoRaw {
|
||||
directive: "note".to_owned(),
|
||||
title: None,
|
||||
additional_classnames: vec!["additional-classname".to_owned()],
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
// v2 syntax is supported
|
||||
assert_eq!(
|
||||
AdmonitionInfoRaw::from_info_string(r#"admonish title="Custom Title" type="question""#)
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
AdmonitionInfoRaw {
|
||||
directive: "question".to_owned(),
|
||||
title: Some("Custom Title".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_admonition_info_from_raw() {
|
||||
assert_eq!(
|
||||
AdmonitionInfo::from(AdmonitionInfoRaw {
|
||||
directive: " ".to_owned(),
|
||||
title: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}),
|
||||
AdmonitionInfo {
|
||||
directive: Directive::Note,
|
||||
title: Some("Note".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
130
src/config/v1.rs
Normal file
130
src/config/v1.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use super::AdmonitionInfoRaw;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
pub(crate) fn from_config_string(config_string: &str) -> Result<AdmonitionInfoRaw, String> {
|
||||
let config_string = config_string.trim();
|
||||
|
||||
static RX_CONFIG_STRING_V1: Lazy<Regex> = Lazy::new(|| {
|
||||
let directive = r#"[a-z]+"#;
|
||||
let css_classname = r#"-?[_a-zA-Z]+[_a-zA-Z0-9-]*"#;
|
||||
let title = r#"".*""#;
|
||||
Regex::new(&format!(
|
||||
"^({directive})?(\\.({css_classname})?)*( {title})?$"
|
||||
))
|
||||
.expect("config string v1 regex")
|
||||
});
|
||||
|
||||
// Check if this is a valid looking v1 directive
|
||||
if !RX_CONFIG_STRING_V1.is_match(config_string) {
|
||||
return Err("Invalid configuration string".to_owned());
|
||||
}
|
||||
|
||||
// If we're just given the directive, handle that
|
||||
let (directive, title) = config_string
|
||||
.split_once(' ')
|
||||
.map(|(directive, title)| (directive, Some(title)))
|
||||
.unwrap_or_else(|| (config_string, None));
|
||||
|
||||
// The title is expected to be a quoted JSON string
|
||||
// If parsing fails, output the error message as the title for the user to correct
|
||||
let title = title
|
||||
.map(|title| {
|
||||
serde_json::from_str::<String>(title)
|
||||
.map_err(|error| format!("Error parsing JSON string: {error}"))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
// If the directive contains additional classes, parse them out
|
||||
const CLASSNAME_SEPARATOR: char = '.';
|
||||
let (directive, additional_classnames) = match directive.split_once(CLASSNAME_SEPARATOR) {
|
||||
None => (directive, Vec::new()),
|
||||
Some((directive, additional_classnames)) => (
|
||||
directive,
|
||||
additional_classnames
|
||||
.split(CLASSNAME_SEPARATOR)
|
||||
.filter(|classname| !classname.is_empty())
|
||||
.map(|classname| classname.to_owned())
|
||||
.collect(),
|
||||
),
|
||||
};
|
||||
|
||||
Ok(AdmonitionInfoRaw {
|
||||
directive: directive.to_owned(),
|
||||
title,
|
||||
additional_classnames,
|
||||
collapsible: false,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_from_config_string() {
|
||||
assert_eq!(
|
||||
from_config_string("").unwrap(),
|
||||
AdmonitionInfoRaw {
|
||||
directive: "".to_owned(),
|
||||
title: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
from_config_string(" ").unwrap(),
|
||||
AdmonitionInfoRaw {
|
||||
directive: "".to_owned(),
|
||||
title: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
from_config_string("unknown").unwrap(),
|
||||
AdmonitionInfoRaw {
|
||||
directive: "unknown".to_owned(),
|
||||
title: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
from_config_string("note").unwrap(),
|
||||
AdmonitionInfoRaw {
|
||||
directive: "note".to_owned(),
|
||||
title: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
from_config_string("note.additional-classname").unwrap(),
|
||||
AdmonitionInfoRaw {
|
||||
directive: "note".to_owned(),
|
||||
title: None,
|
||||
additional_classnames: vec!["additional-classname".to_owned()],
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_config_string_invalid_title_json() {
|
||||
// Test invalid JSON title
|
||||
assert_eq!(
|
||||
from_config_string(r#"note "\""#).unwrap_err(),
|
||||
"Error parsing JSON string: EOF while parsing a string at line 1 column 3".to_owned()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_config_string_v2_format() {
|
||||
assert_eq!(
|
||||
from_config_string(r#"note title="Custom""#).unwrap_err(),
|
||||
"Invalid configuration string".to_owned()
|
||||
);
|
||||
}
|
||||
}
|
||||
172
src/config/v2.rs
Normal file
172
src/config/v2.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
use super::AdmonitionInfoRaw;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
struct AdmonitionInfoConfig {
|
||||
#[serde(default)]
|
||||
r#type: Option<String>,
|
||||
#[serde(default)]
|
||||
title: Option<String>,
|
||||
#[serde(default)]
|
||||
class: Option<String>,
|
||||
#[serde(default)]
|
||||
collapsible: bool,
|
||||
}
|
||||
|
||||
/// Transform our config string into valid toml
|
||||
fn bare_key_value_pairs_to_toml(pairs: &str) -> String {
|
||||
use regex::Captures;
|
||||
|
||||
static RX_BARE_KEY_ASSIGNMENT: Lazy<Regex> = Lazy::new(|| {
|
||||
let bare_key = r#"[A-Za-z0-9_-]+"#;
|
||||
Regex::new(&format!("(?:{bare_key}) *=")).expect("bare key assignment regex")
|
||||
});
|
||||
|
||||
fn prefix_with_newline(captures: &Captures) -> String {
|
||||
format!(
|
||||
"\n{}",
|
||||
captures
|
||||
.get(0)
|
||||
.expect("capture to have group zero")
|
||||
.as_str()
|
||||
)
|
||||
}
|
||||
|
||||
RX_BARE_KEY_ASSIGNMENT
|
||||
.replace_all(pairs, prefix_with_newline)
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
/// Parse and return the config assuming v2 format.
|
||||
///
|
||||
/// Note that if an error occurs, a parsed struct that can be returned to
|
||||
/// show the error message will be returned.
|
||||
pub(crate) fn from_config_string(config_string: &str) -> Result<AdmonitionInfoRaw, String> {
|
||||
let config_toml = bare_key_value_pairs_to_toml(config_string);
|
||||
let config_toml = config_toml.trim();
|
||||
|
||||
let config: AdmonitionInfoConfig = match toml::from_str(config_toml) {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
let original_error = Err(format!("TOML parsing error: {error}"));
|
||||
|
||||
// For ergonomic reasons, we allow users to specify the directive without
|
||||
// a key. So if parsing fails initially, take the first line,
|
||||
// use that as the directive, and reparse.
|
||||
let (directive, config_toml) = match config_toml.split_once('\n') {
|
||||
Some((directive, config_toml)) => (directive.trim(), config_toml),
|
||||
None => (config_toml, ""),
|
||||
};
|
||||
|
||||
static RX_DIRECTIVE: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r#"^[A-Za-z0-9_-]+$"#).expect("directive regex"));
|
||||
|
||||
if !RX_DIRECTIVE.is_match(directive) {
|
||||
return original_error;
|
||||
}
|
||||
|
||||
let mut config: AdmonitionInfoConfig = match toml::from_str(config_toml) {
|
||||
Ok(config) => config,
|
||||
Err(_) => return original_error,
|
||||
};
|
||||
config.r#type = Some(directive.to_owned());
|
||||
config
|
||||
}
|
||||
};
|
||||
let additional_classnames = config
|
||||
.class
|
||||
.map(|class| {
|
||||
class
|
||||
.split(' ')
|
||||
.filter(|classname| !classname.is_empty())
|
||||
.map(|classname| classname.to_owned())
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
Ok(AdmonitionInfoRaw {
|
||||
directive: config.r#type.unwrap_or_default(),
|
||||
title: config.title,
|
||||
additional_classnames,
|
||||
collapsible: config.collapsible,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_from_config_string_v2() {
|
||||
assert_eq!(
|
||||
from_config_string("").unwrap(),
|
||||
AdmonitionInfoRaw {
|
||||
directive: "".to_owned(),
|
||||
title: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
from_config_string(" ").unwrap(),
|
||||
AdmonitionInfoRaw {
|
||||
directive: "".to_owned(),
|
||||
title: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
from_config_string(r#"type="note" class="additional classname" title="Никита""#)
|
||||
.unwrap(),
|
||||
AdmonitionInfoRaw {
|
||||
directive: "note".to_owned(),
|
||||
title: Some("Никита".to_owned()),
|
||||
additional_classnames: vec!["additional".to_owned(), "classname".to_owned()],
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
// Specifying unknown keys is okay, as long as they're valid
|
||||
assert_eq!(
|
||||
from_config_string(r#"unkonwn="but valid toml""#).unwrap(),
|
||||
AdmonitionInfoRaw {
|
||||
directive: "".to_owned(),
|
||||
title: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
// Just directive is fine
|
||||
assert_eq!(
|
||||
from_config_string(r#"info"#).unwrap(),
|
||||
AdmonitionInfoRaw {
|
||||
directive: "info".to_owned(),
|
||||
title: None,
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
// Directive plus toml config
|
||||
assert_eq!(
|
||||
from_config_string(r#"info title="Information""#).unwrap(),
|
||||
AdmonitionInfoRaw {
|
||||
directive: "info".to_owned(),
|
||||
title: Some("Information".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
}
|
||||
);
|
||||
// Directive after toml config is an error
|
||||
assert!(from_config_string(r#"title="Information" info"#).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_config_string_invalid_toml_value() {
|
||||
assert_eq!(
|
||||
from_config_string(r#"note titlel=""#).unwrap_err(),
|
||||
"TOML parsing error: expected an equals, found a newline at line 1 column 6".to_owned()
|
||||
);
|
||||
}
|
||||
}
|
||||
602
src/lib.rs
602
src/lib.rs
@@ -1,9 +1,52 @@
|
||||
use mdbook::book::{Book, BookItem, Chapter};
|
||||
use mdbook::errors::Result as MdbookResult;
|
||||
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
|
||||
use anyhow::{anyhow, Result};
|
||||
use mdbook::{
|
||||
book::{Book, BookItem},
|
||||
errors::Result as MdbookResult,
|
||||
preprocess::{Preprocessor, PreprocessorContext},
|
||||
utils::unique_id_from_content,
|
||||
};
|
||||
use pulldown_cmark::{CodeBlockKind::*, Event, Options, Parser, Tag};
|
||||
use std::borrow::Cow;
|
||||
use std::str::FromStr;
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
|
||||
mod config;
|
||||
mod types;
|
||||
|
||||
use crate::{config::AdmonitionInfo, types::Directive};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum OnFailure {
|
||||
Bail,
|
||||
Continue,
|
||||
}
|
||||
|
||||
impl Default for OnFailure {
|
||||
fn default() -> Self {
|
||||
Self::Continue
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for OnFailure {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, ()> {
|
||||
match string {
|
||||
"bail" => Ok(Self::Bail),
|
||||
"continue" => Ok(Self::Continue),
|
||||
_ => Ok(Self::Continue),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OnFailure {
|
||||
fn from_context(context: &PreprocessorContext) -> Self {
|
||||
context
|
||||
.config
|
||||
.get("preprocessor.admonish.on_failure")
|
||||
.and_then(|value| value.as_str())
|
||||
.map(|value| OnFailure::from_str(value).unwrap_or_default())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Admonish;
|
||||
|
||||
@@ -12,7 +55,10 @@ impl Preprocessor for Admonish {
|
||||
"admonish"
|
||||
}
|
||||
|
||||
fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> MdbookResult<Book> {
|
||||
fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> MdbookResult<Book> {
|
||||
ensure_compatible_assets_version(ctx)?;
|
||||
let on_failure = OnFailure::from_context(ctx);
|
||||
|
||||
let mut res = None;
|
||||
book.for_each_mut(|item: &mut BookItem| {
|
||||
if let Some(Err(_)) = res {
|
||||
@@ -20,7 +66,7 @@ impl Preprocessor for Admonish {
|
||||
}
|
||||
|
||||
if let BookItem::Chapter(ref mut chapter) = *item {
|
||||
res = Some(Admonish::preprocess(chapter).map(|md| {
|
||||
res = Some(preprocess(&chapter.content, on_failure).map(|md| {
|
||||
chapter.content = md;
|
||||
}));
|
||||
}
|
||||
@@ -34,42 +80,42 @@ impl Preprocessor for Admonish {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Directive {
|
||||
Note,
|
||||
Abstract,
|
||||
Info,
|
||||
Tip,
|
||||
Success,
|
||||
Question,
|
||||
Warning,
|
||||
Failure,
|
||||
Danger,
|
||||
Bug,
|
||||
Example,
|
||||
Quote,
|
||||
}
|
||||
fn ensure_compatible_assets_version(ctx: &PreprocessorContext) -> Result<()> {
|
||||
use semver::{Version, VersionReq};
|
||||
|
||||
impl FromStr for Directive {
|
||||
type Err = ();
|
||||
const REQUIRES_ASSETS_VERSION: &str = std::include_str!("./REQUIRED_ASSETS_VERSION");
|
||||
let requirement = VersionReq::parse(REQUIRES_ASSETS_VERSION.trim()).unwrap();
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, ()> {
|
||||
match string {
|
||||
"note" => Ok(Self::Note),
|
||||
"abstract" | "summary" | "tldr" => Ok(Self::Abstract),
|
||||
"info" | "todo" => Ok(Self::Info),
|
||||
"tip" | "hint" | "important" => Ok(Self::Tip),
|
||||
"success" | "check" | "done" => Ok(Self::Success),
|
||||
"question" | "help" | "faq" => Ok(Self::Question),
|
||||
"warning" | "caution" | "attention" => Ok(Self::Warning),
|
||||
"failure" | "fail" | "missing" => Ok(Self::Failure),
|
||||
"danger" | "error" => Ok(Self::Danger),
|
||||
"bug" => Ok(Self::Bug),
|
||||
"example" => Ok(Self::Example),
|
||||
"quote" | "cite" => Ok(Self::Quote),
|
||||
_ => Err(()),
|
||||
const USER_ACTION: &str = "Please run `mdbook-admonish install` to update installed assets.";
|
||||
const DOCS_REFERENCE: &str = "For more information, see: https://github.com/tommilligan/mdbook-admonish#semantic-versioning";
|
||||
|
||||
let version = match ctx
|
||||
.config
|
||||
.get("preprocessor.admonish.assets_version")
|
||||
.and_then(|value| value.as_str())
|
||||
{
|
||||
Some(version) => version,
|
||||
None => {
|
||||
return Err(anyhow!(
|
||||
r#"ERROR:
|
||||
Incompatible assets installed: required mdbook-admonish assets version '{requirement}', but did not find a version.
|
||||
{USER_ACTION}
|
||||
{DOCS_REFERENCE}"#
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let version = Version::parse(version).unwrap();
|
||||
|
||||
if !requirement.matches(&version) {
|
||||
return Err(anyhow!(
|
||||
r#"ERROR:
|
||||
Incompatible assets installed: required mdbook-admonish assets version '{requirement}', but found '{version}'.
|
||||
{USER_ACTION}
|
||||
{DOCS_REFERENCE}"#
|
||||
));
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Directive {
|
||||
@@ -91,88 +137,57 @@ impl Directive {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct AdmonitionInfoRaw<'a> {
|
||||
directive: &'a str,
|
||||
title: Option<String>,
|
||||
additional_classnames: Option<Vec<&'a str>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct AdmonitionInfo<'a> {
|
||||
directive: Directive,
|
||||
title: Option<String>,
|
||||
additional_classnames: Option<Vec<&'a str>>,
|
||||
}
|
||||
|
||||
impl<'a> From<AdmonitionInfoRaw<'a>> for AdmonitionInfo<'a> {
|
||||
fn from(other: AdmonitionInfoRaw<'a>) -> Self {
|
||||
let AdmonitionInfoRaw {
|
||||
directive: raw_directive,
|
||||
title,
|
||||
additional_classnames,
|
||||
} = other;
|
||||
let (directive, title) = match (Directive::from_str(raw_directive), title) {
|
||||
(Ok(directive), None) => (directive, ucfirst(raw_directive)),
|
||||
(Err(_), None) => (Directive::Note, "Note".to_owned()),
|
||||
(Ok(directive), Some(title)) => (directive, title),
|
||||
(Err(_), Some(title)) => (Directive::Note, title),
|
||||
};
|
||||
// If the user explicitly gave no title, then disable the title bar
|
||||
let title = if title.is_empty() { None } else { Some(title) };
|
||||
Self {
|
||||
directive,
|
||||
title,
|
||||
additional_classnames,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Admonition<'a> {
|
||||
directive: Directive,
|
||||
title: Option<String>,
|
||||
content: &'a str,
|
||||
additional_classnames: Option<Vec<&'a str>>,
|
||||
content: Cow<'a, str>,
|
||||
additional_classnames: Vec<String>,
|
||||
collapsible: bool,
|
||||
}
|
||||
|
||||
impl<'a> Admonition<'a> {
|
||||
pub fn new(info: AdmonitionInfo<'a>, content: &'a str) -> Self {
|
||||
pub fn new(info: AdmonitionInfo, content: &'a str) -> Self {
|
||||
let AdmonitionInfo {
|
||||
directive,
|
||||
title,
|
||||
additional_classnames,
|
||||
collapsible,
|
||||
} = info;
|
||||
Self {
|
||||
directive,
|
||||
title,
|
||||
content,
|
||||
content: Cow::Borrowed(content),
|
||||
additional_classnames,
|
||||
collapsible,
|
||||
}
|
||||
}
|
||||
|
||||
fn html(&self) -> String {
|
||||
fn html(&self, anchor_id: &str) -> String {
|
||||
let mut additional_class = Cow::Borrowed(self.directive.classname());
|
||||
let title = &self.title;
|
||||
let content = &self.content;
|
||||
|
||||
let title_block = if self.collapsible { "summary" } else { "div" };
|
||||
|
||||
let title_html = title
|
||||
.as_ref()
|
||||
.map(|title| {
|
||||
Cow::Owned(format!(
|
||||
r#"<div class="admonition-title">
|
||||
r##"<{title_block} class="admonition-title">
|
||||
|
||||
{title}
|
||||
|
||||
</div>
|
||||
"#
|
||||
<a class="admonition-anchor-link" href="#{ANCHOR_ID_PREFIX}-{anchor_id}"></a>
|
||||
</{title_block}>
|
||||
"##
|
||||
))
|
||||
})
|
||||
.unwrap_or(Cow::Borrowed(""));
|
||||
|
||||
if let Some(additional_classnames) = &self.additional_classnames {
|
||||
if !self.additional_classnames.is_empty() {
|
||||
let mut buffer = additional_class.into_owned();
|
||||
for additional_classname in additional_classnames {
|
||||
for additional_classname in &self.additional_classnames {
|
||||
buffer.push(' ');
|
||||
buffer.push_str(additional_classname);
|
||||
}
|
||||
@@ -180,83 +195,25 @@ impl<'a> Admonition<'a> {
|
||||
additional_class = Cow::Owned(buffer);
|
||||
}
|
||||
|
||||
let admonition_block = if self.collapsible { "details" } else { "div" };
|
||||
// Notes on the HTML template:
|
||||
// - the additional whitespace around the content are deliberate
|
||||
// In line with the commonmark spec, this allows the inner content to be
|
||||
// rendered as markdown paragraphs.
|
||||
format!(
|
||||
r#"<div class="admonition {additional_class}">
|
||||
r#"<{admonition_block} id="{ANCHOR_ID_PREFIX}-{anchor_id}" class="admonition {additional_class}">
|
||||
{title_html}<div>
|
||||
|
||||
{content}
|
||||
|
||||
</div>
|
||||
</div>"#,
|
||||
</{admonition_block}>"#,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const ADMONISH_BLOCK_KEYWORD: &str = "admonish";
|
||||
|
||||
/// Returns:
|
||||
/// - `None` if this is not an `admonish` block.
|
||||
/// - `Some(AdmonitionInfoRaw)` if this is an `admonish` block
|
||||
fn parse_info_string(info_string: &str) -> Option<AdmonitionInfoRaw> {
|
||||
// Get the rest of the info string if this is an admonition
|
||||
let directive_title = if info_string == ADMONISH_BLOCK_KEYWORD {
|
||||
""
|
||||
} else {
|
||||
match info_string.split_once(' ') {
|
||||
Some((ADMONISH_BLOCK_KEYWORD, rest)) => rest,
|
||||
_ => return None,
|
||||
}
|
||||
};
|
||||
|
||||
// If we're just given the directive, handle that
|
||||
let (directive, title) = directive_title
|
||||
.split_once(' ')
|
||||
.map(|(directive, title)| (directive, Some(title)))
|
||||
.unwrap_or_else(|| (directive_title, None));
|
||||
|
||||
// The title is expected to be a quoted JSON string
|
||||
// If parsing fails, output the error message as the title for the user to correct
|
||||
let title = title.map(|title| {
|
||||
serde_json::from_str::<String>(title)
|
||||
.unwrap_or_else(|error| format!("Error parsing JSON string: {error}"))
|
||||
});
|
||||
|
||||
// If the directive contains additional classes, parse them out
|
||||
const CLASSNAME_SEPARATOR: char = '.';
|
||||
let (directive, additional_classnames) = match directive.split_once(CLASSNAME_SEPARATOR) {
|
||||
None => (directive, None),
|
||||
Some((directive, additional_classnames)) => (
|
||||
directive,
|
||||
Some(
|
||||
additional_classnames
|
||||
.split(CLASSNAME_SEPARATOR)
|
||||
.filter(|additional_classname| !additional_classname.is_empty())
|
||||
.collect(),
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
Some(AdmonitionInfoRaw {
|
||||
directive,
|
||||
title,
|
||||
additional_classnames,
|
||||
})
|
||||
}
|
||||
|
||||
/// Make the first letter of `input` upppercase.
|
||||
///
|
||||
/// source: https://stackoverflow.com/a/38406885
|
||||
fn ucfirst(input: &str) -> String {
|
||||
let mut chars = input.chars();
|
||||
match chars.next() {
|
||||
None => String::new(),
|
||||
Some(f) => f.to_uppercase().collect::<String>() + chars.as_str(),
|
||||
}
|
||||
}
|
||||
const ANCHOR_ID_PREFIX: &str = "admonition";
|
||||
const ANCHOR_ID_DEFAULT: &str = "default";
|
||||
|
||||
fn extract_admonish_body(content: &str) -> &str {
|
||||
const PRE_END: char = '\n';
|
||||
@@ -283,15 +240,50 @@ fn extract_admonish_body(content: &str) -> &str {
|
||||
/// Given the content in the span of the code block, and the info string,
|
||||
/// return `Some(Admonition)` if the code block is an admonition.
|
||||
///
|
||||
/// If there is an error parsing the admonition, either:
|
||||
///
|
||||
/// - Display a UI error message output in the book.
|
||||
/// - If configured, break the build.
|
||||
///
|
||||
/// If the code block is not an admonition, return `None`.
|
||||
fn parse_admonition<'a>(info_string: &'a str, content: &'a str) -> Option<Admonition<'a>> {
|
||||
let info = parse_info_string(info_string)?;
|
||||
let info = AdmonitionInfo::from(info);
|
||||
fn parse_admonition<'a>(
|
||||
info_string: &'a str,
|
||||
content: &'a str,
|
||||
on_failure: OnFailure,
|
||||
) -> Option<MdbookResult<Admonition<'a>>> {
|
||||
let info = AdmonitionInfo::from_info_string(info_string)?;
|
||||
let info = match info {
|
||||
Ok(info) => info,
|
||||
// FIXME return error messages to break build if configured
|
||||
// Err(message) => return Some(Err(content)),
|
||||
Err(message) => {
|
||||
return Some(match on_failure {
|
||||
OnFailure::Continue => Ok(Admonition {
|
||||
directive: Directive::Bug,
|
||||
title: Some("Error rendering admonishment".to_owned()),
|
||||
additional_classnames: Vec::new(),
|
||||
collapsible: false,
|
||||
content: Cow::Owned(format!(
|
||||
r#"Failed with: {message}
|
||||
|
||||
Original markdown input:
|
||||
|
||||
``````
|
||||
{content}
|
||||
``````
|
||||
"#
|
||||
)),
|
||||
}),
|
||||
OnFailure::Bail => Err(anyhow!("Error processing admonition, bailing:\n{content}")),
|
||||
})
|
||||
}
|
||||
};
|
||||
let body = extract_admonish_body(content);
|
||||
Some(Admonition::new(info, body))
|
||||
Some(Ok(Admonition::new(info, body)))
|
||||
}
|
||||
|
||||
fn preprocess(content: &str) -> MdbookResult<String> {
|
||||
fn preprocess(content: &str, on_failure: OnFailure) -> MdbookResult<String> {
|
||||
let mut id_counter = Default::default();
|
||||
let mut opts = Options::empty();
|
||||
opts.insert(Options::ENABLE_TABLES);
|
||||
opts.insert(Options::ENABLE_FOOTNOTES);
|
||||
@@ -304,11 +296,17 @@ fn preprocess(content: &str) -> MdbookResult<String> {
|
||||
for (e, span) in events.into_offset_iter() {
|
||||
if let Event::Start(Tag::CodeBlock(Fenced(info_string))) = e.clone() {
|
||||
let span_content = &content[span.start..span.end];
|
||||
let admonition = match parse_admonition(info_string.as_ref(), span_content) {
|
||||
let admonition = match parse_admonition(info_string.as_ref(), span_content, on_failure)
|
||||
{
|
||||
Some(admonition) => admonition,
|
||||
None => continue,
|
||||
};
|
||||
admonish_blocks.push((span, admonition.html()));
|
||||
let admonition = admonition?;
|
||||
let anchor_id = unique_id_from_content(
|
||||
admonition.title.as_deref().unwrap_or(ANCHOR_ID_DEFAULT),
|
||||
&mut id_counter,
|
||||
);
|
||||
admonish_blocks.push((span, admonition.html(&anchor_id)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,62 +319,13 @@ fn preprocess(content: &str) -> MdbookResult<String> {
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
impl Admonish {
|
||||
fn preprocess(chapter: &mut Chapter) -> MdbookResult<String> {
|
||||
preprocess(&chapter.content)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_info_string() {
|
||||
assert_eq!(parse_info_string(""), None);
|
||||
assert_eq!(parse_info_string("adm"), None);
|
||||
assert_eq!(
|
||||
parse_info_string("admonish"),
|
||||
Some(AdmonitionInfoRaw {
|
||||
directive: "",
|
||||
title: None,
|
||||
additional_classnames: None
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
parse_info_string("admonish "),
|
||||
Some(AdmonitionInfoRaw {
|
||||
directive: "",
|
||||
title: None,
|
||||
additional_classnames: None
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
parse_info_string("admonish unknown"),
|
||||
Some(AdmonitionInfoRaw {
|
||||
directive: "unknown",
|
||||
title: None,
|
||||
additional_classnames: None
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
parse_info_string("admonish note"),
|
||||
Some(AdmonitionInfoRaw {
|
||||
directive: "note",
|
||||
title: None,
|
||||
additional_classnames: None
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
parse_info_string("admonish note.additional-classname"),
|
||||
Some(AdmonitionInfoRaw {
|
||||
directive: "note",
|
||||
title: None,
|
||||
additional_classnames: Some(vec!["additional-classname"])
|
||||
})
|
||||
);
|
||||
fn prep(content: &str) -> String {
|
||||
preprocess(content, OnFailure::Continue).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -388,13 +337,14 @@ A simple admonition.
|
||||
Text
|
||||
"#;
|
||||
|
||||
let expected = r#"# Chapter
|
||||
let expected = r##"# Chapter
|
||||
|
||||
<div class="admonition note">
|
||||
<div id="admonition-note" class="admonition note">
|
||||
<div class="admonition-title">
|
||||
|
||||
Note
|
||||
|
||||
<a class="admonition-anchor-link" href="#admonition-note"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -403,9 +353,9 @@ A simple admonition.
|
||||
</div>
|
||||
</div>
|
||||
Text
|
||||
"#;
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, preprocess(content).unwrap());
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -417,13 +367,14 @@ A simple admonition.
|
||||
Text
|
||||
"#;
|
||||
|
||||
let expected = r#"# Chapter
|
||||
let expected = r##"# Chapter
|
||||
|
||||
<div class="admonition warning">
|
||||
<div id="admonition-warning" class="admonition warning">
|
||||
<div class="admonition-title">
|
||||
|
||||
Warning
|
||||
|
||||
<a class="admonition-anchor-link" href="#admonition-warning"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -432,9 +383,9 @@ A simple admonition.
|
||||
</div>
|
||||
</div>
|
||||
Text
|
||||
"#;
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, preprocess(content).unwrap());
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -446,13 +397,14 @@ A simple admonition.
|
||||
Text
|
||||
"#;
|
||||
|
||||
let expected = r#"# Chapter
|
||||
let expected = r##"# Chapter
|
||||
|
||||
<div class="admonition warning">
|
||||
<div id="admonition-read-this" class="admonition warning">
|
||||
<div class="admonition-title">
|
||||
|
||||
Read **this**!
|
||||
|
||||
<a class="admonition-anchor-link" href="#admonition-read-this"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -461,9 +413,9 @@ A simple admonition.
|
||||
</div>
|
||||
</div>
|
||||
Text
|
||||
"#;
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, preprocess(content).unwrap());
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -483,7 +435,7 @@ Text
|
||||
| Row 1 | Row 2 |
|
||||
"#;
|
||||
|
||||
assert_eq!(expected, preprocess(content).unwrap());
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -503,7 +455,7 @@ Text
|
||||
</del>
|
||||
"#;
|
||||
|
||||
assert_eq!(expected, preprocess(content).unwrap());
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -527,7 +479,7 @@ Text
|
||||
2. paragraph 2
|
||||
"#;
|
||||
|
||||
assert_eq!(expected, preprocess(content).unwrap());
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -539,13 +491,14 @@ With <b>html</b> styling.
|
||||
hello
|
||||
"#;
|
||||
|
||||
let expected = r#"
|
||||
let expected = r##"
|
||||
|
||||
<div class="admonition note">
|
||||
<div id="admonition-and-in-the-title" class="admonition note">
|
||||
<div class="admonition-title">
|
||||
|
||||
And "<i>in</i>" the title
|
||||
|
||||
<a class="admonition-anchor-link" href="#admonition-and-in-the-title"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -554,9 +507,9 @@ With <b>html</b> styling.
|
||||
</div>
|
||||
</div>
|
||||
hello
|
||||
"#;
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, preprocess(content).unwrap());
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -568,13 +521,14 @@ Should be respected
|
||||
hello
|
||||
"#;
|
||||
|
||||
let expected = r#"
|
||||
let expected = r##"
|
||||
|
||||
<div class="admonition warning">
|
||||
<div id="admonition-trademark" class="admonition warning">
|
||||
<div class="admonition-title">
|
||||
|
||||
Trademark™
|
||||
|
||||
<a class="admonition-anchor-link" href="#admonition-trademark"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -583,9 +537,9 @@ Should be respected
|
||||
</div>
|
||||
</div>
|
||||
hello
|
||||
"#;
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, preprocess(content).unwrap());
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -596,13 +550,14 @@ Will have bonus classnames
|
||||
```
|
||||
"#;
|
||||
|
||||
let expected = r#"
|
||||
let expected = r##"
|
||||
|
||||
<div class="admonition tip my-style other-style">
|
||||
<div id="admonition-tip" class="admonition tip my-style other-style">
|
||||
<div class="admonition-title">
|
||||
|
||||
Tip
|
||||
|
||||
<a class="admonition-anchor-link" href="#admonition-tip"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -610,9 +565,9 @@ Will have bonus classnames
|
||||
|
||||
</div>
|
||||
</div>
|
||||
"#;
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, preprocess(content).unwrap());
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -623,13 +578,14 @@ Will have bonus classnames
|
||||
```
|
||||
"#;
|
||||
|
||||
let expected = r#"
|
||||
let expected = r##"
|
||||
|
||||
<div class="admonition tip my-style other-style">
|
||||
<div id="admonition-developers-dont-want-you-to-know-this-one-weird-tip" class="admonition tip my-style other-style">
|
||||
<div class="admonition-title">
|
||||
|
||||
Developers don't want you to know this one weird tip!
|
||||
|
||||
<a class="admonition-anchor-link" href="#admonition-developers-dont-want-you-to-know-this-one-weird-tip"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -637,9 +593,9 @@ Will have bonus classnames
|
||||
|
||||
</div>
|
||||
</div>
|
||||
"#;
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, preprocess(content).unwrap());
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -649,17 +605,177 @@ Will have bonus classnames
|
||||
```
|
||||
"#;
|
||||
|
||||
let expected = r#"
|
||||
let expected = r##"
|
||||
|
||||
<div class="admonition note">
|
||||
<div id="admonition-default" class="admonition note">
|
||||
<div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unique_ids_same_title() {
|
||||
let content = r#"
|
||||
```admonish note "My Note"
|
||||
Content zero.
|
||||
```
|
||||
|
||||
```admonish note "My Note"
|
||||
Content one.
|
||||
```
|
||||
"#;
|
||||
|
||||
assert_eq!(expected, preprocess(content).unwrap());
|
||||
let expected = r##"
|
||||
|
||||
<div id="admonition-my-note" class="admonition note">
|
||||
<div class="admonition-title">
|
||||
|
||||
My Note
|
||||
|
||||
<a class="admonition-anchor-link" href="#admonition-my-note"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
Content zero.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="admonition-my-note-1" class="admonition note">
|
||||
<div class="admonition-title">
|
||||
|
||||
My Note
|
||||
|
||||
<a class="admonition-anchor-link" href="#admonition-my-note-1"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
Content one.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v2_config_works() {
|
||||
let content = r#"
|
||||
```admonish tip class="my other-style" title="Article Heading"
|
||||
Bonus content!
|
||||
```
|
||||
"#;
|
||||
|
||||
let expected = r##"
|
||||
|
||||
<div id="admonition-article-heading" class="admonition tip my other-style">
|
||||
<div class="admonition-title">
|
||||
|
||||
Article Heading
|
||||
|
||||
<a class="admonition-anchor-link" href="#admonition-article-heading"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
Bonus content!
|
||||
|
||||
</div>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn continue_on_error_output() {
|
||||
let content = r#"
|
||||
```admonish title="
|
||||
Bonus content!
|
||||
```
|
||||
"#;
|
||||
|
||||
let expected = r##"
|
||||
|
||||
<div id="admonition-error-rendering-admonishment" class="admonition bug">
|
||||
<div class="admonition-title">
|
||||
|
||||
Error rendering admonishment
|
||||
|
||||
<a class="admonition-anchor-link" href="#admonition-error-rendering-admonishment"></a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
Failed with: TOML parsing error: unterminated string at line 1 column 7
|
||||
|
||||
Original markdown input:
|
||||
|
||||
``````
|
||||
```admonish title="
|
||||
Bonus content!
|
||||
```
|
||||
``````
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bail_on_error_output() {
|
||||
let content = r#"
|
||||
```admonish title="
|
||||
Bonus content!
|
||||
```
|
||||
"#;
|
||||
|
||||
assert_eq!(
|
||||
preprocess(content, OnFailure::Bail)
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
r#"Error processing admonition, bailing:
|
||||
```admonish title="
|
||||
Bonus content!
|
||||
```"#
|
||||
.to_owned()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_collapsible() {
|
||||
let content = r#"
|
||||
```admonish collapsible=true
|
||||
Hidden
|
||||
```
|
||||
"#;
|
||||
|
||||
let expected = r##"
|
||||
|
||||
<details id="admonition-note" class="admonition note">
|
||||
<summary class="admonition-title">
|
||||
|
||||
Note
|
||||
|
||||
<a class="admonition-anchor-link" href="#admonition-note"></a>
|
||||
</summary>
|
||||
<div>
|
||||
|
||||
Hidden
|
||||
|
||||
</div>
|
||||
</details>
|
||||
"##;
|
||||
|
||||
assert_eq!(expected, prep(content));
|
||||
}
|
||||
}
|
||||
|
||||
39
src/types.rs
Normal file
39
src/types.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum Directive {
|
||||
Note,
|
||||
Abstract,
|
||||
Info,
|
||||
Tip,
|
||||
Success,
|
||||
Question,
|
||||
Warning,
|
||||
Failure,
|
||||
Danger,
|
||||
Bug,
|
||||
Example,
|
||||
Quote,
|
||||
}
|
||||
|
||||
impl FromStr for Directive {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, ()> {
|
||||
match string {
|
||||
"note" => Ok(Self::Note),
|
||||
"abstract" | "summary" | "tldr" => Ok(Self::Abstract),
|
||||
"info" | "todo" => Ok(Self::Info),
|
||||
"tip" | "hint" | "important" => Ok(Self::Tip),
|
||||
"success" | "check" | "done" => Ok(Self::Success),
|
||||
"question" | "help" | "faq" => Ok(Self::Question),
|
||||
"warning" | "caution" | "attention" => Ok(Self::Warning),
|
||||
"failure" | "fail" | "missing" => Ok(Self::Failure),
|
||||
"danger" | "error" => Ok(Self::Danger),
|
||||
"bug" => Ok(Self::Bug),
|
||||
"example" => Ok(Self::Example),
|
||||
"quote" | "cite" => Ok(Self::Quote),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user