Compare commits

...

216 Commits
v1.0.1 ... main

Author SHA1 Message Date
Tom Milligan
ea6c0a687a Merge pull request #229 from tommilligan/prep-1.20.0
chore: prep v1.20.0 release
2025-06-06 14:53:02 +01:00
Tom Milligan
50b9187ee5 chore: prep v1.20.0 release 2025-06-06 14:35:51 +01:00
Tom Milligan
fbd07ecaf0 Merge pull request #228 from tommilligan/print-collapsed-details
feat: show collapsed content in print view
2025-06-06 14:21:49 +01:00
Tom Milligan
37d03b872e feat: show collapsed content in print view 2025-06-06 12:58:11 +01:00
Tom Milligan
fa91fe3778 Merge pull request #227 from tommilligan/update-deps-2025-06
chore: update dependencies
2025-06-06 12:37:47 +01:00
Tom Milligan
d59dce3533 chore: update dependencies 2025-06-06 12:34:38 +01:00
Tom Milligan
21d86546ff Merge pull request #224 from tommilligan/fix-nested-list-content-indent
fix: do not double indent content in lists
2025-02-17 15:55:07 +00:00
Tom Milligan
79ee592f56 chore: prep v1.19.0 release 2025-02-17 15:42:27 +00:00
Tom Milligan
0d3eb11a2c fix: do not double indent content in lists 2025-02-17 15:38:33 +00:00
CoralPink
43579f902b Hide extra markers displayed in Safari. (#185)
* Hide extra markers displayed in `Safari`.

* [review] move webkit fix

---------

Co-authored-by: Tom Milligan <tom.milligan@uipath.com>
2024-08-17 06:19:01 +01:00
Tom Milligan
7ee3643dc3 Merge pull request #209 from tommilligan/deps-upgrade-2
chore: relax action constraints
2024-08-16 08:54:37 +01:00
Tom Milligan
c4bf2608e7 chore: relax action constraints 2024-08-16 08:50:25 +01:00
Tom Milligan
d4667a5f55 Merge pull request #208 from tommilligan/deps-upgrade
chore: upgrade rust, deps and actions
2024-08-16 08:39:23 +01:00
Tom Milligan
8751b3cd24 chore: upgrade github actions 2024-08-16 07:56:14 +01:00
Tom Milligan
516bbc6e7e chore: update rust lockfile 2024-08-16 07:48:11 +01:00
Tom Milligan
fc5c5eae39 chore: bump msrvc to 1.76.0 2024-08-16 07:46:50 +01:00
Tom Milligan
147a9bdb98 Merge pull request #199 from tommilligan/prep-1.18.0
chore: prep 1.18.0 release
2024-06-20 15:26:16 +01:00
Tom Milligan
540ea8e5e8 chore: prep 1.18.0 release 2024-06-20 14:43:12 +01:00
Tom Milligan
ae4bac21ba Merge pull request #198 from tommilligan/upgrade-deps
chore: upgrade deps
2024-06-20 10:53:11 +01:00
Tom Milligan
f0c4d3764e chore: upgrade deps 2024-06-20 10:42:01 +01:00
Tom Milligan
a942a8094e Merge pull request #197 from tommilligan/toml-serialize-error
fix: use up to date toml for serialization
2024-06-20 10:40:44 +01:00
Tom Milligan
d10a427b0f fix: use up to date toml for serialization 2024-06-20 10:05:48 +01:00
Tom Milligan
41a3b05094 Merge pull request #195 from tommilligan/aria-labelledby
feat: add aria attributes to block
2024-06-15 19:51:45 +01:00
Tom Milligan
d0f73baa3f Merge pull request #194 from tommilligan/readme-examples
readme: add more example projects
2024-06-15 13:46:16 +01:00
Tom Milligan
4c40418090 feat: add aria attributes to block 2024-06-15 13:43:58 +01:00
toastal
30c9ad2269 internal: refactor html rendering 2024-06-15 13:42:38 +01:00
Tom Milligan
f7482415a7 readme: add more example projects 2024-06-15 10:20:37 +01:00
Tom Milligan
08397fbffa Merge pull request #192 from tommilligan/prep-v1.17.1
chore: prep v1.17.1 release
2024-06-11 14:51:18 +01:00
Tom Milligan
b78fc39031 chore: prep v1.17.1 release 2024-06-11 14:14:48 +01:00
Tom Milligan
fe7b475753 Merge pull request #186 from tommilligan/tommilligan-patch-1
fix: remove stray debug statement
2024-05-29 19:54:24 +01:00
Tom Milligan
a56976d085 fix: remove stray debug statement 2024-05-29 19:48:54 +01:00
Tom Milligan
08967d550d ci: fix deploy.yml 2024-05-24 15:06:58 +01:00
Tom Milligan
8fa1411095 Merge pull request #184 from tommilligan/fix-cargo-target-dir-release
ci: be more limited with CARGO_TARGET_DIR
2024-05-24 15:05:19 +01:00
Tom Milligan
a1e5cfa48d ci: be more limited with CARGO_TARGET_DIR 2024-05-24 14:59:46 +01:00
Tom Milligan
33fd522d68 Revert "ci: fix deploy artefacts"
This reverts commit c681ff922d.
2024-05-24 14:50:55 +01:00
Tom Milligan
4805398359 Merge pull request #183 from tommilligan/fix-deploy-artefacts
ci: fix deploy artefacts
2024-05-24 14:47:06 +01:00
Tom Milligan
c681ff922d ci: fix deploy artefacts 2024-05-24 14:46:52 +01:00
Tom Milligan
cd0726aaf2 Merge pull request #182 from tommilligan/prep-1.17.0
chore: bump version to 1.17.0, update ci
2024-05-24 13:37:20 +01:00
Tom Milligan
a08967e073 chore: remove deprecated set-output command 2024-05-24 13:27:29 +01:00
Tom Milligan
f7e6970fa3 chore: upgrade github actions 2024-05-24 13:22:51 +01:00
Tom Milligan
5d2124b319 chore: bump version to 1.17.0 2024-05-24 13:17:20 +01:00
Tom Milligan
d79ebb4fad chore: update v2 wishlist 2024-05-24 13:16:12 +01:00
Tom Milligan
9df896cd77 Merge pull request #181 from tommilligan/fix-breaking-on-equals-in-value
fix: support toml values with equal sign
2024-05-24 13:04:16 +01:00
Tom Milligan
ffb819c315 fix: support toml values with equal sign 2024-05-24 12:59:59 +01:00
Tom Milligan
f278374c88 Merge pull request #180 from tommilligan/better-errors-for-html
fix: better errors on toml parsing failures
2024-05-24 11:01:48 +01:00
Tom Milligan
2c18292401 fix: better errors on toml parsing failures 2024-05-24 10:38:39 +01:00
Tom Milligan
294af2478c Merge pull request #177 from tommilligan/cache-ci-more
ci: cache more cargo target dir
2024-05-19 17:38:27 +01:00
Tom Milligan
278d17792b Merge pull request #178 from tommilligan/fix-changelog
chore: fix changelog
2024-05-19 17:33:39 +01:00
Tom Milligan
9f6c73091a chore: fix changelog 2024-05-19 17:33:24 +01:00
Tom Milligan
9f221abc12 ci: cache more cargo target dir 2024-05-19 17:30:47 +01:00
Tom Milligan
82c7bd4fd9 Merge pull request #176 from tommilligan/prep-1.16.0
chore: prep 1.16.0 release
2024-05-19 17:20:49 +01:00
Tom Milligan
9bca2a66df chore: prep 1.16.0 release 2024-05-19 17:13:16 +01:00
Tom Milligan
80cce8480c Merge pull request #174 from yannickseurin/custom-collapsible
Allow to set the collapsible property for each directive
2024-05-19 17:03:37 +01:00
Yannick Seurin
5d5b73ded6 allow to set collapsible default value for each directive 2024-05-19 16:57:21 +01:00
Tom Milligan
c17a66440c Merge pull request #175 from tommilligan/update-toml
chore: dependency and msrv upgrades
2024-05-19 11:21:54 +01:00
Tom Milligan
068a375647 chore: dependency and msrv upgrades 2024-05-19 11:05:09 +01:00
Tom Milligan
f04016d017 Merge pull request #173 from yannickseurin/snake-case
remove serde kebab-case renaming for AdmonitionDefaults struct
2024-05-19 09:49:38 +01:00
Tom Milligan
3f8bf86ac3 fix: allow css_id_prefix kebab cases for backcompat 2024-05-19 09:43:48 +01:00
Yannick Seurin
8045e217c9 remove serde kebab-case renaming 2024-04-29 22:53:11 +02:00
Tom Milligan
85fde44c09 Merge pull request #170 from meator/git-repository-url
book: Add GitHub link
2024-03-20 11:41:40 +00:00
meator
a2c3e49ef9 book: Add GitHub link 2024-03-19 21:41:02 +01:00
Tom Milligan
8a0ecc5dd1 Merge pull request #167 from tommilligan/prep-1.15.0
chore: prep v1.15.0 release
2023-12-30 21:41:13 +01:00
Tom Milligan
467f5f8f13 chore: prep v1.15.0 release 2023-12-30 20:37:34 +00:00
Tom Milligan
c4a05dae9d Merge pull request #165 from tommilligan/99-custom-admonitions
feat: custom directives
2023-12-30 21:20:35 +01:00
Tom Milligan
1bb14684c4 chore: use LF everywhere in ci 2023-12-30 20:14:53 +00:00
Tom Milligan
b3798b4d9f feat: custom directives 2023-12-30 20:14:53 +00:00
Tom Milligan
26c344a1e6 Merge pull request #163 from tommilligan/fix-161
fix: additional-css UNIX style path normalization
2023-12-18 18:00:43 +01:00
Tom Milligan
b1e6a5ee1e Merge pull request #164 from tommilligan/contributing
chore: split contributing guide
2023-12-18 13:25:57 +01:00
Tom Milligan
0d7b64d1b8 chore: split contributing guide 2023-12-18 12:25:34 +00:00
carlocorradini
9730cd0aaa fix: additional-css UNIX style path normalization 2023-12-18 12:20:54 +00:00
Tom Milligan
dc219f755d Merge pull request #155 from tommilligan/prep-1.14.0
chore: prep v0.14.0 release
2023-11-16 01:04:35 +01:00
Tom Milligan
a2524f890b chore: prep v0.14.0 release 2023-11-16 00:04:19 +00:00
Sky
c3207e4d16 Custom ids (#144)
* Custom ids

- You can now set custom CSS ids for admonishment blocks with the `id` field.
- You can now customize the default CSS id prefix (default is `"admonition-"`).

Co-authored-by: Tom Milligan <tom.milligan@uipath.com>
2023-11-15 22:34:21 +00:00
Tom Milligan
ab63c90231 Merge pull request #154 from joshka/main
fix: use correct case for TL;DR and FAQ
2023-11-15 22:43:20 +01:00
Josh McKinney
d5bdde1f5c fix: use correct case for TL;DR and FAQ
Fixes: #153
2023-11-15 13:24:56 -08:00
Tom Milligan
52ca8fc831 Merge pull request #152 from tommilligan/print-pdf-render
fix: better rendering for pdf/print view
2023-11-13 17:09:38 +01:00
Tom Milligan
e1ea411e9a fix: better rendering for pdf/print view 2023-11-13 15:46:58 +00:00
Tom Milligan
31d5a27a6d Merge pull request #143 from tommilligan/prep-1.13.1
chore: prep 1.13.1 release
2023-10-17 09:59:48 +01:00
Tom Milligan
0f0e02702c chore: prep 1.13.1 release 2023-10-17 09:37:32 +01:00
Tom Milligan
7235d5f349 Merge pull request #142 from tommilligan/rust-version
ci: add msrv to cargo toml, update mdbook version
2023-10-15 13:26:11 +01:00
Tom Milligan
0304995dbb ci: add msrv to cargo toml, update mdbook version 2023-10-15 13:11:01 +01:00
Tom Milligan
197d9cd059 Merge pull request #140 from tommilligan/prep-1.13.0
chore: prep v0.13.0 release
2023-10-06 15:21:27 +01:00
Tom Milligan
04ff932f1f chore: prep v0.13.0 release 2023-10-06 14:52:13 +01:00
Tom Milligan
1526a5d814 Merge pull request #139 from tommilligan/pr-137
prefix directive class names with 'admonish-'
2023-10-06 14:16:45 +01:00
phoenixr-codes
eb21495797 prefix directive class names with 'admonish-'
This change exists to prevent conflicts with the newly added 'warning' class by mdBook. See also: https://github.com/rust-lang/mdBook/blob/master/CHANGELOG.md#added
2023-10-06 13:43:54 +01:00
Tom Milligan
ebe6f7815c Merge pull request #138 from tommilligan/ci-prettier
ci: lint styles with prettier
2023-10-06 13:32:48 +01:00
Tom Milligan
c0c953c865 ci: lint styles with prettier 2023-10-06 13:15:45 +01:00
Tom Milligan
0fa34a66a0 Merge pull request #136 from tommilligan/deps-20231001
chore: deps 20231001
2023-10-02 08:50:53 +01:00
dependabot[bot]
dfc12c3652 chore(deps): bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-02 08:37:26 +01:00
dependabot[bot]
108edfffc5 chore(deps): bump JamesIves/github-pages-deploy-action
Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 4.4.1 to 4.4.3.
- [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases)
- [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/v4.4.1...v4.4.3)

---
updated-dependencies:
- dependency-name: JamesIves/github-pages-deploy-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-02 08:37:26 +01:00
Tom Milligan
38f1933e8f chore: bump rust deps 2023-10-02 08:37:26 +01:00
Tom Milligan
a839038263 Merge pull request #128 from tommilligan/bug-127
markdown: fix panic when searching for indent
2023-09-19 13:02:43 +01:00
Tom Milligan
8501c812d9 chore: prep v1.12.1 2023-09-19 12:21:42 +01:00
Tom Milligan
933432afb2 markdown: fix panic when searching for indent 2023-09-19 12:21:42 +01:00
Tom Milligan
496e8f7c6d Merge pull request #126 from tommilligan/prep-1.12.0
chore: prep v1.12.0 release
2023-09-16 11:22:08 +01:00
Tom Milligan
1877fd1731 chore: upgrade internal dependencies 2023-09-16 10:58:17 +01:00
Tom Milligan
20286b3fae chore: prep v1.12.0 release 2023-09-16 10:55:53 +01:00
Tom Milligan
8e68cf919f Merge pull request #124 from tommilligan/bug-123
feat: support admonitions inside list items
2023-09-10 00:02:58 +01:00
Tom Milligan
02640dab1f feat: support admonitions inside list items 2023-09-09 23:44:58 +01:00
Tom Milligan
771e9c9fd8 Merge pull request #125 from tommilligan/prep-v1.11.1
chore: prepare v1.11.1 release
2023-09-09 23:43:48 +01:00
Tom Milligan
cce9343c47 chore: prepare v1.11.1 release 2023-09-09 23:32:10 +01:00
Tom Milligan
20b158966b Revert "chore: bump lockfile"
This reverts commit 39edc4d92a.
2023-09-09 23:24:26 +01:00
Tom Milligan
491f9cf341 Merge pull request #122 from tommilligan/fix-docs
docs: fix mdbook-toc build failure
2023-09-09 09:12:27 +01:00
Tom Milligan
6deaf1ea2b docs: fix mdbook-toc build failure 2023-09-09 09:12:07 +01:00
Tom Milligan
041e5a566f Merge pull request #121 from tommilligan/update-deps
chore: prep v1.11.0 release
2023-09-09 09:06:44 +01:00
Tom Milligan
99b5a235cf chore: prep v1.11.0 release 2023-09-09 09:05:49 +01:00
Tom Milligan
39edc4d92a chore: bump lockfile 2023-09-09 08:58:32 +01:00
Tom Milligan
7773213093 Merge pull request #118 from eitsupi/eitsupi-patch-1
ci(deploy): Use Ubuntu 20.04 for building linux gnu target binaries
2023-09-09 08:49:24 +01:00
eitsupi
e888fcd021 Use Ubuntu 20.04 for building linux gnu target binaries 2023-09-09 08:48:24 +01:00
Tom Milligan
95dc7582ad Merge pull request #119 from eitsupi/add-aarch64-musl
ci(deploy): deploy aarch64-unknown-linux-musl
2023-09-09 08:43:59 +01:00
eitsupi
b658eb6049 strip via Cargo 2023-09-09 08:31:25 +01:00
eitsupi
623291625a deploy aarch64-unknown-linux-musl 2023-09-09 08:31:25 +01:00
Tom Milligan
4dad5a86c8 chore: fix clippy 2023-09-09 08:28:21 +01:00
Tom Milligan
7e774f4655 chore: prep v1.10.2 release (#116) 2023-08-07 18:58:32 +01:00
Tom Milligan
823cefbcbc fix: unlink mdbook internal toml dependency from mdbook-admonish (#115)
* chore: bump dependencies

* fix: unlink mdbook internal toml dependency from mdbook-admonish
2023-08-07 18:55:02 +01:00
Tom Milligan
a6a2941821 chore: prep v1.10.1 release (#113) 2023-07-28 18:49:46 +01:00
Uriel
faf99a1b76 only modify summary with the admonition-title class (#112)
* only modify `summary` with the `admonition-title` class

* regenerate assets, update snapshot test, fix themes

---------

Co-authored-by: Tom Milligan <tom.milligan@uipath.com>
2023-07-28 18:44:05 +01:00
Jonas
afdc2b03d0 Improvement of links (#111) 2023-07-26 12:19:01 +01:00
Tom Milligan
e55df3e60b Merge pull request #110 from tommilligan/prep-1.10.0
chore: prep v1.10.0 release
2023-07-23 19:51:53 +01:00
Tom Milligan
f3d49b93de chore: prep v1.10.0 release 2023-07-23 19:47:37 +01:00
Tom Milligan
92caf95b34 chore: add notes for future v2 release 2023-07-23 19:38:24 +01:00
Tom Milligan
0742c6c1e8 Merge pull request #109 from tommilligan/run-doctests
feat: add support for test renderer, running doctests
2023-07-23 19:33:20 +01:00
Tom Milligan
60706be3e0 ci: bump MSRV to 1.66.0 2023-07-23 19:21:18 +01:00
Tom Milligan
f5a6b9ef0f ci: build docs on each merge 2023-07-23 19:13:10 +01:00
Tom Milligan
9361b7e7fa book: add configuration docs 2023-07-23 19:10:24 +01:00
Tom Milligan
24bef47b15 internal: restructure book.toml config parsing 2023-07-23 19:10:24 +01:00
Tom Milligan
0eb5fd35c3 internal: restructure configuration parsing 2023-07-23 17:53:19 +01:00
Tom Milligan
de539cd0fd internal: split up lib.rs 2023-07-22 11:32:40 +01:00
Tom Milligan
4842daea1c feat: add support for test renderer, running doctests 2023-07-22 10:44:11 +01:00
Tom Milligan
76212fccfb internal: refactor how whitespace is added 2023-07-22 10:43:47 +01:00
Tom Milligan
681c991a9a Merge pull request #98 from tommilligan/ci-publish-auto
ci: fix automatic crates.io publication
2023-05-05 09:52:54 +01:00
Tom Milligan
1e0a3992d5 ci: fix automatic crates.io publication 2023-05-05 09:16:28 +01:00
Tom Milligan
d97747d195 Merge pull request #91 from tommilligan/prep-1.9.0
chore: prep v1.9.0 release
2023-04-23 14:25:53 +01:00
Tom Milligan
2e2cebfc83 chore: prep v1.9.0 release 2023-04-23 14:11:53 +01:00
Tom Milligan
ab595f18f7 Merge pull request #90 from tommilligan/qvet
ci: add qvet config
2023-04-23 14:01:57 +01:00
Tom Milligan
196585f4f2 ci: add qvet config 2023-04-23 14:01:38 +01:00
Tom Milligan
7ad4d3f18c Merge pull request #89 from tommilligan/error-fences
fix: ensure error fences will always enclose block
2023-04-23 13:38:28 +01:00
Tom Milligan
0324c93efa fix: ensure error fences will always enclose block 2023-04-23 13:21:51 +01:00
Tom Milligan
62dd36624d Merge pull request #88 from tommilligan/better-fences
fix: better code fence handling
2023-04-23 13:11:09 +01:00
Tom Milligan
b3e82df34e fix: better code fence handling 2023-04-23 12:29:54 +01:00
Tom Milligan
e8813eb104 Merge pull request #84 from ShaunSHamilton/feat_default-title
feat: add default title option
2023-04-20 15:59:05 +01:00
Tom Milligan
f606ad8758 internal: rename structs for clarity 2023-04-20 15:43:57 +01:00
Shaun Hamilton
d269838765 feat: add book-wide default values 2023-04-20 15:43:56 +01:00
Tom Milligan
082359e562 Merge pull request #85 from tommilligan/regression-test-alternate-directive
test: add regression test for alternate directives
2023-04-18 07:58:04 +01:00
Tom Milligan
84d163c32f test: add regression test for alternate directives 2023-04-18 07:34:05 +01:00
Tom Milligan
90484a44ea Merge pull request #83 from ShaunSHamilton/fix_title-height
fix(compile_assets): add title min-height
2023-04-16 12:46:38 +01:00
Shaun Hamilton
5e7674d1f9 fix(compile_assets): add title min-height 2023-04-16 12:33:18 +01:00
Tom Milligan
55a654dfca doc: option for processing included files 2023-04-03 11:52:05 +01:00
Tom Milligan
a2f664cac3 chore: update dependencies, toml errors 2023-04-03 08:35:10 +01:00
Tom Milligan
dfb70b0415 chore: rm cargo-audit 2023-04-02 10:03:34 +01:00
Tom Milligan
fb1b789386 chore: dep updates 2023-01-01 12:36:55 +00:00
Tom Milligan
97bcd97c64 chore: bump deps 2022-12-02 17:27:42 +00:00
Tom Milligan
a2a7316b26 bug: fix relative paths 2022-11-26 22:53:46 +00:00
Tom Milligan
f9eb198cd0 bin: better error handling 2022-11-26 22:36:49 +00:00
dependabot[bot]
5530da074b chore(deps): bump JamesIves/github-pages-deploy-action
Bumps [JamesIves/github-pages-deploy-action](https://github.com/JamesIves/github-pages-deploy-action) from 4.4.0 to 4.4.1.
- [Release notes](https://github.com/JamesIves/github-pages-deploy-action/releases)
- [Commits](https://github.com/JamesIves/github-pages-deploy-action/compare/v4.4.0...v4.4.1)

---
updated-dependencies:
- dependency-name: JamesIves/github-pages-deploy-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-01 15:57:30 +00:00
Tom Milligan
0fe2ad52ed chore: cargo dep updates 2022-11-01 15:18:58 +00:00
Tom Milligan
787744f1f1 chore: prep v1.8.0 release 2022-10-23 15:20:31 +01:00
Tom Milligan
603c83e2eb chore: upgrade to clap v4 2022-10-16 09:32:06 +01:00
Tom Milligan
fbdffaa723 chore: upgrade outdated dep versions 2022-10-16 09:32:06 +01:00
dependabot[bot]
7c20d5b2d1 chore(deps): bump actions/cache from 2 to 3
Bumps [actions/cache](https://github.com/actions/cache) from 2 to 3.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-16 08:32:45 +01:00
dependabot[bot]
3507d6b5e0 chore(deps): bump JamesIves/github-pages-deploy-action
Bumps [JamesIves/github-pages-deploy-action](https://github.com/JamesIves/github-pages-deploy-action) from 4.3.3 to 4.4.0.
- [Release notes](https://github.com/JamesIves/github-pages-deploy-action/releases)
- [Commits](https://github.com/JamesIves/github-pages-deploy-action/compare/v4.3.3...v4.4.0)

---
updated-dependencies:
- dependency-name: JamesIves/github-pages-deploy-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-02 10:51:41 +01:00
dependabot[bot]
f81d4d40dd chore(deps): bump actions/download-artifact from 2 to 3
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 2 to 3.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-02 10:51:08 +01:00
dependabot[bot]
ed019a92d9 chore(deps): bump actions/checkout from 2 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-02 10:51:00 +01:00
dependabot[bot]
9dd2ca128c chore(deps): bump actions/upload-artifact from 2 to 3
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-02 10:50:51 +01:00
Matthias Beyer
51120acfd9 Add monthly dependabot
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2022-10-02 10:08:38 +01:00
Tom Milligan
650123645b chore: upgrade dependencies 2022-10-02 09:22:46 +01:00
Tom Milligan
438c1dff5a ci: automatically deploy docs after publish 2022-06-11 10:23:30 +01:00
Tom Milligan
78b7451e49 chore: refactor scripts 2022-06-11 10:23:30 +01:00
Tom Milligan
bb937dc2d2 docs: update readme, documentation 2022-06-11 10:23:30 +01:00
Tom Milligan
db7101cb12 chore: prepare v1.7.0 release 2022-06-11 10:23:30 +01:00
Tom Milligan
fd6c2d0bd0 fix: try config v2 before v1 2022-06-11 10:23:30 +01:00
gggto
72deb8421c feat: add collapsible support 2022-05-18 13:24:00 +01:00
Tom Milligan
7b5a13d6af feat: make anchor links hoverable 2022-05-15 19:09:19 +01:00
Tom Milligan
28dfc5b6c3 feat: better bug output, option to fail mdbook build 2022-05-01 22:12:15 +01:00
Tom Milligan
beb640077f feat: add key/value configuration 2022-05-01 21:48:35 +01:00
Tom Milligan
6b479255a6 chore: prepare v1.6.0 release 2022-04-28 20:07:21 +01:00
Tom Milligan
7fe2fdc329 chore: polish error messages for breaking minor installs 2022-04-28 20:07:21 +01:00
Tom Milligan
e61e6148b5 fix: header bar overflow on firefox 2022-04-27 10:23:13 +01:00
Tom Milligan
0e235cb9b5 feat: clickable anchor links, enforce asset updates 2022-04-27 08:42:38 +01:00
Tom Milligan
c28f2b2fc9 chore: prepare v1.5.0 release 2022-04-25 21:31:54 +01:00
Tom Milligan
2a78ccbc2a ci: force install additional bins 2022-04-25 16:00:38 +01:00
Tom Milligan
5b61637eb7 feat: add unique ids to generated html 2022-04-25 16:00:38 +01:00
Tom Milligan
9786269b49 chore: prepare for v1.4.1 release 2022-04-21 19:33:11 +01:00
Tom Milligan
6517f50353 ci: add deployment workflow for binary artefacts 2022-04-21 19:33:11 +01:00
Tom Milligan
6dc759e358 chore: bump dependency lockfile 2022-04-21 19:33:11 +01:00
Tom Milligan
99f17dd2c5 chore: fix clippy lints 2022-02-28 22:04:20 +00:00
Tom Milligan
7c6f878d6c readme: update links to book 2022-02-28 22:04:20 +00:00
Tom Milligan
3fa5067be9 book: add example book with directive reference 2022-02-28 22:04:20 +00:00
Tom Milligan
8630161fa3 chore: prepare v1.4.0 release 2022-02-21 23:03:48 +00:00
Tom Milligan
0b50fd68ba feat: remove title bar if title empty 2022-02-21 19:39:03 +00:00
Tom Milligan
d851076cbc ci: speed up fast failures 2022-02-21 18:35:12 +00:00
Stephen Chung
af038017d2 feat: support for custom styles via directive.style 2022-02-21 18:11:38 +00:00
Tom Milligan
9fc872b8b8 ci: commit missed integration test file 2022-02-21 14:15:24 +00:00
Tom Milligan
b667271080 fix: remove superfluous <p> tags 2022-02-21 13:08:41 +00:00
Tom Milligan
0417d55ab8 ci: add mdbook integration test 2022-02-21 12:38:15 +00:00
Tom Milligan
82c5a3d5b9 chore: prepare v1.3.1 release 2022-02-21 11:50:36 +00:00
Tom Milligan
ada76bd4bd ci: add github actions, scripts 2022-02-21 11:20:47 +00:00
Tom Milligan
df79d57463 internal: factor out single function core 2022-02-20 20:13:43 +00:00
Tom Milligan
44097136f5 chore: prepare v1.3.2 release 2022-02-20 19:23:22 +00:00
Tom Milligan
455b4c586c readme: add note on JSON escapes 2022-02-20 19:17:40 +00:00
Tom Milligan
1915d10d7e fix: incorrect slicing of non-ascii info strings 2022-02-20 19:11:28 +00:00
Tom Milligan
583bc94542 chore: prepare v1.3.1 release 2022-02-19 10:38:02 +00:00
Tom Milligan
39746696ed fix: dedent generated HTML to avoid code block styling 2022-02-19 10:36:18 +00:00
Tom Milligan
dc9ba9eaa2 fix: update span splitting logic 2022-02-19 10:32:21 +00:00
Tom Milligan
34e120e414 chore: prepare v1.3.0 release 2022-02-19 09:29:30 +00:00
Tom Milligan
3074784c42 readme: add examples and images 2022-02-19 09:23:38 +00:00
Tom Milligan
33009801da fix: allow markdown/html in titles 2022-02-19 09:02:13 +00:00
Tom Milligan
ed029aea5d fix: don't escape html 2022-02-19 08:18:04 +00:00
Tom Milligan
5177b76d76 chore: prepare v1.2.0 release 2022-02-18 22:53:42 +00:00
Tom Milligan
ab73e41359 feat: add support for quoted titles 2022-02-18 22:34:22 +00:00
Tom Milligan
6127679df8 internal: refactor, add pathway for titles 2022-02-18 22:23:28 +00:00
Tom Milligan
9367948599 feat: allow inner content to render as markdown 2022-02-18 21:53:59 +00:00
Tom Milligan
b1c7880282 chore: prepare v1.1.0 release 2022-02-18 17:49:46 +00:00
Tom Milligan
a16622010d css: add rules for builtin mdbook themes 2022-02-18 17:47:28 +00:00
Tom Milligan
c2ae3a4f1b chore: add readme badges 2022-02-17 15:48:02 +00:00
Tom Milligan
e4a79ed877 final readme typo, honest 2022-02-17 13:48:00 +00:00
73 changed files with 9226 additions and 2074 deletions

10
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: monthly
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: monthly

121
.github/workflows/check.yml vendored Normal file
View File

@@ -0,0 +1,121 @@
on: [pull_request]
name: check
jobs:
# Fast test before we kick off all the other jobs
fast-test:
name: Fast test
runs-on: ubuntu-24.04
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Cache build files
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: fast-test-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
- name: Install toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Install more toolchain
run: rustup component add rustfmt clippy
- name: Run tests
run: cargo clippy --all-targets -- -D warnings && cargo fmt -- --check && cargo test
# Test, and also do other things like doctests and examples
detailed-test:
needs: fast-test
name: Test main target
runs-on: ubuntu-24.04
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Cache build files
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
~/.cargo/bin
/tmp/cargo-install-target-dir
key: detailed-test-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
- name: Install toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: rustfmt, clippy
- name: Enable Corepack
run: corepack enable
- name: Install node toolchain
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "yarn"
cache-dependency-path: compile_assets/yarn.lock
- name: Install additional test dependencies
env:
CARGO_TARGET_DIR: "/tmp/cargo-install-target-dir"
run: ./scripts/install
- name: Run check script
run: ./scripts/check
# Test on all supported platforms
test:
needs: fast-test
name: Test all other targets
strategy:
matrix:
os:
- ubuntu-24.04
rust:
- stable
- beta
- 1.82.0
experimental:
- false
include:
# Run a canary test on nightly that's allowed to fail
- os: ubuntu-24.04
rust: nightly
experimental: true
# Test only stable on Windows, presume we'd get same result on other
# versions as Linux
- os: windows-2022
rust: stable
experimental: false
exclude:
# Don't bother retesting stable linux, we did it in the comprehensive test
- os: ubuntu-24.04
rust: stable
experimental: false
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}
steps:
# This is required, otherwise we get files with CRLF on Windows
# Which causes tests relying on data loaded from files to fail
- name: Set git to use LF everywhere
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- name: Checkout sources
uses: actions/checkout@v4
- name: Cache build files
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: test-${{ matrix.os }}-${{ matrix.rust }}-cargo-${{ hashFiles('**/Cargo.toml') }}
- name: Install toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- name: Run tests
run: cargo test

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

@@ -0,0 +1,155 @@
# Based on https://github.com/starship/starship/blob/master/.github/workflows/deploy.yml
name: Deploy
on:
push:
tags:
- "*"
env:
CRATE_NAME: mdbook-admonish
jobs:
# Build sources for every OS
github_build:
name: Build release binaries
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-unknown-linux-musl
os: ubuntu-latest
name: aarch64-unknown-linux-musl.tar.gz
- target: x86_64-unknown-linux-gnu
# Deliberately pinned to the same version `mdbook` uses to build
# binaries, so we use the same glibc version
#
# ref: https://github.com/rust-lang/mdBook/pull/1955
os: ubuntu-24.04
name: x86_64-unknown-linux-gnu.tar.gz
- target: x86_64-unknown-linux-musl
os: ubuntu-latest
name: x86_64-unknown-linux-musl.tar.gz
- target: x86_64-apple-darwin
os: macOS-latest
name: x86_64-apple-darwin.tar.gz
- target: x86_64-pc-windows-msvc
os: windows-latest
name: x86_64-pc-windows-msvc.zip
runs-on: ${{ matrix.os }}
steps:
- name: Setup | Checkout
uses: actions/checkout@v4
# Cache files between builds
- name: Setup | Cache Cargo
uses: actions/cache@v4
with:
# Note that we don't cache the `target` directory here
# so we do a completely clean rebuild for artefacts
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Setup | Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
target: ${{ matrix.target }}
- name: Setup | cross
if: endsWith(matrix.target, '-unknown-linux-musl')
uses: taiki-e/install-action@v2
with:
tool: cross
- name: Build | Build
if: ${{ !endsWith(matrix.target, '-unknown-linux-musl') }}
run: cargo build --release --target ${{ matrix.target }}
- name: Build | Build (musl)
if: endsWith(matrix.target, '-unknown-linux-musl')
run: cross build --release --target ${{ matrix.target }}
- name: Post Setup | Extract tag name
shell: bash
run: echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
id: extract_tag
- name: Post Setup | Prepare artifacts [Windows]
if: matrix.os == 'windows-latest'
run: |
mkdir target/stage
cd target/${{ matrix.target }}/release
7z a ../../stage/${{ env.CRATE_NAME }}-${{ steps.extract_tag.outputs.tag }}-${{ matrix.name }} ${{ env.CRATE_NAME }}.exe
cd -
- name: Post Setup | Prepare artifacts [-nix]
if: matrix.os != 'windows-latest'
run: |
mkdir target/stage
cd target/${{ matrix.target }}/release
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@v4
with:
name: ${{ env.CRATE_NAME }}-${{ steps.extract_tag.outputs.tag }}-${{ matrix.name }}
path: target/stage/*
# Idempotency: overwrite artefact by name if we're rerunning the deployment
overwrite: true
# Create GitHub release with Rust build targets and release notes
github_release:
name: Create GitHub Release
needs: github_build
runs-on: ubuntu-latest
steps:
- name: Setup | Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup | Artifacts
uses: actions/download-artifact@v4
- name: Setup | Extract version
shell: bash
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
id: extract_version
- name: Setup | Release notes
run: |
cat CHANGELOG.md | sed -n '/^## ${{ steps.extract_version.outputs.version }}$/,/^## /p' | sed '$d' > RELEASE.md
- name: Build | Publish
uses: softprops/action-gh-release@v2
with:
files: ${{ env.CRATE_NAME }}-*/${{ env.CRATE_NAME }}-*
body_path: RELEASE.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Upload to crates.io
publish:
name: Publish to crates.io
needs: github_release
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
~/.cargo/bin
# We reuse the cache from our detailed test environment, if available
key: detailed-test-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
- name: Install toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Publish crate
env:
CARGO_LOGIN_TOKEN: ${{ secrets.CARGO_LOGIN_TOKEN }}
run: ./scripts/publish

45
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: docs
on:
push:
branches:
- "main"
workflow_dispatch:
permissions:
contents: write
jobs:
publish:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
~/.cargo/bin
/tmp/cargo-install-target-dir
# We reuse the cache from our detailed test environment, if available
key: detailed-test-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
- name: Install toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Install mdbook
env:
CARGO_TARGET_DIR: "/tmp/cargo-install-target-dir"
run: ./scripts/install-mdbook
- name: Install mdbook extras
env:
CARGO_TARGET_DIR: "/tmp/cargo-install-target-dir"
run: ./book/scripts/install-mdbook-extras
- name: Build book
run: ./scripts/build-book
- name: Push docs
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: book/book

View File

@@ -1,4 +1,303 @@
## Changelog
# Changelog
## v1.20.0
### Changed
- MSRV (minimum supported rust version) is now 1.82.0 ([#227](https://github.com/tommilligan/mdbook-admonish/pull/227))
- Collapsible blocks now show content in print view ([#228](https://github.com/tommilligan/mdbook-admonish/pull/228)). Thanks to [@igor-petruk](https://github.com/igor-petruk) for raising this issue.
## v1.19.0
### Changed
- MSRV (minimum supported rust version) is now 1.76.0 ([#208](https://github.com/tommilligan/mdbook-admonish/pull/208))
### Fixed
- Fixed blocks not rendering correctly in indented list items. Thanks to [@JorelAli](https://github.com/JorelAli) for the bug report! ([#224](https://github.com/tommilligan/mdbook-admonish/pull/224))
## v1.18.0
### Changed
- Add ARIA attributes to generated blocks. Thanks to [@toastal](https://github.com/toastal) for suggesting this feature! ([#195](https://github.com/tommilligan/mdbook-admonish/pull/195))
- Note: This subtly alters the emitted HTML, and could cause additional styles applied to blocks to break. Native `mdbook-admonish` styles are not affected.
### Fixed
- Fixed some valid configurations producing TOML serialization errors. Thanks to [@DianaNites](https://github.com/DianaNites) for reporting this! ([#197](https://github.com/tommilligan/mdbook-admonish/pull/197))
## v1.17.1
### Fixed
- Removed a stray debug statement ([#186](https://github.com/tommilligan/mdbook-admonish/pull/186))
## v1.17.0
### Changed
- Blocks should have key-value options separated by commas. Existing syntax remains is supported for back-compatibility. See [the documentation on Additional Options](https://tommilligan.github.io/mdbook-admonish/#additional-options) for more details ([#181](https://github.com/tommilligan/mdbook-admonish/pull/181))
### Fixed
- Titles contining `=` will now render correctly. Thanks to [@s00500](https://github.com/s00500) for the bug report! ([#181](https://github.com/tommilligan/mdbook-admonish/pull/181))
## v1.16.0
### Changed
- MSRV (minimum supported rust version) is now 1.74.0 ([#175](https://github.com/tommilligan/mdbook-admonish/pull/175))
- `custom` directives should now be configured under the `directive.custom` option. Existing `custom` configurations are supported for back compatibility ([#179](https://github.com/tommilligan/mdbook-admonish/pull/174))
### Added
- Make blocks `collapsible` on a per-directive basis. Thanks to [@yannickseurin](https://github.com/yannickseurin) for contributing this feature! ([#174](https://github.com/tommilligan/mdbook-admonish/pull/174))
### Fixed
- The `css_id_prefix` option now uses snake case for consistency (kebab case remains supported for back compatibility). Thanks to [@yannickseurin](https://github.com/yannickseurin) for fixing this! ([#173](https://github.com/tommilligan/mdbook-admonish/pull/173))
## 1.15.0
### Added
- Support [custom directives](https://tommilligan.github.io/mdbook-admonish/overview.html#custom-blocks) with the new `mdbook-admonish generate-custom` helper. See the [mdbook-admonish book](https://tommilligan.github.io/mdbook-admonish/overview.html#custom-blocks) for guidance. Thanks to [@Sky9x](https://github.com/Sky9x) for helping design this feature! ([#165](https://github.com/tommilligan/mdbook-admonish/pull/165))
### Fixed
- `additional-css` unix style path normalization. Thanks to [@carlocorradini](https://github.com/carlocorradini) for reporting and fixing! ([#163](https://github.com/tommilligan/mdbook-admonish/pull/163))
## 1.14.0
### Changed
- Styles version updated to `3.0.1`. Run `mdbook-admonish install` to update.
### Added
- You can now set custom CSS ids for admonition blocks with the `id` field. Thanks to [@Sky9x](https://github.com/Sky9x) for contributing this feature! ([#144](https://github.com/tommilligan/mdbook-admonish/pull/144))
- You can also now customize the CSS id prefix with the config option `default.css_id_prefix`
### Fixed
- Improve rendering of blocks in print/PDF view. Thanks to [@csk111165](https://github.com/csk111165) for the report ([#152](https://github.com/tommilligan/mdbook-admonish/issues/152))
- Fix the default titles for `tldr` and `faq` directives looking bad. They now render as `TL;DR` and `FAQ` by default. Thanks [@joshka](https://github.com/joshka) for fixing this! ([#154](https://github.com/tommilligan/mdbook-admonish/pull/154))
## 1.13.1
### Changed
- Bumped internal `mdbook` version to `0.4.35` ([#142](https://github.com/tommilligan/mdbook-admonish/pull/142))
### Fixed
- Relaxed `clap` dependency to fix compilation error when using other `mdbook-*` plugins. Thanks to [@joshka](https://github.com/joshka) for the [report](https://github.com/tommilligan/mdbook-admonish/pull/141)! ([#142](https://github.com/tommilligan/mdbook-admonish/pull/142))
## 1.13.0
### Changed
- Required styles version is now `^3.0.0` (release `1.13.0`). Run `mdbook-admonish install` to update.
- Internal CSS classnames for directives are now prefixed with `admonish-`, so `warning` is now `admonish-warning`. This avoids a conflict with upstream classnames introduced in `mdbook 0.4.35`. Thanks to [@phoenixr-codes](https://github.com/phoenixr-codes) for the report and fix! ([#139](https://github.com/tommilligan/mdbook-admonish/pull/139))
### Fixed
- Some minor inconsistencies in SCSS (and downstream CSS) styles were fixed by adopting Prettier linting ([#138](https://github.com/tommilligan/mdbook-admonish/pull/138))
## 1.12.1
### Fixed
- Panic when searching for an indent in non-ASCII content. Thanks to [@CoralPink](https://github.com/CoralPink) for the report! ([#128](https://github.com/tommilligan/mdbook-admonish/pull/128))
## 1.12.0
### Added
- Admonitions are now supported when indented inside other elements, such as a list. Thanks to [@mattburgess](https://github.com/mattburgess) for the report! ([#124](https://github.com/tommilligan/mdbook-admonish/pull/124))
## 1.11.1
### Fixed
- Reverted internal dependency upgrades that unintentionally increased MSRV from 1.66.0 in 1.11.0
## 1.11.0 (yanked)
**Note:** This release has been yanked.
It unintentionally increased the MSRV from 1.66.0
### Changed
- `gnu` prebuilt binaries are now built on `ubuntu-20.04` to match `mdbook` binaries. Thanks to [@eitsupi](https://github.com/eitsupi) for the fix! ([#118](https://github.com/tommilligan/mdbook-admonish/pull/118))
### Added
- `aarch64-unknown-linux-musl` prebuilt binary now available ([#119](https://github.com/tommilligan/mdbook-admonish/pull/119))
## 1.10.2
### Fixed
- Fixed `cargo install mdbook-admonish` failing due to an internal dependency mismatch with `mdbook` ([#115](https://github.com/tommilligan/mdbook-admonish/pull/115))
## 1.10.1
### Fixed
- Only restyle `summary` elements generated by `mdbook-admonish`. Thanks to [@ImUrX](https://github.com/ImUrX) for the report and fix! ([#112](https://github.com/tommilligan/mdbook-admonish/pull/112))
## 1.10.0
### Changed
- MSRV (minimum supported rust version) is now 1.66.0 for mdbook v0.4.32 ([#109](https://github.com/tommilligan/mdbook-admonish/pull/109))
### Added
- Support `mdbook test` running doctests inside admonish blocks. Opt-in to this by setting `renderer.test.action_mode = "strip"` ([#109](https://github.com/tommilligan/mdbook-admonish/pull/109))
- Log a warning when an invalid admonish block is encountered ([#109](https://github.com/tommilligan/mdbook-admonish/pull/109))
### Fixed
- Document all `book.toml` configuration options [in the reference](https://tommilligan.github.io/mdbook-admonish/reference.html), some of which were previously undocumened ([#109](https://github.com/tommilligan/mdbook-admonish/pull/109))
## 1.9.0
### Changed
- Styles version updated to `2.0.1`. Run `mdbook-admonish install` to update.
- MSRV (minimum supported rust version) is now 1.64.0 for clap v4 ([#79](https://github.com/tommilligan/mdbook-admonish/pull/79))
- More verbose error messages for invalid TOML configurations ([#79](https://github.com/tommilligan/mdbook-admonish/pull/79))
### Added
- User can set book-wide default for title and collapsible properties ([#84](https://github.com/tommilligan/mdbook-admonish/pull/84)), thanks to [@ShaunSHamilton](https://github.com/ShaunSHamilton)
### Fixed
- Custom installation and CSS directories are now normalized ([#49](https://github.com/tommilligan/mdbook-admonish/pull/49))
- Fix title bars with no text rendering badly ([#83](https://github.com/tommilligan/mdbook-admonish/pull/83)), thanks to [@ShaunSHamilton](https://github.com/ShaunSHamilton)
- Better error message display on crash ([#48](https://github.com/tommilligan/mdbook-admonish/pull/48))
- Better support for commonmark code fence syntax ([#88](https://github.com/tommilligan/mdbook-admonish/pull/88), [#89](https://github.com/tommilligan/mdbook-admonish/pull/89))
## 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 collapsible 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
- Bumped locked dependency versions (mdbook v0.4.18)
### Packaging
- Support building and releasing binary artefacts.
## 1.4.0
### Added
- Additional classnames can be specified using `directive.classname` syntax
- Support removing the title bar entirely
### Fixed
- Removed superfluous empty `<p>` tags in output
## 1.3.3
### Fixed
- Fixed compilation failure with no default features
- MSRV (minimum supported rust version) documented as 1.58.0
## 1.3.2
### Fixed
- Fixed incorrect admonition title/panic when terminating with non-ascii characters
- Updated readme to note double-JSON string escapes
## 1.3.1
### Fixed
- Flattened indentation of generated HTML, otherwise it's styled as a markdown code block
- Fixed edge cases where the info string changes length when parsed, causing title/body to be incorrectly split
## 1.3.0 (yanked)
**Note:** This release has been yanked.
It unintentionally introduced a serious parsing bug.
### Added
- Add additional examples and images in readme
- Allow markdown styling in title content
### Fixed
- Fix HTML being stripping from body content
## 1.2.0
### Added
- Support custom title text
## 1.1.0
### Added
- CSS rules for the builtin mdbook themes, to adjust card background color
## 1.0.1

31
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,31 @@
# Contributing
## Workflow
Please submit a PR from a **new branch** in your fork.
Please do not submit a PR from your fork's `main` branch, as it makes collaborating on/editing the branch a pain.
## Project design
- Compiled CSS styles are built and committed from SCSS sources. See the `compile_assets` folder for details.
- `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.

2460
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,16 @@
[package]
name = "mdbook-admonish"
version = "1.0.1"
version = "1.20.0"
edition = "2021"
rust-version = "1.76.0"
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"]
keywords = ["mdbook", "markdown", "material", "design", "ui"]
readme = "README.md"
[[bin]]
@@ -17,26 +18,40 @@ name = "mdbook-admonish"
path = "src/bin/mdbook-admonish.rs"
required-features = ["cli"]
[profile.release]
strip = true
[lib]
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 = { version = "1.0.79", optional = true }
toml_edit = { version = "0.13.4", optional = true }
anyhow = "1.0.86"
# Note: clap 4.4 increases MSRV to 1.70.0 (2023-06-01)
# To use MSRV supported dependencies, install using the lockfile with
# `cargo install mdbook-admonish --locked`
clap = { version = "4.5", default-features = false, features = ["std", "derive"], optional = true }
env_logger = { version = "0.11", default-features = false, optional = true }
log = "0.4.21"
mdbook = "0.4.40"
once_cell = "1.19.0"
path-slash = "0.2.1"
pulldown-cmark = "0.13"
regex = "1.10.5"
semver = "1.0.23"
serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.117"
toml = "0.8.14"
toml_edit = { version = "0.22.14", optional = true }
hex_color = { version = "3.0.0", features = ["serde"] }
[dev-dependencies]
pretty_assertions = "1.1.0"
pretty_assertions = "1.4.0"
[features]
default = ["cli", "cli-install"]
# Enable the command line binary
cli = ["clap", "env_logger", "log", "serde_json"]
cli = ["clap", "env_logger"]
# Enable installation of files and configuration
cli-install = ["toml_edit"]

112
README.md
View File

@@ -1,7 +1,9 @@
# 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.
[![Latest version](https://img.shields.io/crates/v/mdbook-admonish.svg)](https://crates.io/crates/mdbook-admonish)
[![docs.rs](https://img.shields.io/badge/docs-available-brightgreen)](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.
It turns this:
@@ -13,7 +15,17 @@ A beautifully styled message.
into this:
![Simple Message](simple-message.png)
![Simple Message](img/simple-message.png)
## Examples
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.
Projects using mdbook-admonish include:
- [The Rhai Book](https://rhai.rs/book/) ([source](https://github.com/rhaiscript/book))
- [The Trunk Guide](https://trunkrs.dev/guide/) ([source](https://github.com/trunk-rs/trunk))
- [PRQL language book](https://prql-lang.org/book/) ([source](https://github.com/PRQL/prql))
## Usage
@@ -25,15 +37,9 @@ My example is the best!
```
````
You can also leave out the admonition type, in which case it will default to `note`:
![Best Example](img/best-example.png)
````
```admonish
A plain note.
```
````
See the [mkdocs-material docs](https://squidfunk.github.io/mkdocs-material/reference/admonitions/#supported-types) for a list of supported admonitions. You'll fine:
See the [reference page](https://tommilligan.github.io/mdbook-admonish/reference.html) for a list of supported admonitions. You'll find:
- `info`
- `warning`
@@ -42,17 +48,40 @@ See the [mkdocs-material docs](https://squidfunk.github.io/mkdocs-material/refer
and quite a few more!
You can also leave out the admonition type altogether, in which case it will default to `note`:
````
```admonish
A plain note.
```
````
![Plain Note](img/plain-note.png)
### Additional Options
See the [`mdbook-admonish` book](https://tommilligan.github.io/mdbook-admonish/) for additional options, such as:
- Custom titles
- Custom styling
- Collapsible blocks
## Installation
Install the tool:
```bash
cargo install mdbook-admonish
# If you get compilation/installation errors, try a locked installation
cargo install mdbook-admonish --locked
```
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
@@ -77,13 +106,64 @@ Then, build your book as usual:
mdbook path/to/book
```
### Reproducible builds
For a reproducible build suitable for use in CI or scripts, please:
- Pin to a specific version
- Install with lockfile dependencies
- Always install the latest CSS assets
```bash
cargo install mdbook-admonish --vers "1.5.0" --locked
mdbook-admonish install path/to/your/book
```
The Minimum Supported Rust Version (MSRV) is documented in `Cargo.toml`, and noted in the `CHANGELOG.md`. We aims to support around six months of stable Rust.
### Updates
**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`.
### Process included files
You can ensure that content inlined with `{{#include}}` is also processed by [setting the `after` option](https://rust-lang.github.io/mdBook/format/configuration/preprocessors.html#require-a-certain-order):
```toml
[preprocessor.admonish]
after = ["links"]
```
This will expand `include` directives, before expanding `admonish` blocks.
### 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
- Compiled CSS styles are built and committed from SCSS sources. See the `compile_assets` folder for details.
- `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.
See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines on developing.
## Thanks

2
book/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/book
/mdbook-admonish.css

25
book/book.toml Normal file
View File

@@ -0,0 +1,25 @@
[book]
authors = ["Tom Milligan"]
language = "en"
multilingual = false
src = "src"
title = "The mdbook-admonish book"
git-repository-url = "https://github.com/tommilligan/mdbook-admonish"
[preprocessor]
[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "3.1.0" # do not edit: managed by `mdbook-admonish install`
[preprocessor.admonish.directive.custom]
expensive = { icon = "./money-bag.svg", color = "#24ab38" }
[preprocessor.toc]
command = "mdbook-toc"
renderer = ["html"]
[output]
[output.html]
additional-css = ["./mdbook-admonish.css", "./mdbook-admonish-custom.css"]

View File

@@ -0,0 +1,20 @@
:root {
--md-admonition-icon--admonish-expensive: url("data:image/svg+xml;charset=utf-8,<svg width='800px' height='800px' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --><!-- ref: https://www.svgrepo.com/svg/526038/money-bag --><!-- Used under CC Attribution License; Author: Solar Icons --><path fill-rule='evenodd' clip-rule='evenodd' d='M12.052 1.25H11.948C11.0495 1.24997 10.3003 1.24995 9.70552 1.32991C9.07773 1.41432 8.51093 1.59999 8.05546 2.05546C7.59999 2.51093 7.41432 3.07773 7.32991 3.70552C7.27259 4.13189 7.25637 5.15147 7.25179 6.02566C5.22954 6.09171 4.01536 6.32778 3.17157 7.17157C2 8.34315 2 10.2288 2 14C2 17.7712 2 19.6569 3.17157 20.8284C4.34314 22 6.22876 22 9.99998 22H14C17.7712 22 19.6569 22 20.8284 20.8284C22 19.6569 22 17.7712 22 14C22 10.2288 22 8.34315 20.8284 7.17157C19.9846 6.32778 18.7705 6.09171 16.7482 6.02566C16.7436 5.15147 16.7274 4.13189 16.6701 3.70552C16.5857 3.07773 16.4 2.51093 15.9445 2.05546C15.4891 1.59999 14.9223 1.41432 14.2945 1.32991C13.6997 1.24995 12.9505 1.24997 12.052 1.25ZM15.2479 6.00188C15.2434 5.15523 15.229 4.24407 15.1835 3.9054C15.1214 3.44393 15.0142 3.24644 14.8839 3.11612C14.7536 2.9858 14.5561 2.87858 14.0946 2.81654C13.6116 2.7516 12.964 2.75 12 2.75C11.036 2.75 10.3884 2.7516 9.90539 2.81654C9.44393 2.87858 9.24644 2.9858 9.11612 3.11612C8.9858 3.24644 8.87858 3.44393 8.81654 3.9054C8.771 4.24407 8.75661 5.15523 8.75208 6.00188C9.1435 6 9.55885 6 10 6H14C14.4412 6 14.8565 6 15.2479 6.00188ZM12 9.25C12.4142 9.25 12.75 9.58579 12.75 10V10.0102C13.8388 10.2845 14.75 11.143 14.75 12.3333C14.75 12.7475 14.4142 13.0833 14 13.0833C13.5858 13.0833 13.25 12.7475 13.25 12.3333C13.25 11.9493 12.8242 11.4167 12 11.4167C11.1758 11.4167 10.75 11.9493 10.75 12.3333C10.75 12.7174 11.1758 13.25 12 13.25C13.3849 13.25 14.75 14.2098 14.75 15.6667C14.75 16.857 13.8388 17.7155 12.75 17.9898V18C12.75 18.4142 12.4142 18.75 12 18.75C11.5858 18.75 11.25 18.4142 11.25 18V17.9898C10.1612 17.7155 9.25 16.857 9.25 15.6667C9.25 15.2525 9.58579 14.9167 10 14.9167C10.4142 14.9167 10.75 15.2525 10.75 15.6667C10.75 16.0507 11.1758 16.5833 12 16.5833C12.8242 16.5833 13.25 16.0507 13.25 15.6667C13.25 15.2826 12.8242 14.75 12 14.75C10.6151 14.75 9.25 13.7903 9.25 12.3333C9.25 11.143 10.1612 10.2845 11.25 10.0102V10C11.25 9.58579 11.5858 9.25 12 9.25Z' fill='%231C274C'/></svg>");
}
:is(.admonition):is(.admonish-expensive) {
border-color: #24ab38;
}
:is(.admonish-expensive) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(36, 171, 56, 0.1);
}
:is(.admonish-expensive) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #24ab38;
mask-image: var(--md-admonition-icon--admonish-expensive);
-webkit-mask-image: var(--md-admonition-icon--admonish-expensive);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}

6
book/money-bag.svg Normal file
View File

@@ -0,0 +1,6 @@
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<!-- ref: https://www.svgrepo.com/svg/526038/money-bag -->
<!-- Used under CC Attribution License; Author: Solar Icons -->
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.052 1.25H11.948C11.0495 1.24997 10.3003 1.24995 9.70552 1.32991C9.07773 1.41432 8.51093 1.59999 8.05546 2.05546C7.59999 2.51093 7.41432 3.07773 7.32991 3.70552C7.27259 4.13189 7.25637 5.15147 7.25179 6.02566C5.22954 6.09171 4.01536 6.32778 3.17157 7.17157C2 8.34315 2 10.2288 2 14C2 17.7712 2 19.6569 3.17157 20.8284C4.34314 22 6.22876 22 9.99998 22H14C17.7712 22 19.6569 22 20.8284 20.8284C22 19.6569 22 17.7712 22 14C22 10.2288 22 8.34315 20.8284 7.17157C19.9846 6.32778 18.7705 6.09171 16.7482 6.02566C16.7436 5.15147 16.7274 4.13189 16.6701 3.70552C16.5857 3.07773 16.4 2.51093 15.9445 2.05546C15.4891 1.59999 14.9223 1.41432 14.2945 1.32991C13.6997 1.24995 12.9505 1.24997 12.052 1.25ZM15.2479 6.00188C15.2434 5.15523 15.229 4.24407 15.1835 3.9054C15.1214 3.44393 15.0142 3.24644 14.8839 3.11612C14.7536 2.9858 14.5561 2.87858 14.0946 2.81654C13.6116 2.7516 12.964 2.75 12 2.75C11.036 2.75 10.3884 2.7516 9.90539 2.81654C9.44393 2.87858 9.24644 2.9858 9.11612 3.11612C8.9858 3.24644 8.87858 3.44393 8.81654 3.9054C8.771 4.24407 8.75661 5.15523 8.75208 6.00188C9.1435 6 9.55885 6 10 6H14C14.4412 6 14.8565 6 15.2479 6.00188ZM12 9.25C12.4142 9.25 12.75 9.58579 12.75 10V10.0102C13.8388 10.2845 14.75 11.143 14.75 12.3333C14.75 12.7475 14.4142 13.0833 14 13.0833C13.5858 13.0833 13.25 12.7475 13.25 12.3333C13.25 11.9493 12.8242 11.4167 12 11.4167C11.1758 11.4167 10.75 11.9493 10.75 12.3333C10.75 12.7174 11.1758 13.25 12 13.25C13.3849 13.25 14.75 14.2098 14.75 15.6667C14.75 16.857 13.8388 17.7155 12.75 17.9898V18C12.75 18.4142 12.4142 18.75 12 18.75C11.5858 18.75 11.25 18.4142 11.25 18V17.9898C10.1612 17.7155 9.25 16.857 9.25 15.6667C9.25 15.2525 9.58579 14.9167 10 14.9167C10.4142 14.9167 10.75 15.2525 10.75 15.6667C10.75 16.0507 11.1758 16.5833 12 16.5833C12.8242 16.5833 13.25 16.0507 13.25 15.6667C13.25 15.2826 12.8242 14.75 12 14.75C10.6151 14.75 9.25 13.7903 9.25 12.3333C9.25 11.143 10.1612 10.2845 11.25 10.0102V10C11.25 9.58579 11.5858 9.25 12 9.25Z" fill="#1C274C"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -exuo pipefail
cd "$(dirname "$0")"/../..
if ! mdbook-toc --version; then
cargo install mdbook-toc --version 0.14.1 --force
fi

5
book/src/SUMMARY.md Normal file
View File

@@ -0,0 +1,5 @@
# Summary
- [Overview](./overview.md)
- [Reference](./reference.md)
- [Examples](./examples.md)

15
book/src/examples.md Normal file
View File

@@ -0,0 +1,15 @@
# Examples
## Combining multiple custom properties
Note that the comma `,` is used to seperate custom options.
````
```admonish quote collapsible=true, title='A title that really <span style="color: #e70073">pops</span>'
To really <b><span style="color: #e70073">grab</span></b> your reader's attention.
```
````
```admonish quote collapsible=true, title='A title that really <span style="color: #e70073">pops</span>'
To really <b><span style="color: #e70073">grab</span></b> your reader's attention.
```

244
book/src/overview.md Normal file
View File

@@ -0,0 +1,244 @@
# mdbook-admonish
<!-- toc -->
## Intoduction
[![Latest version](https://img.shields.io/crates/v/mdbook-admonish.svg)](https://crates.io/crates/mdbook-admonish)
[![docs.rs](https://img.shields.io/docsrs/mdbook-admonish)](https://docs.rs/mdbook-admonish)
A preprocessor for [mdbook](https://github.com/rust-lang/mdBook) to add [Material Design](https://material.io/design) admonishments, based on the [mkdocs-material](https://squidfunk.github.io/mkdocs-material/reference/admonitions/) implementation.
It turns this:
````
```admonish info
A beautifully styled message.
```
````
into this:
```admonish info
A beautifully styled message.
```
## Usage
### A basic `admonish` block
Use any [fenced code-block](https://spec.commonmark.org/0.30/#fenced-code-blocks) as you normally would, but annotate it with `admonish <admonition type>`:
````
```admonish example
My example is the best!
```
````
```admonish example
My example is the best!
```
See the [list of directives](./reference.md#directives) for a full list of supported admonitions. You'll find:
- `info`
- `warning`
- `danger`
- `example`
and quite a few more!
You can also leave out the admonition type altogether, in which case it will default to `note`:
````
```admonish
A plain note.
```
````
```admonish
A plain note.
```
### Invalid blocks
By default, if an `admonish` block cannot be parsed, an error will be rendered in the output:
````
```admonish title="\j"
This block will error
```
````
```admonish title="\j"
This block will error
```
You can also configure the build to fail loudly, by setting `on_failure = "bail"` in `book.toml`. See the [configuration reference](./reference.md#booktoml-configuration) for more details.
### Additional Options
You can pass additional options to each block. Options are given like a [TOML Inline Table](https://toml.io/en/v1.0.0#inline-table), as key-value pairs separated by commas.
`mdbook-admonish` parses options by wrapping your options in an inline table before parsing them, so please consult [The TOML Reference](https://toml.io) if you run into any syntax errors. Be aware that:
- Key-value pairs must be separated with a comma `,`
- TOML escapes must be escaped again - for instance, write `\"` as `\\"`.
- For complex strings such as HTML, you may want to use a [literal string](https://toml.io/en/v1.0.0#string) to avoid complex escape sequences
Note that some options can be passed globally, through the `default` section in `book.toml`. See the [configuration reference](./reference.md#booktoml-configuration) for more details.
#### Custom title
A custom title can be provided:
````
```admonish warning title="Data loss"
The following steps can lead to irrecoverable data corruption.
```
````
```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 title=""
This will take a while, go and grab a drink of water.
```
````
```admonish success title=""
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 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 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>, `*`.
```
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!"
```
~~~
````
```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 title="Stylish", class="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 CSS ID
If you want to customize the CSS `id` field, set `id="custom-id"`.
This will ignore [`default.css_id_prefix`](reference.md#default).
The default id is a normalized version of the admonishment's title,
prefixed with the `default.css_id_prefix`,
with an appended number if multiple blocks would have the same id.
Setting the `id` field will _ignore_ all other ids and the duplicate counter.
````
```admonish info title="My Info", id="my-special-info"
Link to this block with `#my-special-info` instead of the default `#admonition-my-info`.
```
````
#### Collapsible
For a block to be initially collapsible, and then be openable, set `collapsible=true`:
````
```admonish title="Sneaky", collapsible=true
Content will be hidden initially.
```
````
Will yield something like the following HTML, which you can then apply styles to:
```admonish title="Sneaky", collapsible=true
Content will be hidden initially.
```
### Custom blocks
You can add new block types via the `book.toml` config:
```toml
# book.toml
[[preprocessor.admonish.custom]]
directive = "expensive"
icon = "./money-bag.svg"
color = "#24ab38"
aliases = ["money", "cash", "budget"]
```
You must then generate the relevant CSS file, and reference it in the `output.html` section.
`mdbook-admonish` has a helper to quickly do this for you:
```bash
# Generates a file at ./mdbook-admonish-custom.css with your styles in
$ mdbook-admonish generate-custom ./mdbook-admonish-custom.css
```
```toml
# book.toml
[output.html]
# Reference the new file, so it's bundled in with book styles
additional-css = ["./mdbook-admonish.css", "./mdbook-admonish-custom.css"]
```
You can then reference the new directive (or alias) like usual in your blocks.
````
```admonish expensive
Remember, this operation costs money!
```
````
```admonish expensive
Remember, this operation costs money!
```
You can also set a default `title`. See the [Reference](./reference.md) page for more details.

215
book/src/reference.md Normal file
View File

@@ -0,0 +1,215 @@
# Reference
<!-- toc -->
## `book.toml` configuration
See below for all configuration options available to add in `book.toml`.
The options should all be nested under `preprocessor.admonish`; for example:
```toml
[preprocessor.admonish]
on_failure = "bail"
[preprocessor.admonish.default]
collapsible = true
[preprocessor.admonish.renderer.test]
render_mode = "strip"
```
### `on_failure`
Optional. Default value: `continue`.
The action to take when an invalid `admonish` block is encountered:
- `continue` (default): Continue processing future blocks, do not fail the build. If rendering to HTML, an error message will be displayed in the book output.
- `bail`: Abort the build.
### `default`
Optional.
Default values to use, when not provided in an `admonish` block explicitly.
Subfields:
- `default.title` (optional): Title to use for blocks. Defaults to the directive used in titlecase.
- `default.collapsible` (optional, default: `false`): Make blocks collapsible by default when set to `true`.
- `default.css_id_prefix` (optional, default: `"admonition-"`): The default css id prefix to add to the id of all blocks. Ignored on blocks with an `id` field.
### `renderer`
````admonish tip
It is recommended that you set:
```toml
[preprocessor.admonish.renderer.test]
render_mode = "strip"
```
This allows `mdbook test` to find and test rust examples within `admonish` blocks.
This will be the default behaviour in the next `mdbook-admonish` major version.
````
Optional.
Additional settings to apply, depending on the renderer that is running.
The most common renderers used are:
- `html`: Used by `mdbook build` to build the final book output.
- `test`: Used by `mdbook test` to find and run doctests.
Subfields:
- `renderer.<renderer_name>.render_mode` (optional): The action `mdbook-admonish` should take when running with this renderer.
- Valid values:
- `html`: Convert `admonish` blocks into HTML output.
- `preserve`: Do nothing. Leave the book untouched.
- `strip`: Strip `admonish`-specific syntax, leaving the inner content untouched.
- Default values:
- For the `html` renderer, the default value is `html`.
- For all other renderers, the default value is `preserve`.
### `directive`
Optional.
Settings relating to each type of block.
#### `builtin`
Optional.
Override the settings of a builtin directive.
The subkey of `builtin` is the directive to override. This must be the first directive listed in the [Directives](#directives) section below, e.g. `warning` (not `caution` or other aliases).
```toml
[preprocessor.admonish.directive.builtin.warning]
collapsible = true
```
Subfields:
- `collapsible` (optional): The default boolean value of the collapsible property for this type of block.
#### `custom`
Optional.
Additional types of block to support. The subkey of `custom` is the new directive to support.
You must run `mdbook-admonish generate-custom` after updating these values, to generate the correct styles.
```toml
[preprocessor.admonish.directive.custom.expensive]
icon = "./money-bag.svg"
color = "#24ab38"
collapsible = true
aliases = ["money", "cash", "budget"]
```
Subfields:
- `icon`: A filepath relative to the book root to load an SVG icon from.
- `color`: An RGB hex encoded color to use for the icon.
- `collapsible` (optional): The default boolean value of the collapsible property for this type of block.
- `aliases` (optional): One or more alternative directives to use this block.
- `title` (optional): The default title for this type of block. If not specified, defaults to the directive in title case. To give each alias a custom title, add multiple custom blocks.
### `command`
Required.
Used by `mdbook` to know how to call the `mdbook-admonish` plugin.
Running this command with the `--version` flag from your shell should work, for the plugin to function.
### `assets_version`
Optional.
This is automatically updated by `mdbook-admonish install` and should not be edited.
## Directives
All supported directives are listed below.
Custom directives can be added via the `custom` config option above.
`note`
```admonish note
Rust is a multi-paradigm, general-purpose programming language designed for performance and safety, especially safe concurrency.
```
`abstract`, `summary`, `tldr`
```admonish abstract
Rust is a multi-paradigm, general-purpose programming language designed for performance and safety, especially safe concurrency.
```
`info`, `todo`
```admonish info
Rust is a multi-paradigm, general-purpose programming language designed for performance and safety, especially safe concurrency.
```
`tip`, `hint`, `important`
```admonish tip
Rust is a multi-paradigm, general-purpose programming language designed for performance and safety, especially safe concurrency.
```
`success`, `check`, `done`
```admonish success
Rust is a multi-paradigm, general-purpose programming language designed for performance and safety, especially safe concurrency.
```
`question`, `help`, `faq`
```admonish question
Rust is a multi-paradigm, general-purpose programming language designed for performance and safety, especially safe concurrency.
```
`warning`, `caution`, `attention`
```admonish warning
Rust is a multi-paradigm, general-purpose programming language designed for performance and safety, especially safe concurrency.
```
`failure`, `fail`, `missing`
```admonish failure
Rust is a multi-paradigm, general-purpose programming language designed for performance and safety, especially safe concurrency.
```
`danger`, `error`
```admonish danger
Rust is a multi-paradigm, general-purpose programming language designed for performance and safety, especially safe concurrency.
```
`bug`
```admonish bug
Rust is a multi-paradigm, general-purpose programming language designed for performance and safety, especially safe concurrency.
```
`example`
```admonish example
Rust is a multi-paradigm, general-purpose programming language designed for performance and safety, especially safe concurrency.
```
`quote`, `cite`
```admonish quote
Rust is a multi-paradigm, general-purpose programming language designed for performance and safety, especially safe concurrency.
```

6
book/v2.md Normal file
View File

@@ -0,0 +1,6 @@
# Notes for a v2 release
## Default behaviour changes
- `on_failure` default changed from `continue` to `bail`
- `preprocessor.admonish.renderer.test.render_mode` default changed from `preserve` to `strip`

View File

@@ -1,3 +1,5 @@
/node_modules
*.css
*.css.map
.yarn/
.pnp*

View File

@@ -4,9 +4,17 @@
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "sass --no-source-map scss/mdbook-admonish.scss ../src/bin/assets/mdbook-admonish.css"
"build": "yarn run build-prod && yarn run build-custom-expected",
"build-prod": "sass --no-source-map scss/mdbook-admonish.scss ../src/bin/assets/mdbook-admonish.css",
"build-custom-expected": "sass --no-source-map scss/mdbook-admonish-custom-expected.scss ../src/test_data/mdbook-admonish-custom-expected.css",
"lint": "prettier --check .",
"fix": "prettier --write ."
},
"dependencies": {
"sass": "^1.49.7"
}
},
"devDependencies": {
"prettier": "^3.0.3"
},
"packageManager": "yarn@4.8.1+sha512.bc946f2a022d7a1a38adfc15b36a66a3807a67629789496c3714dd1703d2e6c6b1c69ff9ec3b43141ac7a1dd853b7685638eb0074300386a59c18df351ef8ff6"
}

View File

@@ -22,6 +22,7 @@
@use "sass:color";
@use "sass:list";
@use "./lib";
// ----------------------------------------------------------------------------
// Variables
@@ -30,45 +31,48 @@
/// Admonition flavours
$admonitions: (
// pencil
note: $clr-blue-a200 "<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>",
admonish-note: $clr-blue-a200
"<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>",
// clipboard-text
abstract summary tldr: $clr-light-blue-a400 "<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>",
admonish-abstract admonish-summary admonish-tldr: $clr-light-blue-a400
"<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>",
// information
info todo: $clr-cyan-a700 "<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>",
admonish-info admonish-todo: $clr-cyan-a700
"<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>",
// fire
tip hint important: $clr-teal-a700 "<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>",
admonish-tip admonish-hint admonish-important: $clr-teal-a700
"<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>",
// check-bold
success check done: $clr-green-a700 "<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>",
admonish-success admonish-check admonish-done: $clr-green-a700
"<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>",
// help-circle
question help faq: $clr-light-green-a700 "<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>",
admonish-question admonish-help admonish-faq: $clr-light-green-a700
"<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>",
// alert
warning caution attention: $clr-orange-a400 "<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>",
admonish-warning admonish-caution admonish-attention: $clr-orange-a400
"<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>",
// close-thick
failure fail missing: $clr-red-a200 "<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>",
admonish-failure admonish-fail admonish-missing: $clr-red-a200
"<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>",
// lighting-bold
danger error: $clr-red-a400 "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>",
admonish-danger admonish-error: $clr-red-a400
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>",
// bug
bug: $clr-pink-a400 "<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>",
admonish-bug: $clr-pink-a400
"<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>",
// format-list-numbered
example: $clr-deep-purple-a200 "<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>",
admonish-example: $clr-deep-purple-a200
"<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>",
// format-quote-close
quote cite: $clr-grey "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>",
admonish-quote admonish-cite: $clr-grey
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>"
) !default;
// ----------------------------------------------------------------------------
// Rules: layout
// ----------------------------------------------------------------------------
// Admonition variables
:root {
@each $names, $props in $admonitions {
--md-admonition-icon--#{nth($names, 1)}:
url("data:image/svg+xml;charset=utf-8,#{nth($props, 2)}");
}
}
// ----------------------------------------------------------------------------
// Static content - base for all admonitions
// ----------------------------------------------------------------------------
// Admonition
:is(.admonition) {
@@ -84,10 +88,13 @@ $admonitions: (
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);;
box-shadow:
0 0.2rem 1rem rgba(0, 0, 0, 0.05),
0 0 0.1rem rgba(0, 0, 0, 0.1);
// [print]: Omit shadow as it may lead to rendering errors
// During print mode in browser, pdf renderer
@media print {
// Omit shadow as it may lead to rendering errors
box-shadow: none;
}
@@ -114,18 +121,54 @@ $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: 1rem;
&: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) {
:is(.admonition-title, summary.admonition-title) {
position: relative;
min-height: 4rem;
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;
// Always print title bar tint
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
// Compatilility with rendering markdown inside the content
display: flex;
// Compatilility with rendering markdown inside the content
& p {
margin: 0;
}
// Adjust spacing for title-only admonitions
html &:last-child {
@@ -136,10 +179,13 @@ $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;
// Always print icon
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
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;
@@ -148,41 +194,89 @@ $admonitions: (
-webkit-mask-size: contain;
content: "";
}
// Show anchor link on hover over title
&:hover a.admonition-anchor-link {
display: initial;
}
}
// ----------------------------------------------------------------------------
// Rules: flavours
// ----------------------------------------------------------------------------
// During print mode in browser, pdf renderer
@media print {
// Expand collapsed sections to show details
details.admonition::details-content {
display: contents;
}
}
@each $names, $props in $admonitions {
$name: list.nth($names, 1);
$tint: list.nth($props, 1);
// Admonition flavour selectors
$flavours: ();
@each $name in $names {
$flavours: list.join($flavours, ".#{$name}", $separator: comma);
summary.admonition-title {
details.admonition > &::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;
}
// Admonition flavour
:is(.admonition):is(#{$flavours}) {
border-color: $tint;
details[open].admonition > &::after {
transform: rotate(90deg);
}
// Admonition flavour title
:is(#{$flavours}) > :is(.admonition-title, summary) {
background-color: color.adjust($tint, $alpha: -0.9);
border-color: $tint;
// Hide details marker for Safari that loves to show it anyway
// ref: https://github.com/tommilligan/mdbook-admonish/pull/185
&::-webkit-details-marker {
display: none;
}
}
// Admonition icon
&::before {
background-color: $tint;
mask-image: var(--md-admonition-icon--#{$name});
-webkit-mask-image: var(--md-admonition-icon--#{$name});
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
:root {
--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>");
}
// Generate rules for each specified admonition variant
@include lib.from-admonitions($admonitions);
// ----------------------------------------------------------------------------
// Rules: themes
// ----------------------------------------------------------------------------
//
// One rule per builtin theme in mdbook, overriding the default fg/bg if matched
//
// This must be after the variant CSS, so it can override they styles set there
.navy {
& :is(.admonition) {
background-color: var(--sidebar-bg);
}
}
.ayu,
.coal {
& :is(.admonition) {
background-color: var(--theme-hover);
}
}
.rust {
& :is(.admonition) {
background-color: var(--sidebar-bg);
color: var(--sidebar-fg);
}
& .admonition-anchor-link {
&:link,
&:visited {
color: var(--sidebar-fg);
}
}
}

View File

@@ -0,0 +1,52 @@
@use "sass:color";
@use "sass:list";
@mixin from-admonitions($admonitions) {
// ----------------------------------------------------------------------------
// Rules: layout
// ----------------------------------------------------------------------------
// Admonition variables
:root {
@each $names, $props in $admonitions {
--md-admonition-icon--#{nth($names, 1)}: url("data:image/svg+xml;charset=utf-8,#{nth($props, 2)}");
}
}
// ----------------------------------------------------------------------------
// Rules: flavours
// ----------------------------------------------------------------------------
@each $names, $props in $admonitions {
$name: list.nth($names, 1);
$tint: list.nth($props, 1);
// Admonition flavour selectors
$flavours: ();
@each $name in $names {
$flavours: list.join($flavours, ".#{$name}", $separator: comma);
}
// Admonition flavour
:is(.admonition):is(#{$flavours}) {
border-color: $tint;
}
// Admonition flavour title
:is(#{$flavours}) > :is(.admonition-title, summary.admonition-title) {
background-color: color.adjust($tint, $alpha: -0.9);
// Admonition icon
&::before {
background-color: $tint;
mask-image: var(--md-admonition-icon--#{$name});
-webkit-mask-image: var(--md-admonition-icon--#{$name});
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
// This file aims to generate the subset of CSS specific to a single admonition directive.
//
// This is used for unit test data in the rust css generation module.
@use "sass:color";
@use "sass:list";
@use "./lib";
@import "./material-color";
$admonitions: (
admonish-note: $clr-blue-a200
"<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>",
) !default;
// Generate rules for each specified admonition variant
@include lib.from-admonitions($admonitions);

File diff suppressed because it is too large Load Diff

BIN
img/best-example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
img/code-bug.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
img/complex-message.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
img/data-loss.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
img/no-title-bar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
img/plain-note.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

2
integration/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/book
/mdbook-admonish.css

26
integration/book.toml Normal file
View File

@@ -0,0 +1,26 @@
[book]
authors = ["Tom Milligan"]
language = "en"
multilingual = false
src = "src"
title = "mdbook-admonish-integration"
[preprocessor]
[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "3.1.0" # do not edit: managed by `mdbook-admonish install`
after = ["links"]
[[preprocessor.admonish.custom]]
directive = "frog"
icon = "./frog.svg"
color = "#9004CC"
[preprocessor.admonish.renderer.test]
render_mode = "strip"
[output]
[output.html]
additional-css = ["./mdbook-admonish.css", "./mdbook-admonish-custom.css"]

View File

@@ -0,0 +1,26 @@
[book]
authors = ["Tom Milligan"]
language = "en"
multilingual = false
src = "src"
title = "mdbook-admonish-integration"
[preprocessor]
[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "3.1.0" # do not edit: managed by `mdbook-admonish install`
after = ["links"]
[[preprocessor.admonish.custom]]
directive = "frog"
icon = "./frog.svg"
color = "#9004CC"
[preprocessor.admonish.renderer.test]
render_mode = "strip"
[output]
[output.html]
additional-css = ["./mdbook-admonish.css", "./mdbook-admonish-custom.css"]

View File

@@ -0,0 +1,164 @@
<h1 id="chapter-1"><a class="header" href="#chapter-1">Chapter 1</a></h1>
<div id="admonition-what-is-this" class="admonition admonish-abstract" role="note" aria-labelledby="admonition-what-is-this-title">
<div class="admonition-title">
<div id="admonition-what-is-this-title">
<p>What <i>is</i> this?</p>
</div>
<a class="admonition-anchor-link" href="#admonition-what-is-this"></a>
</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 id="admonition-note" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-title">
<div class="admonition-title">
<div id="admonition-note-title">
<p>Note</p>
</div>
<a class="admonition-anchor-link" href="#admonition-note"></a>
</div>
<div>
<p>Simples</p>
</div>
</div>
<div id="admonition-frog" class="admonition admonish-frog" role="note" aria-labelledby="admonition-frog-title">
<div class="admonition-title">
<div id="admonition-frog-title">
<p>Frog</p>
</div>
<a class="admonition-anchor-link" href="#admonition-frog"></a>
</div>
<div>
<p>Custom frog directive</p>
</div>
</div>
<div id="admonition-default" class="admonition admonish-warning" role="note">
<div>
<p>No title, only body</p>
</div>
</div>
<div id="admonition-error-rendering-admonishment" class="admonition admonish-bug" role="note" aria-labelledby="admonition-error-rendering-admonishment-title">
<div class="admonition-title">
<div id="admonition-error-rendering-admonishment-title">
<p>Error rendering admonishment</p>
</div>
<a class="admonition-anchor-link" href="#admonition-error-rendering-admonishment"></a>
</div>
<div>
<p>Failed with:</p>
<pre><code class="language-log">'title="' is not a valid directive or TOML key-value pair.
TOML parsing error: TOML parse error at line 1, column 21
|
1 | config = { title=" }
| ^
invalid basic string
</code></pre>
<p>Original markdown input:</p>
<pre><code class="language-markdown">```admonish title="
No title, only body
```
</code></pre>
</div>
</div>
<details id="admonition-note-1" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-1-title">
<summary class="admonition-title">
<div id="admonition-note-1-title">
<p>Note</p>
</div>
<a class="admonition-anchor-link" href="#admonition-note-1"></a>
</summary>
<div>
<p>Hidden on load</p>
</div>
</details>
<div id="admonition-warning" class="admonition admonish-warning" role="note" aria-labelledby="admonition-warning-title">
<div class="admonition-title">
<div id="admonition-warning-title">
<p>Warning</p>
</div>
<a class="admonition-anchor-link" href="#admonition-warning"></a>
</div>
<div>
<p>This is a commonly shared warning!</p>
</div>
</div>
<div id="admonition-note-2" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-2-title">
<div class="admonition-title">
<div id="admonition-note-2-title">
<p>Note</p>
</div>
<a class="admonition-anchor-link" href="#admonition-note-2"></a>
</div>
<div>
<pre><code class="language-bash">Nested code block
</code></pre>
</div>
</div>
<div id="admonition-note-3" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-3-title">
<div class="admonition-title">
<div id="admonition-note-3-title">
<p>Note</p>
</div>
<a class="admonition-anchor-link" href="#admonition-note-3"></a>
</div>
<div>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>let x = 10;
x = 20;
<span class="boring">}</span></code></pre></pre>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>let x = 10;
let x = 20;
<span class="boring">}</span></code></pre></pre>
</div>
</div>
<p>In a list:</p>
<ol>
<li>
<p>Thing one</p>
<pre><code class="language-sh">Thing one
</code></pre>
</li>
<li>
<p>Thing two</p>
<div id="admonition-note-4" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-4-title">
<div class="admonition-title">
<div id="admonition-note-4-title">
<p>Note</p>
</div>
<a class="admonition-anchor-link" href="#admonition-note-4"></a>
</div>
<div>
<p>Thing two</p>
</div>
</div>
<ol>
<li>
<p>Thing two nested</p>
<div id="admonition-note-5" class="admonition admonish-note" role="note" aria-labelledby="admonition-note-5-title">
<div class="admonition-title">
<div id="admonition-note-5-title">
<p>Note</p>
</div>
<a class="admonition-anchor-link" href="#admonition-note-5"></a>
</div>
<div>
<p>Thing two nested (should not be a code block)
Regression tests for https://github.com/tommilligan/mdbook-admonish/issues/223</p>
</div>
</div>
</li>
</ol>
</li>
<li>
<p>Thing three</p>
<pre><code class="language-sh">Thing three
</code></pre>
</li>
</ol>

View File

@@ -0,0 +1,20 @@
:root {
--md-admonition-icon--admonish-frog: url("data:image/svg+xml;charset=utf-8,<svg height='800px' width='800px' version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 512 512' xml:space='preserve'><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --><!-- ref: https://www.svgrepo.com/svg/232692/frog --><!-- Used under CC0 Licence --><path d='M472,268.109H40c-4.418,0-8,3.582-8,8s3.582,8,8,8h432c4.418,0,8-3.582,8-8S476.418,268.109,472,268.109z'/><path d='M320,300.109H192c-4.418,0-8,3.582-8,8s3.582,8,8,8h128c4.418,0,8-3.582,8-8S324.418,300.109,320,300.109z'/><path d='M483.105,224.949c-9.717-8.094-13.741-11.93-11.921-21.398c5.588-10.328,8.816-22.464,8.816-35.441c0-21.712-9.036-41.065-23.058-53.523c-8.67-14.834-24.283-25.463-43.124-29.246c-20.05-4.027-40.644,0.458-55.09,11.994C321.682,126.917,290,140.109,256,140.109s-65.682-13.192-102.728-42.776c-14.446-11.535-35.04-16.02-55.09-11.994c-18.841,3.783-34.454,14.412-43.124,29.246C41.036,127.044,32,146.397,32,168.109c0,12.977,3.228,25.113,8.816,35.441c1.82,9.468-2.204,13.305-11.921,21.398C16.701,235.106,0,249.018,0,284.109c0,30.939,44.4,68.017,85.951,92.4c55.883,32.792,117.864,51.6,170.049,51.6s114.166-18.808,170.049-51.6C467.6,352.126,512,315.049,512,284.109C512,249.018,495.299,235.106,483.105,224.949z M464,168.109c0,10.585-2.696,20.437-7.312,28.661c-0.078,0.131-0.148,0.266-0.219,0.402c-7.916,13.829-21.31,22.937-36.47,22.937c-24.262,0-44-23.327-44-52s19.738-52,44-52c9.282,0,17.896,3.423,25.002,9.245c0.351,0.356,0.731,0.674,1.135,0.955C456.965,135.79,464,150.995,464,168.109z M65.857,126.314c0.407-0.282,0.791-0.603,1.144-0.962c7.105-5.82,15.718-9.242,24.999-9.242c24.262,0,44,23.327,44,52s-19.738,52-44,52c-15.159,0-28.552-9.107-36.468-22.935c-0.072-0.138-0.143-0.275-0.222-0.407C50.696,188.545,48,178.694,48,168.11C48,150.999,55.032,135.795,65.857,126.314z M417.951,362.709c-52.713,30.933-113.256,49.4-161.951,49.4s-109.238-18.467-161.951-49.4C48.095,335.744,16,303.422,16,284.109c0-27.596,11.761-37.393,23.135-46.867c5.547-4.621,11.494-9.583,15.006-16.426c10.334,9.552,23.514,15.293,37.858,15.293c33.084,0,60-30.505,60-68c0-33.803-21.879-61.914-50.429-67.125c15.396-3.025,30.975,0.273,41.717,8.852c40.075,32.002,74.837,46.273,112.712,46.273s72.637-14.271,112.712-46.273c10.743-8.579,26.321-11.878,41.717-8.852C381.879,106.195,360,134.307,360,168.109c0,37.495,26.916,68,60,68c14.344,0,27.524-5.741,37.858-15.293c3.512,6.842,9.459,11.805,15.006,16.426C484.239,246.717,496,256.513,496,284.109C496,303.422,463.905,335.744,417.951,362.709z'/><path d='M92,180.109c8.445,0,36-1.154,36-16s-27.555-16-36-16s-36,1.154-36,16S83.555,180.109,92,180.109z'/><path d='M384,164.109c0,14.846,27.555,16,36,16s36-1.154,36-16s-27.555-16-36-16S384,149.263,384,164.109z'/><path d='M232,228.109c4.418,0,8-3.582,8-8v-8c0-4.418-3.582-8-8-8s-8,3.582-8,8v8C224,224.528,227.582,228.109,232,228.109z'/><path d='M280,228.109c4.418,0,8-3.582,8-8v-8c0-4.418-3.582-8-8-8s-8,3.582-8,8v8C272,224.528,275.582,228.109,280,228.109z'/></svg>");
}
:is(.admonition):is(.admonish-frog) {
border-color: #9004cc;
}
:is(.admonish-frog) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(144, 4, 204, 0.1);
}
:is(.admonish-frog) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #9004cc;
mask-image: var(--md-admonition-icon--admonish-frog);
-webkit-mask-image: var(--md-admonition-icon--admonish-frog);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}

29
integration/frog.svg Normal file
View File

@@ -0,0 +1,29 @@
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" xml:space="preserve">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<!-- ref: https://www.svgrepo.com/svg/232692/frog -->
<!-- Used under CC0 Licence -->
<path d="M472,268.109H40c-4.418,0-8,3.582-8,8s3.582,8,8,8h432c4.418,0,8-3.582,8-8S476.418,268.109,472,268.109z"/>
<path d="M320,300.109H192c-4.418,0-8,3.582-8,8s3.582,8,8,8h128c4.418,0,8-3.582,8-8S324.418,300.109,320,300.109z"/>
<path d="M483.105,224.949c-9.717-8.094-13.741-11.93-11.921-21.398c5.588-10.328,8.816-22.464,8.816-35.441
c0-21.712-9.036-41.065-23.058-53.523c-8.67-14.834-24.283-25.463-43.124-29.246c-20.05-4.027-40.644,0.458-55.09,11.994
C321.682,126.917,290,140.109,256,140.109s-65.682-13.192-102.728-42.776c-14.446-11.535-35.04-16.02-55.09-11.994
c-18.841,3.783-34.454,14.412-43.124,29.246C41.036,127.044,32,146.397,32,168.109c0,12.977,3.228,25.113,8.816,35.441
c1.82,9.468-2.204,13.305-11.921,21.398C16.701,235.106,0,249.018,0,284.109c0,30.939,44.4,68.017,85.951,92.4
c55.883,32.792,117.864,51.6,170.049,51.6s114.166-18.808,170.049-51.6C467.6,352.126,512,315.049,512,284.109
C512,249.018,495.299,235.106,483.105,224.949z M464,168.109c0,10.585-2.696,20.437-7.312,28.661
c-0.078,0.131-0.148,0.266-0.219,0.402c-7.916,13.829-21.31,22.937-36.47,22.937c-24.262,0-44-23.327-44-52s19.738-52,44-52
c9.282,0,17.896,3.423,25.002,9.245c0.351,0.356,0.731,0.674,1.135,0.955C456.965,135.79,464,150.995,464,168.109z M65.857,126.314
c0.407-0.282,0.791-0.603,1.144-0.962c7.105-5.82,15.718-9.242,24.999-9.242c24.262,0,44,23.327,44,52s-19.738,52-44,52
c-15.159,0-28.552-9.107-36.468-22.935c-0.072-0.138-0.143-0.275-0.222-0.407C50.696,188.545,48,178.694,48,168.11
C48,150.999,55.032,135.795,65.857,126.314z M417.951,362.709c-52.713,30.933-113.256,49.4-161.951,49.4
s-109.238-18.467-161.951-49.4C48.095,335.744,16,303.422,16,284.109c0-27.596,11.761-37.393,23.135-46.867
c5.547-4.621,11.494-9.583,15.006-16.426c10.334,9.552,23.514,15.293,37.858,15.293c33.084,0,60-30.505,60-68
c0-33.803-21.879-61.914-50.429-67.125c15.396-3.025,30.975,0.273,41.717,8.852c40.075,32.002,74.837,46.273,112.712,46.273
s72.637-14.271,112.712-46.273c10.743-8.579,26.321-11.878,41.717-8.852C381.879,106.195,360,134.307,360,168.109
c0,37.495,26.916,68,60,68c14.344,0,27.524-5.741,37.858-15.293c3.512,6.842,9.459,11.805,15.006,16.426
C484.239,246.717,496,256.513,496,284.109C496,303.422,463.905,335.744,417.951,362.709z"/>
<path d="M92,180.109c8.445,0,36-1.154,36-16s-27.555-16-36-16s-36,1.154-36,16S83.555,180.109,92,180.109z"/>
<path d="M384,164.109c0,14.846,27.555,16,36,16s36-1.154,36-16s-27.555-16-36-16S384,149.263,384,164.109z"/>
<path d="M232,228.109c4.418,0,8-3.582,8-8v-8c0-4.418-3.582-8-8-8s-8,3.582-8,8v8C224,224.528,227.582,228.109,232,228.109z"/>
<path d="M280,228.109c4.418,0,8-3.582,8-8v-8c0-4.418-3.582-8-8-8s-8,3.582-8,8v8C272,224.528,275.582,228.109,280,228.109z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,20 @@
:root {
--md-admonition-icon--admonish-frog: url("data:image/svg+xml;charset=utf-8,<svg height='800px' width='800px' version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 512 512' xml:space='preserve'><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --><!-- ref: https://www.svgrepo.com/svg/232692/frog --><!-- Used under CC0 Licence --><path d='M472,268.109H40c-4.418,0-8,3.582-8,8s3.582,8,8,8h432c4.418,0,8-3.582,8-8S476.418,268.109,472,268.109z'/><path d='M320,300.109H192c-4.418,0-8,3.582-8,8s3.582,8,8,8h128c4.418,0,8-3.582,8-8S324.418,300.109,320,300.109z'/><path d='M483.105,224.949c-9.717-8.094-13.741-11.93-11.921-21.398c5.588-10.328,8.816-22.464,8.816-35.441c0-21.712-9.036-41.065-23.058-53.523c-8.67-14.834-24.283-25.463-43.124-29.246c-20.05-4.027-40.644,0.458-55.09,11.994C321.682,126.917,290,140.109,256,140.109s-65.682-13.192-102.728-42.776c-14.446-11.535-35.04-16.02-55.09-11.994c-18.841,3.783-34.454,14.412-43.124,29.246C41.036,127.044,32,146.397,32,168.109c0,12.977,3.228,25.113,8.816,35.441c1.82,9.468-2.204,13.305-11.921,21.398C16.701,235.106,0,249.018,0,284.109c0,30.939,44.4,68.017,85.951,92.4c55.883,32.792,117.864,51.6,170.049,51.6s114.166-18.808,170.049-51.6C467.6,352.126,512,315.049,512,284.109C512,249.018,495.299,235.106,483.105,224.949z M464,168.109c0,10.585-2.696,20.437-7.312,28.661c-0.078,0.131-0.148,0.266-0.219,0.402c-7.916,13.829-21.31,22.937-36.47,22.937c-24.262,0-44-23.327-44-52s19.738-52,44-52c9.282,0,17.896,3.423,25.002,9.245c0.351,0.356,0.731,0.674,1.135,0.955C456.965,135.79,464,150.995,464,168.109z M65.857,126.314c0.407-0.282,0.791-0.603,1.144-0.962c7.105-5.82,15.718-9.242,24.999-9.242c24.262,0,44,23.327,44,52s-19.738,52-44,52c-15.159,0-28.552-9.107-36.468-22.935c-0.072-0.138-0.143-0.275-0.222-0.407C50.696,188.545,48,178.694,48,168.11C48,150.999,55.032,135.795,65.857,126.314z M417.951,362.709c-52.713,30.933-113.256,49.4-161.951,49.4s-109.238-18.467-161.951-49.4C48.095,335.744,16,303.422,16,284.109c0-27.596,11.761-37.393,23.135-46.867c5.547-4.621,11.494-9.583,15.006-16.426c10.334,9.552,23.514,15.293,37.858,15.293c33.084,0,60-30.505,60-68c0-33.803-21.879-61.914-50.429-67.125c15.396-3.025,30.975,0.273,41.717,8.852c40.075,32.002,74.837,46.273,112.712,46.273s72.637-14.271,112.712-46.273c10.743-8.579,26.321-11.878,41.717-8.852C381.879,106.195,360,134.307,360,168.109c0,37.495,26.916,68,60,68c14.344,0,27.524-5.741,37.858-15.293c3.512,6.842,9.459,11.805,15.006,16.426C484.239,246.717,496,256.513,496,284.109C496,303.422,463.905,335.744,417.951,362.709z'/><path d='M92,180.109c8.445,0,36-1.154,36-16s-27.555-16-36-16s-36,1.154-36,16S83.555,180.109,92,180.109z'/><path d='M384,164.109c0,14.846,27.555,16,36,16s36-1.154,36-16s-27.555-16-36-16S384,149.263,384,164.109z'/><path d='M232,228.109c4.418,0,8-3.582,8-8v-8c0-4.418-3.582-8-8-8s-8,3.582-8,8v8C224,224.528,227.582,228.109,232,228.109z'/><path d='M280,228.109c4.418,0,8-3.582,8-8v-8c0-4.418-3.582-8-8-8s-8,3.582-8,8v8C272,224.528,275.582,228.109,280,228.109z'/></svg>");
}
:is(.admonition):is(.admonish-frog) {
border-color: #9004cc;
}
:is(.admonish-frog) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(144, 4, 204, 0.1);
}
:is(.admonish-frog) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #9004cc;
mask-image: var(--md-admonition-icon--admonish-frog);
-webkit-mask-image: var(--md-admonition-icon--admonish-frog);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}

92
integration/scripts/check Executable file
View File

@@ -0,0 +1,92 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")"/..
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 "Generating custom CSS (book)"
mdbook-admonish generate-custom mdbook-admonish-custom.css
eprintln "Verifying generated book custom css"
set +e
diff -u \
"expected/mdbook-admonish-custom.css" \
"./mdbook-admonish-custom.css"
DIFF_RESULT=$?
set -e
if [ "$DIFF_RESULT" != 0 ]; then
eprintln ""
eprintln "error: generated custom css was different than expected"
eprintln ""
eprintln "error: If you expected the output to change, run:"
eprintln "cp ./integration/mdbook-admonish-custom.css ./integration/expected/mdbook-admonish-custom.css"
eprintln "and commit the result"
exit 1
fi
eprintln "Building mdbook"
mdbook build
eprintln "Verifying generated html"
set +e
diff -u \
"expected/chapter_1_main.html" \
<(./scripts/get-snapshot)
DIFF_RESULT=$?
set -e
if [ "$DIFF_RESULT" != 0 ]; then
eprintln ""
eprintln "error: generated html was different than expected"
eprintln ""
eprintln "error: If you expected the output to change, run:"
eprintln "./integration/scripts/update-snapshot"
eprintln "and commit the result"
exit 1
fi
eprintln "Verifying mdbook test runs doctests"
set +e
TEST_RESULT="$(mdbook test 2>&1 | grep "1 passed; 1 failed")"
set -e
if [[ "$TEST_RESULT" != "test result: FAILED. 1 passed; 1 failed;"* ]]; then
eprintln ""
eprintln "error: mdbook test did not complete as expected"
eprintln ""
eprintln "Full output:"
mdbook test
exit 1
fi

View File

@@ -0,0 +1,7 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")"/..
sed '1,/<main>/d;/<\/main>/,$d' book/chapter_1.html

View File

@@ -0,0 +1,7 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")"/..
./scripts/get-snapshot > expected/chapter_1_main.html

View File

@@ -0,0 +1,3 @@
# Summary
- [Chapter 1](./chapter_1.md)

View File

@@ -0,0 +1,74 @@
# Chapter 1
```admonish abstract "What <i>is</i> this?"
This book acts as an integration test for `mdbook-admonish`.
It verifies that `mdbook` post-processes our generated HTML in the way we expect.
```
```admonish
Simples
```
```admonish frog
Custom frog directive
```
```admonish warning ""
No title, only body
```
```admonish title="
No title, only body
```
```admonish collapsible=true
Hidden on load
```
{{#include common_warning.md}}
````admonish
```bash
Nested code block
```
````
````admonish
```rust
let x = 10;
x = 20;
```
```rust
let x = 10;
let x = 20;
```
````
In a list:
1. Thing one
```sh
Thing one
```
1. Thing two
```admonish
Thing two
```
1. Thing two nested
```admonish
Thing two nested (should not be a code block)
Regression tests for https://github.com/tommilligan/mdbook-admonish/issues/223
```
1. Thing three
```sh
Thing three
```

View File

@@ -0,0 +1,3 @@
```admonish warning
This is a commonly shared warning!
```

14
qvet.yml Normal file
View File

@@ -0,0 +1,14 @@
action:
ready:
type: link
name: "Push New Version Tag"
url: "https://github.com/tommilligan/mdbook-admonish"
commit:
ignore:
merges: true
release:
identifiers:
- type: tag
pattern: "^v[0-9]"

24
scripts/build-book Executable file
View 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"

57
scripts/check Executable file
View File

@@ -0,0 +1,57 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")"/..
function eprintln() {
>&2 echo "$1"
}
# Node things
pushd compile_assets > /dev/null
eprintln "Linting style sources"
yarn run lint
eprintln "Checking compiled styles up to date"
COMITTED_ASSETS="$(cat ../src/bin/assets/mdbook-admonish.css)"
yarn run build
RECOMPILED_ASSETS="$(cat ../src/bin/assets/mdbook-admonish.css)"
set +e
diff -u <(printf "%s" "$COMITTED_ASSETS") <(printf "%s" "$RECOMPILED_ASSETS")
DIFF_RESULT=$?
set -e
if [ "$DIFF_RESULT" != 0 ]; then
eprintln ""
eprintln "error: committed assets are not up to date"
eprintln ""
eprintln "error: To update committed assets, please run"
eprintln "cd compile_assets && yarn run build"
eprintln "and commit the result"
exit 1
fi
popd > /dev/null
# Rust things
eprintln "Formatting sources"
cargo fmt -- --check
eprintln "Linting sources"
cargo clippy --all-targets -- -D warnings
eprintln "Running tests (default)"
cargo test
eprintln "Running tests (no features)"
cargo test --no-default-features
eprintln "Running tests (cli)"
cargo test --no-default-features --features cli
eprintln "Building documentation"
cargo doc --no-deps --lib
# Integration test
eprintln "Running mdbook integration test"
./integration/scripts/check

24
scripts/install Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
# Install everything for development/CI
#
# Does not include offline node development stack (i.e. yarn)
set -euo pipefail
function eprintln() {
>&2 echo "$1"
}
cd "$(dirname "$0")"/..
eprintln "Installing additional Rust components"
rustup component add rustfmt clippy
eprintln "Installing mdbook"
./scripts/install-mdbook
eprintln "Installing node dependencies"
pushd compile_assets > /dev/null
yarn install --frozen-lockfile
popd > /dev/null

18
scripts/install-mdbook Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")"/..
function eprintln() {
>&2 echo "$1"
}
VERSION="0.4.51"
eprintln "Checking if mdbook $VERSION is installed"
if [[ "$(mdbook --version)" != "mdbook v$VERSION" ]]; then
eprintln "Installing mdbook $VERSION"
cargo install mdbook --version "$VERSION" --force
fi
eprintln "mdbook $VERSION is installed"

13
scripts/publish Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
set -euxo pipefail
cd "$(dirname "$0")"/..
# Don't log the cargo login token while authenticating
set +x
echo "cargo login ***********************************"
cargo login "${CARGO_LOGIN_TOKEN}"
set -x
cargo publish

18
scripts/rebuild-book Executable file
View 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

View File

@@ -0,0 +1 @@
^3.0.0

1
src/bin/assets/VERSION Normal file
View File

@@ -0,0 +1 @@
3.1.0

View File

@@ -1,30 +1,4 @@
: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>");
}
@charset "UTF-8";
:is(.admonition) {
display: flow-root;
margin: 1.5625em 0;
@@ -56,28 +30,50 @@ html :is(.admonition) > :last-child {
margin-bottom: 1.2rem;
}
:is(.admonition-title, summary) {
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.admonition-title) {
position: relative;
min-height: 4rem;
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;
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
display: flex;
}
html :is(.admonition-title, summary):last-child {
:is(.admonition-title, summary.admonition-title) p {
margin: 0;
}
html :is(.admonition-title, summary.admonition-title):last-child {
margin-bottom: 0;
}
:is(.admonition-title, summary)::before {
:is(.admonition-title, summary.admonition-title)::before {
position: absolute;
top: 0.625em;
inset-inline-start: 1.2rem;
inset-inline-start: 1.6rem;
width: 2rem;
height: 2rem;
background-color: #448aff;
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
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;
@@ -86,219 +82,275 @@ html :is(.admonition-title, summary):last-child {
-webkit-mask-size: contain;
content: "";
}
:is(.admonition-title, summary.admonition-title):hover a.admonition-anchor-link {
display: initial;
}
:is(.admonition):is(.note) {
@media print {
details.admonition::details-content {
display: contents;
}
}
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);
}
summary.admonition-title::-webkit-details-marker {
display: none;
}
:root {
--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>");
}
:root {
--md-admonition-icon--admonish-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--admonish-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--admonish-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--admonish-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--admonish-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--admonish-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--admonish-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--admonish-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--admonish-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--admonish-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--admonish-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--admonish-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):is(.admonish-note) {
border-color: #448aff;
}
:is(.note) > :is(.admonition-title, summary) {
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(68, 138, 255, 0.1);
border-color: #448aff;
}
:is(.note) > :is(.admonition-title, summary)::before {
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #448aff;
mask-image: var(--md-admonition-icon--note);
-webkit-mask-image: var(--md-admonition-icon--note);
mask-image: var(--md-admonition-icon--admonish-note);
-webkit-mask-image: var(--md-admonition-icon--admonish-note);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.abstract, .summary, .tldr) {
:is(.admonition):is(.admonish-abstract, .admonish-summary, .admonish-tldr) {
border-color: #00b0ff;
}
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary) {
:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 176, 255, 0.1);
border-color: #00b0ff;
}
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::before {
:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00b0ff;
mask-image: var(--md-admonition-icon--abstract);
-webkit-mask-image: var(--md-admonition-icon--abstract);
mask-image: var(--md-admonition-icon--admonish-abstract);
-webkit-mask-image: var(--md-admonition-icon--admonish-abstract);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.info, .todo) {
:is(.admonition):is(.admonish-info, .admonish-todo) {
border-color: #00b8d4;
}
:is(.info, .todo) > :is(.admonition-title, summary) {
:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 184, 212, 0.1);
border-color: #00b8d4;
}
:is(.info, .todo) > :is(.admonition-title, summary)::before {
:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00b8d4;
mask-image: var(--md-admonition-icon--info);
-webkit-mask-image: var(--md-admonition-icon--info);
mask-image: var(--md-admonition-icon--admonish-info);
-webkit-mask-image: var(--md-admonition-icon--admonish-info);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.tip, .hint, .important) {
:is(.admonition):is(.admonish-tip, .admonish-hint, .admonish-important) {
border-color: #00bfa5;
}
:is(.tip, .hint, .important) > :is(.admonition-title, summary) {
:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 191, 165, 0.1);
border-color: #00bfa5;
}
:is(.tip, .hint, .important) > :is(.admonition-title, summary)::before {
:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00bfa5;
mask-image: var(--md-admonition-icon--tip);
-webkit-mask-image: var(--md-admonition-icon--tip);
mask-image: var(--md-admonition-icon--admonish-tip);
-webkit-mask-image: var(--md-admonition-icon--admonish-tip);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.success, .check, .done) {
:is(.admonition):is(.admonish-success, .admonish-check, .admonish-done) {
border-color: #00c853;
}
:is(.success, .check, .done) > :is(.admonition-title, summary) {
:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 200, 83, 0.1);
border-color: #00c853;
}
:is(.success, .check, .done) > :is(.admonition-title, summary)::before {
:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00c853;
mask-image: var(--md-admonition-icon--success);
-webkit-mask-image: var(--md-admonition-icon--success);
mask-image: var(--md-admonition-icon--admonish-success);
-webkit-mask-image: var(--md-admonition-icon--admonish-success);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.question, .help, .faq) {
:is(.admonition):is(.admonish-question, .admonish-help, .admonish-faq) {
border-color: #64dd17;
}
:is(.question, .help, .faq) > :is(.admonition-title, summary) {
:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(100, 221, 23, 0.1);
border-color: #64dd17;
}
:is(.question, .help, .faq) > :is(.admonition-title, summary)::before {
:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #64dd17;
mask-image: var(--md-admonition-icon--question);
-webkit-mask-image: var(--md-admonition-icon--question);
mask-image: var(--md-admonition-icon--admonish-question);
-webkit-mask-image: var(--md-admonition-icon--admonish-question);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.warning, .caution, .attention) {
:is(.admonition):is(.admonish-warning, .admonish-caution, .admonish-attention) {
border-color: #ff9100;
}
:is(.warning, .caution, .attention) > :is(.admonition-title, summary) {
:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(255, 145, 0, 0.1);
border-color: #ff9100;
}
:is(.warning, .caution, .attention) > :is(.admonition-title, summary)::before {
:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #ff9100;
mask-image: var(--md-admonition-icon--warning);
-webkit-mask-image: var(--md-admonition-icon--warning);
mask-image: var(--md-admonition-icon--admonish-warning);
-webkit-mask-image: var(--md-admonition-icon--admonish-warning);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.failure, .fail, .missing) {
:is(.admonition):is(.admonish-failure, .admonish-fail, .admonish-missing) {
border-color: #ff5252;
}
:is(.failure, .fail, .missing) > :is(.admonition-title, summary) {
:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(255, 82, 82, 0.1);
border-color: #ff5252;
}
:is(.failure, .fail, .missing) > :is(.admonition-title, summary)::before {
:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #ff5252;
mask-image: var(--md-admonition-icon--failure);
-webkit-mask-image: var(--md-admonition-icon--failure);
mask-image: var(--md-admonition-icon--admonish-failure);
-webkit-mask-image: var(--md-admonition-icon--admonish-failure);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.danger, .error) {
:is(.admonition):is(.admonish-danger, .admonish-error) {
border-color: #ff1744;
}
:is(.danger, .error) > :is(.admonition-title, summary) {
:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(255, 23, 68, 0.1);
border-color: #ff1744;
}
:is(.danger, .error) > :is(.admonition-title, summary)::before {
:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #ff1744;
mask-image: var(--md-admonition-icon--danger);
-webkit-mask-image: var(--md-admonition-icon--danger);
mask-image: var(--md-admonition-icon--admonish-danger);
-webkit-mask-image: var(--md-admonition-icon--admonish-danger);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.bug) {
:is(.admonition):is(.admonish-bug) {
border-color: #f50057;
}
:is(.bug) > :is(.admonition-title, summary) {
:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(245, 0, 87, 0.1);
border-color: #f50057;
}
:is(.bug) > :is(.admonition-title, summary)::before {
:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #f50057;
mask-image: var(--md-admonition-icon--bug);
-webkit-mask-image: var(--md-admonition-icon--bug);
mask-image: var(--md-admonition-icon--admonish-bug);
-webkit-mask-image: var(--md-admonition-icon--admonish-bug);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.example) {
:is(.admonition):is(.admonish-example) {
border-color: #7c4dff;
}
:is(.example) > :is(.admonition-title, summary) {
:is(.admonish-example) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(124, 77, 255, 0.1);
border-color: #7c4dff;
}
:is(.example) > :is(.admonition-title, summary)::before {
:is(.admonish-example) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #7c4dff;
mask-image: var(--md-admonition-icon--example);
-webkit-mask-image: var(--md-admonition-icon--example);
mask-image: var(--md-admonition-icon--admonish-example);
-webkit-mask-image: var(--md-admonition-icon--admonish-example);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.quote, .cite) {
:is(.admonition):is(.admonish-quote, .admonish-cite) {
border-color: #9e9e9e;
}
:is(.quote, .cite) > :is(.admonition-title, summary) {
:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(158, 158, 158, 0.1);
border-color: #9e9e9e;
}
:is(.quote, .cite) > :is(.admonition-title, summary)::before {
:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #9e9e9e;
mask-image: var(--md-admonition-icon--quote);
-webkit-mask-image: var(--md-admonition-icon--quote);
mask-image: var(--md-admonition-icon--admonish-quote);
-webkit-mask-image: var(--md-admonition-icon--admonish-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);
}
.rust .admonition-anchor-link:link, .rust .admonition-anchor-link:visited {
color: var(--sidebar-fg);
}

View File

@@ -1,58 +1,85 @@
use clap::{crate_version, Arg, ArgMatches, Command};
use mdbook::errors::Error;
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use mdbook::preprocess::{CmdPreprocessor, Preprocessor};
use mdbook_admonish::Admonish;
use serde::Deserialize;
use std::fs;
use std::io;
use std::path::PathBuf;
use std::process;
use std::{io, process};
/// mdbook preprocessor to add support for admonitions
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
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"),
);
#[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>,
},
/// Generate CSS file for custom directives.
GenerateCustom {
/// Root directory for the book, should contain the configuration file (`book.toml`)
///
/// If not set, defaults to the current directory.
#[arg(long)]
dir: Option<PathBuf>,
/// File to write generated css to.
output: 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 handle_preprocessing() -> Result<(), Error> {
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(".")),
),
Some(Commands::GenerateCustom { dir, output }) => {
handle_generate_custom(dir.unwrap_or_else(|| PathBuf::from(".")), output)
}
}
}
fn handle_preprocessing() -> std::result::Result<(), mdbook::errors::Error> {
let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
if ctx.mdbook_version != mdbook::MDBOOK_VERSION {
@@ -70,9 +97,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 {
@@ -82,16 +108,59 @@ fn handle_supports(sub_args: &ArgMatches) -> ! {
}
}
#[derive(Deserialize)]
struct Config {
#[serde(default)]
preprocessor: Preprocessors,
}
#[derive(Default, Deserialize)]
struct Preprocessors {
#[serde(default)]
admonish: Option<toml::Table>,
#[serde(flatten)]
_other: toml::Table,
}
/// Load the plugin specific config as a toml string, for private deserialization.
fn admonish_config_string(config: &Config) -> Result<String> {
Ok(toml::to_string(
&config
.preprocessor
.admonish
.as_ref()
.context("No configuration for mdbook-admonish in book.toml")?,
)?)
}
fn handle_generate_custom(proj_dir: PathBuf, output: PathBuf) -> Result<()> {
let config = proj_dir.join("book.toml");
log::info!("Reading configuration file '{}'", config.display());
let data = fs::read_to_string(&config)
.with_context(|| format!("can't read configuration file '{}'", config.display()))?;
let config: Config = toml::from_str(&data).context("Invalid configuration file")?;
let css =
mdbook_admonish::custom::css_from_config(&proj_dir, &admonish_config_string(&config)?)?;
log::info!("Writing custom CSS file '{}'", output.display());
fs::write(output, css)?;
Ok(())
}
#[cfg(feature = "cli-install")]
mod install {
use clap::ArgMatches;
use anyhow::{Context, Result};
use path_slash::PathExt;
use std::borrow::Cow;
use std::path::Path;
use std::{
fs::{self, File},
io::Write,
path::PathBuf,
process,
};
use toml_edit::{self, Array, Document, Item, Table, Value};
use toml_edit::{self, Array, DocumentMut, Item, Table, Value};
const ADMONISH_CSS_FILES: &[(&str, &[u8])] = &[(
"mdbook-admonish.css",
@@ -111,36 +180,49 @@ 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);
// Normalize path to UNIX style.
// This avoids conflicts/rewriting files when projects are used under different
// operating systems (e.g. on Windows, after being used on Linux)
//
// https://github.com/tommilligan/mdbook-admonish/issues/161
fn normalize_config_file_path(path: &Path) -> Result<Cow<'_, str>> {
path.to_slash()
.context("UNIX style path normalization error")
}
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");
.parse::<DocumentMut>()
.context("configuration is not valid TOML")?;
if preprocessor(&mut doc).is_err() {
log::info!("Unexpected configuration, not updating prereprocessor configuration");
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 preprocessor 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.clone()).join(name);
// Normalize path to remove no-op components
// https://github.com/tommilligan/mdbook-admonish/issues/47
let filepath: PathBuf = filepath.components().collect();
if let Ok(ref mut additional_css) = additional_css {
if !additional_css.contains_str(filepath_str) {
let filepath_str = normalize_config_file_path(&filepath)?;
if !additional_css.contains_str(&filepath_str) {
log::info!("Adding '{filepath_str}' to 'additional-css'");
additional_css.push(filepath_str);
additional_css.push(filepath_str.as_ref());
}
} else {
log::warn!("Unexpected configuration, not updating 'additional-css'");
@@ -150,18 +232,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,24 +253,22 @@ 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.
///
/// Return `Err` if the existing configuration is unknown.
fn additional_css<'a>(doc: &'a mut Document) -> Result<&'a mut Array, ()> {
fn additional_css(doc: &mut DocumentMut) -> Result<&mut Array, ()> {
let doc = doc.as_table_mut();
let empty_table = Item::Table(Table::default());
let empty_array = Item::Value(Value::Array(Array::default()));
Ok(doc
.entry("output")
doc.entry("output")
.or_insert(empty_table.clone())
.as_table_mut()
.map(|item| {
.and_then(|item| {
item.entry("html")
.or_insert(empty_table)
.as_table_mut()?
@@ -197,14 +277,13 @@ A beautifully styled message.
.as_value_mut()?
.as_array_mut()
})
.flatten()
.ok_or(())?)
.ok_or(())
}
/// Return the preprocessor table for admonish, initializing if required.
///
/// Return `Err` if the existing configuration is unknown.
fn preprocessor(doc: &mut Document) -> Result<&mut Item, ()> {
fn preprocessor(doc: &mut DocumentMut) -> Result<&mut Item, ()> {
let doc = doc.as_table_mut();
let empty_table = Item::Table(Table::default());
@@ -217,4 +296,24 @@ A beautifully styled message.
item["command"] = toml_edit::value("mdbook-admonish");
Ok(item)
}
#[cfg(test)]
mod test {
use super::*;
/// This test seems redundant, but would fail on Windows.
///
/// We want to always convert to a fixed output string style, independant
/// of runtime platform, and forward slashes in relative paths are fine on
/// Windows.
#[test]
fn test_normalize_config_file_path() {
let input = PathBuf::from(".")
.join("css-dir")
.join("mdbook-admonish.css");
let expected = "./css-dir/mdbook-admonish.css";
let actual = normalize_config_file_path(&input).unwrap();
assert_eq!(actual.as_ref(), expected);
}
}
}

325
src/book_config.rs Normal file
View File

@@ -0,0 +1,325 @@
use anyhow::{Context, Result};
use mdbook::preprocess::PreprocessorContext;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use crate::types::{AdmonitionDefaults, BuiltinDirective, BuiltinDirectiveConfig};
/// Loads the plugin configuration from mdbook internals.
///
/// Roundtrips config to string, to avoid linking the plugin's internal version of toml
/// to the one publically exposed by the mdbook library.
pub(crate) fn admonish_config_from_context(ctx: &PreprocessorContext) -> Result<Config> {
let table: String = toml::to_string(
ctx.config
.get_preprocessor("admonish")
.context("No configuration for mdbook-admonish in book.toml")?,
)
.context("Could not serialize mdbook-admonish config. This is a bug in the toml library.")?;
admonish_config_from_str(&table)
}
pub(crate) fn admonish_config_from_str(data: &str) -> Result<Config> {
let readonly: ConfigReadonly =
toml::from_str(data).context("Invalid mdbook-admonish configuration in book.toml")?;
let config = readonly.into();
log::debug!("Loaded admonish config: {:?}", config);
Ok(config)
}
/// All valid input states including back-compatibility fields.
///
/// This struct deliberately does not implement Serialize as it never meant to
/// be written, only converted to Config.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
struct ConfigReadonly {
#[serde(default)]
pub on_failure: OnFailure,
#[serde(default)]
pub default: AdmonitionDefaults,
#[serde(default)]
pub renderer: HashMap<String, RendererConfig>,
#[serde(default)]
pub assets_version: Option<String>,
#[serde(default)]
pub custom: Vec<CustomDirectiveReadonly>,
#[serde(default)]
pub builtin: HashMap<BuiltinDirective, BuiltinDirectiveConfig>,
#[serde(default)]
pub directive: DirectiveConfig,
}
/// The canonical config format, without back-compatibility
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
pub(crate) struct Config {
#[serde(default)]
pub on_failure: OnFailure,
#[serde(default)]
pub default: AdmonitionDefaults,
#[serde(default)]
pub renderer: HashMap<String, RendererConfig>,
#[serde(default)]
pub assets_version: Option<String>,
#[serde(default)]
pub directive: DirectiveConfig,
}
impl From<ConfigReadonly> for Config {
fn from(other: ConfigReadonly) -> Self {
let ConfigReadonly {
on_failure,
default,
renderer,
assets_version,
custom,
builtin,
mut directive,
} = other;
// Merge deprecated config fields into main config object
directive.custom.extend(
custom
.into_iter()
.map(|CustomDirectiveReadonly { directive, config }| (directive, config)),
);
directive.builtin.extend(builtin);
Self {
on_failure,
default,
renderer,
assets_version,
directive,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
pub(crate) struct DirectiveConfig {
#[serde(default)]
pub custom: HashMap<String, CustomDirective>,
#[serde(default)]
pub builtin: HashMap<BuiltinDirective, BuiltinDirectiveConfig>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub(crate) struct CustomDirective {
/// Path to an SVG file, relative to the book root.
pub icon: PathBuf,
/// Primary color for this directive.
pub color: hex_color::HexColor,
/// Alternative directives the user can specify
#[serde(default)]
pub aliases: Vec<String>,
/// Title to use, human readable.
#[serde(default)]
pub title: Option<String>,
/// Default collapsible value.
#[serde(default)]
pub collapsible: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub(crate) struct CustomDirectiveReadonly {
/// The primary directive. Used for CSS classnames
pub directive: String,
/// Path to an SVG file, relative to the book root.
#[serde(flatten)]
config: CustomDirective,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub(crate) struct RendererConfig {
pub render_mode: Option<RenderMode>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub(crate) enum RenderMode {
Preserve,
Strip,
Html,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub(crate) enum OnFailure {
Bail,
Continue,
}
impl Default for OnFailure {
fn default() -> Self {
Self::Continue
}
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
use crate::types::BuiltinDirective;
#[test]
fn empty_config_okay() -> Result<()> {
let actual = admonish_config_from_str("")?;
let expected = Config::default();
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn css_id_prefix_kebab_case_allowed() -> Result<()> {
let expected = Config {
default: AdmonitionDefaults {
css_id_prefix: Some("flam-".to_owned()),
..Default::default()
},
..Default::default()
};
// Snake case okay
let actual = admonish_config_from_str(r#"default = { css_id_prefix = "flam-" }"#)?;
assert_eq!(actual, expected);
// Kebab case back-compat okay
let actual = admonish_config_from_str(r#"default = { css-id-prefix = "flam-" }"#)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn merge_old_and_new_custom_directives() -> Result<()> {
let serialized = r##"
[directive.custom.purple]
icon = "/tmp/test-directive.svg"
color = "#9B4F96"
aliases = ["test-directive-alias-0"]
title = "Purple"
collapsible = true
[[custom]]
directive = "blue"
icon = "/tmp/test-directive.svg"
color = "#0038A8"
aliases = []
title = "Blue"
"##;
let expected = Config {
directive: DirectiveConfig {
custom: HashMap::from([
(
"purple".to_owned(),
CustomDirective {
icon: PathBuf::from("/tmp/test-directive.svg"),
color: hex_color::HexColor::from((155, 79, 150)),
aliases: vec!["test-directive-alias-0".to_owned()],
title: Some("Purple".to_owned()),
collapsible: Some(true),
},
),
(
"blue".to_owned(),
CustomDirective {
icon: PathBuf::from("/tmp/test-directive.svg"),
color: hex_color::HexColor::from((0, 56, 168)),
aliases: vec![],
title: Some("Blue".to_owned()),
collapsible: None,
},
),
]),
..Default::default()
},
..Default::default()
};
let actual = admonish_config_from_str(serialized)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn full_config_roundtrip() -> Result<()> {
let input = Config {
default: AdmonitionDefaults {
css_id_prefix: Some("flam-".to_owned()),
collapsible: true,
title: Some("".to_owned()),
},
assets_version: Some("1.1.1".to_owned()),
directive: DirectiveConfig {
custom: HashMap::from([(
"test-directive".to_owned(),
CustomDirective {
icon: PathBuf::from("/tmp/test-directive.svg"),
color: hex_color::HexColor::from((155, 79, 150)),
aliases: vec!["test-directive-alias-0".to_owned()],
title: Some("test-directive-title".to_owned()),
collapsible: Some(true),
},
)]),
builtin: HashMap::from([(
BuiltinDirective::Warning,
BuiltinDirectiveConfig {
collapsible: Some(true),
},
)]),
},
on_failure: OnFailure::Bail,
renderer: HashMap::from([(
"test-mode".to_owned(),
RendererConfig {
render_mode: Some(RenderMode::Strip),
},
)]),
};
let expected = r##"on_failure = "bail"
assets_version = "1.1.1"
[default]
title = ""
collapsible = true
css_id_prefix = "flam-"
[renderer.test-mode]
render_mode = "strip"
[directive.custom.test-directive]
icon = "/tmp/test-directive.svg"
color = "#9B4F96"
aliases = ["test-directive-alias-0"]
title = "test-directive-title"
collapsible = true
[directive.builtin.warning]
collapsible = true
"##;
let serialized = toml::to_string(&input)?;
assert_eq!(serialized, expected);
let actual = admonish_config_from_str(&serialized)?;
assert_eq!(actual, input);
Ok(())
}
}

120
src/config/mod.rs Normal file
View File

@@ -0,0 +1,120 @@
mod toml_wrangling;
mod v1;
mod v2;
mod v3;
/// Configuration as described by the instance of an admonition in markdown.
///
/// This structure represents the configuration the user must provide in each
/// instance.
#[derive(Debug, PartialEq, Default)]
pub(crate) struct InstanceConfig {
pub(crate) directive: String,
pub(crate) title: Option<String>,
pub(crate) id: Option<String>,
pub(crate) additional_classnames: Vec<String>,
pub(crate) collapsible: Option<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 InstanceConfig {
/// Returns:
/// - `None` if this is not an `admonish` block.
/// - `Some(InstanceConfig)` 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)?;
Some(Self::from_admonish_config_string(config_string))
}
/// Parse an info string that is known to be for `admonish`.
fn from_admonish_config_string(config_string: &str) -> Result<Self, String> {
// If we succeed at parsing v3, return that. Otherwise hold onto the error
let config_v3_error = match v3::from_config_string(config_string) {
Ok(config) => return Ok(config),
Err(error) => error,
};
// If we succeed at parsing v2, return that
if let Ok(config) = v2::from_config_string(config_string) {
return Ok(config);
};
// If we succeed at parsing v1, return that.
if let Ok(config) = v1::from_config_string(config_string) {
return Ok(config);
}
// Otherwise return our v3 error.
Err(config_v3_error)
}
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_from_info_string() {
// Not admonition blocks
assert_eq!(InstanceConfig::from_info_string(""), None);
assert_eq!(InstanceConfig::from_info_string("adm"), None);
// v1 syntax is supported back compatibly
assert_eq!(
InstanceConfig::from_info_string("admonish note.additional-classname")
.unwrap()
.unwrap(),
InstanceConfig {
directive: "note".to_owned(),
title: None,
id: None,
additional_classnames: vec!["additional-classname".to_owned()],
collapsible: None,
}
);
// v2 syntax is supported
assert_eq!(
InstanceConfig::from_info_string(
r#"admonish title="Custom Title" type="question" id="my-id""#
)
.unwrap()
.unwrap(),
InstanceConfig {
directive: "question".to_owned(),
title: Some("Custom Title".to_owned()),
id: Some("my-id".to_owned()),
additional_classnames: Vec::new(),
collapsible: None,
}
);
// v3 syntax is supported
assert_eq!(
InstanceConfig::from_info_string(
r#"admonish title="Custom Title", type="question", id="my-id""#
)
.unwrap()
.unwrap(),
InstanceConfig {
directive: "question".to_owned(),
title: Some("Custom Title".to_owned()),
id: Some("my-id".to_owned()),
additional_classnames: Vec::new(),
collapsible: None,
}
);
}
}

View File

@@ -0,0 +1,44 @@
use once_cell::sync::Lazy;
use regex::Regex;
use serde::Deserialize;
use std::fmt::Display;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub(crate) struct UserInput {
#[serde(default)]
pub r#type: Option<String>,
#[serde(default)]
pub title: Option<String>,
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub class: Option<String>,
#[serde(default)]
pub collapsible: Option<bool>,
}
impl UserInput {
pub fn classnames(&self) -> Vec<String> {
self.class
.as_ref()
.map(|class| {
class
.split(' ')
.filter(|classname| !classname.is_empty())
.map(|classname| classname.to_owned())
.collect()
})
.unwrap_or_default()
}
}
pub(crate) static RX_DIRECTIVE: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"^[A-Za-z0-9_-]+$"#).expect("directive regex"));
pub(crate) fn format_toml_parsing_error(error: impl Display) -> String {
format!("TOML parsing error: {error}")
}
pub(crate) fn format_invalid_directive(directive: &str, original_error: impl Display) -> String {
format!("'{directive}' is not a valid directive or TOML key-value pair.\n\n{original_error}")
}

136
src/config/v1.rs Normal file
View File

@@ -0,0 +1,136 @@
use super::InstanceConfig;
use once_cell::sync::Lazy;
use regex::Regex;
pub(crate) fn from_config_string(config_string: &str) -> Result<InstanceConfig, 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(InstanceConfig {
directive: directive.to_owned(),
title,
id: None,
additional_classnames,
collapsible: None,
})
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_from_config_string() {
assert_eq!(
from_config_string("").unwrap(),
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
}
);
assert_eq!(
from_config_string(" ").unwrap(),
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
}
);
assert_eq!(
from_config_string("unknown").unwrap(),
InstanceConfig {
directive: "unknown".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
}
);
assert_eq!(
from_config_string("note").unwrap(),
InstanceConfig {
directive: "note".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
}
);
assert_eq!(
from_config_string("note.additional-classname").unwrap(),
InstanceConfig {
directive: "note".to_owned(),
title: None,
id: None,
additional_classnames: vec!["additional-classname".to_owned()],
collapsible: None,
}
);
}
#[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()
);
}
}

210
src/config/v2.rs Normal file
View File

@@ -0,0 +1,210 @@
use super::toml_wrangling::{
format_invalid_directive, format_toml_parsing_error, UserInput, RX_DIRECTIVE,
};
use super::InstanceConfig;
use once_cell::sync::Lazy;
use regex::Regex;
/// 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(|| Regex::new(r#"(?:[A-Za-z0-9_-]+) *="#).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()
}
fn user_input_from_config_toml(config_toml: &str) -> Result<UserInput, String> {
toml::from_str(config_toml).map_err(format_toml_parsing_error)
}
/// 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.
///
/// The basic idea here is to accept space separated key-value pairs, break them
/// onto separate lines, and then parse them as a TOML document.
/// This breaks when values contain a literal '=' sign, for which v3 syntax should be used.
pub(crate) fn from_config_string(config_string: &str) -> Result<InstanceConfig, String> {
let config_toml = bare_key_value_pairs_to_toml(config_string);
let config_toml = config_toml.trim();
let config: UserInput = match toml::from_str(config_toml) {
Ok(config) => config,
Err(error) => {
let original_error = 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, ""),
};
if !RX_DIRECTIVE.is_match(directive) {
return Err(format_invalid_directive(directive, original_error));
}
let mut config = user_input_from_config_toml(config_toml)?;
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(InstanceConfig {
directive: config.r#type.unwrap_or_default(),
title: config.title,
id: config.id,
additional_classnames,
collapsible: config.collapsible,
})
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_from_config_string_v2() -> Result<(), ()> {
fn check(config_string: &str, expected: InstanceConfig) -> Result<(), ()> {
let actual = match from_config_string(config_string) {
Ok(config) => config,
Err(error) => panic!("Expected config to be valid, got error:\n\n{}", error),
};
assert_eq!(actual, expected);
Ok(())
}
check(
"",
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
check(
" ",
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
check(
r#"type="note" class="additional classname" title="Никита" collapsible=true"#,
InstanceConfig {
directive: "note".to_owned(),
title: Some("Никита".to_owned()),
id: None,
additional_classnames: vec!["additional".to_owned(), "classname".to_owned()],
collapsible: Some(true),
},
)?;
// Specifying unknown keys is okay, as long as they're valid
check(
r#"unkonwn="but valid toml""#,
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
// Just directive is fine
check(
r#"info"#,
InstanceConfig {
directive: "info".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
// Directive plus toml config
check(
r#"info title="Information" collapsible=false"#,
InstanceConfig {
directive: "info".to_owned(),
title: Some("Information".to_owned()),
id: None,
additional_classnames: Vec::new(),
collapsible: Some(false),
},
)?;
// Test custom id
check(
r#"info title="My Info" id="my-info-custom-id""#,
InstanceConfig {
directive: "info".to_owned(),
title: Some("My Info".to_owned()),
id: Some("my-info-custom-id".to_owned()),
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
// Directive after toml config is an error
assert!(from_config_string(r#"title="Information" info"#).is_err());
Ok(())
}
#[test]
fn test_from_config_string_invalid_directive() {
assert_eq!(
from_config_string(r#"oh!wow titlel=""#).unwrap_err(),
r#"'oh!wow' is not a valid directive or TOML key-value pair.
TOML parsing error: TOML parse error at line 1, column 3
|
1 | oh!wow
| ^
expected `.`, `=`
"#
);
}
#[test]
fn test_from_config_string_invalid_toml_value() {
assert_eq!(
from_config_string(r#"note titlel=""#).unwrap_err(),
r#"TOML parsing error: TOML parse error at line 1, column 9
|
1 | titlel="
| ^
invalid basic string
"#
);
}
}

202
src/config/v3.rs Normal file
View File

@@ -0,0 +1,202 @@
use super::toml_wrangling::{
format_invalid_directive, format_toml_parsing_error, UserInput, RX_DIRECTIVE,
};
use super::InstanceConfig;
use serde::Deserialize;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
struct Wrapper<T> {
config: T,
}
/// Transform our config string into valid toml
fn bare_inline_table_to_toml(pairs: &str) -> String {
format!("config = {{ {pairs} }}")
}
fn user_input_from_config_string(config_string: &str) -> Result<UserInput, String> {
match toml::from_str::<Wrapper<_>>(&bare_inline_table_to_toml(config_string)) {
Ok(wrapper) => Ok(wrapper.config),
Err(error) => Err(format_toml_parsing_error(error)),
}
}
/// Parse and return the config assuming v3 format.
///
/// Note that if an error occurs, a parsed struct that can be returned to
/// show the error message will be returned.
///
/// The basic idea here is to accept the inside of an inline table, wrap it,
/// parse it, and then use the toml values.
pub(crate) fn from_config_string(config_string: &str) -> Result<InstanceConfig, String> {
let config_string = config_string.trim();
let config = match user_input_from_config_string(config_string) {
Ok(config) => config,
Err(error) => {
// For ergonomic reasons, we allow users to specify the directive without
// a key. So if parsing fails initially, take the first word,
// use that as the directive, and reparse.
let (directive, config_string) = match config_string.split_once(' ') {
Some((directive, config_string)) => (directive.trim(), config_string.trim()),
None => (config_string, ""),
};
if !RX_DIRECTIVE.is_match(directive) {
return Err(format_invalid_directive(directive, error));
}
let mut config = user_input_from_config_string(config_string)?;
config.r#type = Some(directive.to_owned());
config
}
};
let additional_classnames = config.classnames();
Ok(InstanceConfig {
directive: config.r#type.unwrap_or_default(),
title: config.title,
id: config.id,
additional_classnames,
collapsible: config.collapsible,
})
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_from_config_string_v3() -> Result<(), ()> {
fn check(config_string: &str, expected: InstanceConfig) -> Result<(), ()> {
let actual = match from_config_string(config_string) {
Ok(config) => config,
Err(error) => {
panic!("Expected config '{config_string}' to be valid, got error:\n\n{error}")
}
};
assert_eq!(actual, expected);
Ok(())
}
check(
"",
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
check(
" ",
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
check(
r#"type="note", class="additional classname", title="Никита", collapsible=true"#,
InstanceConfig {
directive: "note".to_owned(),
title: Some("Никита".to_owned()),
id: None,
additional_classnames: vec!["additional".to_owned(), "classname".to_owned()],
collapsible: Some(true),
},
)?;
// Specifying unknown keys is okay, as long as they're valid
check(
r#"unkonwn="but valid toml""#,
InstanceConfig {
directive: "".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
// Just directive is fine
check(
r#"info"#,
InstanceConfig {
directive: "info".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
// Directive plus toml config
check(
r#"info title="Information", collapsible=false"#,
InstanceConfig {
directive: "info".to_owned(),
title: Some("Information".to_owned()),
id: None,
additional_classnames: Vec::new(),
collapsible: Some(false),
},
)?;
// Test custom id
check(
r#"info title="My Info", id="my-info-custom-id""#,
InstanceConfig {
directive: "info".to_owned(),
title: Some("My Info".to_owned()),
id: Some("my-info-custom-id".to_owned()),
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
// Directive after toml config is an error
assert!(from_config_string(r#"title="Information" info"#).is_err());
// HTML with quotes inside content
// Note that we use toml literal (single quoted) strings here
check(
r#"info title='My <span class="emphasis">Title</span>'"#,
InstanceConfig {
directive: "info".to_owned(),
title: Some(r#"My <span class="emphasis">Title</span>"#.to_owned()),
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
)?;
Ok(())
}
#[test]
fn test_from_config_string_invalid_directive() {
assert_eq!(
from_config_string(r#"oh!wow titlel=""#).unwrap_err(),
r#"'oh!wow' is not a valid directive or TOML key-value pair.
TOML parsing error: TOML parse error at line 1, column 14
|
1 | config = { oh!wow titlel=" }
| ^
expected `.`, `=`
"#
);
}
#[test]
fn test_from_config_string_invalid_toml_value() {
assert_eq!(
from_config_string(r#"note titlel=""#).unwrap_err(),
r#"TOML parsing error: TOML parse error at line 1, column 22
|
1 | config = { titlel=" }
| ^
invalid basic string
"#
);
}
}

111
src/custom.rs Normal file
View File

@@ -0,0 +1,111 @@
//! This module is responsible for generating custom CSS for new admonition variants.
//!
//! It has unit tests to ensure the output matches that of the compile_assets CSS.
use anyhow::{anyhow, Context, Result};
use hex_color::{Case, HexColor};
use once_cell::sync::Lazy;
use regex::Regex;
use std::fs;
use std::path::Path;
static RX_COLLAPSE_NEWLINES: Lazy<Regex> =
Lazy::new(|| Regex::new(r"[\r\n]+\s*").expect("invalid whitespace regex"));
// Do some simple things to make the svg input probably a valid data url
// Based on this gist: https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0
fn svg_to_data_url(svg: &str) -> String {
const XMLNS: &str = r#"http://www.w3.org/2000/svg"#;
//
let mut svg = RX_COLLAPSE_NEWLINES.replace_all(svg, "").to_string();
if !svg.contains(XMLNS) {
log::warn!("Your SVG file does not contain '<svg xmlns=\"{XMLNS}\"', it will likely fail to render.");
}
svg = svg
.replace('"', "'")
.replace('%', "%25")
.replace('#', "%23")
.replace('{', "%7B")
.replace('}', "%7D");
format!("url(\"data:image/svg+xml;charset=utf-8,{}\")", svg)
}
/// Given a valid set of inputs, generate the relevant CSS.
///
/// It is up to the caller to validate inputs.
fn directive_css(name: &str, svg_data: &str, tint: HexColor) -> String {
let data_url = svg_to_data_url(svg_data);
let tint_faint = format!("rgba({}, {}, {}, {})", tint.r, tint.g, tint.b, 0.1);
let tint = tint.display_rgb().with_case(Case::Lower);
format!(
":root {{
--md-admonition-icon--admonish-{name}: {data_url};
}}
:is(.admonition):is(.admonish-{name}) {{
border-color: {tint};
}}
:is(.admonish-{name}) > :is(.admonition-title, summary.admonition-title) {{
background-color: {tint_faint};
}}
:is(.admonish-{name}) > :is(.admonition-title, summary.admonition-title)::before {{
background-color: {tint};
mask-image: var(--md-admonition-icon--admonish-{name});
-webkit-mask-image: var(--md-admonition-icon--admonish-{name});
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}}
",
name = name,
data_url = data_url,
tint = tint,
tint_faint = tint_faint
)
}
#[doc(hidden)]
pub fn css_from_config(book_dir: &Path, config: &str) -> Result<String> {
let config = crate::book_config::admonish_config_from_str(config)?;
let custom_directives = config.directive.custom;
if custom_directives.is_empty() {
return Err(anyhow!("No custom directives provided"));
}
log::info!("Loaded {} custom directives", custom_directives.len());
let mut css = String::new();
for (directive_name, directive) in custom_directives.iter() {
let svg = fs::read_to_string(book_dir.join(&directive.icon))
.with_context(|| format!("can't read icon file '{}'", directive.icon.display()))?;
css.push_str(&directive_css(directive_name, &svg, directive.color));
}
Ok(css)
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
const GENERATED_CSS: &str = include_str!("./test_data/mdbook-admonish-custom-expected.css");
const NOTE_SVG: &str = r#"<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>
"#;
// Verify the generated CSS here against a sample from the compile_assets output.
//
// The ensures that any new custom CSS will be in line with official styles.
#[test]
fn verify_against_generated_css() {
let actual = directive_css("note", NOTE_SVG, HexColor::parse("#448aff").unwrap());
assert_eq!(
GENERATED_CSS, actual,
"Rust generated CSS is out of step with SCSS generated CSS"
)
}
}

View File

@@ -1,333 +1,16 @@
use mdbook::book::{Book, BookItem, Chapter};
use mdbook::errors::Result;
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
use pulldown_cmark::{CodeBlockKind::*, Event, Options, Parser, Tag};
use std::str::FromStr;
//! For usage and reference, see the [mdbook-admonish book](https://tommilligan.github.io/mdbook-admonish/)
//!
//! Documentation is hosted externally, as docs.rs does not currently support plugins.
pub struct Admonish;
mod book_config;
mod config;
#[doc(hidden)]
pub mod custom;
mod markdown;
mod parse;
mod preprocessor;
mod render;
mod resolve;
mod types;
impl Preprocessor for Admonish {
fn name(&self) -> &str {
"admonish"
}
fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result<Book> {
let mut res = None;
book.for_each_mut(|item: &mut BookItem| {
if let Some(Err(_)) = res {
return;
}
if let BookItem::Chapter(ref mut chapter) = *item {
res = Some(Admonish::add_admonish(chapter).map(|md| {
chapter.content = md;
}));
}
});
res.unwrap_or(Ok(())).map(|_| book)
}
fn supports_renderer(&self, renderer: &str) -> bool {
renderer == "html"
}
}
fn escape_html(s: &str) -> String {
let mut output = String::new();
for c in s.chars() {
match c {
'<' => output.push_str("&lt;"),
'>' => output.push_str("&gt;"),
'"' => output.push_str("&quot;"),
'&' => output.push_str("&amp;"),
_ => output.push(c),
}
}
output
}
#[derive(Debug, PartialEq)]
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) -> std::result::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(()),
}
}
}
fn parse_info_string(info_string: &str) -> Option<Option<&str>> {
if info_string == "admonish" {
return Some(None);
}
match info_string.split_once(' ') {
Some(("admonish", directive)) => Some(Some(directive)),
_ => None,
}
}
/// 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(),
}
}
fn add_admonish(content: &str) -> Result<String> {
let mut opts = Options::empty();
opts.insert(Options::ENABLE_TABLES);
opts.insert(Options::ENABLE_FOOTNOTES);
opts.insert(Options::ENABLE_STRIKETHROUGH);
opts.insert(Options::ENABLE_TASKLISTS);
let mut admonish_blocks = vec![];
let events = Parser::new_ext(content, opts);
for (e, span) in events.into_offset_iter() {
if let Event::Start(Tag::CodeBlock(Fenced(info_string))) = e.clone() {
let directive_raw = match parse_info_string(info_string.as_ref()) {
Some(directive) => directive,
None => continue,
};
let directive = directive_raw
.map(|directive| Directive::from_str(directive).ok())
.flatten()
.unwrap_or(Directive::Note);
const PRE_START: &str = "```";
const PRE_END: &str = "\n";
const POST: &str = "```";
let start_index = span.start + PRE_START.len() + info_string.len() + PRE_END.len();
let end_index = span.end - POST.len();
let admonish_content = &content[start_index..end_index];
let admonish_content = escape_html(admonish_content);
let admonish_content = admonish_content.trim();
let directive_title = directive_raw
.map(ucfirst)
.unwrap_or_else(|| "Note".to_owned());
let directive_classname = match directive {
Directive::Note => "note",
Directive::Abstract => "abstract",
Directive::Info => "info",
Directive::Tip => "tip",
Directive::Success => "success",
Directive::Question => "question",
Directive::Warning => "warning",
Directive::Failure => "failure",
Directive::Danger => "danger",
Directive::Bug => "bug",
Directive::Example => "example",
Directive::Quote => "quote",
};
let admonish_code = format!(
r#"<div class="admonition {directive_classname}">
<p class="admonition-title">{directive_title}</p>
<p>{admonish_content}</p>
</div>"#
);
admonish_blocks.push((span, admonish_code.clone()));
}
}
let mut content = content.to_string();
for (span, block) in admonish_blocks.iter().rev() {
let pre_content = &content[..span.start];
let post_content = &content[span.end..];
content = format!("{}\n{}{}", pre_content, block, post_content);
}
Ok(content)
}
impl Admonish {
fn add_admonish(chapter: &mut Chapter) -> Result<String> {
add_admonish(&chapter.content)
}
}
#[cfg(test)]
mod test {
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(None));
assert_eq!(parse_info_string("admonish "), Some(Some("")));
assert_eq!(parse_info_string("admonish unknown"), Some(Some("unknown")));
assert_eq!(parse_info_string("admonish note"), Some(Some("note")));
}
#[test]
fn adds_admonish() {
let content = r#"# Chapter
```admonish
A simple admonition.
```
Text
"#;
let expected = r#"# Chapter
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>A simple admonition.</p>
</div>
Text
"#;
assert_eq!(expected, add_admonish(content).unwrap());
}
#[test]
fn adds_admonish_directive() {
let content = r#"# Chapter
```admonish warning
A simple admonition.
```
Text
"#;
let expected = r#"# Chapter
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<p>A simple admonition.</p>
</div>
Text
"#;
assert_eq!(expected, add_admonish(content).unwrap());
}
#[test]
fn leaves_tables_untouched() {
// Regression test.
// Previously we forgot to enable the same markdwon extensions as mdbook itself.
let content = r#"# Heading
| Head 1 | Head 2 |
|--------|--------|
| Row 1 | Row 2 |
"#;
let expected = r#"# Heading
| Head 1 | Head 2 |
|--------|--------|
| Row 1 | Row 2 |
"#;
assert_eq!(expected, add_admonish(content).unwrap());
}
#[test]
fn leaves_html_untouched() {
// Regression test.
// Don't remove important newlines for syntax nested inside HTML
let content = r#"# Heading
<del>
*foo*
</del>
"#;
let expected = r#"# Heading
<del>
*foo*
</del>
"#;
assert_eq!(expected, add_admonish(content).unwrap());
}
#[test]
fn html_in_list() {
// Regression test.
// Don't remove important newlines for syntax nested inside HTML
let content = r#"# Heading
1. paragraph 1
```
code 1
```
2. paragraph 2
"#;
let expected = r#"# Heading
1. paragraph 1
```
code 1
```
2. paragraph 2
"#;
assert_eq!(expected, add_admonish(content).unwrap());
}
#[test]
fn escape_in_admonish_block() {
let content = r#"
```admonish
classDiagram
class PingUploader {
<<interface>>
+Upload() UploadResult
}
```
hello
"#;
let expected = r#"
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>classDiagram
class PingUploader {
&lt;&lt;interface&gt;&gt;
+Upload() UploadResult
}</p>
</div>
hello
"#;
assert_eq!(expected, add_admonish(content).unwrap());
}
}
pub use crate::preprocessor::Admonish;

1264
src/markdown.rs Normal file

File diff suppressed because it is too large Load Diff

240
src/parse.rs Normal file
View File

@@ -0,0 +1,240 @@
use anyhow::{anyhow, Result};
use std::borrow::Cow;
use crate::{
book_config::OnFailure,
render::Admonition,
resolve::AdmonitionMeta,
types::{BuiltinDirective, CssId, Overrides},
};
/// 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`.
pub(crate) fn parse_admonition<'a>(
info_string: &'a str,
overrides: &'a Overrides,
content: &'a str,
on_failure: OnFailure,
indent: usize,
) -> Option<Result<Admonition<'a>>> {
// We need to know fence details anyway for error messages
let extracted = extract_admonish_body(content);
let info = AdmonitionMeta::from_info_string(info_string, overrides)?;
let info = match info {
Ok(info) => info,
Err(message) => {
// Construct a fence capable of enclosing whatever we wrote for the
// actual input block
let fence = extracted.fence;
let enclosing_fence: String = std::iter::repeat(fence.character)
.take(fence.length + 1)
.collect();
return Some(match on_failure {
OnFailure::Continue => {
log::warn!(
r#"Error processing admonition. To fail the build instead of continuing, set 'on_failure = "bail"'"#
);
Ok(Admonition {
directive: BuiltinDirective::Bug.to_string(),
title: "Error rendering admonishment".to_owned(),
css_id: CssId::Prefix("admonition-".to_owned()),
additional_classnames: Vec::new(),
collapsible: false,
content: Cow::Owned(format!(
r#"Failed with:
```log
{message}
```
Original markdown input:
{enclosing_fence}markdown
{content}
{enclosing_fence}
"#
)),
indent,
})
}
OnFailure::Bail => Err(anyhow!("Error processing admonition, bailing:\n{content}")),
});
}
};
Some(Ok(Admonition::new(
info,
extracted.body,
// Note that this is a bit hacky - the fence information comes from the start
// of the block, and includes the whole line.
//
// This is more likely to be what we want, as ending indentation is unrelated
// according to the commonmark spec (ref https://spec.commonmark.org/0.12/#example-85)
//
// The main case we're worried about here is indenting enough to be inside list items,
// and in this case the starting code fence must be indented enough to be considered
// part of the list item.
//
// The hacky thing is that we're considering line indent in the document as a whole,
// not relative to the context of some containing item. But I think that's what we
// want for now, anyway.
indent,
)))
}
/// We can't trust the info string length to find the start of the body
/// it may change length if it contains HTML or character escapes.
///
/// So we scan for the first newline and use that.
/// If gods forbid it doesn't exist for some reason, just include the whole info string.
fn extract_admonish_body_start_index(content: &str) -> usize {
let index = content
.find('\n')
// Start one character _after_ the newline
.map(|index| index + 1);
// If we can't get a valid index, include all content
match index {
// Couldn't find a newline
None => 0,
Some(index) => {
// Index out of bound of content
if index > (content.len() - 1) {
0
} else {
index
}
}
}
}
fn extract_admonish_body_end_index(content: &str) -> (usize, Fence) {
let fence_character = content.chars().next_back().unwrap_or('`');
let number_fence_characters = content
.chars()
.rev()
.position(|c| c != fence_character)
.unwrap_or_default();
let fence = Fence::new(fence_character, number_fence_characters);
let index = content.len() - fence.length;
(index, fence)
}
#[derive(Debug, PartialEq)]
struct Fence {
character: char,
length: usize,
}
impl Fence {
fn new(character: char, length: usize) -> Self {
Self { character, length }
}
}
#[derive(Debug, PartialEq)]
struct Extracted<'a> {
body: &'a str,
fence: Fence,
}
/// Given the whole text content of the code fence, extract the body.
///
/// This really feels like we should get the markdown parser to do it for us,
/// but it's not really clear a good way of doing that.
///
/// ref: https://spec.commonmark.org/0.30/#fenced-code-blocks
fn extract_admonish_body(content: &str) -> Extracted<'_> {
let start_index = extract_admonish_body_start_index(content);
let (end_index, fence) = extract_admonish_body_end_index(content);
let admonish_content = &content[start_index..end_index];
// The newline after a code block is technically optional, so we have to
// trim it off dynamically.
let body = admonish_content.trim_end();
Extracted { body, fence }
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_extract_start() {
for (text, expected) in [
("```sane example\ncontent```", 16),
("~~~~~\nlonger fence", 6),
// empty
("```\n```", 4),
// bounds check, should not index outside of content
("```\n", 0),
] {
let actual = extract_admonish_body_start_index(text);
assert_eq!(actual, expected);
}
}
#[test]
fn test_extract_end() {
for (text, expected) in [
("\n```", (1, Fence::new('`', 3))),
// different lengths
("\n``````", (1, Fence::new('`', 6))),
("\n~~~~", (1, Fence::new('~', 4))),
// whitespace before fence end
("\n ```", (4, Fence::new('`', 3))),
("content\n```", (8, Fence::new('`', 3))),
] {
let actual = extract_admonish_body_end_index(text);
assert_eq!(actual, expected);
}
}
#[test]
fn test_extract() {
fn content_fence(body: &'static str, character: char, length: usize) -> Extracted<'static> {
Extracted {
body,
fence: Fence::new(character, length),
}
}
for (text, expected) in [
// empty
("```\n```", content_fence("", '`', 3)),
// standard
(
"```admonish\ncontent\n```",
content_fence("content", '`', 3),
),
// whitespace
(
"```admonish \n content \n ```",
content_fence(" content", '`', 3),
),
// longer
(
"``````admonish\ncontent\n``````",
content_fence("content", '`', 6),
),
// unequal
(
"~~~admonish\ncontent\n~~~~~",
// longer (end) fence returned
content_fence("content", '~', 5),
),
] {
let actual = extract_admonish_body(text);
assert_eq!(actual, expected);
}
}
}

269
src/preprocessor.rs Normal file
View File

@@ -0,0 +1,269 @@
use anyhow::{anyhow, Result};
use mdbook::{
book::{Book, BookItem},
errors::Result as MdbookResult,
preprocess::{Preprocessor, PreprocessorContext},
};
use crate::{
book_config::{admonish_config_from_context, Config, RenderMode},
markdown::preprocess,
types::{Overrides, RenderTextMode},
};
pub struct Admonish;
impl Preprocessor for Admonish {
fn name(&self) -> &str {
"admonish"
}
fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> MdbookResult<Book> {
let config = admonish_config_from_context(ctx)?;
ensure_compatible_assets_version(&config)?;
let custom_directives = config
.directive
.custom
.into_iter()
.map(Into::into)
.collect();
let on_failure = config.on_failure;
let admonition_defaults = config.default;
let overrides = Overrides {
book: admonition_defaults,
custom: custom_directives,
builtin: config.directive.builtin,
};
// Load what rendering we should do from config, falling back to a default
let render_mode = config
.renderer
.get(&ctx.renderer)
.and_then(|renderer| renderer.render_mode)
.unwrap_or_else(|| {
// By default only render html for the html renderer
// For everything else, do nothing
if &ctx.renderer == "html" {
RenderMode::Html
} else {
RenderMode::Preserve
}
});
let render_text_mode = match render_mode {
RenderMode::Preserve => return Ok(book),
RenderMode::Html => RenderTextMode::Html,
RenderMode::Strip => RenderTextMode::Strip,
};
let mut res = None;
book.for_each_mut(|item: &mut BookItem| {
if let Some(Err(_)) = res {
return;
}
if let BookItem::Chapter(ref mut chapter) = *item {
res = Some(
preprocess(&chapter.content, on_failure, &overrides, render_text_mode).map(
|md| {
chapter.content = md;
},
),
);
}
});
res.unwrap_or(Ok(())).map(|_| book)
}
fn supports_renderer(&self, _renderer: &str) -> bool {
// We support all renderers, but will only actually take action
// if configured to do so - or, if it's the html renderer
true
}
}
fn ensure_compatible_assets_version(config: &Config) -> Result<()> {
use semver::{Version, VersionReq};
const REQUIRES_ASSETS_VERSION: &str = std::include_str!("./REQUIRED_ASSETS_VERSION");
let requirement = VersionReq::parse(REQUIRES_ASSETS_VERSION.trim()).unwrap();
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 &config.assets_version {
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(())
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
use serde_json::{json, Value};
fn mock_book(content: &str) -> Book {
serde_json::from_value(json!({
"sections": [
{
"Chapter": {
"name": "Chapter 1",
"content": content,
"number": [1],
"sub_items": [],
"path": "chapter_1.md",
"source_path": "chapter_1.md",
"parent_names": []
}
}
],
"__non_exhaustive": null
}))
.unwrap()
}
fn mock_context(admonish: &Value, renderer: &str) -> PreprocessorContext {
let value = json!({
"root": "/path/to/book",
"config": {
"book": {
"authors": ["AUTHOR"],
"language": "en",
"multilingual": false,
"src": "src",
"title": "TITLE"
},
"preprocessor": {
"admonish": admonish,
}
},
"renderer": renderer,
"mdbook_version": "0.4.21"
});
serde_json::from_value(value).unwrap()
}
#[test]
fn run_html() {
let content = r#"
````admonish title="Title"
```rust
let x = 10;
x = 20;
```
````
"#;
let expected_content = r##"
<div id="admonition-title" class="admonition admonish-note" role="note" aria-labelledby="admonition-title-title">
<div class="admonition-title">
<div id="admonition-title-title">
Title
</div>
<a class="admonition-anchor-link" href="#admonition-title"></a>
</div>
<div>
```rust
let x = 10;
x = 20;
```
</div>
</div>
"##;
let ctx = mock_context(
&json!({
"assets_version": "3.0.0"
}),
"html",
);
let book = mock_book(content);
let expected_book = mock_book(expected_content);
assert_eq!(Admonish.run(&ctx, book).unwrap(), expected_book)
}
#[test]
fn run_test_preserves_by_default() {
let content = r#"
````admonish title="Title"
```rust
let x = 10;
x = 20;
```
````
"#;
let ctx = mock_context(
&json!({
"assets_version": "3.0.0"
}),
"test",
);
let book = mock_book(content);
let expected_book = book.clone();
assert_eq!(Admonish.run(&ctx, book).unwrap(), expected_book)
}
#[test]
fn run_test_can_strip() {
let content = r#"
````admonish title="Title"
```rust
let x = 10;
x = 20;
```
````
"#;
let expected_content = r#"
```rust
let x = 10;
x = 20;
```
"#;
let ctx = mock_context(
&json!({
"assets_version": "3.0.0",
"renderer": {
"test": {
"render_mode": "strip",
},
},
}),
"test",
);
let book = mock_book(content);
let expected_book = mock_book(expected_content);
assert_eq!(Admonish.run(&ctx, book).unwrap(), expected_book)
}
}

134
src/render.rs Normal file
View File

@@ -0,0 +1,134 @@
use mdbook::utils::unique_id_from_content;
use std::borrow::Cow;
use std::collections::HashMap;
use crate::{resolve::AdmonitionMeta, types::CssId};
#[derive(Debug, PartialEq)]
pub(crate) struct Admonition<'a> {
pub(crate) directive: String,
pub(crate) title: String,
pub(crate) content: Cow<'a, str>,
pub(crate) css_id: CssId,
pub(crate) additional_classnames: Vec<String>,
pub(crate) collapsible: bool,
pub(crate) indent: usize,
}
impl<'a> Admonition<'a> {
pub(crate) fn new(info: AdmonitionMeta, content: &'a str, indent: usize) -> Self {
let AdmonitionMeta {
directive,
title,
css_id,
additional_classnames,
collapsible,
} = info;
Self {
directive,
title,
content: Cow::Borrowed(content),
css_id,
additional_classnames,
collapsible,
indent,
}
}
pub(crate) fn html(self, id_counter: &mut HashMap<String, usize>) -> String {
let anchor_id = match &self.css_id {
CssId::Verbatim(id) => Cow::Borrowed(id.as_str()),
CssId::Prefix(prefix) => {
let id = unique_id_from_content(
if !self.title.is_empty() {
&self.title
} else {
ANCHOR_ID_DEFAULT
},
id_counter,
);
Cow::Owned(format!("{}{}", prefix, id))
}
};
let title = &self.title;
let content = &self.content;
let indent = " ".repeat(self.indent);
let (titlebar_html, title_id) = if !title.is_empty() {
let titlebar_element = if self.collapsible { "summary" } else { "div" };
let title_id = format!("{anchor_id}-title");
let titlebar_html = Cow::Owned(format!(
r##"{indent}<{titlebar_element} class="admonition-title">
{indent}<div id="{title_id}">
{indent}
{indent}{title}
{indent}
{indent}</div>
{indent}<a class="admonition-anchor-link" href="#{anchor_id}"></a>
{indent}</{titlebar_element}>
"##
));
(titlebar_html, Some(title_id))
} else {
(Cow::Borrowed(""), None)
};
let mut classes = vec![
"admonition".to_owned(),
format!("admonish-{}", self.directive),
];
classes.extend(self.additional_classnames);
let classes = classes.join(" ");
let mut attributes = vec![
("id", anchor_id),
("class", Cow::Owned(classes)),
("role", Cow::Borrowed("note")),
];
if let Some(title_id) = title_id {
attributes.push(("aria-labelledby", Cow::Owned(title_id)));
}
let attributes = join_attributes(&attributes);
let admonition_element = 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.
// - We should not indent the inner content, as it retains the indent
// it is written with.
format!(
r#"
{indent}<{admonition_element} {attributes}>
{titlebar_html}{indent}<div>
{content}
{indent}</div>
{indent}</{admonition_element}>"#,
)
}
/// Strips all admonish syntax, leaving the plain content of the block.
pub(crate) fn strip(self) -> String {
// Add in newlines to preserve line numbering for test output
// These replace the code fences we stripped out
format!("\n{}\n", self.content)
}
}
fn join_attributes(attributes: &[(impl AsRef<str>, impl AsRef<str>)]) -> String {
let mut buffer = String::new();
for (key, value) in attributes {
buffer.push_str(key.as_ref());
buffer.push_str(r#"=""#);
buffer.push_str(value.as_ref());
buffer.push_str(r#"" "#);
}
buffer.pop();
buffer
}
const ANCHOR_ID_DEFAULT: &str = "default";

460
src/resolve.rs Normal file
View File

@@ -0,0 +1,460 @@
use crate::config::InstanceConfig;
use crate::types::{BuiltinDirective, CssId, CustomDirective, CustomDirectiveMap, Overrides};
use std::fmt;
use std::str::FromStr;
/// All information required to render an admonition.
///
/// i.e. all configured options have been resolved at this point.
#[derive(Debug, PartialEq)]
pub(crate) struct AdmonitionMeta {
pub directive: String,
pub title: String,
pub css_id: CssId,
pub additional_classnames: Vec<String>,
pub collapsible: bool,
}
/// Wrapper type to hold any value directive configuration.
enum Directive {
Builtin(BuiltinDirective),
Custom(CustomDirective),
}
impl fmt::Display for Directive {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Builtin(builtin) => builtin.fmt(f),
Self::Custom(custom) => f.write_str(&custom.directive),
}
}
}
impl Directive {
fn from_str(custom_directive_map: &CustomDirectiveMap, string: &str) -> Result<Self, ()> {
if let Ok(builtin) = BuiltinDirective::from_str(string) {
return Ok(Self::Builtin(builtin));
}
if let Some(config) = custom_directive_map.get(string) {
return Ok(Self::Custom(config.clone()));
}
Err(())
}
fn title(self, raw_directive: &str) -> String {
match self {
Directive::Builtin(_) => format_builtin_directive_title(raw_directive),
Directive::Custom(custom) => custom
.title
.clone()
.unwrap_or_else(|| uppercase_first(raw_directive)),
}
}
}
impl AdmonitionMeta {
pub fn from_info_string(
info_string: &str,
overrides: &Overrides,
) -> Option<Result<Self, String>> {
InstanceConfig::from_info_string(info_string)
.map(|raw| raw.map(|raw| Self::resolve(raw, overrides)))
}
/// Combine the per-admonition configuration with global defaults (and
/// other logic) to resolve the values needed for rendering.
fn resolve(raw: InstanceConfig, overrides: &Overrides) -> Self {
let InstanceConfig {
directive: raw_directive,
title,
id,
additional_classnames,
collapsible,
} = raw;
// Use values from block, else load default value
let title = title.or_else(|| overrides.book.title.clone());
let directive = Directive::from_str(&overrides.custom, &raw_directive);
let collapsible = match directive {
// If the directive is a builin one, use collapsible from block, else use default
// value of the builtin directive, else use global default value
Ok(Directive::Builtin(directive)) => collapsible.unwrap_or(
overrides
.builtin
.get(&directive)
.and_then(|config| config.collapsible)
.unwrap_or(overrides.book.collapsible),
),
// If the directive is a custom one, use collapsible from block, else use default
// value of the custom directive, else use global default value
Ok(Directive::Custom(ref custom_dir)) => {
collapsible.unwrap_or(custom_dir.collapsible.unwrap_or(overrides.book.collapsible))
}
Err(_) => collapsible.unwrap_or(overrides.book.collapsible),
};
// Load the directive (and title, if one still not given)
let (directive, title) = match (directive, title) {
(Ok(directive), None) => (directive.to_string(), directive.title(&raw_directive)),
(Err(_), None) => (BuiltinDirective::Note.to_string(), "Note".to_owned()),
(Ok(directive), Some(title)) => (directive.to_string(), title),
(Err(_), Some(title)) => (BuiltinDirective::Note.to_string(), title),
};
let css_id = if let Some(verbatim) = id {
CssId::Verbatim(verbatim)
} else {
const DEFAULT_CSS_ID_PREFIX: &str = "admonition-";
CssId::Prefix(
overrides
.book
.css_id_prefix
.clone()
.unwrap_or_else(|| DEFAULT_CSS_ID_PREFIX.to_owned()),
)
};
Self {
directive,
title,
css_id,
additional_classnames,
collapsible,
}
}
}
/// Format the title of an admonition directive
///
/// We special case a few words to make them look nicer (e.g. "tldr" -> "TL;DR" and "faq" -> "FAQ").
fn format_builtin_directive_title(input: &str) -> String {
match input {
"tldr" => "TL;DR".to_owned(),
"faq" => "FAQ".to_owned(),
_ => uppercase_first(input),
}
}
/// Make the first letter of `input` uppercase.
///
/// source: https://stackoverflow.com/a/38406885
fn uppercase_first(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 std::collections::HashMap;
use crate::types::{AdmonitionDefaults, BuiltinDirectiveConfig};
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_format_builtin_directive_title() {
assert_eq!(format_builtin_directive_title(""), "");
assert_eq!(format_builtin_directive_title("a"), "A");
assert_eq!(format_builtin_directive_title("tldr"), "TL;DR");
assert_eq!(format_builtin_directive_title("faq"), "FAQ");
assert_eq!(format_builtin_directive_title("note"), "Note");
assert_eq!(format_builtin_directive_title("abstract"), "Abstract");
// Unicode
assert_eq!(format_builtin_directive_title("🦀"), "🦀");
}
#[test]
fn test_admonition_info_from_raw() {
assert_eq!(
AdmonitionMeta::resolve(
InstanceConfig {
directive: " ".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&Overrides::default(),
),
AdmonitionMeta {
directive: "note".to_owned(),
title: "Note".to_owned(),
css_id: CssId::Prefix("admonition-".to_owned()),
additional_classnames: Vec::new(),
collapsible: false,
}
);
}
#[test]
fn test_admonition_info_from_raw_with_defaults() {
assert_eq!(
AdmonitionMeta::resolve(
InstanceConfig {
directive: " ".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&Overrides {
book: AdmonitionDefaults {
title: Some("Important!!!".to_owned()),
css_id_prefix: Some("custom-prefix-".to_owned()),
collapsible: true,
},
..Default::default()
}
),
AdmonitionMeta {
directive: "note".to_owned(),
title: "Important!!!".to_owned(),
css_id: CssId::Prefix("custom-prefix-".to_owned()),
additional_classnames: Vec::new(),
collapsible: true,
}
);
}
#[test]
fn test_admonition_info_from_raw_with_defaults_and_custom_id() {
assert_eq!(
AdmonitionMeta::resolve(
InstanceConfig {
directive: " ".to_owned(),
title: None,
id: Some("my-custom-id".to_owned()),
additional_classnames: Vec::new(),
collapsible: None,
},
&Overrides {
book: AdmonitionDefaults {
title: Some("Important!!!".to_owned()),
css_id_prefix: Some("ignored-custom-prefix-".to_owned()),
collapsible: true,
},
..Default::default()
}
),
AdmonitionMeta {
directive: "note".to_owned(),
title: "Important!!!".to_owned(),
css_id: CssId::Verbatim("my-custom-id".to_owned()),
additional_classnames: Vec::new(),
collapsible: true,
}
);
}
#[test]
fn test_admonition_info_from_raw_with_custom_directive() {
assert_eq!(
AdmonitionMeta::resolve(
InstanceConfig {
directive: "frog".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&Overrides {
custom: [CustomDirective {
directive: "frog".to_owned(),
aliases: Vec::new(),
title: None,
collapsible: None,
}]
.into_iter()
.collect(),
..Default::default()
}
),
AdmonitionMeta {
directive: "frog".to_owned(),
title: "Frog".to_owned(),
css_id: CssId::Prefix("admonition-".to_owned()),
additional_classnames: Vec::new(),
collapsible: false,
}
);
}
#[test]
fn test_admonition_info_from_raw_with_custom_directive_and_title() {
assert_eq!(
AdmonitionMeta::resolve(
InstanceConfig {
directive: "frog".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&Overrides {
custom: [CustomDirective {
directive: "frog".to_owned(),
aliases: Vec::new(),
title: Some("🏳️‍🌈".to_owned()),
collapsible: None,
}]
.into_iter()
.collect(),
..Default::default()
}
),
AdmonitionMeta {
directive: "frog".to_owned(),
title: "🏳️‍🌈".to_owned(),
css_id: CssId::Prefix("admonition-".to_owned()),
additional_classnames: Vec::new(),
collapsible: false,
}
);
}
#[test]
fn test_admonition_info_from_raw_with_custom_directive_alias() {
assert_eq!(
AdmonitionMeta::resolve(
InstanceConfig {
directive: "toad".to_owned(),
title: Some("Still a frog".to_owned()),
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&Overrides {
custom: [CustomDirective {
directive: "frog".to_owned(),
aliases: vec!["newt".to_owned(), "toad".to_owned()],
title: Some("🏳️‍🌈".to_owned()),
collapsible: None,
}]
.into_iter()
.collect(),
..Default::default()
}
),
AdmonitionMeta {
directive: "frog".to_owned(),
title: "Still a frog".to_owned(),
css_id: CssId::Prefix("admonition-".to_owned()),
additional_classnames: Vec::new(),
collapsible: false,
}
);
}
#[test]
fn test_admonition_info_from_raw_with_collapsible_custom_directive() {
assert_eq!(
AdmonitionMeta::resolve(
InstanceConfig {
directive: "frog".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&Overrides {
custom: [CustomDirective {
directive: "frog".to_owned(),
aliases: Vec::new(),
title: None,
collapsible: Some(true),
}]
.into_iter()
.collect(),
..Default::default()
}
),
AdmonitionMeta {
directive: "frog".to_owned(),
title: "Frog".to_owned(),
css_id: CssId::Prefix("admonition-".to_owned()),
additional_classnames: Vec::new(),
collapsible: true,
}
);
}
#[test]
fn test_admonition_info_from_raw_with_collapsible_builtin_directive() {
assert_eq!(
AdmonitionMeta::resolve(
InstanceConfig {
directive: "abstract".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&Overrides {
book: AdmonitionDefaults {
title: None,
css_id_prefix: None,
collapsible: false,
},
builtin: HashMap::from([(
BuiltinDirective::Abstract,
BuiltinDirectiveConfig {
collapsible: Some(true),
}
)]),
..Default::default()
}
),
AdmonitionMeta {
directive: "abstract".to_owned(),
title: "Abstract".to_owned(),
css_id: CssId::Prefix("admonition-".to_owned()),
additional_classnames: Vec::new(),
collapsible: true,
}
);
}
#[test]
fn test_admonition_info_from_raw_with_non_collapsible_builtin_directive() {
assert_eq!(
AdmonitionMeta::resolve(
InstanceConfig {
directive: "abstract".to_owned(),
title: None,
id: None,
additional_classnames: Vec::new(),
collapsible: None,
},
&Overrides {
book: AdmonitionDefaults {
title: None,
css_id_prefix: None,
collapsible: true,
},
builtin: HashMap::from([(
BuiltinDirective::Abstract,
BuiltinDirectiveConfig {
collapsible: Some(false),
}
)]),
..Default::default()
}
),
AdmonitionMeta {
directive: "abstract".to_owned(),
title: "Abstract".to_owned(),
css_id: CssId::Prefix("admonition-".to_owned()),
additional_classnames: Vec::new(),
collapsible: false,
}
);
}
}

View File

@@ -0,0 +1,20 @@
:root {
--md-admonition-icon--admonish-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>");
}
:is(.admonition):is(.admonish-note) {
border-color: #448aff;
}
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(68, 138, 255, 0.1);
}
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #448aff;
mask-image: var(--md-admonition-icon--admonish-note);
-webkit-mask-image: var(--md-admonition-icon--admonish-note);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}

178
src/types.rs Normal file
View File

@@ -0,0 +1,178 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
/// Book wide defaults that may be provided by the user.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
pub(crate) struct AdmonitionDefaults {
#[serde(default)]
pub(crate) title: Option<String>,
#[serde(default)]
pub(crate) collapsible: bool,
#[serde(default)]
// For backwards compatibility, we support this field with kebab-case style
// naming, even though this was introduced in error.
#[serde(alias = "css-id-prefix")]
pub(crate) css_id_prefix: Option<String>,
}
/// First class supported directives by the crate.
///
/// These are guaranteed to have valid CSS/icons available.
///
/// Custom directives can also be added via the book.toml config.
#[derive(Debug, PartialEq, Clone, Copy, Eq, Deserialize, Serialize, Hash)]
#[serde(rename_all = "lowercase")]
pub(crate) enum BuiltinDirective {
Note,
Abstract,
Info,
Tip,
Success,
Question,
Warning,
Failure,
Danger,
Bug,
Example,
Quote,
}
impl FromStr for BuiltinDirective {
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(()),
}
}
}
impl fmt::Display for BuiltinDirective {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let value = match self {
Self::Note => "note",
Self::Abstract => "abstract",
Self::Info => "info",
Self::Tip => "tip",
Self::Success => "success",
Self::Question => "question",
Self::Warning => "warning",
Self::Failure => "failure",
Self::Danger => "danger",
Self::Bug => "bug",
Self::Example => "example",
Self::Quote => "quote",
};
f.write_str(value)
}
}
/// The subset of information we care about during plugin runtime for custom directives.
///
/// This drops information only needed during CSS generation.
#[derive(Debug, Clone)]
pub(crate) struct CustomDirective {
pub directive: String,
pub aliases: Vec<String>,
pub title: Option<String>,
pub collapsible: Option<bool>,
}
impl From<(String, crate::book_config::CustomDirective)> for CustomDirective {
fn from((directive, config): (String, crate::book_config::CustomDirective)) -> Self {
let crate::book_config::CustomDirective {
aliases,
title,
collapsible,
..
} = config;
Self {
directive,
aliases,
title,
collapsible,
}
}
}
/// A map from the user given directive to underlying config.
///
/// The terminology is a bit mixed here - this map allows any input-directive,
/// and returns the output-directive config.
///
/// i.e. this is the step alias mapping happens at
#[derive(Debug, Clone, Default)]
pub(crate) struct CustomDirectiveMap {
inner: HashMap<String, CustomDirective>,
}
impl CustomDirectiveMap {
pub fn get(&self, key: &str) -> Option<&CustomDirective> {
self.inner.get(key)
}
}
impl FromIterator<CustomDirective> for CustomDirectiveMap {
fn from_iter<I: IntoIterator<Item = CustomDirective>>(iter: I) -> Self {
let mut inner = HashMap::default();
for config in iter.into_iter() {
inner
.entry(config.directive.clone())
.or_insert(config.clone());
for alias in config.aliases.iter() {
inner.entry(alias.clone()).or_insert(config.clone());
}
}
Self { inner }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub(crate) struct BuiltinDirectiveConfig {
/// Default collapsible value.
#[serde(default)]
pub collapsible: Option<bool>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RenderTextMode {
Strip,
Html,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum CssId {
/// id="my-id" in the admonishment
///
/// used directly for the id field
Verbatim(String),
/// the prefix from default.css_id_prefix (or "admonish-" if not specified)
///
/// will generate the rest of the id based on the title
Prefix(String),
}
#[derive(Debug, Clone, Default)]
pub(crate) struct Overrides {
pub book: AdmonitionDefaults,
pub builtin: HashMap<BuiltinDirective, BuiltinDirectiveConfig>,
pub custom: CustomDirectiveMap,
}

13
v2.md Normal file
View File

@@ -0,0 +1,13 @@
# v2 Notes
## Compatibility to drop
- v1 config loading system from block info strings
- v2 config loading system from block info strings
- Support for `custom` configuration, moved to `directive.custom`
- `css-id-prefix` kebab case
## Wishlist
- `mdbook` not to use a broken version of `toml` in the public api
- `mdbook` to support loading css files from plugins