summaryrefslogtreecommitdiffstats
path: root/vendor/mdbook
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /vendor/mdbook
parentInitial commit. (diff)
downloadrustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz
rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/mdbook')
-rw-r--r--vendor/mdbook/.cargo-checksum.json1
-rw-r--r--vendor/mdbook/CHANGELOG.md819
-rw-r--r--vendor/mdbook/CODE_OF_CONDUCT.md3
-rw-r--r--vendor/mdbook/CONTRIBUTING.md130
-rw-r--r--vendor/mdbook/Cargo.lock1991
-rw-r--r--vendor/mdbook/Cargo.toml165
-rw-r--r--vendor/mdbook/LICENSE362
-rw-r--r--vendor/mdbook/README.md20
-rwxr-xr-xvendor/mdbook/ci/install-hub.sh24
-rwxr-xr-xvendor/mdbook/ci/install-rust.sh19
-rwxr-xr-xvendor/mdbook/ci/make-release.sh47
-rw-r--r--vendor/mdbook/examples/nop-preprocessor.rs104
-rw-r--r--vendor/mdbook/release.toml3
-rw-r--r--vendor/mdbook/src/book/book.rs640
-rw-r--r--vendor/mdbook/src/book/init.rs207
-rw-r--r--vendor/mdbook/src/book/mod.rs836
-rw-r--r--vendor/mdbook/src/book/summary.rs1097
-rw-r--r--vendor/mdbook/src/cmd/build.rs50
-rw-r--r--vendor/mdbook/src/cmd/clean.rs44
-rw-r--r--vendor/mdbook/src/cmd/init.rs126
-rw-r--r--vendor/mdbook/src/cmd/mod.rs10
-rw-r--r--vendor/mdbook/src/cmd/serve.rs177
-rw-r--r--vendor/mdbook/src/cmd/test.rs54
-rw-r--r--vendor/mdbook/src/cmd/watch.rs175
-rw-r--r--vendor/mdbook/src/config.rs1190
-rw-r--r--vendor/mdbook/src/lib.rs119
-rw-r--r--vendor/mdbook/src/main.rs150
-rw-r--r--vendor/mdbook/src/preprocess/cmd.rs207
-rw-r--r--vendor/mdbook/src/preprocess/index.rs105
-rw-r--r--vendor/mdbook/src/preprocess/links.rs937
-rw-r--r--vendor/mdbook/src/preprocess/mod.rs70
-rw-r--r--vendor/mdbook/src/renderer/html_handlebars/hbs_renderer.rs1106
-rw-r--r--vendor/mdbook/src/renderer/html_handlebars/helpers/mod.rs3
-rw-r--r--vendor/mdbook/src/renderer/html_handlebars/helpers/navigation.rs290
-rw-r--r--vendor/mdbook/src/renderer/html_handlebars/helpers/theme.rs28
-rw-r--r--vendor/mdbook/src/renderer/html_handlebars/helpers/toc.rs203
-rw-r--r--vendor/mdbook/src/renderer/html_handlebars/mod.rs9
-rw-r--r--vendor/mdbook/src/renderer/html_handlebars/search.rs286
-rw-r--r--vendor/mdbook/src/renderer/markdown_renderer.rs52
-rw-r--r--vendor/mdbook/src/renderer/mod.rs265
-rw-r--r--vendor/mdbook/src/theme/ayu-highlight.css78
-rw-r--r--vendor/mdbook/src/theme/book.js679
-rw-r--r--vendor/mdbook/src/theme/css/chrome.css534
-rw-r--r--vendor/mdbook/src/theme/css/general.css191
-rw-r--r--vendor/mdbook/src/theme/css/print.css54
-rw-r--r--vendor/mdbook/src/theme/css/variables.css253
-rw-r--r--vendor/mdbook/src/theme/favicon.pngbin0 -> 5679 bytes
-rwxr-xr-xvendor/mdbook/src/theme/favicon.svg22
-rw-r--r--vendor/mdbook/src/theme/head.hbs1
-rw-r--r--vendor/mdbook/src/theme/header.hbs1
-rw-r--r--vendor/mdbook/src/theme/index.hbs314
-rw-r--r--vendor/mdbook/src/theme/mod.rs270
-rw-r--r--vendor/mdbook/src/theme/redirect.hbs12
-rw-r--r--vendor/mdbook/src/theme/searcher/mod.rs6
-rw-r--r--vendor/mdbook/src/theme/searcher/searcher.js483
-rw-r--r--vendor/mdbook/src/theme/tomorrow-night.css102
-rw-r--r--vendor/mdbook/src/utils/fs.rs275
-rw-r--r--vendor/mdbook/src/utils/mod.rs494
-rw-r--r--vendor/mdbook/src/utils/string.rs255
-rw-r--r--vendor/mdbook/src/utils/toml_ext.rs130
-rw-r--r--vendor/mdbook/test_book/book.toml27
-rw-r--r--vendor/mdbook/test_book/src/README.md12
-rw-r--r--vendor/mdbook/test_book/src/SUMMARY.md33
-rw-r--r--vendor/mdbook/test_book/src/individual/README.md17
-rw-r--r--vendor/mdbook/test_book/src/individual/blockquote.md30
-rw-r--r--vendor/mdbook/test_book/src/individual/code.md31
-rw-r--r--vendor/mdbook/test_book/src/individual/emphasis.md13
-rw-r--r--vendor/mdbook/test_book/src/individual/heading.md15
-rw-r--r--vendor/mdbook/test_book/src/individual/image.md27
-rw-r--r--vendor/mdbook/test_book/src/individual/linebreak.md8
-rw-r--r--vendor/mdbook/test_book/src/individual/link_hr.md15
-rw-r--r--vendor/mdbook/test_book/src/individual/list.md35
-rw-r--r--vendor/mdbook/test_book/src/individual/mixed.md61
-rw-r--r--vendor/mdbook/test_book/src/individual/paragraph.md25
-rw-r--r--vendor/mdbook/test_book/src/individual/strikethrough.md5
-rw-r--r--vendor/mdbook/test_book/src/individual/table.md28
-rw-r--r--vendor/mdbook/test_book/src/individual/task.md11
-rw-r--r--vendor/mdbook/test_book/src/languages/README.md47
-rw-r--r--vendor/mdbook/test_book/src/languages/highlight.md927
-rw-r--r--vendor/mdbook/test_book/src/prefix.md3
-rw-r--r--vendor/mdbook/test_book/src/rust/README.md1
-rw-r--r--vendor/mdbook/test_book/src/rust/rust_codeblock.md27
-rw-r--r--vendor/mdbook/test_book/src/suffix.md3
-rw-r--r--vendor/mdbook/tests/alternative_backends.rs164
-rw-r--r--vendor/mdbook/tests/build_process.rs78
-rw-r--r--vendor/mdbook/tests/cli/build.rs28
-rw-r--r--vendor/mdbook/tests/cli/cmd.rs7
-rw-r--r--vendor/mdbook/tests/cli/mod.rs3
-rw-r--r--vendor/mdbook/tests/cli/test.rs34
-rw-r--r--vendor/mdbook/tests/cli_tests.rs2
-rw-r--r--vendor/mdbook/tests/custom_preprocessors.rs56
-rw-r--r--vendor/mdbook/tests/dummy_book/index_html_test/SUMMARY.md11
-rw-r--r--vendor/mdbook/tests/dummy_book/index_html_test/chapter_1.md1
-rw-r--r--vendor/mdbook/tests/dummy_book/mod.rs145
-rw-r--r--vendor/mdbook/tests/dummy_book/src/README.md5
-rw-r--r--vendor/mdbook/tests/dummy_book/src/SUMMARY.md22
-rw-r--r--vendor/mdbook/tests/dummy_book/src/conclusion.md20
-rw-r--r--vendor/mdbook/tests/dummy_book/src/example.rs6
-rw-r--r--vendor/mdbook/tests/dummy_book/src/first/duplicate-headers.md9
-rw-r--r--vendor/mdbook/tests/dummy_book/src/first/includes.md3
-rw-r--r--vendor/mdbook/tests/dummy_book/src/first/index.md5
-rw-r--r--vendor/mdbook/tests/dummy_book/src/first/markdown.md29
-rw-r--r--vendor/mdbook/tests/dummy_book/src/first/nested-test-with-anchors.rs11
-rw-r--r--vendor/mdbook/tests/dummy_book/src/first/nested-test.rs1
-rw-r--r--vendor/mdbook/tests/dummy_book/src/first/nested.md31
-rw-r--r--vendor/mdbook/tests/dummy_book/src/first/no-headers.md5
-rw-r--r--vendor/mdbook/tests/dummy_book/src/first/partially-included-test-with-anchors.rs11
-rw-r--r--vendor/mdbook/tests/dummy_book/src/first/partially-included-test.rs7
-rw-r--r--vendor/mdbook/tests/dummy_book/src/first/recursive.md2
-rw-r--r--vendor/mdbook/tests/dummy_book/src/first/unicode.md21
-rw-r--r--vendor/mdbook/tests/dummy_book/src/intro.md3
-rw-r--r--vendor/mdbook/tests/dummy_book/src/second.md5
-rw-r--r--vendor/mdbook/tests/dummy_book/src/second/nested.md16
-rw-r--r--vendor/mdbook/tests/dummy_book/src2/README.md1
-rw-r--r--vendor/mdbook/tests/dummy_book/src2/SUMMARY.md7
-rw-r--r--vendor/mdbook/tests/dummy_book/src2/first/README.md1
-rw-r--r--vendor/mdbook/tests/dummy_book/src2/second/README.md1
-rw-r--r--vendor/mdbook/tests/dummy_book/src2/second/index.md1
-rw-r--r--vendor/mdbook/tests/dummy_book/summary-formatting/SUMMARY.md6
-rw-r--r--vendor/mdbook/tests/init.rs144
-rw-r--r--vendor/mdbook/tests/parse_existing_summary_files.rs43
-rw-r--r--vendor/mdbook/tests/rendered_output.rs844
-rw-r--r--vendor/mdbook/tests/searchindex_fixture.json6991
-rw-r--r--vendor/mdbook/tests/summary_md_files/example_book.md20
-rw-r--r--vendor/mdbook/tests/summary_md_files/rust_by_example.md191
-rw-r--r--vendor/mdbook/tests/summary_md_files/rust_ffi_guide.md19
-rw-r--r--vendor/mdbook/tests/summary_md_files/the_book-2nd_edition.md130
-rw-r--r--vendor/mdbook/tests/testing.rs26
-rw-r--r--vendor/mdbook/triagebot.toml12
129 files changed, 26857 insertions, 0 deletions
diff --git a/vendor/mdbook/.cargo-checksum.json b/vendor/mdbook/.cargo-checksum.json
new file mode 100644
index 000000000..c18c39d0f
--- /dev/null
+++ b/vendor/mdbook/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"e357e91c8cc3c7523b764c8554f3c9fd9f2062a11da20e05b6afa28f76e81a04","CODE_OF_CONDUCT.md":"3c4d1c4de2e6991695f5dc495f7530ecb188dfafdb1f47a1323ce7159987accd","CONTRIBUTING.md":"67ecae009bca8348ea8a042128ecc1a5affa98299310eaa9bc1fd50d1d12a450","Cargo.lock":"9bd6a84f91737c1bcd8786d835c3ba35a81ed9a667f760486446c0cfe6e16c35","Cargo.toml":"1318c7e161da7c3e5212fe58d4edb1b088d90292305a72cd931bedb1c07e7312","LICENSE":"af175b9d96ee93c21a036152e1b905b0b95304d4ae8c2c921c7609100ba8df7e","README.md":"44e4086a85125c978b6c56be6beeb5d5b24c7394c96e281c1c589217eff4ca5f","ci/install-hub.sh":"d229a71892d2f622fecfefe3c3046efc127b25d5aa95baea2cce3391213198fb","ci/install-rust.sh":"c7b63b4aff867d6da241c23b45121f3efa9cf62b43f839f0eb0d4d0f86580dd6","ci/make-release.sh":"8d9fc60ef6445518a58c4a3ed71156d080c1f7093cf9d499754ba7afb54d61c0","examples/nop-preprocessor.rs":"307712ffca66503884bcd47a151a143a090c80432ccf2f0f3a9a63d4d385ca44","release.toml":"25e91966f4a77515244adf5775d638590916c550f9d25bba6024d629b71840e8","src/book/book.rs":"f11bfb675b437d2358852cb98c8f13e1dd70c3e7ae6524433f8029100f8fceec","src/book/init.rs":"c3bee6cba312ef7a454157ad5c9578573945d91cdc271bcabcf2c10ee08d9f1e","src/book/mod.rs":"76380d9d82742e7a2e763c4f646f745dc40a7de190f978ca3edc56720e777c24","src/book/summary.rs":"cb2c31c1ca52aa603e9ee148c8c5659b6480e5c800df7d83374589282459dae8","src/cmd/build.rs":"911729cab9e1bdc703fcf1822338e3069a420ef13c37fc974256e8f8241934e9","src/cmd/clean.rs":"e64cb90de4e39a356d3998ed0902502a6c9d5553c1975c1943ee23fe65ae7d00","src/cmd/init.rs":"d1005ca6aa03cb2e22bc9591e9798feaa5245f4224601b263718ba66257febf9","src/cmd/mod.rs":"29116e5ca90e916a5f4ce62581a29508670ef184ec3e20569e2ad90b57472abe","src/cmd/serve.rs":"4b8fd772dc1989f904ac678d30e607b873bfc6a76f96c1ccaf1728b03139eedd","src/cmd/test.rs":"abc8704c686044cbc1ad58bf1f7502df58278381c23ca8f7a65083ccf9c1e640","src/cmd/watch.rs":"002ce90c2cf6cc586256fd8eb3dcb8ea5399ef3852eb1bfd6853558bce879a1a","src/config.rs":"8694ad151995a4a5c82b31ca7bacbe5bd88507216fa1211e4aa2fc8e582beb23","src/lib.rs":"28d27f81d52675609e6e17ae6cf9f9e1289bae260db8428b64e80c1fa77eec16","src/main.rs":"b38040c126a7e2d315f6d36cd32417cba3fab03dce2acbc6ce3ed89d5a019759","src/preprocess/cmd.rs":"80c49515195d97b40b67cdcc8314390e0d00830198c11a862ddcc0d7cd455de6","src/preprocess/index.rs":"d6d34bc61165dd5f42005e5d601d6729c40cacd656358ede17798effa309e508","src/preprocess/links.rs":"fc661e3600caaf00f17a4619bfb330252ccec36a6f51749553adab797fc0e21a","src/preprocess/mod.rs":"367474958e77777a84b973f904f4d029356c295d7515d4a1b2216e8e42659810","src/renderer/html_handlebars/hbs_renderer.rs":"e08b414b2c1b5275eadfb95241929569782c9b46790424f31e4c2e3b23be9284","src/renderer/html_handlebars/helpers/mod.rs":"d83520feecce350b03b7ffaa9f34d6db2fe7316a46562be694b8eef537124191","src/renderer/html_handlebars/helpers/navigation.rs":"31196f9c63dfa5664474828761200cef6c137c0e31ce984863735c54c46e2316","src/renderer/html_handlebars/helpers/theme.rs":"06713b5de794eaf68538e929cce58fe55a6e48afd5e9d3bb211ef47809d18af2","src/renderer/html_handlebars/helpers/toc.rs":"c6b10b46a663f4cb14c510a64a273d74f4bbf8d606ed09233c72ed2c49e717bb","src/renderer/html_handlebars/mod.rs":"b6be4801cf49040edc9dad216ed12c26dce87e2dc4724ee24729e4a8b67734b0","src/renderer/html_handlebars/search.rs":"d0593cc95e38b5e54ec9d57673acc4fcbc015843a1352ccb9887507265e85ad2","src/renderer/markdown_renderer.rs":"319453627fa416d44e5459a3f9f4cf07b87a4133ff97df5fc962768c9235c65f","src/renderer/mod.rs":"b51c8c1a6a845feef1c90d3d8278925af44c63b90a62203394d0114ec014877e","src/theme/FontAwesome/css/font-awesome.min.css":"799aeb25cc0373fdee0e1b1db7ad6c2f6a0e058dfadaa3379689f583213190bd","src/theme/FontAwesome/fonts/FontAwesome.otf":"444dd4366615ffc4a16d012b2fa90137065d3ccb410fa6fd5e4ddd7b5e4ffcd5","src/theme/FontAwesome/fonts/fontawesome-webfont.eot":"7bfcab6db99d5cfbf1705ca0536ddc78585432cc5fa41bbd7ad0f009033b2979","src/theme/FontAwesome/fonts/fontawesome-webfont.svg":"ad6157926c1622ba4e1d03d478f1541368524bfc46f51e42fe0d945f7ef323e4","src/theme/FontAwesome/fonts/fontawesome-webfont.ttf":"aa58f33f239a0fb02f5c7a6c45c043d7a9ac9a093335806694ecd6d4edc0d6a8","src/theme/FontAwesome/fonts/fontawesome-webfont.woff":"ba0c59deb5450f5cb41b3f93609ee2d0d995415877ddfa223e8a8a7533474f07","src/theme/FontAwesome/fonts/fontawesome-webfont.woff2":"2adefcbc041e7d18fcf2d417879dc5a09997aa64d675b7a3c4b6ce33da13f3fe","src/theme/ayu-highlight.css":"56612340a6f36525686c259f1bf2a8a3796181e97fcdd1eda5de3f24ac472dce","src/theme/book.js":"51c8bb0d83fb098b48f050fb3fe35db8c6a76b9422bba26987e2b7368cd8c828","src/theme/clipboard.min.js":"1626706afc88d95ebe1173b553ec732c6dc82a576989315fdf5e7779af738a44","src/theme/css/chrome.css":"db65abef094b57f6020920f9665f3a2cc3c7e8af5c76eab11ea901c80c842666","src/theme/css/general.css":"ea4cc8961178c487f1355e52dea73c90c77eb51d47d4a0ac39161896b898cad9","src/theme/css/print.css":"a4278dff9af38765eb9d344aa56dcc652ac79c73afc408385b62a4b611b89c14","src/theme/css/variables.css":"f4cb4563e98ae650cfacab224aa3f27931a389ed3b210b4f9aa379b5ccaa75b1","src/theme/favicon.png":"8114d1fc74f4b5621ad9afde7746ed9cf7e420be317a6e29023d2298d58aa15b","src/theme/favicon.svg":"de23e50b1c4dd6e052b3e21d444fcd4b13568b3840ac3c99d9be4e9263c0ef59","src/theme/fonts/OPEN-SANS-LICENSE.txt":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","src/theme/fonts/SOURCE-CODE-PRO-LICENSE.txt":"d1e6d465a83ba1a3be52db6484868cf5812ae9bbf91abdad3900ba0165afcf93","src/theme/fonts/fonts.css":"2db113e6ebede8403c607db3dceb5acc53c247720d5955d22f7db56beb7139b6","src/theme/fonts/mod.rs":"9b97835fbc610c46245ac52f8d40df8f28ace0c4fe7aca0847835ee83cc21a15","src/theme/fonts/open-sans-v17-all-charsets-300.woff2":"7736aa3596c468515c3209f2f9d68cfae96d94c05689bcc11a5dce426a6ee2e8","src/theme/fonts/open-sans-v17-all-charsets-300italic.woff2":"2c7b95c08df0d228caec6d4bfed06da0f7ab6b76ea5cc3f75b5c6ae416bc571b","src/theme/fonts/open-sans-v17-all-charsets-600.woff2":"486c67592731a0b36a89dba1fd0b97aeb73f236bbf60dbf28d7c6b5723c07989","src/theme/fonts/open-sans-v17-all-charsets-600italic.woff2":"1a3e865977024f444834a75a1b33b89b93134c93007ae3d6e14f24e6c88d8dfb","src/theme/fonts/open-sans-v17-all-charsets-700.woff2":"c22fe8c70c36f1d862903b772eaed864d3a8fa849473c9caff224fdb852428e4","src/theme/fonts/open-sans-v17-all-charsets-700italic.woff2":"238ae9593944112bee8dd65f8ebc5f3d3862160a8a245fbe1ee3150bc9a2fd81","src/theme/fonts/open-sans-v17-all-charsets-800.woff2":"3d2c812adf74deb36fead3ff8469800d3c0b23eb2c858ae49310291f89490146","src/theme/fonts/open-sans-v17-all-charsets-800italic.woff2":"ba1521ec219db9bc5bfec0e3e7a897369d98b30d4e853ee4aa525322784428b8","src/theme/fonts/open-sans-v17-all-charsets-italic.woff2":"6c9463f7096c0b9d610e095ed248ac1e8a8da7e92d17e9be544f3baced7b62b2","src/theme/fonts/open-sans-v17-all-charsets-regular.woff2":"2e3b1d34ac67763ab50652da19305d4b3694c6b6e6bf35f4b98411ce4af646d2","src/theme/fonts/source-code-pro-v11-all-charsets-500.woff2":"2bdd9410b0141db3cbbf4cfc3818cc6fad279e8e63940940e06cd6af76ccbfcf","src/theme/head.hbs":"56b3ab3c6eabd4723d4794ecd0a7452aa8903c55a2106d60bceacc74d76311c3","src/theme/header.hbs":"1fd27c9ccd016060dc4d6e77f12bf58b26e7c604aebe2577a67097f95a3de70a","src/theme/highlight.css":"2371a33638d229e1c07fcdc907f027c976d1f44ff733713ea51ac69d9abdc92a","src/theme/highlight.js":"5a2b5dadd60831dd1f82220223e2ab18e627061912cc89b5c450ab2c8f26ff90","src/theme/index.hbs":"9689cafe3007315d15d8c3b4ad8856c1e89e33d9827d070f9a29ae55198ee6cc","src/theme/mod.rs":"b8224089da80257f2a6550691ef4334b5f746520dfcbf61906224e4afbcf44ba","src/theme/playground_editor/ace.js":"2a3cd908c9619862b52f621ce2a40f76b772eb51c17308b14bd26d1809af8f87","src/theme/playground_editor/editor.js":"16ca416ca77428fe23cb8e18afbd3626a6a86723d6b6e189c47da95d9e9bdc31","src/theme/playground_editor/mod.rs":"b6b0f99f00ccb83cedfe5ce892834b46936a468611d056eae0f146e59711dc5c","src/theme/playground_editor/mode-rust.js":"2c9d5c9af5ae32612aef1ca5653e3473ed40747d36ecb4a97719ff14707d8535","src/theme/playground_editor/theme-dawn.js":"4493f9c88ed7185f7bb4195be77018d21cdc439a34bd4e5da64b566eb996fbe8","src/theme/playground_editor/theme-tomorrow_night.js":"9dbe62a913ebe3fd9667f41f69c0301bacd963081c69abb0219e4acac4710f60","src/theme/redirect.hbs":"c10b6e36dda1a4f222aa61cd1c6180a2f2937397379adba2c10b9b41e446709e","src/theme/searcher/elasticlunr.min.js":"ef4e11c157b1e2e89782d30bd726f2d5ff7834ea5e26ad02474325f8b1f126c9","src/theme/searcher/mark.min.js":"09e88c2cfaf23ea8a37b5681433eafea97033af632ecc948c8c1ee9944647743","src/theme/searcher/mod.rs":"36979040719b2fa39d1b78808e41cdb6e52e0ea5137c84820b437ed015278072","src/theme/searcher/searcher.js":"597b15cf5684c9293e4ecd86ceedbf88d367fe0283ec1874acd54f8b774b545b","src/theme/tomorrow-night.css":"1b14fba454be34c9b836084edf51b27ffd416a2fed973e216ac0f3e12b2b81c6","src/utils/fs.rs":"5417733b346470d0e1449d253e78a9d0db48c7a2de07c2c11f79f3f11dada30b","src/utils/mod.rs":"7efa06f8ec5edade36e5fd73ea8b120126736b8b54b5ef71f716dc6a355454cc","src/utils/string.rs":"4501bd4ca47a818ab16568ec52de3ac58f1fa07dace7e1a78cc4a752cec7169d","src/utils/toml_ext.rs":"20f88d22b19e431e15e1e9a34f0c4b4870cf38e45c766ab221d4eeaef16b2af6","test_book/book.toml":"fc9302acd6fd547988fd89192a8a9152aa8d00bfc1f75c83aadf95aa09e63ffd","test_book/src/README.md":"249f5ea54537dcbf399ce7ed1fd3bec371180258a89ea362fe05f1ca37c77a38","test_book/src/SUMMARY.md":"ec2beffb3d989c39ede3c8077ebed5c2aa3e65e4a10e29606cf8ef1bc7f87888","test_book/src/individual/README.md":"e796db1b0c812d8c9b4814406a1834ebbe60aaadee4692ba50e49aab9af46b82","test_book/src/individual/blockquote.md":"1263bd94b62e22630ad87501057e6098265d6d377baba51b04e1992c07a74450","test_book/src/individual/code.md":"6c9db5996e0d9787a7f85fdafc0cb9dcc9c19e2f2349f4fc169bb10ca5053a7e","test_book/src/individual/emphasis.md":"3c770aa9cae69106bdbab6dde5b35554f58914b82c8abb1a4f94164a0b16a2e3","test_book/src/individual/heading.md":"f137ef8d2808444c0a7f4f9756b1df11d7527273a1b84a81aca21658a12bc98d","test_book/src/individual/image.md":"8e8275cad5995224fcd26b500f433921aeb031ab2ed05cb0a2b324013f3f312d","test_book/src/individual/linebreak.md":"45e8c3171b7341210a7153d65aefd41d9d3430b346bf181bb5c72e2a59937060","test_book/src/individual/link_hr.md":"d703b9e8f39b39a6c801e4f6e13024d5651fcc4ef42552ef4d87d010168cd879","test_book/src/individual/list.md":"9b175eed63c4791d4f2b5f4619492c98b93778fc450328e5e958f81bd203620e","test_book/src/individual/mixed.md":"fc2fa38add98e93125fbf2072c5b4e0e8ba5516d51ab7c9dc066f960a947e9ca","test_book/src/individual/paragraph.md":"28fe3340a563e411304bb9cf763d6753e5ece492ead874d1e2067a41bd3717a8","test_book/src/individual/strikethrough.md":"84e380220cb7a27ecd2bc84ac196a973c4fd0e0793b59b1e44906080817207d4","test_book/src/individual/table.md":"5a59663043d5968e599b04c2838257229fcc98e1146086f75491b7be222a0041","test_book/src/individual/task.md":"ffed0203911c2737f0a4dee1ddfbb63e9a991be387cc9bb996fe4e3f6831bdb5","test_book/src/languages/README.md":"feeb3cb192f02ac7966acb480301874b6c3f354edb3e2476b4fe233acfd62168","test_book/src/languages/highlight.md":"db38b73be4ffd4148bbf4ab73419fd84310f2e2566bd7014ea8af66f64000625","test_book/src/prefix.md":"2dff7330dfafe1f1536b3f7ad4ca0d605cc53974caf74d24279708c8c42655bf","test_book/src/rust/README.md":"04fdf7da9f3619cae66ec7ebf4d30ec64d89e642d7b68c7b03c96ce88a286056","test_book/src/rust/rust_codeblock.md":"d2270c20023440879a54362667ff32efc67aed98f6a730546d52d35ef063cf48","test_book/src/suffix.md":"8478a1969c09bbbef0f06f477bc5aa21ed38f8ad5308eceb41fd845fe00b695e","tests/alternative_backends.rs":"cbff1ccc5361202870f619d438b1d84b1bb66a23a7cc91d8ce48dd29e297ac6c","tests/build_process.rs":"a958404fc41ff5c00b08b4c69d26f0862160e20c781b4bea88977d85304b6ef5","tests/cli/build.rs":"a7c99bf9fa92d57162260edc157789b712055790d206ce1f576ffc0a5ba8ce97","tests/cli/cmd.rs":"5839a288417c703d89a3bf82d3e8ab7695cc86d84e46d7a79a2f1686ed74a14f","tests/cli/mod.rs":"9c0985a84be7ba5cf6b4b076246b7e1045f6f91861d6350d18c7615820b14e3f","tests/cli/test.rs":"4302c429b9e9d2050068dd244983cce60200e3acfc5f953621374fc450ea4e81","tests/cli_tests.rs":"7eb7715e4ddc2d5fab9011b938c4ab6b0eb4b0ab8f83cff7aad91352be3b3096","tests/custom_preprocessors.rs":"22a5da0ed34ab2fe85d87ceb70e06c2a5065d66c363a39ce7d45715ae974ad1c","tests/dummy_book/index_html_test/SUMMARY.md":"6cb42be52279c2e62e145c425d8833343f83e6cd8d4951e89cdb322633ff7779","tests/dummy_book/index_html_test/chapter_1.md":"cac4f4a3609ec22eb519b0e07d1d1bdc15ac3d741f29f9b97280f700b96ded66","tests/dummy_book/mod.rs":"b474756f39c442ecd973a3abe3b88834d745b38db40f6c290cdce6c12c8beb91","tests/dummy_book/src/README.md":"2f6e578b9f31ff5e1d18ae40865355d59e56a23e02798bd8166751245ef4eb9b","tests/dummy_book/src/SUMMARY.md":"fa878d52ca2af76c83c126a768b1d16cfc050ffcd316836aea1667a0f2c7fca0","tests/dummy_book/src/conclusion.md":"eca0543344979e938b43debf3d310290a628e8b7b45d3d9bec63c21e61284a4c","tests/dummy_book/src/example.rs":"3be109d6faa3c6104c8fb5e8ffdfe3eba73d0a5ed4f537685fe2cf2c36e657d0","tests/dummy_book/src/first/duplicate-headers.md":"8bac1bdff305a473004774178daf112131d994b3ef2432cc4f3af03495df2ac6","tests/dummy_book/src/first/includes.md":"747f8f1eb4d394c1fdb773acddb7ffdc641e3ad71b0d15afb0f4e2de13a729ed","tests/dummy_book/src/first/index.md":"e7d1af4cb454c0be73bf1e2f01a97541fba385cd04ac5c9a7acc99809adedee8","tests/dummy_book/src/first/markdown.md":"461753f7f5105e35bc246c752065a44ea1d238f6890bd01b7c34d0a5746f7902","tests/dummy_book/src/first/nested-test-with-anchors.rs":"6fdecb4c62201f2dae36ea7e35693238edca03dcfc42d33eaf1685bc957a6d1d","tests/dummy_book/src/first/nested-test.rs":"ca4cdf552842a957be49ffc753f7585dba3c0036a076fea68174719599d426fa","tests/dummy_book/src/first/nested.md":"485d24764340c0413939e52533ab304c07bb55bd23d0ad9e31151ff889425048","tests/dummy_book/src/first/no-headers.md":"74302855224031296e7739c3a5d881ad4a737d9f2db8f2123f74708ad399721f","tests/dummy_book/src/first/partially-included-test-with-anchors.rs":"501d96562da17cbec46444a004340e711b3fac4b3384f12d7c4844909742c8c8","tests/dummy_book/src/first/partially-included-test.rs":"1bdd1a4f8fdc41ea10868f97de0fe315b2cc592d4cf4aabd784f17dc8c0a6e90","tests/dummy_book/src/first/recursive.md":"9b574239c94c24412dbd918d7b85f557a80cec44d245663cfe1ecdd8ed16765e","tests/dummy_book/src/first/unicode.md":"04a548a642ba5cbc346670a8b38c1aeb5ad23d5a967849783171c634b682de63","tests/dummy_book/src/intro.md":"2749920beee7f563f3ace2bbef204dc66382e052c7945bf27e66912351959a90","tests/dummy_book/src/second.md":"b1344cb47f69621ed68197b81652df1f86254b71b4e3dcbdcf707a4e1fee3340","tests/dummy_book/src/second/nested.md":"b0da2ce6ce1ac5fbda92b5cf7a6a09d39abd267095a29f177e44cd4e8d333d45","tests/dummy_book/src2/README.md":"1bcffb061e737e022f132f78a2dd3193fc0951d0b104e50ddc860d9bc260f505","tests/dummy_book/src2/SUMMARY.md":"4354bec1cae7db3c48dfe869e28e8e29958daef937b33da5e729211372ccb9c9","tests/dummy_book/src2/first/README.md":"ba505ed6c1e18c85ce2ca7070021c719933ce2cf9f28de8565c188c0e4a95d87","tests/dummy_book/src2/second/README.md":"2fb4a90a1359fe3725c3d95888452afd514b0b86bdc4e2768442a71bf5b642a0","tests/dummy_book/src2/second/index.md":"5bf990bec982b9e87d8dfb230fb2dfc96318d9065f97a3d3c5cf27bd134c8bdd","tests/dummy_book/summary-formatting/SUMMARY.md":"ad48a6278c3dbe069cd9c22ae3f04b403a03e0df09b4574a29ec38f41afb83a5","tests/init.rs":"823d7f09142c26b5e40f0769440d9fa23948dbfc9168beaa35bd1082b20bccf1","tests/parse_existing_summary_files.rs":"f4b019e66ffc7f59efd7ec9da73bb70180e4cc23ff27904bf4dfd26491ef64a3","tests/rendered_output.rs":"b7447053a746887392b43911c44d9323cde486a136dddae5a6b33f14b18ebc03","tests/searchindex_fixture.json":"05acb10087b2dd082121f581f17aedff59c40e692a5e572acc72a43a4c5178fd","tests/summary_md_files/example_book.md":"e1dd09043d9548612b0bb4e607a8796317272aa1fe7a2bda55db6e6f47d3fc5a","tests/summary_md_files/rust_by_example.md":"c46c8eab64780297b851be196a04d573da69553f4d9c33e24fbd5fb2867efbfd","tests/summary_md_files/rust_ffi_guide.md":"26874d9ad22cfdc2a587e7a495a4404247821d2b6e8eabe07334cacc5a4ea365","tests/summary_md_files/the_book-2nd_edition.md":"7ae64929c45aa7d67560c77e5280a540c22d1b0d2f8fd85f8392ce064c2d5f0a","tests/testing.rs":"c80d579682b969045d0c067f7ae5ab425255f83642f9dfd11a42f3de75e1b4a8","triagebot.toml":"ae5430fdb5a3c49c350a40b81d4404f661e0d80ba7999dcaac50dbcfdee271ad"},"package":"23f3e133c6d515528745ffd3b9f0c7d975ae039f0b6abb099f2168daa2afb4f9"} \ No newline at end of file
diff --git a/vendor/mdbook/CHANGELOG.md b/vendor/mdbook/CHANGELOG.md
new file mode 100644
index 000000000..686004c5d
--- /dev/null
+++ b/vendor/mdbook/CHANGELOG.md
@@ -0,0 +1,819 @@
+# Changelog
+
+## mdBook 0.4.21
+[92afe9b...8f01d02](https://github.com/rust-lang/mdBook/compare/92afe9b...8f01d02)
+
+### Fixed
+- Fixed an issue where mdBook would fail to compile with Rust nightly-2022-07-22.
+ [#1861](https://github.com/rust-lang/mdBook/pull/1861)
+
+## mdBook 0.4.20
+[53055e0...da166e0](https://github.com/rust-lang/mdBook/compare/53055e0...da166e0)
+
+### Fixed
+- Fixed a regression in 0.4.19 where inline code would have excessive padding
+ in some situations such as headings.
+ [#1855](https://github.com/rust-lang/mdBook/pull/1855)
+
+## mdBook 0.4.19
+[ae275ad...53055e0](https://github.com/rust-lang/mdBook/compare/ae275ad...53055e0)
+
+### Added
+- The `serve` command now supports HEAD requests.
+ [#1825](https://github.com/rust-lang/mdBook/pull/1825)
+
+### Changed
+- An error is now generated when a custom theme directory does not exist.
+ [#1791](https://github.com/rust-lang/mdBook/pull/1791)
+- Very wide tables now have independent horizontal scrolling so that scrolling
+ to see the rest of the table will not scroll the entire page.
+ [#1617](https://github.com/rust-lang/mdBook/pull/1617)
+- The buttons on code blocks are now only shown when the mouse cursor hovers
+ over them (or tapped on mobile). There is also some extra spacing to reduce
+ the overlap with the code.
+ [#1806](https://github.com/rust-lang/mdBook/pull/1806)
+- The first chapter always generates an `index.html` file. Previously it would
+ only generate the index file for prefix chapters.
+ [#1829](https://github.com/rust-lang/mdBook/pull/1829)
+
+### Fixed
+- `mdbook serve --open` now properly handles the case if the first chapter is a draft.
+ [#1714](https://github.com/rust-lang/mdBook/pull/1714)
+ [#1830](https://github.com/rust-lang/mdBook/pull/1830)
+- Very long words (over 80 characters) are no longer indexed to avoid a stack overflow.
+ [#1833](https://github.com/rust-lang/mdBook/pull/1833)
+
+## mdBook 0.4.18
+[981b79b...ae275ad](https://github.com/rust-lang/mdBook/compare/981b79b...ae275ad)
+
+### Fixed
+- Fixed rendering of SUMMARY links that contain markdown escapes or other
+ markdown elements.
+ [#1785](https://github.com/rust-lang/mdBook/pull/1785)
+
+## mdBook 0.4.17
+[a5fddfa...981b79b](https://github.com/rust-lang/mdBook/compare/a5fddfa...981b79b)
+
+### Fixed
+- Fixed parsing of `output.html.print` configuration table.
+ [#1775](https://github.com/rust-lang/mdBook/pull/1775)
+
+## mdBook 0.4.16
+[68a5c09...a5fddfa](https://github.com/rust-lang/mdBook/compare/68a5c09...a5fddfa)
+
+### Added
+- Added `output.html.print.page-break` config option to control whether or not
+ there is a page break between chapters in the print output.
+ [#1728](https://github.com/rust-lang/mdBook/pull/1728)
+- Added `output.html.playground.runnable` config option to globally disable
+ the run button in code blocks.
+ [#1546](https://github.com/rust-lang/mdBook/pull/1546)
+
+### Changed
+- The `mdbook serve` live reload websocket now uses the protocol, host, and
+ port of the current page, allowing access through a proxy.
+ [#1771](https://github.com/rust-lang/mdBook/pull/1771)
+- The 404 not-found page now includes the books title in the HTML title tag.
+ [#1693](https://github.com/rust-lang/mdBook/pull/1693)
+- Migrated to clap 3.0 which which handles CLI option parsing.
+ [#1731](https://github.com/rust-lang/mdBook/pull/1731)
+
+### Fixed
+- Minor fixes to the markdown parser.
+ [#1729](https://github.com/rust-lang/mdBook/pull/1729)
+- Fixed incorrect parsing in `SUMMARY.md` when it didn't start with a title.
+ [#1744](https://github.com/rust-lang/mdBook/pull/1744)
+- Fixed duplicate anchor IDs for links in search results.
+ [#1749](https://github.com/rust-lang/mdBook/pull/1749)
+
+## mdBook 0.4.15
+[5eb7d46...68a5c09](https://github.com/rust-lang/mdBook/compare/5eb7d46...68a5c09)
+
+### Changed
+- Major update to expand the documentation located at <https://rust-lang.github.io/mdBook/>.
+ [#1709](https://github.com/rust-lang/mdBook/pull/1709)
+ [#1710](https://github.com/rust-lang/mdBook/pull/1710)
+- Updated the markdown parser with various fixes for common-mark compliance.
+ [#1712](https://github.com/rust-lang/mdBook/pull/1712)
+
+## mdBook 0.4.14
+[ffa8284...c9b6be8](https://github.com/rust-lang/mdBook/compare/ffa8284...c9b6be8)
+
+### Added
+- The 2021 Rust edition option has been stabilized.
+ [#1642](https://github.com/rust-lang/mdBook/pull/1642)
+
+### Changed
+- Header anchors no longer include any HTML tags. Previously only a small
+ subset were excluded.
+ [#1683](https://github.com/rust-lang/mdBook/pull/1683)
+- Deprecated the google-analytics option. Books using this option should place
+ the appropriate code in the `theme/head.hbs` file instead.
+ [#1675](https://github.com/rust-lang/mdBook/pull/1675)
+
+### Fixed
+- Updated the markdown parser which brings in a few small fixes and removes
+ the custom smart quote handling.
+ [#1668](https://github.com/rust-lang/mdBook/pull/1668)
+- Fixed iOS Safari enlarging text when going into landscape mode.
+ [#1685](https://github.com/rust-lang/mdBook/pull/1685)
+
+## mdBook 0.4.13
+[e6629cd...f55028b](https://github.com/rust-lang/mdBook/compare/e6629cd...f55028b)
+
+### Added
+
+- Added the ability to specify the preprocessor order.
+ [#1607](https://github.com/rust-lang/mdBook/pull/1607)
+
+### Fixed
+
+- Include chapters with no headers in the search index
+ [#1637](https://github.com/rust-lang/mdBook/pull/1637)
+- Switched to the `opener` crate for opening a web browser, which should fix
+ some issues with blocking.
+ [#1656](https://github.com/rust-lang/mdBook/pull/1656)
+- Fixed clicking the border of the theme switcher breaking the theme selection.
+ [#1651](https://github.com/rust-lang/mdBook/pull/1651)
+
+## mdBook 0.4.12
+[14add9c...8b4e488](https://github.com/rust-lang/mdBook/compare/14add9c...8b4e488)
+
+### Changed
+- Reverted the change to update to highlight.js 11, as it broke hidden code lines.
+ [#1597](https://github.com/rust-lang/mdBook/pull/1621)
+
+## mdBook 0.4.11
+[e440094...2cf00d0](https://github.com/rust-lang/mdBook/compare/e440094...2cf00d0)
+
+### Added
+- Added support for Rust 2021 edition.
+ [#1596](https://github.com/rust-lang/mdBook/pull/1596)
+- Added `mdbook completions` subcommand which provides shell completions.
+ [#1425](https://github.com/rust-lang/mdBook/pull/1425)
+- Added `--title` and `--ignore` flags to `mdbook init` to avoid the
+ interactive input.
+ [#1559](https://github.com/rust-lang/mdBook/pull/1559)
+
+### Changed
+- If running a Rust example does not have any output, it now displays the text
+ "No output" instead of not showing anything.
+ [#1599](https://github.com/rust-lang/mdBook/pull/1599)
+- Code block language tags can now be separated by space or tab (along with
+ commas) to match the behavior of other sites like GitHub and rustdoc.
+ [#1469](https://github.com/rust-lang/mdBook/pull/1469)
+- Updated `warp` (the web server) to the latest version.
+ This also updates the minimum supported Rust version to 1.46.
+ [#1612](https://github.com/rust-lang/mdBook/pull/1612)
+- Updated to highlight.js 11. This has various highlighting improvements.
+ [#1597](https://github.com/rust-lang/mdBook/pull/1597)
+
+### Fixed
+- Inline code blocks inside a header are no longer highlighted when
+ `output.html.playground.editable` is `true`.
+ [#1613](https://github.com/rust-lang/mdBook/pull/1613)
+
+## mdBook 0.4.10
+[2f7293a...dc2062a](https://github.com/rust-lang/mdBook/compare/2f7293a...dc2062a)
+
+### Changed
+- Reverted breaking change in 0.4.9 that removed the `__non_exhaustive` marker
+ on the `Book` struct.
+ [#1572](https://github.com/rust-lang/mdBook/pull/1572)
+- Updated handlebars to 4.0.
+ [#1550](https://github.com/rust-lang/mdBook/pull/1550)
+- Removed the `chapter_begin` id on the print page's chapter separators.
+ [#1541](https://github.com/rust-lang/mdBook/pull/1541)
+
+## mdBook 0.4.9
+[7e01cf9...d325c60](https://github.com/rust-lang/mdBook/compare/7e01cf9...d325c60)
+
+### Changed
+- Updated all dependencies and raised the minimum Rust version to 1.42.
+ [#1528](https://github.com/rust-lang/mdBook/pull/1528)
+- Added more detail to error message when a preprocessor fails.
+ [#1526](https://github.com/rust-lang/mdBook/pull/1526)
+- Set max-width of HTML video tags to 100% to match img tags.
+ [#1542](https://github.com/rust-lang/mdBook/pull/1542)
+
+### Fixed
+- Type errors when parsing `book.toml` are no longer ignored.
+ [#1539](https://github.com/rust-lang/mdBook/pull/1539)
+- Better handling if `mdbook serve` fails to start the http server.
+ [#1555](https://github.com/rust-lang/mdBook/pull/1555)
+- Fixed the path for `edit-url-template` if the book used a source directory
+ other than `src`.
+ [#1554](https://github.com/rust-lang/mdBook/pull/1554)
+
+## mdBook 0.4.8
+[fcceee4...b592b10](https://github.com/rust-lang/mdBook/compare/fcceee4...b592b10)
+
+### Added
+- Added the option `output.html.edit-url-template` which can be a URL which is
+ linked on each page to direct the user to a site (such as GitHub) where the
+ user can directly suggest an edit for the page they are currently reading.
+ [#1506](https://github.com/rust-lang/mdBook/pull/1506)
+
+### Changed
+- Printed output now includes a page break between chapters.
+ [#1485](https://github.com/rust-lang/mdBook/pull/1485)
+
+### Fixed
+- HTML, such as HTML comments, is now ignored if it appears above the title line
+ in `SUMMARY.md`.
+ [#1437](https://github.com/rust-lang/mdBook/pull/1437)
+
+## mdBook 0.4.7
+[9a9eb01...c83bbd6](https://github.com/rust-lang/mdBook/compare/9a9eb01...c83bbd6)
+
+### Changed
+- Updated shlex parser to fix a minor parsing issue (used by the
+ preprocessor/backend custom command config).
+ [#1471](https://github.com/rust-lang/mdBook/pull/1471)
+- Enhanced text contrast of `light` theme to improve accessibility.
+ [#1470](https://github.com/rust-lang/mdBook/pull/1470)
+
+### Fixed
+- Fixed some issues with fragment scrolling and linking.
+ [#1463](https://github.com/rust-lang/mdBook/pull/1463)
+
+## mdBook 0.4.6
+[eaa6914...1a0c296](https://github.com/rust-lang/mdBook/compare/eaa6914...1a0c296)
+
+### Changed
+- The chapter name is now included in the search breadcrumbs.
+ [#1389](https://github.com/rust-lang/mdBook/pull/1389)
+- Pressing Escape will remove the `?highlight` argument from the URL.
+ [#1427](https://github.com/rust-lang/mdBook/pull/1427)
+- `mdbook init --theme` will now place the theme in the root of the book
+ directory instead of in the `src` directory.
+ [#1432](https://github.com/rust-lang/mdBook/pull/1432)
+- A custom renderer that sets the `command` to a relative path now interprets
+ the relative path relative to the book root. Previously it was inconsistent
+ based on the platform (either relative to the current directory, or relative
+ to the renderer output directory). Paths relative to the output directory
+ are still supported with a deprecation warning.
+ [#1418](https://github.com/rust-lang/mdBook/pull/1418)
+- The `theme` directory in the config is now interpreted as relative to the
+ book root, instead of the current directory.
+ [#1405](https://github.com/rust-lang/mdBook/pull/1405)
+- Handle UTF-8 BOM for chapter sources.
+ [#1285](https://github.com/rust-lang/mdBook/pull/1285)
+- Removed extra whitespace added to `{{#playground}}` snippets.
+ [#1375](https://github.com/rust-lang/mdBook/pull/1375)
+
+### Fixed
+- Clicking on a search result with multiple search words will now correctly
+ highlight all of the words.
+ [#1426](https://github.com/rust-lang/mdBook/pull/1426)
+- Properly handle `<` and `>` characters in the table of contents.
+ [#1376](https://github.com/rust-lang/mdBook/pull/1376)
+- Fixed to properly serialize the `build` table in the config, which prevented
+ setting it in the API.
+ [#1378](https://github.com/rust-lang/mdBook/pull/1378)
+
+## mdBook 0.4.5
+[eaa6914...f66df09](https://github.com/rust-lang/mdBook/compare/eaa6914...f66df09)
+
+### Fixed
+
+- Fixed XSS in the search page.
+ [CVE-2020-26297](https://groups.google.com/g/rustlang-security-announcements/c/3-sO6of29O0)
+ [648c9ae](https://github.com/rust-lang/mdBook/commit/648c9ae772bec83f0a5954d17b4287d5bb1d6606)
+
+## mdBook 0.4.4
+[4df9ec9...01836ba](https://github.com/rust-lang/mdBook/compare/4df9ec9...01836ba)
+
+### Added
+- Added the `output.html.print.enable` configuration value to disable the
+ "print" page.
+ [#1169](https://github.com/rust-lang/mdBook/pull/1169)
+- Added a list of supported languages for syntax-highlighting to the
+ documentation.
+ [#1345](https://github.com/rust-lang/mdBook/pull/1345)
+
+### Fixed
+- Now supports symbolic links for files in the `src` directory.
+ [#1323](https://github.com/rust-lang/mdBook/pull/1323)
+
+## mdBook 0.4.3
+[9278b83...4df9ec9](https://github.com/rust-lang/mdBook/compare/9278b83...4df9ec9)
+
+### Added
+- Added `output.html.cname` option to emit a `CNAME` file which is used by
+ GitHub Pages to know which domain is being used.
+ [#1311](https://github.com/rust-lang/mdBook/pull/1311)
+
+### Changed
+- `mdbook test` no longer stops on the first test failure, but instead will
+ run all the tests.
+ [#1313](https://github.com/rust-lang/mdBook/pull/1313)
+- Removed the `local` font source for Source Code Pro, as the locally
+ installed font may not render properly on FireFox on macOS.
+ [#1307](https://github.com/rust-lang/mdBook/pull/1307)
+
+### Fixed
+- Added newline to end of `.nojekyll` file.
+ [#1310](https://github.com/rust-lang/mdBook/pull/1310)
+- Fixed missing space before draft chapter titles.
+ [#1309](https://github.com/rust-lang/mdBook/pull/1309)
+
+## mdBook 0.4.2
+[649f355...9278b83](https://github.com/rust-lang/mdBook/compare/649f355...9278b83)
+
+### Changed
+- The "show hidden lines" icon has changed from the "expand" icon to an "eye".
+ [#1281](https://github.com/rust-lang/mdBook/pull/1281)
+- Updated highlight.js. This adds several languages: c, c-like (effectively
+ cpp), csharp (replaces cs), kotlin, less, lua, php-template, plaintext,
+ python-repl, r, scss, typescript.
+ [#1277](https://github.com/rust-lang/mdBook/pull/1277)
+
+### Fixed
+- Fixed SUMMARY links that contained newlines.
+ [#1291](https://github.com/rust-lang/mdBook/pull/1291)
+- Fixed SUMMARY links that contain `%20` spaces.
+ [#1293](https://github.com/rust-lang/mdBook/pull/1293)
+- Fixed favicon so that if only the png or svg is overridden, the other is not
+ automatically included in the `<link>` tag.
+ [#1272](https://github.com/rust-lang/mdBook/pull/1272)
+
+## mdBook 0.4.1
+[d4df7e7...649f355](https://github.com/rust-lang/mdBook/compare/d4df7e7...649f355)
+
+### Changed
+- Removed several outdated dev-dependencies.
+ [#1267](https://github.com/rust-lang/mdBook/pull/1267)
+
+### Fixed
+- Fixed sidebar scrolling if the book includes part titles.
+ [#1265](https://github.com/rust-lang/mdBook/pull/1265)
+- Don't include the default favicon if only one of the PNG or SVG is overridden.
+ [#1266](https://github.com/rust-lang/mdBook/pull/1266)
+
+## mdBook 0.4.0
+[99ecd4f...d4df7e7](https://github.com/rust-lang/mdBook/compare/99ecd4f...d4df7e7)
+
+### Breaking Changes
+- Several of the changes in the release have altered the public API of the
+ mdbook library.
+- Many dependencies have been updated or replaced.
+ This also removes the `--websocket-hostname` and `--websocket-port` from
+ the `serve` command.
+ [#1211](https://github.com/rust-lang/mdBook/pull/1211)
+- A new "404" page is now automatically rendered. This requires knowledge of
+ the base URL of your site to work properly. If you decide to use this as
+ your 404 page, you should set the `site-url` setting in the book
+ configuration so mdbook can generate the links correctly. Alternatively you
+ can disable the 404 page generation, or set up your own 404 handling in your
+ web server.
+ [#1221](https://github.com/rust-lang/mdBook/pull/1221)
+- The `debug` and `output` features have been removed as they were unused.
+ [#1211](https://github.com/rust-lang/mdBook/pull/1211)
+- If you are using customized themes, you may want to consider setting the
+ `preferred-dark-theme` config setting, as it now defaults to "navy".
+ [#1199](https://github.com/rust-lang/mdBook/pull/1199)
+- "Playpen" has been renamed to "playground". This is generally backwards
+ compatible for users, but `{{#playpen}}` will now display warnings. This may
+ impact books that have modified the "playpen" elements in the theme.
+ [#1241](https://github.com/rust-lang/mdBook/pull/1241)
+- If a renderer is not installed, it is now treated as an error. If you want
+ the old behavior of ignoring missing renderers, set the `optional` setting
+ for that renderer.
+ [#1122](https://github.com/rust-lang/mdBook/pull/1122)
+- If you have a custom favicon, you may need to look into adding an SVG
+ version, otherwise the default SVG icon will be displayed.
+ [#1230](https://github.com/rust-lang/mdBook/pull/1230)
+
+### Added
+- Added a new `[rust]` configuration section to `book.toml`, which allows
+ setting the default edition with `edition = "2018"`.
+ [#1163](https://github.com/rust-lang/mdBook/pull/1163)
+- Renderers can now be marked as `optional`, so that they will be ignored if
+ the renderer is not installed.
+ [#1122](https://github.com/rust-lang/mdBook/pull/1122)
+- Added `head.hbs` to allow adding content to the `<head>` section in HTML.
+ [#1206](https://github.com/rust-lang/mdBook/pull/1206)
+- Added "draft chapters". These are chapters listed without a link to indicate
+ content yet to be written.
+ [#1153](https://github.com/rust-lang/mdBook/pull/1153)
+- Added "parts" to split a book into different sections. Headers can be added
+ to `SUMMARY.md` to signify different sections.
+ [#1171](https://github.com/rust-lang/mdBook/pull/1171)
+- Added generation of a "404" page for handling missing pages and broken links.
+ [#1221](https://github.com/rust-lang/mdBook/pull/1221)
+- Added configuration section for specifying URL redirects.
+ [#1237](https://github.com/rust-lang/mdBook/pull/1237)
+- Added an SVG favicon that works with light and dark colors schemes.
+ [#1230](https://github.com/rust-lang/mdBook/pull/1230)
+
+### Changed
+- Changed default Rust attribute of `allow(unused_variables)` to `allow(unused)`.
+ [#1195](https://github.com/rust-lang/mdBook/pull/1195)
+- Fonts are now served locally instead of from the Google Fonts CDN. The
+ `copy-fonts` option was added to disable this if you want to supply your own
+ fonts.
+ [#1188](https://github.com/rust-lang/mdBook/pull/1188)
+- Switched the built-in webserver for the `serve` command to a new
+ implementation. This results in some internal differences in how websockets
+ are handled, which removes the separate websocket options. This should also
+ make it easier to serve multiple books at once.
+ [#1211](https://github.com/rust-lang/mdBook/pull/1211)
+- The default dark theme is now "navy".
+ [#1199](https://github.com/rust-lang/mdBook/pull/1199)
+- "Playpen" has been renamed to "playground", matching the actual name of the
+ service which was renamed many years ago.
+ [#1241](https://github.com/rust-lang/mdBook/pull/1241)
+
+### Fixed
+- Links with the `+` symbol should now work.
+ [#1208](https://github.com/rust-lang/mdBook/pull/1208)
+- The `MDBOOK_BOOK` environment variable now correctly allows overriding the
+ entire book configuration.
+ [#1207](https://github.com/rust-lang/mdBook/pull/1207)
+- The sidebar can no longer be dragged outside of the window.
+ [#1229](https://github.com/rust-lang/mdBook/pull/1229)
+- Hide the Rust Playground "play" button for `no_run` code samples.
+ [#1249](https://github.com/rust-lang/mdBook/pull/1249)
+- Fixed the `--dest-dir` command-line option for the `serve` and `watch`
+ commands.
+ [#1228](https://github.com/rust-lang/mdBook/pull/1228)
+- Hotkey handlers are now disabled in `text` input fields (for example, typing
+ `S` in a custom text input field).
+ [#1244](https://github.com/rust-lang/mdBook/pull/1244)
+
+## mdBook 0.3.7
+[88684d8...99ecd4f](https://github.com/rust-lang/mdBook/compare/88684d8...99ecd4f)
+
+### Changed
+- Code spans in headers are no longer highlighted as code.
+ [#1162](https://github.com/rust-lang/mdBook/pull/1162)
+- The sidebar will now scroll the activate page to the middle instead of the top.
+ [#1161](https://github.com/rust-lang/mdBook/pull/1161)
+- Reverted change to reject build output within the `src` directory, and
+ instead add a check that prevents infinite copies.
+ [#1181](https://github.com/rust-lang/mdBook/pull/1181)
+ [#1026](https://github.com/rust-lang/mdBook/pull/1026)
+
+### Fixed
+- Fixed sidebar line-height jumping for collapsed chapters.
+ [#1182](https://github.com/rust-lang/mdBook/pull/1182)
+- Fixed theme selector focus.
+ [#1170](https://github.com/rust-lang/mdBook/pull/1170)
+
+## mdBook 0.3.6
+[efdb832...88684d8](https://github.com/rust-lang/mdBook/compare/efdb832...88684d8)
+
+### Added
+- `MDBook::execute_build_process` is now publicly accessible in the API so
+ that plugins can more easily initiate the build process.
+ [#1099](https://github.com/rust-lang/mdBook/pull/1099)
+
+### Changed
+- Use a different color for Ayu theme's highlighting for Rust attributes (uses
+ a bright color instead of the comment color).
+ [#1133](https://github.com/rust-lang/mdBook/pull/1133)
+- Adjusted spacing of sidebar entries.
+ [#1137](https://github.com/rust-lang/mdBook/pull/1137)
+- Slightly adjusted line-height of `<p>`, `<ul>`, and `<ol>`.
+ [#1136](https://github.com/rust-lang/mdBook/pull/1136)
+- Handlebars updated to 3.0.
+ [#1130](https://github.com/rust-lang/mdBook/pull/1130)
+
+### Fixed
+- Fix an issue with sidebar scroll position on reload.
+ [#1108](https://github.com/rust-lang/mdBook/pull/1108)
+- `mdbook serve` will retain the current scroll position when the page is reloaded.
+ [#1097](https://github.com/rust-lang/mdBook/pull/1097)
+- Fixed the page name if the book didn't have a title to not be prefixed with ` - `.
+ [#1145](https://github.com/rust-lang/mdBook/pull/1145)
+- HTML attributes `rel=next` and `rel=previous` are now supported in "wide"
+ mode (previously they were only set in narrow mode).
+ [#1150](https://github.com/rust-lang/mdBook/pull/1150)
+- Prevent recursive copies when the destination directory is contained in the
+ source directory.
+ [#1135](https://github.com/rust-lang/mdBook/pull/1135)
+- Adjusted the menu bar animation to not immediately obscure the top content.
+ [#989](https://github.com/rust-lang/mdBook/pull/989)
+- Fix for comments in SUMMARY.md that appear between items.
+ [#1167](https://github.com/rust-lang/mdBook/pull/1167)
+
+## mdBook 0.3.5
+[6e0d0fa...efdb832](https://github.com/rust-lang/mdBook/compare/6e0d0fa...efdb832)
+
+### Changed
+- The `default-theme` config setting is now case-insensitive.
+ [#1079](https://github.com/rust-lang/mdBook/pull/1079)
+
+### Fixed
+- Fixed `#` hidden Rust code lines not rendering properly.
+ [#1088](https://github.com/rust-lang/mdBook/pull/1088)
+- Updated pulldown-cmark to 0.6.1, fixing several issues.
+ [#1021](https://github.com/rust-lang/mdBook/pull/1021)
+
+## mdBook 0.3.4
+[e5f77aa...6e0d0fa](https://github.com/rust-lang/mdBook/compare/e5f77aa...6e0d0fa)
+
+### Changed
+- Switch to relative `rem` font sizes from `px`.
+ [#894](https://github.com/rust-lang/mdBook/pull/894)
+- Migrated repository to https://github.com/rust-lang/mdBook/
+ [#1083](https://github.com/rust-lang/mdBook/pull/1083)
+
+## mdBook 0.3.3
+[2b649fe...e5f77aa](https://github.com/rust-lang/mdBook/compare/2b649fe...e5f77aa)
+
+### Changed
+- Improvements to the automatic dark theme selection.
+ [#1069](https://github.com/rust-lang/mdBook/pull/1069)
+- Fragment links now prevent scrolling the header behind the menu bar.
+ [#1077](https://github.com/rust-lang/mdBook/pull/1077)
+
+### Fixed
+- Fixed error when building a book that has a spacer immediately after the
+ first chapter.
+ [#1075](https://github.com/rust-lang/mdBook/pull/1075)
+
+## mdBook 0.3.2
+[9cd47eb...2b649fe](https://github.com/rust-lang/mdBook/compare/9cd47eb...2b649fe)
+
+### Added
+- Added a markdown renderer, which is off by default. This may be useful for
+ debugging preprocessors.
+ [#1018](https://github.com/rust-lang/mdBook/pull/1018)
+- Code samples may now include line numbers with the
+ `output.html.playpen.line-numbers` configuration value.
+ [#1035](https://github.com/rust-lang/mdBook/pull/1035)
+- The `watch` and `serve` commands will now ignore files listed in
+ `.gitignore`.
+ [#1044](https://github.com/rust-lang/mdBook/pull/1044)
+- Added automatic dark-theme detection based on the CSS `prefers-color-scheme`
+ feature. This may be enabled by setting `output.html.preferred-dark-theme`
+ to your preferred dark theme.
+ [#1037](https://github.com/rust-lang/mdBook/pull/1037)
+- Added `rustdoc_include` preprocessor. This makes it easier to include
+ portions of an external Rust source file. The rest of the file is hidden,
+ but the user may expand it to see the entire file, and will continue to work
+ with `mdbook test`.
+ [#1003](https://github.com/rust-lang/mdBook/pull/1003)
+- Added Ctrl-Enter shortcut to the playpen editor to automatically run the
+ sample.
+ [#1066](https://github.com/rust-lang/mdBook/pull/1066)
+- Added `output.html.playpen.copyable` configuration option to disable
+ the copy button.
+ [#1050](https://github.com/rust-lang/mdBook/pull/1050)
+- Added ability to dynamically expand and fold sections within the sidebar.
+ See the `output.html.fold` configuration to enable this feature.
+ [#1027](https://github.com/rust-lang/mdBook/pull/1027)
+
+### Changed
+- Use standard `scrollbar-color` CSS along with webkit extension
+ [#816](https://github.com/rust-lang/mdBook/pull/816)
+- The renderer build directory is no longer deleted before the renderer is
+ run. This allows a backend to cache results between runs.
+ [#985](https://github.com/rust-lang/mdBook/pull/985)
+- Next/prev links now highlight on hover to indicate it is clickable.
+ [#994](https://github.com/rust-lang/mdBook/pull/994)
+- Increase padding of table headers.
+ [#824](https://github.com/rust-lang/mdBook/pull/824)
+- Errors in `[output.html]` config are no longer ignored.
+ [#1033](https://github.com/rust-lang/mdBook/pull/1033)
+- Updated highlight.js for syntax highlighting updates (primarily to add
+ async/await to Rust highlighting).
+ [#1041](https://github.com/rust-lang/mdBook/pull/1041)
+- Raised minimum supported rust version to 1.35.
+ [#1003](https://github.com/rust-lang/mdBook/pull/1003)
+- Hidden code lines are no longer dynamically removed via JavaScript, but
+ instead managed with CSS.
+ [#846](https://github.com/rust-lang/mdBook/pull/846)
+ [#1065](https://github.com/rust-lang/mdBook/pull/1065)
+- Changed the default font set for the ACE editor, giving preference to
+ "Source Code Pro".
+ [#1062](https://github.com/rust-lang/mdBook/pull/1062)
+- Windows 32-bit releases are no longer published.
+ [#1071](https://github.com/rust-lang/mdBook/pull/1071)
+
+### Fixed
+- Fixed sidebar auto-scrolling.
+ [#1052](https://github.com/rust-lang/mdBook/pull/1052)
+- Fixed error message when running `clean` multiple times.
+ [#1055](https://github.com/rust-lang/mdBook/pull/1055)
+- Actually fix the "next" link on index.html. The previous fix didn't work.
+ [#1005](https://github.com/rust-lang/mdBook/pull/1005)
+- Stop using `inline-block` for `inline code`, fixing selection highlighting
+ and some rendering issues.
+ [#1058](https://github.com/rust-lang/mdBook/pull/1058)
+- Fix header auto-hide on browsers with momentum scrolling that allows
+ negative `scrollTop`.
+ [#1070](https://github.com/rust-lang/mdBook/pull/1070)
+
+## mdBook 0.3.1
+[69a08ef...9cd47eb](https://github.com/rust-lang/mdBook/compare/69a08ef...9cd47eb)
+
+### Added
+- 🔥 Added ability to include files using anchor points instead of line numbers.
+ [#851](https://github.com/rust-lang/mdBook/pull/851)
+- Added `language` configuration value to set the language of the book, which
+ will affect things like the `<html lang="en">` tag.
+ [#941](https://github.com/rust-lang/mdBook/pull/941)
+
+### Changed
+- Updated to handlebars 2.0.
+ [#977](https://github.com/rust-lang/mdBook/pull/977)
+
+### Fixed
+- Fixed memory leak warning.
+ [#967](https://github.com/rust-lang/mdBook/pull/967)
+- Fix more print.html links.
+ [#963](https://github.com/rust-lang/mdBook/pull/963)
+- Fixed crash on some unicode input.
+ [#978](https://github.com/rust-lang/mdBook/pull/978)
+
+## mdBook 0.3.0
+[6cbc41d...69a08ef](https://github.com/rust-lang/mdBook/compare/6cbc41d...69a08ef)
+
+### Added
+- Added ability to resize the sidebar.
+ [#849](https://github.com/rust-lang/mdBook/pull/849)
+- Added `load_with_config_and_summary` function to `MDBook` to be able to
+ build a book with a custom `Summary`.
+ [#883](https://github.com/rust-lang/mdBook/pull/883)
+- Set `noindex` on `print.html` page to prevent robots from indexing it.
+ [#844](https://github.com/rust-lang/mdBook/pull/844)
+- Added support for ~~strikethrough~~ and GitHub-style tasklists.
+ [#952](https://github.com/rust-lang/mdBook/pull/952)
+
+### Changed
+- Command-line help output is now colored.
+ [#861](https://github.com/rust-lang/mdBook/pull/861)
+- The build directory is now deleted before rendering starts, instead of after
+ if finishes.
+ [#878](https://github.com/rust-lang/mdBook/pull/878)
+- Removed dependency on `same-file` crate.
+ [#903](https://github.com/rust-lang/mdBook/pull/903)
+- 💥 Renamed `with_preprecessor` to `with_preprocessor`.
+ [#906](https://github.com/rust-lang/mdBook/pull/906)
+- Updated ACE editor to 1.4.4, should remove a JavaScript console warning.
+ [#935](https://github.com/rust-lang/mdBook/pull/935)
+- Dependencies have been updated.
+ [#934](https://github.com/rust-lang/mdBook/pull/934)
+ [#945](https://github.com/rust-lang/mdBook/pull/945)
+- Highlight.js has been updated. This fixes some TOML highlighting, and adds
+ Julia support.
+ [#942](https://github.com/rust-lang/mdBook/pull/942)
+- 🔥 Updated to pulldown-cmark 0.5. This may have significant changes to the
+ formatting of existing books, as the newer version has more accurate
+ interpretation of the CommonMark spec and a large number of bug fixes and
+ changes.
+ [#898](https://github.com/rust-lang/mdBook/pull/898)
+- The `diff` language should now highlight correctly.
+ [#943](https://github.com/rust-lang/mdBook/pull/943)
+- Make the blank region of a header not clickable.
+ [#948](https://github.com/rust-lang/mdBook/pull/948)
+- Rustdoc tests now use the preprocessed content instead of the raw,
+ unpreprocessed content.
+ [#891](https://github.com/rust-lang/mdBook/pull/891)
+
+### Fixed
+- Fixed file change detection so that `mdbook serve` only reloads once when
+ multiple files are changed at once.
+ [#870](https://github.com/rust-lang/mdBook/pull/870)
+- Fixed on-hover color highlighting for links in sidebar.
+ [#834](https://github.com/rust-lang/mdBook/pull/834)
+- Fixed loss of focus when clicking the "Copy" button in code blocks.
+ [#867](https://github.com/rust-lang/mdBook/pull/867)
+- Fixed incorrectly stripping the path for `additional-js` files.
+ [#796](https://github.com/rust-lang/mdBook/pull/796)
+- Fixed color of `code spans` that are links.
+ [#905](https://github.com/rust-lang/mdBook/pull/905)
+- Fixed "next" navigation on index.html.
+ [#916](https://github.com/rust-lang/mdBook/pull/916)
+- Fixed keyboard chapter navigation for `file` urls.
+ [#915](https://github.com/rust-lang/mdBook/pull/915)
+- Fixed bad wrapping for inline code on some browsers.
+ [#818](https://github.com/rust-lang/mdBook/pull/818)
+- Properly load an existing `SUMMARY.md` in `mdbook init`.
+ [#841](https://github.com/rust-lang/mdBook/pull/841)
+- Fixed some broken links in `print.html`.
+ [#871](https://github.com/rust-lang/mdBook/pull/871)
+- The Rust Playground link now supports the 2018 edition.
+ [#946](https://github.com/rust-lang/mdBook/pull/946)
+
+## mdBook 0.2.3 (2018-01-18)
+[2c20c99...6cbc41d](https://github.com/rust-lang/mdBook/compare/2c20c99...6cbc41d)
+
+### Added
+- Added an optional button to the top of the page which will link to a git
+ repository. Use the `git-repository-url` and `git-repository-icon` options
+ in the `[output.html]` section to enable it and set its appearance.
+ [#802](https://github.com/rust-lang/mdBook/pull/802)
+- Added a `default-theme` option to the `[output.html]` section.
+ [#804](https://github.com/rust-lang/mdBook/pull/804)
+
+### Changed
+- 💥 Header ID anchors no longer add an arbitrary `a` character for headers
+ that start with a non-ascii-alphabetic character.
+ [#788](https://github.com/rust-lang/mdBook/pull/788)
+
+### Fixed
+- Fix websocket hostname usage
+ [#865](https://github.com/rust-lang/mdBook/pull/865)
+- Fixing links in print.html
+ [#866](https://github.com/rust-lang/mdBook/pull/866)
+
+## mdBook 0.2.2 (2018-10-19)
+[7e2e095...2c20c99](https://github.com/rust-lang/mdBook/compare/7e2e095...2c20c99)
+
+### Added
+- 🎉 Process-based custom preprocessors. See [the
+ docs](https://rust-lang.github.io/mdBook/for_developers/preprocessors.html)
+ for more.
+ [#792](https://github.com/rust-lang/mdBook/pull/792)
+
+- 🎉 Configurable preprocessors.
+
+ Added `build.use-default-preprocessors` boolean TOML key to allow disabling
+ the built-in `links` and `index` preprocessors.
+
+ Added `[preprocessor]` TOML tables to configure each preprocessor.
+
+ Specifying `[preprocessor.links]` or `[preprocessor.index]` will enable the
+ respective built-in preprocessor if `build.use-default-preprocessors` is
+ `false`.
+
+ Added `fn supports_renderer(&self, renderer: &str) -> bool` to the
+ `Preprocessor` trait to specify if the preprocessor supports the given
+ renderer. The default implementation always returns `true`.
+
+ `Preprocessor::run` now takes a book by value instead of a mutable
+ reference. It should return a `Book` value with the intended modifications.
+
+ Added `PreprocessorContext::renderer` to indicate the renderer being used.
+
+ [#658](https://github.com/rust-lang/mdBook/pull/658)
+ [#787](https://github.com/rust-lang/mdBook/pull/787)
+
+### Fixed
+- Fix paths to additional CSS and JavaScript files
+ [#777](https://github.com/rust-lang/mdBook/pull/777)
+- Ensure section numbers are correctly incremented after a horizontal
+ separator
+ [#790](https://github.com/rust-lang/mdBook/pull/790)
+
+## mdBook 0.2.1 (2018-08-22)
+[91ffca1...7e2e095](https://github.com/rust-lang/mdBook/compare/91ffca1...7e2e095)
+
+### Changed
+- Update to handlebars-rs 1.0
+ [#761](https://github.com/rust-lang/mdBook/pull/761)
+
+### Fixed
+- Fix table colors, broken by Stylus -> CSS transition
+ [#765](https://github.com/rust-lang/mdBook/pull/765)
+
+## mdBook 0.2.0 (2018-08-02)
+
+### Changed
+- 💥 This release changes how links are handled in mdBook. Previously, relative
+ links were interpreted relative to the book's root. In `0.2.0`+ links are
+ relative to the page they are in, and use the `.md` extension. This has [several
+ advantages](https://github.com/rust-lang/mdBook/pull/603#issue-166701447),
+ such as making links work in other markdown viewers like GitHub. You will
+ likely have to change links in your book to accommodate this change. For
+ example, a book with this layout:
+
+ ```
+ chapter_1/
+ section_1.md
+ section_2.md
+ SUMMARY.md
+ ```
+
+ Previously a link in `section_1.md` to `section_2.md` would look like this:
+ ```markdown
+ [section_2](chapter_1/section_2.html)
+ ```
+
+ Now it must be changed to this:
+ ```markdown
+ [section_2](section_2.md)
+ ```
+
+- 💥 `mdbook test --library-path` now accepts a comma-delimited list of
+ arguments rather than taking all following arguments. This makes it easier
+ to handle the trailing book directory argument without always needing to put
+ ` -- ` before it. Multiple instances of the option continue to be accepted:
+ `mdbook test -L foo -L bar`.
+
+- 💥 `mdbook serve` has some of its options renamed for clarity. See `mdbook
+ help serve` for details.
+
+- Embedded rust playpens now use the "stable" playground API.
+ [#754](https://github.com/rust-lang/mdBook/pull/754)
+
+### Fixed
+- Escaped includes (`\{{#include file.rs}}`) will now render correctly.
+ [f30ce01](https://github.com/rust-lang/mdBook/commit/f30ce0184d71e342141145472bf816419d30a2c5)
+- `index.html` will now render correctly when the book's first section is
+ inside a subdirectory.
+ [#756](https://github.com/rust-lang/mdBook/pull/756)
diff --git a/vendor/mdbook/CODE_OF_CONDUCT.md b/vendor/mdbook/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..e3708bc48
--- /dev/null
+++ b/vendor/mdbook/CODE_OF_CONDUCT.md
@@ -0,0 +1,3 @@
+# The Rust Code of Conduct
+
+The Code of Conduct for this repository [can be found online](https://www.rust-lang.org/conduct.html).
diff --git a/vendor/mdbook/CONTRIBUTING.md b/vendor/mdbook/CONTRIBUTING.md
new file mode 100644
index 000000000..72010ad2e
--- /dev/null
+++ b/vendor/mdbook/CONTRIBUTING.md
@@ -0,0 +1,130 @@
+# Contributing
+
+Welcome stranger!
+
+If you have come here to learn how to contribute to mdBook, we have some tips for you!
+
+First of all, don't hesitate to ask questions!
+Use the [issue tracker](https://github.com/rust-lang/mdBook/issues), no question is too simple.
+
+### Issues to work on
+
+Any issue is up for the grabbing, but if you are starting out, you might be interested in the
+[E-Easy issues](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AE-Easy).
+Those are issues that are considered more straightforward for beginners to Rust or the codebase itself.
+These issues can be a good launching pad for more involved issues. Easy tasks for a first time contribution
+include documentation improvements, new tests, examples, updating dependencies, etc.
+
+If you come from a web development background, you might be interested in issues related to web technologies tagged
+[A-JavaScript](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-JavaScript),
+[A-Style](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-Style),
+[A-HTML](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-HTML) or
+[A-Mobile](https://github.com/rust-lang/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AA-Mobile).
+
+When you decide you want to work on a specific issue, ping us on that issue so that we can assign it to you.
+Again, do not hesitate to ask questions. We will gladly mentor anyone that want to tackle an issue.
+
+Issues on the issue tracker are categorized with the following labels:
+
+- **A**-prefixed labels state which area of the project an issue relates to.
+- **E**-prefixed labels show an estimate of the experience necessary to fix the issue.
+- **M**-prefixed labels are meta-issues used for questions, discussions, or tracking issues
+- **S**-prefixed labels show the status of the issue
+- **T**-prefixed labels show the type of issue
+
+### Building mdBook
+
+mdBook builds on stable Rust, if you want to build mdBook from source, here are the steps to follow:
+
+1. Navigate to the directory of your choice
+0. Clone this repository with git.
+
+ ```
+ git clone https://github.com/rust-lang/mdBook.git
+ ```
+0. Navigate into the newly created `mdBook` directory
+0. Run `cargo build`
+
+The resulting binary can be found in `mdBook/target/debug/` under the name `mdbook` or `mdbook.exe`.
+
+### Code Quality
+
+We love code quality and Rust has some excellent tools to assist you with contributions.
+
+#### Formatting Code with rustfmt
+
+Before you make your Pull Request to the project, please run it through the `rustfmt` utility.
+This will ensure we have good quality source code that is better for us all to maintain.
+
+[rustfmt](https://github.com/rust-lang/rustfmt) has a lot more information on the project.
+The quick guide is
+
+1. Install it
+ ```
+ rustup component add rustfmt
+ ```
+1. You can now run `rustfmt` on a single file simply by...
+ ```
+ rustfmt src/path/to/your/file.rs
+ ```
+ ... or you can format the entire project with
+ ```
+ cargo fmt
+ ```
+ When run through `cargo` it will format all bin and lib files in the current crate.
+
+For more information, such as running it from your favourite editor, please see the `rustfmt` project. [rustfmt](https://github.com/rust-lang/rustfmt)
+
+
+#### Finding Issues with Clippy
+
+Clippy is a code analyser/linter detecting mistakes, and therefore helps to improve your code.
+Like formatting your code with `rustfmt`, running clippy regularly and before your Pull Request will
+help us maintain awesome code.
+
+The best documentation can be found over at [rust-clippy](https://github.com/rust-lang/rust-clippy)
+
+1. To install
+ ```
+ rustup component add clippy
+ ```
+2. Running clippy
+ ```
+ cargo clippy
+ ```
+
+Clippy has an ever growing list of checks, that are managed in [lint files](https://rust-lang.github.io/rust-clippy/master/index.html).
+
+### Making a pull-request
+
+When you feel comfortable that your changes could be integrated into mdBook, you can create a pull-request on GitHub.
+One of the core maintainers will then approve the changes or request some changes before it gets merged.
+
+If you want to make your pull-request even better, you might want to run [Clippy](https://github.com/Manishearth/rust-clippy)
+and [rustfmt](https://github.com/rust-lang/rustfmt) on the code first.
+This is not a requirement though and will never block a pull-request from being merged.
+
+That's it, happy contributions! :tada: :tada: :tada:
+
+## Browser compatibility and testing
+
+Currently we don't have a strict browser compatibility matrix due to our limited resources.
+We generally strive to keep mdBook compatible with a relatively recent browser on all of the most major platforms.
+That is, supporting Chrome, Safari, Firefox, Edge on Windows, macOS, Linux, iOS, and Android.
+If possible, do your best to avoid breaking older browser releases.
+
+Any change to the HTML or styling is encouraged to manually check on as many browsers and platforms that you can.
+Unfortunately at this time we don't have any automated UI or browser testing, so your assistance in testing is appreciated.
+
+## Updating higlight.js
+
+The following are instructions for updating [highlight.js](https://highlightjs.org/).
+
+1. Clone the repository at <https://github.com/highlightjs/highlight.js>
+1. Check out a tagged release (like `10.1.1`).
+1. Run `npm install`
+1. Run `node tools/build.js :common apache armasm coffeescript d handlebars haskell http julia nginx properties r scala x86asm yaml`
+1. Compare the language list that it spits out to the one in [`syntax-highlighting.md`](https://github.com/camelid/mdBook/blob/master/guide/src/format/theme/syntax-highlighting.md). If any are missing, add them to the list and rebuild (and update these docs). If any are added to the common set, add them to `syntax-highlighting.md`.
+1. Copy `build/highlight.min.js` to mdbook's directory [`highlight.js`](https://github.com/rust-lang/mdBook/blob/master/src/theme/highlight.js).
+1. Be sure to check the highlight.js [CHANGES](https://github.com/highlightjs/highlight.js/blob/main/CHANGES.md) for any breaking changes. Breaking changes that would affect users will need to wait until the next major release.
+1. Build mdbook with the new file and build some books with the new version and compare the output with a variety of languages to see if anything changes. The [test_book](https://github.com/rust-lang/mdBook/tree/master/test_book) contains a chapter with many languages to examine.
diff --git a/vendor/mdbook/Cargo.lock b/vendor/mdbook/Cargo.lock
new file mode 100644
index 000000000..10fae527f
--- /dev/null
+++ b/vendor/mdbook/Cargo.lock
@@ -0,0 +1,1991 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ammonia"
+version = "3.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e445c26125ff80316eaea16e812d717b147b82a68682bd4730f74d4845c8b35"
+dependencies = [
+ "html5ever",
+ "lazy_static",
+ "maplit",
+ "markup5ever_rcdom",
+ "matches",
+ "tendril",
+ "url",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf"
+
+[[package]]
+name = "assert_cmd"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d20831bd004dda4c7c372c19cdabff369f794a95e955b3f13fe460e3e1ae95f"
+dependencies = [
+ "bstr",
+ "doc-comment",
+ "predicates",
+ "predicates-core",
+ "predicates-tree",
+ "wait-timeout",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bit-set"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "block-buffer"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
+dependencies = [
+ "block-padding",
+ "byte-tools",
+ "byteorder",
+ "generic-array 0.12.4",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
+dependencies = [
+ "byte-tools",
+]
+
+[[package]]
+name = "bstr"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
+dependencies = [
+ "lazy_static",
+ "memchr",
+ "regex-automata",
+]
+
+[[package]]
+name = "byte-tools"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "clap"
+version = "3.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a30c3bf9ff12dfe5dae53f0a96e0febcd18420d1c0e7fad77796d9d5c4b5375"
+dependencies = [
+ "atty",
+ "bitflags",
+ "indexmap",
+ "lazy_static",
+ "os_str_bytes",
+ "strsim",
+ "termcolor",
+ "textwrap",
+]
+
+[[package]]
+name = "clap_complete"
+version = "3.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d044e9db8cd0f68191becdeb5246b7462e4cf0c069b19ae00d1bf3fa9889498d"
+dependencies = [
+ "clap",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "ctor"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "diff"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
+
+[[package]]
+name = "difflib"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
+
+[[package]]
+name = "digest"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
+dependencies = [
+ "generic-array 0.12.4",
+]
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "doc-comment"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "elasticlunr-rs"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6dae5cac90640734ee881bc5f21b6e5123f4e5235e52428db114abffc2391d6"
+dependencies = [
+ "regex",
+ "serde",
+ "serde_derive",
+ "serde_json",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "fake-simd"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
+
+[[package]]
+name = "filetime"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "redox_syscall",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "float-cmp"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "fsevent"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6"
+dependencies = [
+ "bitflags",
+ "fsevent-sys",
+]
+
+[[package]]
+name = "fsevent-sys"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "fuchsia-zircon"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
+dependencies = [
+ "bitflags",
+ "fuchsia-zircon-sys",
+]
+
+[[package]]
+name = "fuchsia-zircon-sys"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
+
+[[package]]
+name = "futf"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b"
+dependencies = [
+ "mac",
+ "new_debug_unreachable",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c54913bae956fb8df7f4dc6fc90362aa72e69148e3f39041fbe8742d21e0ac57"
+dependencies = [
+ "autocfg",
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
+
+[[package]]
+name = "futures-task"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbe54a98670017f3be909561f6ad13e810d9a51f3f061b902062ca3da80799f2"
+
+[[package]]
+name = "futures-util"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67eb846bfd58e44a8481a00049e82c43e0ccb5d61f8dc071057cb19249dd4d78"
+dependencies = [
+ "autocfg",
+ "futures-core",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+ "proc-macro-hack",
+ "proc-macro-nested",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi 0.10.2+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "gitignore"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78aa90e4620c1498ac434c06ba6e521b525794bbdacf085d490cc794b4a2f9a4"
+dependencies = [
+ "glob",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
+[[package]]
+name = "h2"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7f3675cfef6a30c8031cf9e6493ebdc3bb3272a3fea3923c4210d1830e6a472"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "handlebars"
+version = "4.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd85ecabdb47308d28d3a4113224fefcab2510ccb4e463aee0a1362eb84c756a"
+dependencies = [
+ "log",
+ "pest",
+ "pest_derive",
+ "quick-error",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
+[[package]]
+name = "headers"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0b7591fb62902706ae8e7aaff416b1b0fa2c0fd0878b46dc13baa3712d8a855"
+dependencies = [
+ "base64",
+ "bitflags",
+ "bytes",
+ "headers-core",
+ "http",
+ "mime",
+ "sha-1 0.9.7",
+ "time",
+]
+
+[[package]]
+name = "headers-core"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
+dependencies = [
+ "http",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "html5ever"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b"
+dependencies = [
+ "log",
+ "mac",
+ "markup5ever",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "http"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "399c583b2979440c60be0821a6199eca73bc3c8dcd9d070d75ac726e2c6186e5"
+dependencies = [
+ "bytes",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
+
+[[package]]
+name = "httpdate"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "hyper"
+version = "0.14.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b61cf2d1aebcf6e6352c97b81dc2244ca29194be1b276f5d8ad5c6330fffb11"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "idna"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "inotify"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f"
+dependencies = [
+ "bitflags",
+ "inotify-sys",
+ "libc",
+]
+
+[[package]]
+name = "inotify-sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "iovec"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5"
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "mac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
+
+[[package]]
+name = "maplit"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
+
+[[package]]
+name = "markup5ever"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd"
+dependencies = [
+ "log",
+ "phf",
+ "phf_codegen",
+ "string_cache",
+ "string_cache_codegen",
+ "tendril",
+]
+
+[[package]]
+name = "markup5ever_rcdom"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f015da43bcd8d4f144559a3423f4591d69b8ce0652c905374da7205df336ae2b"
+dependencies = [
+ "html5ever",
+ "markup5ever",
+ "tendril",
+ "xml5ever",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
+
+[[package]]
+name = "mdbook"
+version = "0.4.21"
+dependencies = [
+ "ammonia",
+ "anyhow",
+ "assert_cmd",
+ "chrono",
+ "clap",
+ "clap_complete",
+ "elasticlunr-rs",
+ "env_logger",
+ "futures-util",
+ "gitignore",
+ "handlebars",
+ "lazy_static",
+ "log",
+ "memchr",
+ "notify",
+ "opener",
+ "predicates",
+ "pretty_assertions",
+ "pulldown-cmark",
+ "regex",
+ "select",
+ "semver",
+ "serde",
+ "serde_json",
+ "shlex",
+ "tempfile",
+ "tokio",
+ "toml",
+ "topological-sort",
+ "walkdir",
+ "warp",
+]
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "mio"
+version = "0.6.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
+dependencies = [
+ "cfg-if 0.1.10",
+ "fuchsia-zircon",
+ "fuchsia-zircon-sys",
+ "iovec",
+ "kernel32-sys",
+ "libc",
+ "log",
+ "miow 0.2.2",
+ "net2",
+ "slab",
+ "winapi 0.2.8",
+]
+
+[[package]]
+name = "mio"
+version = "0.7.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16"
+dependencies = [
+ "libc",
+ "log",
+ "miow 0.3.7",
+ "ntapi",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "mio-extras"
+version = "2.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
+dependencies = [
+ "lazycell",
+ "log",
+ "mio 0.6.23",
+ "slab",
+]
+
+[[package]]
+name = "miow"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
+dependencies = [
+ "kernel32-sys",
+ "net2",
+ "winapi 0.2.8",
+ "ws2_32-sys",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "net2"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
+
+[[package]]
+name = "normalize-line-endings"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
+
+[[package]]
+name = "notify"
+version = "4.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257"
+dependencies = [
+ "bitflags",
+ "filetime",
+ "fsevent",
+ "fsevent-sys",
+ "inotify",
+ "libc",
+ "mio 0.6.23",
+ "mio-extras",
+ "walkdir",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "ntapi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "opaque-debug"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "opener"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ea3ebcd72a54701f56345f16785a6d3ac2df7e986d273eb4395c0b01db17952"
+dependencies = [
+ "bstr",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "os_str_bytes"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "output_vt100"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "pest"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
+dependencies = [
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
+dependencies = [
+ "maplit",
+ "pest",
+ "sha-1 0.8.2",
+]
+
+[[package]]
+name = "phf"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
+dependencies = [
+ "phf_shared",
+ "rand 0.7.3",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[package]]
+name = "precomputed-hash"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+
+[[package]]
+name = "predicates"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc3d91237f5de3bcd9d927e24d03b495adb6135097b001cea7403e2d573d00a9"
+dependencies = [
+ "difflib",
+ "float-cmp",
+ "itertools",
+ "normalize-line-endings",
+ "predicates-core",
+ "regex",
+]
+
+[[package]]
+name = "predicates-core"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451"
+
+[[package]]
+name = "predicates-tree"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2"
+dependencies = [
+ "predicates-core",
+ "treeline",
+]
+
+[[package]]
+name = "pretty_assertions"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563"
+dependencies = [
+ "ansi_term",
+ "ctor",
+ "diff",
+ "output_vt100",
+]
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
+[[package]]
+name = "proc-macro-nested"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "pulldown-cmark"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34f197a544b0c9ab3ae46c359a7ec9cbbb5c7bf97054266fecb7ead794a181d6"
+dependencies = [
+ "bitflags",
+ "memchr",
+ "unicase",
+]
+
+[[package]]
+name = "quick-error"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
+
+[[package]]
+name = "quote"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom 0.1.16",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc 0.2.0",
+ "rand_pcg",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.3",
+ "rand_hc 0.3.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.3",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom 0.1.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom 0.2.3",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
+dependencies = [
+ "rand_core 0.6.3",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+
+[[package]]
+name = "select"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ee061f90afcc8678bef7a78d0d121683f0ba753f740ff7005f833ec445876b7"
+dependencies = [
+ "bit-set",
+ "html5ever",
+ "markup5ever_rcdom",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
+
+[[package]]
+name = "serde"
+version = "1.0.129"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.129"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e57ae87ad533d9a56427558b516d0adac283614e347abf85b0dc0cbbf0a249f3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
+dependencies = [
+ "block-buffer 0.7.3",
+ "digest 0.8.1",
+ "fake-simd",
+ "opaque-debug 0.2.3",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.9.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a0c8611594e2ab4ebbf06ec7cbbf0a99450b8570e96cbf5188b5d5f6ef18d81"
+dependencies = [
+ "block-buffer 0.9.0",
+ "cfg-if 1.0.0",
+ "cpufeatures",
+ "digest 0.9.0",
+ "opaque-debug 0.3.0",
+]
+
+[[package]]
+name = "shlex"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d"
+
+[[package]]
+name = "siphasher"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "729a25c17d72b06c68cb47955d44fda88ad2d3e7d77e025663fdd69b93dd71a1"
+
+[[package]]
+name = "slab"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590"
+
+[[package]]
+name = "socket2"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "765f090f0e423d2b55843402a07915add955e7d60657db13707a159727326cad"
+dependencies = [
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "string_cache"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a"
+dependencies = [
+ "lazy_static",
+ "new_debug_unreachable",
+ "phf_shared",
+ "precomputed-hash",
+ "serde",
+]
+
+[[package]]
+name = "string_cache_codegen"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "syn"
+version = "1.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "rand 0.8.4",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "tendril"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33"
+dependencies = [
+ "futf",
+ "mac",
+ "utf-8",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
+
+[[package]]
+name = "thiserror"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
+dependencies = [
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "tokio"
+version = "1.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a"
+dependencies = [
+ "bytes",
+ "libc",
+ "memchr",
+ "mio 0.7.13",
+ "num_cpus",
+ "pin-project-lite",
+ "tokio-macros",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8"
+dependencies = [
+ "futures-util",
+ "log",
+ "pin-project",
+ "tokio",
+ "tungstenite",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "topological-sort"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa7c7f42dea4b1b99439786f5633aeb9c14c1b53f75e282803c2ec2ad545873c"
+
+[[package]]
+name = "tower-service"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
+
+[[package]]
+name = "tracing"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
+dependencies = [
+ "cfg-if 1.0.0",
+ "log",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ca517f43f0fb96e0c3072ed5c275fe5eece87e8cb52f4a77b69226d3b1c9df8"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "treeline"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
+
+[[package]]
+name = "try-lock"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+
+[[package]]
+name = "tungstenite"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5"
+dependencies = [
+ "base64",
+ "byteorder",
+ "bytes",
+ "http",
+ "httparse",
+ "log",
+ "rand 0.8.4",
+ "sha-1 0.9.7",
+ "thiserror",
+ "url",
+ "utf-8",
+]
+
+[[package]]
+name = "typenum"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
+
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "url"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "wait-timeout"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi 0.3.9",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "warp"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "headers",
+ "http",
+ "hyper",
+ "log",
+ "mime",
+ "mime_guess",
+ "percent-encoding",
+ "pin-project",
+ "scoped-tls",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-stream",
+ "tokio-tungstenite",
+ "tokio-util",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "ws2_32-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
+
+[[package]]
+name = "xml5ever"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b1b52e6e8614d4a58b8e70cf51ec0cc21b256ad8206708bcff8139b5bbd6a59"
+dependencies = [
+ "log",
+ "mac",
+ "markup5ever",
+ "time",
+]
diff --git a/vendor/mdbook/Cargo.toml b/vendor/mdbook/Cargo.toml
new file mode 100644
index 000000000..2e00e186d
--- /dev/null
+++ b/vendor/mdbook/Cargo.toml
@@ -0,0 +1,165 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+name = "mdbook"
+version = "0.4.21"
+authors = [
+ "Mathieu David <mathieudavid@mathieudavid.org>",
+ "Michael-F-Bryan <michaelfbryan@gmail.com>",
+ "Matt Ickstadt <mattico8@gmail.com>",
+]
+exclude = ["/guide/*"]
+description = "Creates a book from markdown files"
+documentation = "http://rust-lang.github.io/mdBook/index.html"
+readme = "README.md"
+keywords = [
+ "book",
+ "gitbook",
+ "rustbook",
+ "markdown",
+]
+license = "MPL-2.0"
+repository = "https://github.com/rust-lang/mdBook"
+
+[[bin]]
+name = "mdbook"
+doc = false
+
+[dependencies.ammonia]
+version = "3"
+optional = true
+
+[dependencies.anyhow]
+version = "1.0.28"
+
+[dependencies.chrono]
+version = "0.4"
+
+[dependencies.clap]
+version = "3.0"
+features = ["cargo"]
+
+[dependencies.clap_complete]
+version = "3.0"
+
+[dependencies.elasticlunr-rs]
+version = "3.0.0"
+optional = true
+
+[dependencies.env_logger]
+version = "0.9.0"
+
+[dependencies.futures-util]
+version = "0.3.4"
+optional = true
+
+[dependencies.gitignore]
+version = "1.0"
+optional = true
+
+[dependencies.handlebars]
+version = "4.0"
+
+[dependencies.lazy_static]
+version = "1.0"
+
+[dependencies.log]
+version = "0.4"
+
+[dependencies.memchr]
+version = "2.0"
+
+[dependencies.notify]
+version = "4.0"
+optional = true
+
+[dependencies.opener]
+version = "0.5"
+
+[dependencies.pulldown-cmark]
+version = "0.9.1"
+default-features = false
+
+[dependencies.regex]
+version = "1.5.5"
+
+[dependencies.serde]
+version = "1.0"
+features = ["derive"]
+
+[dependencies.serde_json]
+version = "1.0"
+
+[dependencies.shlex]
+version = "1"
+
+[dependencies.tempfile]
+version = "3.0"
+
+[dependencies.tokio]
+version = "1"
+features = [
+ "macros",
+ "rt-multi-thread",
+]
+optional = true
+
+[dependencies.toml]
+version = "0.5.1"
+
+[dependencies.topological-sort]
+version = "0.1.0"
+
+[dependencies.warp]
+version = "0.3.2"
+features = ["websocket"]
+optional = true
+default-features = false
+
+[dev-dependencies.assert_cmd]
+version = "1"
+
+[dev-dependencies.predicates]
+version = "2"
+
+[dev-dependencies.pretty_assertions]
+version = "1.2.1"
+
+[dev-dependencies.select]
+version = "0.5"
+
+[dev-dependencies.semver]
+version = "1.0"
+
+[dev-dependencies.walkdir]
+version = "2.0"
+
+[features]
+default = [
+ "watch",
+ "serve",
+ "search",
+]
+search = [
+ "elasticlunr-rs",
+ "ammonia",
+]
+serve = [
+ "futures-util",
+ "tokio",
+ "warp",
+]
+watch = [
+ "notify",
+ "gitignore",
+]
diff --git a/vendor/mdbook/LICENSE b/vendor/mdbook/LICENSE
new file mode 100644
index 000000000..be2cc4dfb
--- /dev/null
+++ b/vendor/mdbook/LICENSE
@@ -0,0 +1,362 @@
+Mozilla Public License, version 2.0
+
+1. Definitions
+
+1.1. "Contributor"
+
+ means each individual or legal entity that creates, contributes to the
+ creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+
+ means the combination of the Contributions of others (if any) used by a
+ Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+
+ means Source Code Form to which the initial Contributor has attached the
+ notice in Exhibit A, the Executable Form of such Source Code Form, and
+ Modifications of such Source Code Form, in each case including portions
+ thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ a. that the initial Contributor has attached the notice described in
+ Exhibit B to the Covered Software; or
+
+ b. that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the terms of
+ a Secondary License.
+
+1.6. "Executable Form"
+
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+
+ means a work that combines Covered Software with other material, in a
+ separate file or files, that is not Covered Software.
+
+1.8. "License"
+
+ means this document.
+
+1.9. "Licensable"
+
+ means having the right to grant, to the maximum extent possible, whether
+ at the time of the initial grant or subsequently, any and all of the
+ rights conveyed by this License.
+
+1.10. "Modifications"
+
+ means any of the following:
+
+ a. any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered Software; or
+
+ b. any new file in Source Code Form that contains any Covered Software.
+
+1.11. "Patent Claims" of a Contributor
+
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the License,
+ by the making, using, selling, offering for sale, having made, import,
+ or transfer of either its Contributions or its Contributor Version.
+
+1.12. "Secondary License"
+
+ means either the GNU General Public License, Version 2.0, the GNU Lesser
+ General Public License, Version 2.1, the GNU Affero General Public
+ License, Version 3.0, or any later versions of those licenses.
+
+1.13. "Source Code Form"
+
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that controls, is
+ controlled by, or is under common control with You. For purposes of this
+ definition, "control" means (a) the power, direct or indirect, to cause
+ the direction or management of such entity, whether by contract or
+ otherwise, or (b) ownership of more than fifty percent (50%) of the
+ outstanding shares or beneficial ownership of such entity.
+
+
+2. License Grants and Conditions
+
+2.1. Grants
+
+ Each Contributor hereby grants You a world-wide, royalty-free,
+ non-exclusive license:
+
+ a. under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+ b. under Patent Claims of such Contributor to make, use, sell, offer for
+ sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+ The licenses granted in Section 2.1 with respect to any Contribution
+ become effective for each Contribution on the date the Contributor first
+ distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+ The licenses granted in this Section 2 are the only rights granted under
+ this License. No additional rights or licenses will be implied from the
+ distribution or licensing of Covered Software under this License.
+ Notwithstanding Section 2.1(b) above, no patent license is granted by a
+ Contributor:
+
+ a. for any code that a Contributor has removed from Covered Software; or
+
+ b. for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+ c. under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+ This License does not grant any rights in the trademarks, service marks,
+ or logos of any Contributor (except as may be necessary to comply with
+ the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+ No Contributor makes additional grants as a result of Your choice to
+ distribute the Covered Software under a subsequent version of this
+ License (see Section 10.2) or under the terms of a Secondary License (if
+ permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+ Each Contributor represents that the Contributor believes its
+ Contributions are its original creation(s) or it has sufficient rights to
+ grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+ This License is not intended to limit any rights You have under
+ applicable copyright doctrines of fair use, fair dealing, or other
+ equivalents.
+
+2.7. Conditions
+
+ Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
+ Section 2.1.
+
+
+3. Responsibilities
+
+3.1. Distribution of Source Form
+
+ All distribution of Covered Software in Source Code Form, including any
+ Modifications that You create or to which You contribute, must be under
+ the terms of this License. You must inform recipients that the Source
+ Code Form of the Covered Software is governed by the terms of this
+ License, and how they can obtain a copy of this License. You may not
+ attempt to alter or restrict the recipients' rights in the Source Code
+ Form.
+
+3.2. Distribution of Executable Form
+
+ If You distribute Covered Software in Executable Form then:
+
+ a. such Covered Software must also be made available in Source Code Form,
+ as described in Section 3.1, and You must inform recipients of the
+ Executable Form how they can obtain a copy of such Source Code Form by
+ reasonable means in a timely manner, at a charge no more than the cost
+ of distribution to the recipient; and
+
+ b. You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter the
+ recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+ You may create and distribute a Larger Work under terms of Your choice,
+ provided that You also comply with the requirements of this License for
+ the Covered Software. If the Larger Work is a combination of Covered
+ Software with a work governed by one or more Secondary Licenses, and the
+ Covered Software is not Incompatible With Secondary Licenses, this
+ License permits You to additionally distribute such Covered Software
+ under the terms of such Secondary License(s), so that the recipient of
+ the Larger Work may, at their option, further distribute the Covered
+ Software under the terms of either this License or such Secondary
+ License(s).
+
+3.4. Notices
+
+ You may not remove or alter the substance of any license notices
+ (including copyright notices, patent notices, disclaimers of warranty, or
+ limitations of liability) contained within the Source Code Form of the
+ Covered Software, except that You may alter any license notices to the
+ extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+ You may choose to offer, and to charge a fee for, warranty, support,
+ indemnity or liability obligations to one or more recipients of Covered
+ Software. However, You may do so only on Your own behalf, and not on
+ behalf of any Contributor. You must make it absolutely clear that any
+ such warranty, support, indemnity, or liability obligation is offered by
+ You alone, and You hereby agree to indemnify every Contributor for any
+ liability incurred by such Contributor as a result of warranty, support,
+ indemnity or liability terms You offer. You may include additional
+ disclaimers of warranty and limitations of liability specific to any
+ jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+
+ If it is impossible for You to comply with any of the terms of this License
+ with respect to some or all of the Covered Software due to statute,
+ judicial order, or regulation then You must: (a) comply with the terms of
+ this License to the maximum extent possible; and (b) describe the
+ limitations and the code they affect. Such description must be placed in a
+ text file included with all distributions of the Covered Software under
+ this License. Except to the extent prohibited by statute or regulation,
+ such description must be sufficiently detailed for a recipient of ordinary
+ skill to be able to understand it.
+
+5. Termination
+
+5.1. The rights granted under this License will terminate automatically if You
+ fail to comply with any of its terms. However, if You become compliant,
+ then the rights granted under this License from a particular Contributor
+ are reinstated (a) provisionally, unless and until such Contributor
+ explicitly and finally terminates Your grants, and (b) on an ongoing
+ basis, if such Contributor fails to notify You of the non-compliance by
+ some reasonable means prior to 60 days after You have come back into
+ compliance. Moreover, Your grants from a particular Contributor are
+ reinstated on an ongoing basis if such Contributor notifies You of the
+ non-compliance by some reasonable means, this is the first time You have
+ received notice of non-compliance with this License from such
+ Contributor, and You become compliant prior to 30 days after Your receipt
+ of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+ infringement claim (excluding declaratory judgment actions,
+ counter-claims, and cross-claims) alleging that a Contributor Version
+ directly or indirectly infringes any patent, then the rights granted to
+ You by any and all Contributors for the Covered Software under Section
+ 2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
+ license agreements (excluding distributors and resellers) which have been
+ validly granted by You or Your distributors under this License prior to
+ termination shall survive termination.
+
+6. Disclaimer of Warranty
+
+ Covered Software is provided under this License on an "as is" basis,
+ without warranty of any kind, either expressed, implied, or statutory,
+ including, without limitation, warranties that the Covered Software is free
+ of defects, merchantable, fit for a particular purpose or non-infringing.
+ The entire risk as to the quality and performance of the Covered Software
+ is with You. Should any Covered Software prove defective in any respect,
+ You (not any Contributor) assume the cost of any necessary servicing,
+ repair, or correction. This disclaimer of warranty constitutes an essential
+ part of this License. No use of any Covered Software is authorized under
+ this License except under this disclaimer.
+
+7. Limitation of Liability
+
+ Under no circumstances and under no legal theory, whether tort (including
+ negligence), contract, or otherwise, shall any Contributor, or anyone who
+ distributes Covered Software as permitted above, be liable to You for any
+ direct, indirect, special, incidental, or consequential damages of any
+ character including, without limitation, damages for lost profits, loss of
+ goodwill, work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses, even if such party shall have been
+ informed of the possibility of such damages. This limitation of liability
+ shall not apply to liability for death or personal injury resulting from
+ such party's negligence to the extent applicable law prohibits such
+ limitation. Some jurisdictions do not allow the exclusion or limitation of
+ incidental or consequential damages, so this exclusion and limitation may
+ not apply to You.
+
+8. Litigation
+
+ Any litigation relating to this License may be brought only in the courts
+ of a jurisdiction where the defendant maintains its principal place of
+ business and such litigation shall be governed by laws of that
+ jurisdiction, without reference to its conflict-of-law provisions. Nothing
+ in this Section shall prevent a party's ability to bring cross-claims or
+ counter-claims.
+
+9. Miscellaneous
+
+ This License represents the complete agreement concerning the subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. Any law or regulation which provides that
+ the language of a contract shall be construed against the drafter shall not
+ be used to construe this License against a Contributor.
+
+
+10. Versions of the License
+
+10.1. New Versions
+
+ Mozilla Foundation is the license steward. Except as provided in Section
+ 10.3, no one other than the license steward has the right to modify or
+ publish new versions of this License. Each version will be given a
+ distinguishing version number.
+
+10.2. Effect of New Versions
+
+ You may distribute the Covered Software under the terms of the version
+ of the License under which You originally received the Covered Software,
+ or under the terms of any subsequent version published by the license
+ steward.
+
+10.3. Modified Versions
+
+ If you create software not governed by this License, and you want to
+ create a new license for such software, you may create and use a
+ modified version of this License if you rename the license and remove
+ any references to the name of the license steward (except to note that
+ such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+ Licenses If You choose to distribute Source Code Form that is
+ Incompatible With Secondary Licenses under the terms of this version of
+ the License, the notice described in Exhibit B of this License must be
+ attached.
+
+Exhibit A - Source Code Form License Notice
+
+ This Source Code Form is subject to the
+ terms of the Mozilla Public License, v.
+ 2.0. If a copy of the MPL was not
+ distributed with this file, You can
+ obtain one at
+ http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular file,
+then You may include the notice in a location (such as a LICENSE file in a
+relevant directory) where a recipient would be likely to look for such a
+notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+
+ This Source Code Form is "Incompatible
+ With Secondary Licenses", as defined by
+ the Mozilla Public License, v. 2.0.
diff --git a/vendor/mdbook/README.md b/vendor/mdbook/README.md
new file mode 100644
index 000000000..b2177cf30
--- /dev/null
+++ b/vendor/mdbook/README.md
@@ -0,0 +1,20 @@
+# mdBook
+
+[![Build Status](https://github.com/rust-lang/mdBook/workflows/CI/badge.svg?event=push)](https://github.com/rust-lang/mdBook/actions?workflow=CI)
+[![crates.io](https://img.shields.io/crates/v/mdbook.svg)](https://crates.io/crates/mdbook)
+[![LICENSE](https://img.shields.io/github/license/rust-lang/mdBook.svg)](LICENSE)
+
+mdBook is a utility to create modern online books from Markdown files.
+
+Check out the **[User Guide]** for a list of features and installation and usage information.
+The User Guide also serves as a demonstration to showcase what a book looks like.
+
+If you are interested in contributing to the development of mdBook, check out the [Contribution Guide].
+
+## License
+
+All the code in this repository is released under the ***Mozilla Public License v2.0***, for more information take a look at the [LICENSE] file.
+
+[User Guide]: https://rust-lang.github.io/mdBook/
+[contribution guide]: https://github.com/rust-lang/mdBook/blob/master/CONTRIBUTING.md
+[LICENSE]: https://github.com/rust-lang/mdBook/blob/master/LICENSE
diff --git a/vendor/mdbook/ci/install-hub.sh b/vendor/mdbook/ci/install-hub.sh
new file mode 100755
index 000000000..38da2c8cb
--- /dev/null
+++ b/vendor/mdbook/ci/install-hub.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+# Installs the `hub` executable into hub/bin
+set -ex
+case $1 in
+ ubuntu*)
+ curl -LsSf https://github.com/github/hub/releases/download/v2.12.8/hub-linux-amd64-2.12.8.tgz -o hub.tgz
+ mkdir hub
+ tar -xzvf hub.tgz --strip=1 -C hub
+ ;;
+ macos*)
+ curl -LsSf https://github.com/github/hub/releases/download/v2.12.8/hub-darwin-amd64-2.12.8.tgz -o hub.tgz
+ mkdir hub
+ tar -xzvf hub.tgz --strip=1 -C hub
+ ;;
+ windows*)
+ curl -LsSf https://github.com/github/hub/releases/download/v2.12.8/hub-windows-amd64-2.12.8.zip -o hub.zip
+ 7z x hub.zip -ohub
+ ;;
+ *)
+ echo "OS should be first parameter, was: $1"
+ ;;
+esac
+
+echo "$PWD/hub/bin" >> $GITHUB_PATH
diff --git a/vendor/mdbook/ci/install-rust.sh b/vendor/mdbook/ci/install-rust.sh
new file mode 100755
index 000000000..c608c70d0
--- /dev/null
+++ b/vendor/mdbook/ci/install-rust.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+# Install/update rust.
+# The first argument should be the toolchain to install.
+
+set -ex
+if [ -z "$1" ]
+then
+ echo "First parameter must be toolchain to install."
+ exit 1
+fi
+TOOLCHAIN="$1"
+
+rustup set profile minimal
+rustup component remove --toolchain=$TOOLCHAIN rust-docs || echo "already removed"
+rustup update --no-self-update $TOOLCHAIN
+rustup default $TOOLCHAIN
+rustup -V
+rustc -Vv
+cargo -V
diff --git a/vendor/mdbook/ci/make-release.sh b/vendor/mdbook/ci/make-release.sh
new file mode 100755
index 000000000..761923bf9
--- /dev/null
+++ b/vendor/mdbook/ci/make-release.sh
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+# Builds the release and creates an archive and optionally deploys to GitHub.
+set -ex
+
+if [[ -z "$GITHUB_REF" ]]
+then
+ echo "GITHUB_REF must be set"
+ exit 1
+fi
+# Strip mdbook-refs/tags/ from the start of the ref.
+TAG=${GITHUB_REF#*/tags/}
+
+host=$(rustc -Vv | grep ^host: | sed -e "s/host: //g")
+export CARGO_PROFILE_RELEASE_LTO=true
+cargo build --bin mdbook --release
+cd target/release
+case $1 in
+ ubuntu*)
+ asset="mdbook-$TAG-$host.tar.gz"
+ tar czf ../../$asset mdbook
+ ;;
+ macos*)
+ asset="mdbook-$TAG-$host.tar.gz"
+ # There is a bug with BSD tar on macOS where the first 8MB of the file are
+ # sometimes all NUL bytes. See https://github.com/actions/cache/issues/403
+ # and https://github.com/rust-lang/cargo/issues/8603 for some more
+ # information. An alternative solution here is to install GNU tar, but
+ # flushing the disk cache seems to work, too.
+ sudo /usr/sbin/purge
+ tar czf ../../$asset mdbook
+ ;;
+ windows*)
+ asset="mdbook-$TAG-$host.zip"
+ 7z a ../../$asset mdbook.exe
+ ;;
+ *)
+ echo "OS should be first parameter, was: $1"
+ ;;
+esac
+cd ../..
+
+if [[ -z "$GITHUB_TOKEN" ]]
+then
+ echo "$GITHUB_TOKEN not set, skipping deploy."
+else
+ hub release edit -m "" --attach $asset $TAG
+fi
diff --git a/vendor/mdbook/examples/nop-preprocessor.rs b/vendor/mdbook/examples/nop-preprocessor.rs
new file mode 100644
index 000000000..ace400936
--- /dev/null
+++ b/vendor/mdbook/examples/nop-preprocessor.rs
@@ -0,0 +1,104 @@
+use crate::nop_lib::Nop;
+use clap::{App, Arg, ArgMatches};
+use mdbook::book::Book;
+use mdbook::errors::Error;
+use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
+use semver::{Version, VersionReq};
+use std::io;
+use std::process;
+
+pub fn make_app() -> App<'static> {
+ App::new("nop-preprocessor")
+ .about("A mdbook preprocessor which does precisely nothing")
+ .subcommand(
+ App::new("supports")
+ .arg(Arg::new("renderer").required(true))
+ .about("Check whether a renderer is supported by this preprocessor"),
+ )
+}
+
+fn main() {
+ let matches = make_app().get_matches();
+
+ // Users will want to construct their own preprocessor here
+ let preprocessor = Nop::new();
+
+ if let Some(sub_args) = matches.subcommand_matches("supports") {
+ handle_supports(&preprocessor, sub_args);
+ } else if let Err(e) = handle_preprocessing(&preprocessor) {
+ eprintln!("{}", e);
+ process::exit(1);
+ }
+}
+
+fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
+ let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
+
+ let book_version = Version::parse(&ctx.mdbook_version)?;
+ let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?;
+
+ if !version_req.matches(&book_version) {
+ eprintln!(
+ "Warning: The {} plugin was built against version {} of mdbook, \
+ but we're being called from version {}",
+ pre.name(),
+ mdbook::MDBOOK_VERSION,
+ ctx.mdbook_version
+ );
+ }
+
+ let processed_book = pre.run(&ctx, book)?;
+ serde_json::to_writer(io::stdout(), &processed_book)?;
+
+ Ok(())
+}
+
+fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! {
+ let renderer = sub_args.value_of("renderer").expect("Required argument");
+ let supported = pre.supports_renderer(renderer);
+
+ // Signal whether the renderer is supported by exiting with 1 or 0.
+ if supported {
+ process::exit(0);
+ } else {
+ process::exit(1);
+ }
+}
+
+/// The actual implementation of the `Nop` preprocessor. This would usually go
+/// in your main `lib.rs` file.
+mod nop_lib {
+ use super::*;
+
+ /// A no-op preprocessor.
+ pub struct Nop;
+
+ impl Nop {
+ pub fn new() -> Nop {
+ Nop
+ }
+ }
+
+ impl Preprocessor for Nop {
+ fn name(&self) -> &str {
+ "nop-preprocessor"
+ }
+
+ fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book, Error> {
+ // In testing we want to tell the preprocessor to blow up by setting a
+ // particular config value
+ if let Some(nop_cfg) = ctx.config.get_preprocessor(self.name()) {
+ if nop_cfg.contains_key("blow-up") {
+ anyhow::bail!("Boom!!1!");
+ }
+ }
+
+ // we *are* a no-op preprocessor after all
+ Ok(book)
+ }
+
+ fn supports_renderer(&self, renderer: &str) -> bool {
+ renderer != "not-supported"
+ }
+ }
+}
diff --git a/vendor/mdbook/release.toml b/vendor/mdbook/release.toml
new file mode 100644
index 000000000..2a24e041b
--- /dev/null
+++ b/vendor/mdbook/release.toml
@@ -0,0 +1,3 @@
+sign-commit = true
+push-remote = "origin"
+tag-prefix = "v"
diff --git a/vendor/mdbook/src/book/book.rs b/vendor/mdbook/src/book/book.rs
new file mode 100644
index 000000000..d28c22dad
--- /dev/null
+++ b/vendor/mdbook/src/book/book.rs
@@ -0,0 +1,640 @@
+use std::collections::VecDeque;
+use std::fmt::{self, Display, Formatter};
+use std::fs::{self, File};
+use std::io::{Read, Write};
+use std::path::{Path, PathBuf};
+
+use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
+use crate::config::BuildConfig;
+use crate::errors::*;
+use crate::utils::bracket_escape;
+
+use serde::{Deserialize, Serialize};
+
+/// Load a book into memory from its `src/` directory.
+pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book> {
+ let src_dir = src_dir.as_ref();
+ let summary_md = src_dir.join("SUMMARY.md");
+
+ let mut summary_content = String::new();
+ File::open(&summary_md)
+ .with_context(|| format!("Couldn't open SUMMARY.md in {:?} directory", src_dir))?
+ .read_to_string(&mut summary_content)?;
+
+ let summary = parse_summary(&summary_content)
+ .with_context(|| format!("Summary parsing failed for file={:?}", summary_md))?;
+
+ if cfg.create_missing {
+ create_missing(src_dir, &summary).with_context(|| "Unable to create missing chapters")?;
+ }
+
+ load_book_from_disk(&summary, src_dir)
+}
+
+fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
+ let mut items: Vec<_> = summary
+ .prefix_chapters
+ .iter()
+ .chain(summary.numbered_chapters.iter())
+ .chain(summary.suffix_chapters.iter())
+ .collect();
+
+ while !items.is_empty() {
+ let next = items.pop().expect("already checked");
+
+ if let SummaryItem::Link(ref link) = *next {
+ if let Some(ref location) = link.location {
+ let filename = src_dir.join(location);
+ if !filename.exists() {
+ if let Some(parent) = filename.parent() {
+ if !parent.exists() {
+ fs::create_dir_all(parent)?;
+ }
+ }
+ debug!("Creating missing file {}", filename.display());
+
+ let mut f = File::create(&filename).with_context(|| {
+ format!("Unable to create missing file: {}", filename.display())
+ })?;
+ writeln!(f, "# {}", bracket_escape(&link.name))?;
+ }
+ }
+
+ items.extend(&link.nested_items);
+ }
+ }
+
+ Ok(())
+}
+
+/// A dumb tree structure representing a book.
+///
+/// For the moment a book is just a collection of [`BookItems`] which are
+/// accessible by either iterating (immutably) over the book with [`iter()`], or
+/// recursively applying a closure to each section to mutate the chapters, using
+/// [`for_each_mut()`].
+///
+/// [`iter()`]: #method.iter
+/// [`for_each_mut()`]: #method.for_each_mut
+#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
+pub struct Book {
+ /// The sections in this book.
+ pub sections: Vec<BookItem>,
+ __non_exhaustive: (),
+}
+
+impl Book {
+ /// Create an empty book.
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ /// Get a depth-first iterator over the items in the book.
+ pub fn iter(&self) -> BookItems<'_> {
+ BookItems {
+ items: self.sections.iter().collect(),
+ }
+ }
+
+ /// Recursively apply a closure to each item in the book, allowing you to
+ /// mutate them.
+ ///
+ /// # Note
+ ///
+ /// Unlike the `iter()` method, this requires a closure instead of returning
+ /// an iterator. This is because using iterators can possibly allow you
+ /// to have iterator invalidation errors.
+ pub fn for_each_mut<F>(&mut self, mut func: F)
+ where
+ F: FnMut(&mut BookItem),
+ {
+ for_each_mut(&mut func, &mut self.sections);
+ }
+
+ /// Append a `BookItem` to the `Book`.
+ pub fn push_item<I: Into<BookItem>>(&mut self, item: I) -> &mut Self {
+ self.sections.push(item.into());
+ self
+ }
+}
+
+pub fn for_each_mut<'a, F, I>(func: &mut F, items: I)
+where
+ F: FnMut(&mut BookItem),
+ I: IntoIterator<Item = &'a mut BookItem>,
+{
+ for item in items {
+ if let BookItem::Chapter(ch) = item {
+ for_each_mut(func, &mut ch.sub_items);
+ }
+
+ func(item);
+ }
+}
+
+/// Enum representing any type of item which can be added to a book.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub enum BookItem {
+ /// A nested chapter.
+ Chapter(Chapter),
+ /// A section separator.
+ Separator,
+ /// A part title.
+ PartTitle(String),
+}
+
+impl From<Chapter> for BookItem {
+ fn from(other: Chapter) -> BookItem {
+ BookItem::Chapter(other)
+ }
+}
+
+/// The representation of a "chapter", usually mapping to a single file on
+/// disk however it may contain multiple sub-chapters.
+#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
+pub struct Chapter {
+ /// The chapter's name.
+ pub name: String,
+ /// The chapter's contents.
+ pub content: String,
+ /// The chapter's section number, if it has one.
+ pub number: Option<SectionNumber>,
+ /// Nested items.
+ pub sub_items: Vec<BookItem>,
+ /// The chapter's location, relative to the `SUMMARY.md` file.
+ pub path: Option<PathBuf>,
+ /// The chapter's source file, relative to the `SUMMARY.md` file.
+ pub source_path: Option<PathBuf>,
+ /// An ordered list of the names of each chapter above this one in the hierarchy.
+ pub parent_names: Vec<String>,
+}
+
+impl Chapter {
+ /// Create a new chapter with the provided content.
+ pub fn new<P: Into<PathBuf>>(
+ name: &str,
+ content: String,
+ p: P,
+ parent_names: Vec<String>,
+ ) -> Chapter {
+ let path: PathBuf = p.into();
+ Chapter {
+ name: name.to_string(),
+ content,
+ path: Some(path.clone()),
+ source_path: Some(path),
+ parent_names,
+ ..Default::default()
+ }
+ }
+
+ /// Create a new draft chapter that is not attached to a source markdown file (and thus
+ /// has no content).
+ pub fn new_draft(name: &str, parent_names: Vec<String>) -> Self {
+ Chapter {
+ name: name.to_string(),
+ content: String::new(),
+ path: None,
+ source_path: None,
+ parent_names,
+ ..Default::default()
+ }
+ }
+
+ /// Check if the chapter is a draft chapter, meaning it has no path to a source markdown file.
+ pub fn is_draft_chapter(&self) -> bool {
+ self.path.is_none()
+ }
+}
+
+/// Use the provided `Summary` to load a `Book` from disk.
+///
+/// You need to pass in the book's source directory because all the links in
+/// `SUMMARY.md` give the chapter locations relative to it.
+pub(crate) fn load_book_from_disk<P: AsRef<Path>>(summary: &Summary, src_dir: P) -> Result<Book> {
+ debug!("Loading the book from disk");
+ let src_dir = src_dir.as_ref();
+
+ let prefix = summary.prefix_chapters.iter();
+ let numbered = summary.numbered_chapters.iter();
+ let suffix = summary.suffix_chapters.iter();
+
+ let summary_items = prefix.chain(numbered).chain(suffix);
+
+ let mut chapters = Vec::new();
+
+ for summary_item in summary_items {
+ let chapter = load_summary_item(summary_item, src_dir, Vec::new())?;
+ chapters.push(chapter);
+ }
+
+ Ok(Book {
+ sections: chapters,
+ __non_exhaustive: (),
+ })
+}
+
+fn load_summary_item<P: AsRef<Path> + Clone>(
+ item: &SummaryItem,
+ src_dir: P,
+ parent_names: Vec<String>,
+) -> Result<BookItem> {
+ match item {
+ SummaryItem::Separator => Ok(BookItem::Separator),
+ SummaryItem::Link(ref link) => {
+ load_chapter(link, src_dir, parent_names).map(BookItem::Chapter)
+ }
+ SummaryItem::PartTitle(title) => Ok(BookItem::PartTitle(title.clone())),
+ }
+}
+
+fn load_chapter<P: AsRef<Path>>(
+ link: &Link,
+ src_dir: P,
+ parent_names: Vec<String>,
+) -> Result<Chapter> {
+ let src_dir = src_dir.as_ref();
+
+ let mut ch = if let Some(ref link_location) = link.location {
+ debug!("Loading {} ({})", link.name, link_location.display());
+
+ let location = if link_location.is_absolute() {
+ link_location.clone()
+ } else {
+ src_dir.join(link_location)
+ };
+
+ let mut f = File::open(&location)
+ .with_context(|| format!("Chapter file not found, {}", link_location.display()))?;
+
+ let mut content = String::new();
+ f.read_to_string(&mut content).with_context(|| {
+ format!("Unable to read \"{}\" ({})", link.name, location.display())
+ })?;
+
+ if content.as_bytes().starts_with(b"\xef\xbb\xbf") {
+ content.replace_range(..3, "");
+ }
+
+ let stripped = location
+ .strip_prefix(&src_dir)
+ .expect("Chapters are always inside a book");
+
+ Chapter::new(&link.name, content, stripped, parent_names.clone())
+ } else {
+ Chapter::new_draft(&link.name, parent_names.clone())
+ };
+
+ let mut sub_item_parents = parent_names;
+
+ ch.number = link.number.clone();
+
+ sub_item_parents.push(link.name.clone());
+ let sub_items = link
+ .nested_items
+ .iter()
+ .map(|i| load_summary_item(i, src_dir, sub_item_parents.clone()))
+ .collect::<Result<Vec<_>>>()?;
+
+ ch.sub_items = sub_items;
+
+ Ok(ch)
+}
+
+/// A depth-first iterator over the items in a book.
+///
+/// # Note
+///
+/// This struct shouldn't be created directly, instead prefer the
+/// [`Book::iter()`] method.
+pub struct BookItems<'a> {
+ items: VecDeque<&'a BookItem>,
+}
+
+impl<'a> Iterator for BookItems<'a> {
+ type Item = &'a BookItem;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let item = self.items.pop_front();
+
+ if let Some(&BookItem::Chapter(ref ch)) = item {
+ // if we wanted a breadth-first iterator we'd `extend()` here
+ for sub_item in ch.sub_items.iter().rev() {
+ self.items.push_front(sub_item);
+ }
+ }
+
+ item
+ }
+}
+
+impl Display for Chapter {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ if let Some(ref section_number) = self.number {
+ write!(f, "{} ", section_number)?;
+ }
+
+ write!(f, "{}", self.name)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::io::Write;
+ use tempfile::{Builder as TempFileBuilder, TempDir};
+
+ const DUMMY_SRC: &str = "
+# Dummy Chapter
+
+this is some dummy text.
+
+And here is some \
+ more text.
+";
+
+ /// Create a dummy `Link` in a temporary directory.
+ fn dummy_link() -> (Link, TempDir) {
+ let temp = TempFileBuilder::new().prefix("book").tempdir().unwrap();
+
+ let chapter_path = temp.path().join("chapter_1.md");
+ File::create(&chapter_path)
+ .unwrap()
+ .write_all(DUMMY_SRC.as_bytes())
+ .unwrap();
+
+ let link = Link::new("Chapter 1", chapter_path);
+
+ (link, temp)
+ }
+
+ /// Create a nested `Link` written to a temporary directory.
+ fn nested_links() -> (Link, TempDir) {
+ let (mut root, temp_dir) = dummy_link();
+
+ let second_path = temp_dir.path().join("second.md");
+
+ File::create(&second_path)
+ .unwrap()
+ .write_all(b"Hello World!")
+ .unwrap();
+
+ let mut second = Link::new("Nested Chapter 1", &second_path);
+ second.number = Some(SectionNumber(vec![1, 2]));
+
+ root.nested_items.push(second.clone().into());
+ root.nested_items.push(SummaryItem::Separator);
+ root.nested_items.push(second.into());
+
+ (root, temp_dir)
+ }
+
+ #[test]
+ fn load_a_single_chapter_from_disk() {
+ let (link, temp_dir) = dummy_link();
+ let should_be = Chapter::new(
+ "Chapter 1",
+ DUMMY_SRC.to_string(),
+ "chapter_1.md",
+ Vec::new(),
+ );
+
+ let got = load_chapter(&link, temp_dir.path(), Vec::new()).unwrap();
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn load_a_single_chapter_with_utf8_bom_from_disk() {
+ let temp_dir = TempFileBuilder::new().prefix("book").tempdir().unwrap();
+
+ let chapter_path = temp_dir.path().join("chapter_1.md");
+ File::create(&chapter_path)
+ .unwrap()
+ .write_all(("\u{feff}".to_owned() + DUMMY_SRC).as_bytes())
+ .unwrap();
+
+ let link = Link::new("Chapter 1", chapter_path);
+
+ let should_be = Chapter::new(
+ "Chapter 1",
+ DUMMY_SRC.to_string(),
+ "chapter_1.md",
+ Vec::new(),
+ );
+
+ let got = load_chapter(&link, temp_dir.path(), Vec::new()).unwrap();
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn cant_load_a_nonexistent_chapter() {
+ let link = Link::new("Chapter 1", "/foo/bar/baz.md");
+
+ let got = load_chapter(&link, "", Vec::new());
+ assert!(got.is_err());
+ }
+
+ #[test]
+ fn load_recursive_link_with_separators() {
+ let (root, temp) = nested_links();
+
+ let nested = Chapter {
+ name: String::from("Nested Chapter 1"),
+ content: String::from("Hello World!"),
+ number: Some(SectionNumber(vec![1, 2])),
+ path: Some(PathBuf::from("second.md")),
+ source_path: Some(PathBuf::from("second.md")),
+ parent_names: vec![String::from("Chapter 1")],
+ sub_items: Vec::new(),
+ };
+ let should_be = BookItem::Chapter(Chapter {
+ name: String::from("Chapter 1"),
+ content: String::from(DUMMY_SRC),
+ number: None,
+ path: Some(PathBuf::from("chapter_1.md")),
+ source_path: Some(PathBuf::from("chapter_1.md")),
+ parent_names: Vec::new(),
+ sub_items: vec![
+ BookItem::Chapter(nested.clone()),
+ BookItem::Separator,
+ BookItem::Chapter(nested),
+ ],
+ });
+
+ let got = load_summary_item(&SummaryItem::Link(root), temp.path(), Vec::new()).unwrap();
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn load_a_book_with_a_single_chapter() {
+ let (link, temp) = dummy_link();
+ let summary = Summary {
+ numbered_chapters: vec![SummaryItem::Link(link)],
+ ..Default::default()
+ };
+ let should_be = Book {
+ sections: vec![BookItem::Chapter(Chapter {
+ name: String::from("Chapter 1"),
+ content: String::from(DUMMY_SRC),
+ path: Some(PathBuf::from("chapter_1.md")),
+ source_path: Some(PathBuf::from("chapter_1.md")),
+ ..Default::default()
+ })],
+ ..Default::default()
+ };
+
+ let got = load_book_from_disk(&summary, temp.path()).unwrap();
+
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn book_iter_iterates_over_sequential_items() {
+ let book = Book {
+ sections: vec![
+ BookItem::Chapter(Chapter {
+ name: String::from("Chapter 1"),
+ content: String::from(DUMMY_SRC),
+ ..Default::default()
+ }),
+ BookItem::Separator,
+ ],
+ ..Default::default()
+ };
+
+ let should_be: Vec<_> = book.sections.iter().collect();
+
+ let got: Vec<_> = book.iter().collect();
+
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn iterate_over_nested_book_items() {
+ let book = Book {
+ sections: vec![
+ BookItem::Chapter(Chapter {
+ name: String::from("Chapter 1"),
+ content: String::from(DUMMY_SRC),
+ number: None,
+ path: Some(PathBuf::from("Chapter_1/index.md")),
+ source_path: Some(PathBuf::from("Chapter_1/index.md")),
+ parent_names: Vec::new(),
+ sub_items: vec![
+ BookItem::Chapter(Chapter::new(
+ "Hello World",
+ String::new(),
+ "Chapter_1/hello.md",
+ Vec::new(),
+ )),
+ BookItem::Separator,
+ BookItem::Chapter(Chapter::new(
+ "Goodbye World",
+ String::new(),
+ "Chapter_1/goodbye.md",
+ Vec::new(),
+ )),
+ ],
+ }),
+ BookItem::Separator,
+ ],
+ ..Default::default()
+ };
+
+ let got: Vec<_> = book.iter().collect();
+
+ assert_eq!(got.len(), 5);
+
+ // checking the chapter names are in the order should be sufficient here...
+ let chapter_names: Vec<String> = got
+ .into_iter()
+ .filter_map(|i| match *i {
+ BookItem::Chapter(ref ch) => Some(ch.name.clone()),
+ _ => None,
+ })
+ .collect();
+ let should_be: Vec<_> = vec![
+ String::from("Chapter 1"),
+ String::from("Hello World"),
+ String::from("Goodbye World"),
+ ];
+
+ assert_eq!(chapter_names, should_be);
+ }
+
+ #[test]
+ fn for_each_mut_visits_all_items() {
+ let mut book = Book {
+ sections: vec![
+ BookItem::Chapter(Chapter {
+ name: String::from("Chapter 1"),
+ content: String::from(DUMMY_SRC),
+ number: None,
+ path: Some(PathBuf::from("Chapter_1/index.md")),
+ source_path: Some(PathBuf::from("Chapter_1/index.md")),
+ parent_names: Vec::new(),
+ sub_items: vec![
+ BookItem::Chapter(Chapter::new(
+ "Hello World",
+ String::new(),
+ "Chapter_1/hello.md",
+ Vec::new(),
+ )),
+ BookItem::Separator,
+ BookItem::Chapter(Chapter::new(
+ "Goodbye World",
+ String::new(),
+ "Chapter_1/goodbye.md",
+ Vec::new(),
+ )),
+ ],
+ }),
+ BookItem::Separator,
+ ],
+ ..Default::default()
+ };
+
+ let num_items = book.iter().count();
+ let mut visited = 0;
+
+ book.for_each_mut(|_| visited += 1);
+
+ assert_eq!(visited, num_items);
+ }
+
+ #[test]
+ fn cant_load_chapters_with_an_empty_path() {
+ let (_, temp) = dummy_link();
+ let summary = Summary {
+ numbered_chapters: vec![SummaryItem::Link(Link {
+ name: String::from("Empty"),
+ location: Some(PathBuf::from("")),
+ ..Default::default()
+ })],
+
+ ..Default::default()
+ };
+
+ let got = load_book_from_disk(&summary, temp.path());
+ assert!(got.is_err());
+ }
+
+ #[test]
+ fn cant_load_chapters_when_the_link_is_a_directory() {
+ let (_, temp) = dummy_link();
+ let dir = temp.path().join("nested");
+ fs::create_dir(&dir).unwrap();
+
+ let summary = Summary {
+ numbered_chapters: vec![SummaryItem::Link(Link {
+ name: String::from("nested"),
+ location: Some(dir),
+ ..Default::default()
+ })],
+ ..Default::default()
+ };
+
+ let got = load_book_from_disk(&summary, temp.path());
+ assert!(got.is_err());
+ }
+}
diff --git a/vendor/mdbook/src/book/init.rs b/vendor/mdbook/src/book/init.rs
new file mode 100644
index 000000000..264c113d3
--- /dev/null
+++ b/vendor/mdbook/src/book/init.rs
@@ -0,0 +1,207 @@
+use std::fs::{self, File};
+use std::io::Write;
+use std::path::PathBuf;
+
+use super::MDBook;
+use crate::config::Config;
+use crate::errors::*;
+use crate::theme;
+
+/// A helper for setting up a new book and its directory structure.
+#[derive(Debug, Clone, PartialEq)]
+pub struct BookBuilder {
+ root: PathBuf,
+ create_gitignore: bool,
+ config: Config,
+ copy_theme: bool,
+}
+
+impl BookBuilder {
+ /// Create a new `BookBuilder` which will generate a book in the provided
+ /// root directory.
+ pub fn new<P: Into<PathBuf>>(root: P) -> BookBuilder {
+ BookBuilder {
+ root: root.into(),
+ create_gitignore: false,
+ config: Config::default(),
+ copy_theme: false,
+ }
+ }
+
+ /// Set the [`Config`] to be used.
+ pub fn with_config(&mut self, cfg: Config) -> &mut BookBuilder {
+ self.config = cfg;
+ self
+ }
+
+ /// Get the config used by the `BookBuilder`.
+ pub fn config(&self) -> &Config {
+ &self.config
+ }
+
+ /// Should the theme be copied into the generated book (so users can tweak
+ /// it)?
+ pub fn copy_theme(&mut self, copy: bool) -> &mut BookBuilder {
+ self.copy_theme = copy;
+ self
+ }
+
+ /// Should we create a `.gitignore` file?
+ pub fn create_gitignore(&mut self, create: bool) -> &mut BookBuilder {
+ self.create_gitignore = create;
+ self
+ }
+
+ /// Generate the actual book. This will:
+ ///
+ /// - Create the directory structure.
+ /// - Stub out some dummy chapters and the `SUMMARY.md`.
+ /// - Create a `.gitignore` (if applicable)
+ /// - Create a themes directory and populate it (if applicable)
+ /// - Generate a `book.toml` file,
+ /// - Then load the book so we can build it or run tests.
+ pub fn build(&self) -> Result<MDBook> {
+ info!("Creating a new book with stub content");
+
+ self.create_directory_structure()
+ .with_context(|| "Unable to create directory structure")?;
+
+ self.create_stub_files()
+ .with_context(|| "Unable to create stub files")?;
+
+ if self.create_gitignore {
+ self.build_gitignore()
+ .with_context(|| "Unable to create .gitignore")?;
+ }
+
+ if self.copy_theme {
+ self.copy_across_theme()
+ .with_context(|| "Unable to copy across the theme")?;
+ }
+
+ self.write_book_toml()?;
+
+ match MDBook::load(&self.root) {
+ Ok(book) => Ok(book),
+ Err(e) => {
+ error!("{}", e);
+
+ panic!(
+ "The BookBuilder should always create a valid book. If you are seeing this it \
+ is a bug and should be reported."
+ );
+ }
+ }
+ }
+
+ fn write_book_toml(&self) -> Result<()> {
+ debug!("Writing book.toml");
+ let book_toml = self.root.join("book.toml");
+ let cfg = toml::to_vec(&self.config).with_context(|| "Unable to serialize the config")?;
+
+ File::create(book_toml)
+ .with_context(|| "Couldn't create book.toml")?
+ .write_all(&cfg)
+ .with_context(|| "Unable to write config to book.toml")?;
+ Ok(())
+ }
+
+ fn copy_across_theme(&self) -> Result<()> {
+ debug!("Copying theme");
+
+ let html_config = self.config.html_config().unwrap_or_default();
+ let themedir = html_config.theme_dir(&self.root);
+
+ if !themedir.exists() {
+ debug!(
+ "{} does not exist, creating the directory",
+ themedir.display()
+ );
+ fs::create_dir(&themedir)?;
+ }
+
+ let mut index = File::create(themedir.join("index.hbs"))?;
+ index.write_all(theme::INDEX)?;
+
+ let cssdir = themedir.join("css");
+ if !cssdir.exists() {
+ fs::create_dir(&cssdir)?;
+ }
+
+ let mut general_css = File::create(cssdir.join("general.css"))?;
+ general_css.write_all(theme::GENERAL_CSS)?;
+
+ let mut chrome_css = File::create(cssdir.join("chrome.css"))?;
+ chrome_css.write_all(theme::CHROME_CSS)?;
+
+ if html_config.print.enable {
+ let mut print_css = File::create(cssdir.join("print.css"))?;
+ print_css.write_all(theme::PRINT_CSS)?;
+ }
+
+ let mut variables_css = File::create(cssdir.join("variables.css"))?;
+ variables_css.write_all(theme::VARIABLES_CSS)?;
+
+ let mut favicon = File::create(themedir.join("favicon.png"))?;
+ favicon.write_all(theme::FAVICON_PNG)?;
+
+ let mut favicon = File::create(themedir.join("favicon.svg"))?;
+ favicon.write_all(theme::FAVICON_SVG)?;
+
+ let mut js = File::create(themedir.join("book.js"))?;
+ js.write_all(theme::JS)?;
+
+ let mut highlight_css = File::create(themedir.join("highlight.css"))?;
+ highlight_css.write_all(theme::HIGHLIGHT_CSS)?;
+
+ let mut highlight_js = File::create(themedir.join("highlight.js"))?;
+ highlight_js.write_all(theme::HIGHLIGHT_JS)?;
+
+ Ok(())
+ }
+
+ fn build_gitignore(&self) -> Result<()> {
+ debug!("Creating .gitignore");
+
+ let mut f = File::create(self.root.join(".gitignore"))?;
+
+ writeln!(f, "{}", self.config.build.build_dir.display())?;
+
+ Ok(())
+ }
+
+ fn create_stub_files(&self) -> Result<()> {
+ debug!("Creating example book contents");
+ let src_dir = self.root.join(&self.config.book.src);
+
+ let summary = src_dir.join("SUMMARY.md");
+ if !summary.exists() {
+ trace!("No summary found creating stub summary and chapter_1.md.");
+ let mut f = File::create(&summary).with_context(|| "Unable to create SUMMARY.md")?;
+ writeln!(f, "# Summary")?;
+ writeln!(f)?;
+ writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
+
+ let chapter_1 = src_dir.join("chapter_1.md");
+ let mut f =
+ File::create(&chapter_1).with_context(|| "Unable to create chapter_1.md")?;
+ writeln!(f, "# Chapter 1")?;
+ } else {
+ trace!("Existing summary found, no need to create stub files.");
+ }
+ Ok(())
+ }
+
+ fn create_directory_structure(&self) -> Result<()> {
+ debug!("Creating directory tree");
+ fs::create_dir_all(&self.root)?;
+
+ let src = self.root.join(&self.config.book.src);
+ fs::create_dir_all(&src)?;
+
+ let build = self.root.join(&self.config.build.build_dir);
+ fs::create_dir_all(&build)?;
+
+ Ok(())
+ }
+}
diff --git a/vendor/mdbook/src/book/mod.rs b/vendor/mdbook/src/book/mod.rs
new file mode 100644
index 000000000..9745d2b7e
--- /dev/null
+++ b/vendor/mdbook/src/book/mod.rs
@@ -0,0 +1,836 @@
+//! The internal representation of a book and infrastructure for loading it from
+//! disk and building it.
+//!
+//! For examples on using `MDBook`, consult the [top-level documentation][1].
+//!
+//! [1]: ../index.html
+
+#[allow(clippy::module_inception)]
+mod book;
+mod init;
+mod summary;
+
+pub use self::book::{load_book, Book, BookItem, BookItems, Chapter};
+pub use self::init::BookBuilder;
+pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
+
+use std::io::Write;
+use std::path::PathBuf;
+use std::process::Command;
+use std::string::ToString;
+use tempfile::Builder as TempFileBuilder;
+use toml::Value;
+use topological_sort::TopologicalSort;
+
+use crate::errors::*;
+use crate::preprocess::{
+ CmdPreprocessor, IndexPreprocessor, LinkPreprocessor, Preprocessor, PreprocessorContext,
+};
+use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderContext, Renderer};
+use crate::utils;
+
+use crate::config::{Config, RustEdition};
+
+/// The object used to manage and build a book.
+pub struct MDBook {
+ /// The book's root directory.
+ pub root: PathBuf,
+ /// The configuration used to tweak now a book is built.
+ pub config: Config,
+ /// A representation of the book's contents in memory.
+ pub book: Book,
+ renderers: Vec<Box<dyn Renderer>>,
+
+ /// List of pre-processors to be run on the book.
+ preprocessors: Vec<Box<dyn Preprocessor>>,
+}
+
+impl MDBook {
+ /// Load a book from its root directory on disk.
+ pub fn load<P: Into<PathBuf>>(book_root: P) -> Result<MDBook> {
+ let book_root = book_root.into();
+ let config_location = book_root.join("book.toml");
+
+ // the book.json file is no longer used, so we should emit a warning to
+ // let people know to migrate to book.toml
+ if book_root.join("book.json").exists() {
+ warn!("It appears you are still using book.json for configuration.");
+ warn!("This format is no longer used, so you should migrate to the");
+ warn!("book.toml format.");
+ warn!("Check the user guide for migration information:");
+ warn!("\thttps://rust-lang.github.io/mdBook/format/config.html");
+ }
+
+ let mut config = if config_location.exists() {
+ debug!("Loading config from {}", config_location.display());
+ Config::from_disk(&config_location)?
+ } else {
+ Config::default()
+ };
+
+ config.update_from_env();
+
+ if config
+ .html_config()
+ .map_or(false, |html| html.google_analytics.is_some())
+ {
+ warn!(
+ "The output.html.google-analytics field has been deprecated; \
+ it will be removed in a future release.\n\
+ Consider placing the appropriate site tag code into the \
+ theme/head.hbs file instead.\n\
+ The tracking code may be found in the Google Analytics Admin page.\n\
+ "
+ );
+ }
+
+ if log_enabled!(log::Level::Trace) {
+ for line in format!("Config: {:#?}", config).lines() {
+ trace!("{}", line);
+ }
+ }
+
+ MDBook::load_with_config(book_root, config)
+ }
+
+ /// Load a book from its root directory using a custom `Config`.
+ pub fn load_with_config<P: Into<PathBuf>>(book_root: P, config: Config) -> Result<MDBook> {
+ let root = book_root.into();
+
+ let src_dir = root.join(&config.book.src);
+ let book = book::load_book(&src_dir, &config.build)?;
+
+ let renderers = determine_renderers(&config);
+ let preprocessors = determine_preprocessors(&config)?;
+
+ Ok(MDBook {
+ root,
+ config,
+ book,
+ renderers,
+ preprocessors,
+ })
+ }
+
+ /// Load a book from its root directory using a custom `Config` and a custom summary.
+ pub fn load_with_config_and_summary<P: Into<PathBuf>>(
+ book_root: P,
+ config: Config,
+ summary: Summary,
+ ) -> Result<MDBook> {
+ let root = book_root.into();
+
+ let src_dir = root.join(&config.book.src);
+ let book = book::load_book_from_disk(&summary, &src_dir)?;
+
+ let renderers = determine_renderers(&config);
+ let preprocessors = determine_preprocessors(&config)?;
+
+ Ok(MDBook {
+ root,
+ config,
+ book,
+ renderers,
+ preprocessors,
+ })
+ }
+
+ /// Returns a flat depth-first iterator over the elements of the book,
+ /// it returns a [`BookItem`] enum:
+ /// `(section: String, bookitem: &BookItem)`
+ ///
+ /// ```no_run
+ /// # use mdbook::MDBook;
+ /// # use mdbook::book::BookItem;
+ /// # let book = MDBook::load("mybook").unwrap();
+ /// for item in book.iter() {
+ /// match *item {
+ /// BookItem::Chapter(ref chapter) => {},
+ /// BookItem::Separator => {},
+ /// BookItem::PartTitle(ref title) => {}
+ /// }
+ /// }
+ ///
+ /// // would print something like this:
+ /// // 1. Chapter 1
+ /// // 1.1 Sub Chapter
+ /// // 1.2 Sub Chapter
+ /// // 2. Chapter 2
+ /// //
+ /// // etc.
+ /// ```
+ pub fn iter(&self) -> BookItems<'_> {
+ self.book.iter()
+ }
+
+ /// `init()` gives you a `BookBuilder` which you can use to setup a new book
+ /// and its accompanying directory structure.
+ ///
+ /// The `BookBuilder` creates some boilerplate files and directories to get
+ /// you started with your book.
+ ///
+ /// ```text
+ /// book-test/
+ /// ├── book
+ /// └── src
+ /// ├── chapter_1.md
+ /// └── SUMMARY.md
+ /// ```
+ ///
+ /// It uses the path provided as the root directory for your book, then adds
+ /// in a `src/` directory containing a `SUMMARY.md` and `chapter_1.md` file
+ /// to get you started.
+ pub fn init<P: Into<PathBuf>>(book_root: P) -> BookBuilder {
+ BookBuilder::new(book_root)
+ }
+
+ /// Tells the renderer to build our book and put it in the build directory.
+ pub fn build(&self) -> Result<()> {
+ info!("Book building has started");
+
+ for renderer in &self.renderers {
+ self.execute_build_process(&**renderer)?;
+ }
+
+ Ok(())
+ }
+
+ /// Run the entire build process for a particular [`Renderer`].
+ pub fn execute_build_process(&self, renderer: &dyn Renderer) -> Result<()> {
+ let mut preprocessed_book = self.book.clone();
+ let preprocess_ctx = PreprocessorContext::new(
+ self.root.clone(),
+ self.config.clone(),
+ renderer.name().to_string(),
+ );
+
+ for preprocessor in &self.preprocessors {
+ if preprocessor_should_run(&**preprocessor, renderer, &self.config) {
+ debug!("Running the {} preprocessor.", preprocessor.name());
+ preprocessed_book = preprocessor.run(&preprocess_ctx, preprocessed_book)?;
+ }
+ }
+
+ let name = renderer.name();
+ let build_dir = self.build_dir_for(name);
+
+ let mut render_context = RenderContext::new(
+ self.root.clone(),
+ preprocessed_book,
+ self.config.clone(),
+ build_dir,
+ );
+ render_context
+ .chapter_titles
+ .extend(preprocess_ctx.chapter_titles.borrow_mut().drain());
+
+ info!("Running the {} backend", renderer.name());
+ renderer
+ .render(&render_context)
+ .with_context(|| "Rendering failed")
+ }
+
+ /// You can change the default renderer to another one by using this method.
+ /// The only requirement is that your renderer implement the [`Renderer`]
+ /// trait.
+ pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self {
+ self.renderers.push(Box::new(renderer));
+ self
+ }
+
+ /// Register a [`Preprocessor`] to be used when rendering the book.
+ pub fn with_preprocessor<P: Preprocessor + 'static>(&mut self, preprocessor: P) -> &mut Self {
+ self.preprocessors.push(Box::new(preprocessor));
+ self
+ }
+
+ /// Run `rustdoc` tests on the book, linking against the provided libraries.
+ pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
+ let library_args: Vec<&str> = (0..library_paths.len())
+ .map(|_| "-L")
+ .zip(library_paths.into_iter())
+ .flat_map(|x| vec![x.0, x.1])
+ .collect();
+
+ let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
+
+ // FIXME: Is "test" the proper renderer name to use here?
+ let preprocess_context =
+ PreprocessorContext::new(self.root.clone(), self.config.clone(), "test".to_string());
+
+ let book = LinkPreprocessor::new().run(&preprocess_context, self.book.clone())?;
+ // Index Preprocessor is disabled so that chapter paths continue to point to the
+ // actual markdown files.
+
+ let mut failed = false;
+ for item in book.iter() {
+ if let BookItem::Chapter(ref ch) = *item {
+ let chapter_path = match ch.path {
+ Some(ref path) if !path.as_os_str().is_empty() => path,
+ _ => continue,
+ };
+
+ let path = self.source_dir().join(&chapter_path);
+ info!("Testing file: {:?}", path);
+
+ // write preprocessed file to tempdir
+ let path = temp_dir.path().join(&chapter_path);
+ let mut tmpf = utils::fs::create_file(&path)?;
+ tmpf.write_all(ch.content.as_bytes())?;
+
+ let mut cmd = Command::new("rustdoc");
+ cmd.arg(&path).arg("--test").args(&library_args);
+
+ if let Some(edition) = self.config.rust.edition {
+ match edition {
+ RustEdition::E2015 => {
+ cmd.args(&["--edition", "2015"]);
+ }
+ RustEdition::E2018 => {
+ cmd.args(&["--edition", "2018"]);
+ }
+ RustEdition::E2021 => {
+ cmd.args(&["--edition", "2021"]);
+ }
+ }
+ }
+
+ let output = cmd.output()?;
+
+ if !output.status.success() {
+ failed = true;
+ error!(
+ "rustdoc returned an error:\n\
+ \n--- stdout\n{}\n--- stderr\n{}",
+ String::from_utf8_lossy(&output.stdout),
+ String::from_utf8_lossy(&output.stderr)
+ );
+ }
+ }
+ }
+ if failed {
+ bail!("One or more tests failed");
+ }
+ Ok(())
+ }
+
+ /// The logic for determining where a backend should put its build
+ /// artefacts.
+ ///
+ /// If there is only 1 renderer, put it in the directory pointed to by the
+ /// `build.build_dir` key in [`Config`]. If there is more than one then the
+ /// renderer gets its own directory within the main build dir.
+ ///
+ /// i.e. If there were only one renderer (in this case, the HTML renderer):
+ ///
+ /// - build/
+ /// - index.html
+ /// - ...
+ ///
+ /// Otherwise if there are multiple:
+ ///
+ /// - build/
+ /// - epub/
+ /// - my_awesome_book.epub
+ /// - html/
+ /// - index.html
+ /// - ...
+ /// - latex/
+ /// - my_awesome_book.tex
+ ///
+ pub fn build_dir_for(&self, backend_name: &str) -> PathBuf {
+ let build_dir = self.root.join(&self.config.build.build_dir);
+
+ if self.renderers.len() <= 1 {
+ build_dir
+ } else {
+ build_dir.join(backend_name)
+ }
+ }
+
+ /// Get the directory containing this book's source files.
+ pub fn source_dir(&self) -> PathBuf {
+ self.root.join(&self.config.book.src)
+ }
+
+ /// Get the directory containing the theme resources for the book.
+ pub fn theme_dir(&self) -> PathBuf {
+ self.config
+ .html_config()
+ .unwrap_or_default()
+ .theme_dir(&self.root)
+ }
+}
+
+/// Look at the `Config` and try to figure out what renderers to use.
+fn determine_renderers(config: &Config) -> Vec<Box<dyn Renderer>> {
+ let mut renderers = Vec::new();
+
+ if let Some(output_table) = config.get("output").and_then(Value::as_table) {
+ renderers.extend(output_table.iter().map(|(key, table)| {
+ if key == "html" {
+ Box::new(HtmlHandlebars::new()) as Box<dyn Renderer>
+ } else if key == "markdown" {
+ Box::new(MarkdownRenderer::new()) as Box<dyn Renderer>
+ } else {
+ interpret_custom_renderer(key, table)
+ }
+ }));
+ }
+
+ // if we couldn't find anything, add the HTML renderer as a default
+ if renderers.is_empty() {
+ renderers.push(Box::new(HtmlHandlebars::new()));
+ }
+
+ renderers
+}
+
+const DEFAULT_PREPROCESSORS: &[&str] = &["links", "index"];
+
+fn is_default_preprocessor(pre: &dyn Preprocessor) -> bool {
+ let name = pre.name();
+ name == LinkPreprocessor::NAME || name == IndexPreprocessor::NAME
+}
+
+/// Look at the `MDBook` and try to figure out what preprocessors to run.
+fn determine_preprocessors(config: &Config) -> Result<Vec<Box<dyn Preprocessor>>> {
+ // Collect the names of all preprocessors intended to be run, and the order
+ // in which they should be run.
+ let mut preprocessor_names = TopologicalSort::<String>::new();
+
+ if config.build.use_default_preprocessors {
+ for name in DEFAULT_PREPROCESSORS {
+ preprocessor_names.insert(name.to_string());
+ }
+ }
+
+ if let Some(preprocessor_table) = config.get("preprocessor").and_then(Value::as_table) {
+ for (name, table) in preprocessor_table.iter() {
+ preprocessor_names.insert(name.to_string());
+
+ let exists = |name| {
+ (config.build.use_default_preprocessors && DEFAULT_PREPROCESSORS.contains(&name))
+ || preprocessor_table.contains_key(name)
+ };
+
+ if let Some(before) = table.get("before") {
+ let before = before.as_array().ok_or_else(|| {
+ Error::msg(format!(
+ "Expected preprocessor.{}.before to be an array",
+ name
+ ))
+ })?;
+ for after in before {
+ let after = after.as_str().ok_or_else(|| {
+ Error::msg(format!(
+ "Expected preprocessor.{}.before to contain strings",
+ name
+ ))
+ })?;
+
+ if !exists(after) {
+ // Only warn so that preprocessors can be toggled on and off (e.g. for
+ // troubleshooting) without having to worry about order too much.
+ warn!(
+ "preprocessor.{}.after contains \"{}\", which was not found",
+ name, after
+ );
+ } else {
+ preprocessor_names.add_dependency(name, after);
+ }
+ }
+ }
+
+ if let Some(after) = table.get("after") {
+ let after = after.as_array().ok_or_else(|| {
+ Error::msg(format!(
+ "Expected preprocessor.{}.after to be an array",
+ name
+ ))
+ })?;
+ for before in after {
+ let before = before.as_str().ok_or_else(|| {
+ Error::msg(format!(
+ "Expected preprocessor.{}.after to contain strings",
+ name
+ ))
+ })?;
+
+ if !exists(before) {
+ // See equivalent warning above for rationale
+ warn!(
+ "preprocessor.{}.before contains \"{}\", which was not found",
+ name, before
+ );
+ } else {
+ preprocessor_names.add_dependency(before, name);
+ }
+ }
+ }
+ }
+ }
+
+ // Now that all links have been established, queue preprocessors in a suitable order
+ let mut preprocessors = Vec::with_capacity(preprocessor_names.len());
+ // `pop_all()` returns an empty vector when no more items are not being depended upon
+ for mut names in std::iter::repeat_with(|| preprocessor_names.pop_all())
+ .take_while(|names| !names.is_empty())
+ {
+ // The `topological_sort` crate does not guarantee a stable order for ties, even across
+ // runs of the same program. Thus, we break ties manually by sorting.
+ // Careful: `str`'s default sorting, which we are implicitly invoking here, uses code point
+ // values ([1]), which may not be an alphabetical sort.
+ // As mentioned in [1], doing so depends on locale, which is not desirable for deciding
+ // preprocessor execution order.
+ // [1]: https://doc.rust-lang.org/stable/std/cmp/trait.Ord.html#impl-Ord-14
+ names.sort();
+ for name in names {
+ let preprocessor: Box<dyn Preprocessor> = match name.as_str() {
+ "links" => Box::new(LinkPreprocessor::new()),
+ "index" => Box::new(IndexPreprocessor::new()),
+ _ => {
+ // The only way to request a custom preprocessor is through the `preprocessor`
+ // table, so it must exist, be a table, and contain the key.
+ let table = &config.get("preprocessor").unwrap().as_table().unwrap()[&name];
+ let command = get_custom_preprocessor_cmd(&name, table);
+ Box::new(CmdPreprocessor::new(name, command))
+ }
+ };
+ preprocessors.push(preprocessor);
+ }
+ }
+
+ // "If `pop_all` returns an empty vector and `len` is not 0, there are cyclic dependencies."
+ // Normally, `len() == 0` is equivalent to `is_empty()`, so we'll use that.
+ if preprocessor_names.is_empty() {
+ Ok(preprocessors)
+ } else {
+ Err(Error::msg("Cyclic dependency detected in preprocessors"))
+ }
+}
+
+fn get_custom_preprocessor_cmd(key: &str, table: &Value) -> String {
+ table
+ .get("command")
+ .and_then(Value::as_str)
+ .map(ToString::to_string)
+ .unwrap_or_else(|| format!("mdbook-{}", key))
+}
+
+fn interpret_custom_renderer(key: &str, table: &Value) -> Box<CmdRenderer> {
+ // look for the `command` field, falling back to using the key
+ // prepended by "mdbook-"
+ let table_dot_command = table
+ .get("command")
+ .and_then(Value::as_str)
+ .map(ToString::to_string);
+
+ let command = table_dot_command.unwrap_or_else(|| format!("mdbook-{}", key));
+
+ Box::new(CmdRenderer::new(key.to_string(), command))
+}
+
+/// Check whether we should run a particular `Preprocessor` in combination
+/// with the renderer, falling back to `Preprocessor::supports_renderer()`
+/// method if the user doesn't say anything.
+///
+/// The `build.use-default-preprocessors` config option can be used to ensure
+/// default preprocessors always run if they support the renderer.
+fn preprocessor_should_run(
+ preprocessor: &dyn Preprocessor,
+ renderer: &dyn Renderer,
+ cfg: &Config,
+) -> bool {
+ // default preprocessors should be run by default (if supported)
+ if cfg.build.use_default_preprocessors && is_default_preprocessor(preprocessor) {
+ return preprocessor.supports_renderer(renderer.name());
+ }
+
+ let key = format!("preprocessor.{}.renderers", preprocessor.name());
+ let renderer_name = renderer.name();
+
+ if let Some(Value::Array(ref explicit_renderers)) = cfg.get(&key) {
+ return explicit_renderers
+ .iter()
+ .filter_map(Value::as_str)
+ .any(|name| name == renderer_name);
+ }
+
+ preprocessor.supports_renderer(renderer_name)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::str::FromStr;
+ use toml::value::{Table, Value};
+
+ #[test]
+ fn config_defaults_to_html_renderer_if_empty() {
+ let cfg = Config::default();
+
+ // make sure we haven't got anything in the `output` table
+ assert!(cfg.get("output").is_none());
+
+ let got = determine_renderers(&cfg);
+
+ assert_eq!(got.len(), 1);
+ assert_eq!(got[0].name(), "html");
+ }
+
+ #[test]
+ fn add_a_random_renderer_to_the_config() {
+ let mut cfg = Config::default();
+ cfg.set("output.random", Table::new()).unwrap();
+
+ let got = determine_renderers(&cfg);
+
+ assert_eq!(got.len(), 1);
+ assert_eq!(got[0].name(), "random");
+ }
+
+ #[test]
+ fn add_a_random_renderer_with_custom_command_to_the_config() {
+ let mut cfg = Config::default();
+
+ let mut table = Table::new();
+ table.insert("command".to_string(), Value::String("false".to_string()));
+ cfg.set("output.random", table).unwrap();
+
+ let got = determine_renderers(&cfg);
+
+ assert_eq!(got.len(), 1);
+ assert_eq!(got[0].name(), "random");
+ }
+
+ #[test]
+ fn config_defaults_to_link_and_index_preprocessor_if_not_set() {
+ let cfg = Config::default();
+
+ // make sure we haven't got anything in the `preprocessor` table
+ assert!(cfg.get("preprocessor").is_none());
+
+ let got = determine_preprocessors(&cfg);
+
+ assert!(got.is_ok());
+ assert_eq!(got.as_ref().unwrap().len(), 2);
+ assert_eq!(got.as_ref().unwrap()[0].name(), "index");
+ assert_eq!(got.as_ref().unwrap()[1].name(), "links");
+ }
+
+ #[test]
+ fn use_default_preprocessors_works() {
+ let mut cfg = Config::default();
+ cfg.build.use_default_preprocessors = false;
+
+ let got = determine_preprocessors(&cfg).unwrap();
+
+ assert_eq!(got.len(), 0);
+ }
+
+ #[test]
+ fn can_determine_third_party_preprocessors() {
+ let cfg_str = r#"
+ [book]
+ title = "Some Book"
+
+ [preprocessor.random]
+
+ [build]
+ build-dir = "outputs"
+ create-missing = false
+ "#;
+
+ let cfg = Config::from_str(cfg_str).unwrap();
+
+ // make sure the `preprocessor.random` table exists
+ assert!(cfg.get_preprocessor("random").is_some());
+
+ let got = determine_preprocessors(&cfg).unwrap();
+
+ assert!(got.into_iter().any(|p| p.name() == "random"));
+ }
+
+ #[test]
+ fn preprocessors_can_provide_their_own_commands() {
+ let cfg_str = r#"
+ [preprocessor.random]
+ command = "python random.py"
+ "#;
+
+ let cfg = Config::from_str(cfg_str).unwrap();
+
+ // make sure the `preprocessor.random` table exists
+ let random = cfg.get_preprocessor("random").unwrap();
+ let random = get_custom_preprocessor_cmd("random", &Value::Table(random.clone()));
+
+ assert_eq!(random, "python random.py");
+ }
+
+ #[test]
+ fn preprocessor_before_must_be_array() {
+ let cfg_str = r#"
+ [preprocessor.random]
+ before = 0
+ "#;
+
+ let cfg = Config::from_str(cfg_str).unwrap();
+
+ assert!(determine_preprocessors(&cfg).is_err());
+ }
+
+ #[test]
+ fn preprocessor_after_must_be_array() {
+ let cfg_str = r#"
+ [preprocessor.random]
+ after = 0
+ "#;
+
+ let cfg = Config::from_str(cfg_str).unwrap();
+
+ assert!(determine_preprocessors(&cfg).is_err());
+ }
+
+ #[test]
+ fn preprocessor_order_is_honored() {
+ let cfg_str = r#"
+ [preprocessor.random]
+ before = [ "last" ]
+ after = [ "index" ]
+
+ [preprocessor.last]
+ after = [ "links", "index" ]
+ "#;
+
+ let cfg = Config::from_str(cfg_str).unwrap();
+
+ let preprocessors = determine_preprocessors(&cfg).unwrap();
+ let index = |name| {
+ preprocessors
+ .iter()
+ .enumerate()
+ .find(|(_, preprocessor)| preprocessor.name() == name)
+ .unwrap()
+ .0
+ };
+ let assert_before = |before, after| {
+ if index(before) >= index(after) {
+ eprintln!("Preprocessor order:");
+ for preprocessor in &preprocessors {
+ eprintln!(" {}", preprocessor.name());
+ }
+ panic!("{} should come before {}", before, after);
+ }
+ };
+
+ assert_before("index", "random");
+ assert_before("index", "last");
+ assert_before("random", "last");
+ assert_before("links", "last");
+ }
+
+ #[test]
+ fn cyclic_dependencies_are_detected() {
+ let cfg_str = r#"
+ [preprocessor.links]
+ before = [ "index" ]
+
+ [preprocessor.index]
+ before = [ "links" ]
+ "#;
+
+ let cfg = Config::from_str(cfg_str).unwrap();
+
+ assert!(determine_preprocessors(&cfg).is_err());
+ }
+
+ #[test]
+ fn dependencies_dont_register_undefined_preprocessors() {
+ let cfg_str = r#"
+ [preprocessor.links]
+ before = [ "random" ]
+ "#;
+
+ let cfg = Config::from_str(cfg_str).unwrap();
+
+ let preprocessors = determine_preprocessors(&cfg).unwrap();
+
+ assert!(!preprocessors
+ .iter()
+ .any(|preprocessor| preprocessor.name() == "random"));
+ }
+
+ #[test]
+ fn dependencies_dont_register_builtin_preprocessors_if_disabled() {
+ let cfg_str = r#"
+ [preprocessor.random]
+ before = [ "links" ]
+
+ [build]
+ use-default-preprocessors = false
+ "#;
+
+ let cfg = Config::from_str(cfg_str).unwrap();
+
+ let preprocessors = determine_preprocessors(&cfg).unwrap();
+
+ assert!(!preprocessors
+ .iter()
+ .any(|preprocessor| preprocessor.name() == "links"));
+ }
+
+ #[test]
+ fn config_respects_preprocessor_selection() {
+ let cfg_str = r#"
+ [preprocessor.links]
+ renderers = ["html"]
+ "#;
+
+ let cfg = Config::from_str(cfg_str).unwrap();
+
+ // double-check that we can access preprocessor.links.renderers[0]
+ let html = cfg
+ .get_preprocessor("links")
+ .and_then(|links| links.get("renderers"))
+ .and_then(Value::as_array)
+ .and_then(|renderers| renderers.get(0))
+ .and_then(Value::as_str)
+ .unwrap();
+ assert_eq!(html, "html");
+ let html_renderer = HtmlHandlebars::default();
+ let pre = LinkPreprocessor::new();
+
+ let should_run = preprocessor_should_run(&pre, &html_renderer, &cfg);
+ assert!(should_run);
+ }
+
+ struct BoolPreprocessor(bool);
+ impl Preprocessor for BoolPreprocessor {
+ fn name(&self) -> &str {
+ "bool-preprocessor"
+ }
+
+ fn run(&self, _ctx: &PreprocessorContext, _book: Book) -> Result<Book> {
+ unimplemented!()
+ }
+
+ fn supports_renderer(&self, _renderer: &str) -> bool {
+ self.0
+ }
+ }
+
+ #[test]
+ fn preprocessor_should_run_falls_back_to_supports_renderer_method() {
+ let cfg = Config::default();
+ let html = HtmlHandlebars::new();
+
+ let should_be = true;
+ let got = preprocessor_should_run(&BoolPreprocessor(should_be), &html, &cfg);
+ assert_eq!(got, should_be);
+
+ let should_be = false;
+ let got = preprocessor_should_run(&BoolPreprocessor(should_be), &html, &cfg);
+ assert_eq!(got, should_be);
+ }
+}
diff --git a/vendor/mdbook/src/book/summary.rs b/vendor/mdbook/src/book/summary.rs
new file mode 100644
index 000000000..2bd81580f
--- /dev/null
+++ b/vendor/mdbook/src/book/summary.rs
@@ -0,0 +1,1097 @@
+use crate::errors::*;
+use memchr::{self, Memchr};
+use pulldown_cmark::{self, Event, HeadingLevel, Tag};
+use serde::{Deserialize, Serialize};
+use std::fmt::{self, Display, Formatter};
+use std::iter::FromIterator;
+use std::ops::{Deref, DerefMut};
+use std::path::{Path, PathBuf};
+
+/// Parse the text from a `SUMMARY.md` file into a sort of "recipe" to be
+/// used when loading a book from disk.
+///
+/// # Summary Format
+///
+/// **Title:** It's common practice to begin with a title, generally
+/// "# Summary". It's not mandatory and the parser (currently) ignores it, so
+/// you can too if you feel like it.
+///
+/// **Prefix Chapter:** Before the main numbered chapters you can add a couple
+/// of elements that will not be numbered. This is useful for forewords,
+/// introductions, etc. There are however some constraints. You can not nest
+/// prefix chapters, they should all be on the root level. And you can not add
+/// prefix chapters once you have added numbered chapters.
+///
+/// ```markdown
+/// [Title of prefix element](relative/path/to/markdown.md)
+/// ```
+///
+/// **Part Title:** An optional title for the next collect of numbered chapters. The numbered
+/// chapters can be broken into as many parts as desired.
+///
+/// **Numbered Chapter:** Numbered chapters are the main content of the book,
+/// they
+/// will be numbered and can be nested, resulting in a nice hierarchy (chapters,
+/// sub-chapters, etc.)
+///
+/// ```markdown
+/// # Title of Part
+///
+/// - [Title of the Chapter](relative/path/to/markdown.md)
+/// ```
+///
+/// You can either use - or * to indicate a numbered chapter, the parser doesn't
+/// care but you'll probably want to stay consistent.
+///
+/// **Suffix Chapter:** After the numbered chapters you can add a couple of
+/// non-numbered chapters. They are the same as prefix chapters but come after
+/// the numbered chapters instead of before.
+///
+/// All other elements are unsupported and will be ignored at best or result in
+/// an error.
+pub fn parse_summary(summary: &str) -> Result<Summary> {
+ let parser = SummaryParser::new(summary);
+ parser.parse()
+}
+
+/// The parsed `SUMMARY.md`, specifying how the book should be laid out.
+#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
+pub struct Summary {
+ /// An optional title for the `SUMMARY.md`, currently just ignored.
+ pub title: Option<String>,
+ /// Chapters before the main text (e.g. an introduction).
+ pub prefix_chapters: Vec<SummaryItem>,
+ /// The main numbered chapters of the book, broken into one or more possibly named parts.
+ pub numbered_chapters: Vec<SummaryItem>,
+ /// Items which come after the main document (e.g. a conclusion).
+ pub suffix_chapters: Vec<SummaryItem>,
+}
+
+/// A struct representing an entry in the `SUMMARY.md`, possibly with nested
+/// entries.
+///
+/// This is roughly the equivalent of `[Some section](./path/to/file.md)`.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Link {
+ /// The name of the chapter.
+ pub name: String,
+ /// The location of the chapter's source file, taking the book's `src`
+ /// directory as the root.
+ pub location: Option<PathBuf>,
+ /// The section number, if this chapter is in the numbered section.
+ pub number: Option<SectionNumber>,
+ /// Any nested items this chapter may contain.
+ pub nested_items: Vec<SummaryItem>,
+}
+
+impl Link {
+ /// Create a new link with no nested items.
+ pub fn new<S: Into<String>, P: AsRef<Path>>(name: S, location: P) -> Link {
+ Link {
+ name: name.into(),
+ location: Some(location.as_ref().to_path_buf()),
+ number: None,
+ nested_items: Vec::new(),
+ }
+ }
+}
+
+impl Default for Link {
+ fn default() -> Self {
+ Link {
+ name: String::new(),
+ location: Some(PathBuf::new()),
+ number: None,
+ nested_items: Vec::new(),
+ }
+ }
+}
+
+/// An item in `SUMMARY.md` which could be either a separator or a `Link`.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub enum SummaryItem {
+ /// A link to a chapter.
+ Link(Link),
+ /// A separator (`---`).
+ Separator,
+ /// A part title.
+ PartTitle(String),
+}
+
+impl SummaryItem {
+ fn maybe_link_mut(&mut self) -> Option<&mut Link> {
+ match *self {
+ SummaryItem::Link(ref mut l) => Some(l),
+ _ => None,
+ }
+ }
+}
+
+impl From<Link> for SummaryItem {
+ fn from(other: Link) -> SummaryItem {
+ SummaryItem::Link(other)
+ }
+}
+
+/// A recursive descent (-ish) parser for a `SUMMARY.md`.
+///
+///
+/// # Grammar
+///
+/// The `SUMMARY.md` file has a grammar which looks something like this:
+///
+/// ```text
+/// summary ::= title prefix_chapters numbered_chapters
+/// suffix_chapters
+/// title ::= "# " TEXT
+/// | EPSILON
+/// prefix_chapters ::= item*
+/// suffix_chapters ::= item*
+/// numbered_chapters ::= part+
+/// part ::= title dotted_item+
+/// dotted_item ::= INDENT* DOT_POINT item
+/// item ::= link
+/// | separator
+/// separator ::= "---"
+/// link ::= "[" TEXT "]" "(" TEXT ")"
+/// DOT_POINT ::= "-"
+/// | "*"
+/// ```
+///
+/// > **Note:** the `TEXT` terminal is "normal" text, and should (roughly)
+/// > match the following regex: "[^<>\n[]]+".
+struct SummaryParser<'a> {
+ src: &'a str,
+ stream: pulldown_cmark::OffsetIter<'a, 'a>,
+ offset: usize,
+
+ /// We can't actually put an event back into the `OffsetIter` stream, so instead we store it
+ /// here until somebody calls `next_event` again.
+ back: Option<Event<'a>>,
+}
+
+/// Reads `Events` from the provided stream until the corresponding
+/// `Event::End` is encountered which matches the `$delimiter` pattern.
+///
+/// This is the equivalent of doing
+/// `$stream.take_while(|e| e != $delimiter).collect()` but it allows you to
+/// use pattern matching and you won't get errors because `take_while()`
+/// moves `$stream` out of self.
+macro_rules! collect_events {
+ ($stream:expr,start $delimiter:pat) => {
+ collect_events!($stream, Event::Start($delimiter))
+ };
+ ($stream:expr,end $delimiter:pat) => {
+ collect_events!($stream, Event::End($delimiter))
+ };
+ ($stream:expr, $delimiter:pat) => {{
+ let mut events = Vec::new();
+
+ loop {
+ let event = $stream.next().map(|(ev, _range)| ev);
+ trace!("Next event: {:?}", event);
+
+ match event {
+ Some($delimiter) => break,
+ Some(other) => events.push(other),
+ None => {
+ debug!(
+ "Reached end of stream without finding the closing pattern, {}",
+ stringify!($delimiter)
+ );
+ break;
+ }
+ }
+ }
+
+ events
+ }};
+}
+
+impl<'a> SummaryParser<'a> {
+ fn new(text: &str) -> SummaryParser<'_> {
+ let pulldown_parser = pulldown_cmark::Parser::new(text).into_offset_iter();
+
+ SummaryParser {
+ src: text,
+ stream: pulldown_parser,
+ offset: 0,
+ back: None,
+ }
+ }
+
+ /// Get the current line and column to give the user more useful error
+ /// messages.
+ fn current_location(&self) -> (usize, usize) {
+ let previous_text = self.src[..self.offset].as_bytes();
+ let line = Memchr::new(b'\n', previous_text).count() + 1;
+ let start_of_line = memchr::memrchr(b'\n', previous_text).unwrap_or(0);
+ let col = self.src[start_of_line..self.offset].chars().count();
+
+ (line, col)
+ }
+
+ /// Parse the text the `SummaryParser` was created with.
+ fn parse(mut self) -> Result<Summary> {
+ let title = self.parse_title();
+
+ let prefix_chapters = self
+ .parse_affix(true)
+ .with_context(|| "There was an error parsing the prefix chapters")?;
+ let numbered_chapters = self
+ .parse_parts()
+ .with_context(|| "There was an error parsing the numbered chapters")?;
+ let suffix_chapters = self
+ .parse_affix(false)
+ .with_context(|| "There was an error parsing the suffix chapters")?;
+
+ Ok(Summary {
+ title,
+ prefix_chapters,
+ numbered_chapters,
+ suffix_chapters,
+ })
+ }
+
+ /// Parse the affix chapters.
+ fn parse_affix(&mut self, is_prefix: bool) -> Result<Vec<SummaryItem>> {
+ let mut items = Vec::new();
+ debug!(
+ "Parsing {} items",
+ if is_prefix { "prefix" } else { "suffix" }
+ );
+
+ loop {
+ match self.next_event() {
+ Some(ev @ Event::Start(Tag::List(..)))
+ | Some(ev @ Event::Start(Tag::Heading(HeadingLevel::H1, ..))) => {
+ if is_prefix {
+ // we've finished prefix chapters and are at the start
+ // of the numbered section.
+ self.back(ev);
+ break;
+ } else {
+ bail!(self.parse_error("Suffix chapters cannot be followed by a list"));
+ }
+ }
+ Some(Event::Start(Tag::Link(_type, href, _title))) => {
+ let link = self.parse_link(href.to_string());
+ items.push(SummaryItem::Link(link));
+ }
+ Some(Event::Rule) => items.push(SummaryItem::Separator),
+ Some(_) => {}
+ None => break,
+ }
+ }
+
+ Ok(items)
+ }
+
+ fn parse_parts(&mut self) -> Result<Vec<SummaryItem>> {
+ let mut parts = vec![];
+
+ // We want the section numbers to be continues through all parts.
+ let mut root_number = SectionNumber::default();
+ let mut root_items = 0;
+
+ loop {
+ // Possibly match a title or the end of the "numbered chapters part".
+ let title = match self.next_event() {
+ Some(ev @ Event::Start(Tag::Paragraph)) => {
+ // we're starting the suffix chapters
+ self.back(ev);
+ break;
+ }
+
+ Some(Event::Start(Tag::Heading(HeadingLevel::H1, ..))) => {
+ debug!("Found a h1 in the SUMMARY");
+
+ let tags = collect_events!(self.stream, end Tag::Heading(HeadingLevel::H1, ..));
+ Some(stringify_events(tags))
+ }
+
+ Some(ev) => {
+ self.back(ev);
+ None
+ }
+
+ None => break, // EOF, bail...
+ };
+
+ // Parse the rest of the part.
+ let numbered_chapters = self
+ .parse_numbered(&mut root_items, &mut root_number)
+ .with_context(|| "There was an error parsing the numbered chapters")?;
+
+ if let Some(title) = title {
+ parts.push(SummaryItem::PartTitle(title));
+ }
+ parts.extend(numbered_chapters);
+ }
+
+ Ok(parts)
+ }
+
+ /// Finishes parsing a link once the `Event::Start(Tag::Link(..))` has been opened.
+ fn parse_link(&mut self, href: String) -> Link {
+ let href = href.replace("%20", " ");
+ let link_content = collect_events!(self.stream, end Tag::Link(..));
+ let name = stringify_events(link_content);
+
+ let path = if href.is_empty() {
+ None
+ } else {
+ Some(PathBuf::from(href))
+ };
+
+ Link {
+ name,
+ location: path,
+ number: None,
+ nested_items: Vec::new(),
+ }
+ }
+
+ /// Parse the numbered chapters.
+ fn parse_numbered(
+ &mut self,
+ root_items: &mut u32,
+ root_number: &mut SectionNumber,
+ ) -> Result<Vec<SummaryItem>> {
+ let mut items = Vec::new();
+
+ // For the first iteration, we want to just skip any opening paragraph tags, as that just
+ // marks the start of the list. But after that, another opening paragraph indicates that we
+ // have started a new part or the suffix chapters.
+ let mut first = true;
+
+ loop {
+ match self.next_event() {
+ Some(ev @ Event::Start(Tag::Paragraph)) => {
+ if !first {
+ // we're starting the suffix chapters
+ self.back(ev);
+ break;
+ }
+ }
+ // The expectation is that pulldown cmark will terminate a paragraph before a new
+ // heading, so we can always count on this to return without skipping headings.
+ Some(ev @ Event::Start(Tag::Heading(HeadingLevel::H1, ..))) => {
+ // we're starting a new part
+ self.back(ev);
+ break;
+ }
+ Some(ev @ Event::Start(Tag::List(..))) => {
+ self.back(ev);
+ let mut bunch_of_items = self.parse_nested_numbered(root_number)?;
+
+ // if we've resumed after something like a rule the root sections
+ // will be numbered from 1. We need to manually go back and update
+ // them
+ update_section_numbers(&mut bunch_of_items, 0, *root_items);
+ *root_items += bunch_of_items.len() as u32;
+ items.extend(bunch_of_items);
+ }
+ Some(Event::Start(other_tag)) => {
+ trace!("Skipping contents of {:?}", other_tag);
+
+ // Skip over the contents of this tag
+ while let Some(event) = self.next_event() {
+ if event == Event::End(other_tag.clone()) {
+ break;
+ }
+ }
+ }
+ Some(Event::Rule) => {
+ items.push(SummaryItem::Separator);
+ }
+
+ // something else... ignore
+ Some(_) => {}
+
+ // EOF, bail...
+ None => {
+ break;
+ }
+ }
+
+ // From now on, we cannot accept any new paragraph opening tags.
+ first = false;
+ }
+
+ Ok(items)
+ }
+
+ /// Push an event back to the tail of the stream.
+ fn back(&mut self, ev: Event<'a>) {
+ assert!(self.back.is_none());
+ trace!("Back: {:?}", ev);
+ self.back = Some(ev);
+ }
+
+ fn next_event(&mut self) -> Option<Event<'a>> {
+ let next = self.back.take().or_else(|| {
+ self.stream.next().map(|(ev, range)| {
+ self.offset = range.start;
+ ev
+ })
+ });
+
+ trace!("Next event: {:?}", next);
+
+ next
+ }
+
+ fn parse_nested_numbered(&mut self, parent: &SectionNumber) -> Result<Vec<SummaryItem>> {
+ debug!("Parsing numbered chapters at level {}", parent);
+ let mut items = Vec::new();
+
+ loop {
+ match self.next_event() {
+ Some(Event::Start(Tag::Item)) => {
+ let item = self.parse_nested_item(parent, items.len())?;
+ items.push(item);
+ }
+ Some(Event::Start(Tag::List(..))) => {
+ // Skip this tag after comment bacause it is not nested.
+ if items.is_empty() {
+ continue;
+ }
+ // recurse to parse the nested list
+ let (_, last_item) = get_last_link(&mut items)?;
+ let last_item_number = last_item
+ .number
+ .as_ref()
+ .expect("All numbered chapters have numbers");
+
+ let sub_items = self.parse_nested_numbered(last_item_number)?;
+
+ last_item.nested_items = sub_items;
+ }
+ Some(Event::End(Tag::List(..))) => break,
+ Some(_) => {}
+ None => break,
+ }
+ }
+
+ Ok(items)
+ }
+
+ fn parse_nested_item(
+ &mut self,
+ parent: &SectionNumber,
+ num_existing_items: usize,
+ ) -> Result<SummaryItem> {
+ loop {
+ match self.next_event() {
+ Some(Event::Start(Tag::Paragraph)) => continue,
+ Some(Event::Start(Tag::Link(_type, href, _title))) => {
+ let mut link = self.parse_link(href.to_string());
+
+ let mut number = parent.clone();
+ number.0.push(num_existing_items as u32 + 1);
+ trace!(
+ "Found chapter: {} {} ({})",
+ number,
+ link.name,
+ link.location
+ .as_ref()
+ .map(|p| p.to_str().unwrap_or(""))
+ .unwrap_or("[draft]")
+ );
+
+ link.number = Some(number);
+
+ return Ok(SummaryItem::Link(link));
+ }
+ other => {
+ warn!("Expected a start of a link, actually got {:?}", other);
+ bail!(self.parse_error(
+ "The link items for nested chapters must only contain a hyperlink"
+ ));
+ }
+ }
+ }
+ }
+
+ fn parse_error<D: Display>(&self, msg: D) -> Error {
+ let (line, col) = self.current_location();
+ anyhow::anyhow!(
+ "failed to parse SUMMARY.md line {}, column {}: {}",
+ line,
+ col,
+ msg
+ )
+ }
+
+ /// Try to parse the title line.
+ fn parse_title(&mut self) -> Option<String> {
+ loop {
+ match self.next_event() {
+ Some(Event::Start(Tag::Heading(HeadingLevel::H1, ..))) => {
+ debug!("Found a h1 in the SUMMARY");
+
+ let tags = collect_events!(self.stream, end Tag::Heading(HeadingLevel::H1, ..));
+ return Some(stringify_events(tags));
+ }
+ // Skip a HTML element such as a comment line.
+ Some(Event::Html(_)) => {}
+ // Otherwise, no title.
+ Some(ev) => {
+ self.back(ev);
+ return None;
+ }
+ _ => return None,
+ }
+ }
+ }
+}
+
+fn update_section_numbers(sections: &mut [SummaryItem], level: usize, by: u32) {
+ for section in sections {
+ if let SummaryItem::Link(ref mut link) = *section {
+ if let Some(ref mut number) = link.number {
+ number.0[level] += by;
+ }
+
+ update_section_numbers(&mut link.nested_items, level, by);
+ }
+ }
+}
+
+/// Gets a pointer to the last `Link` in a list of `SummaryItem`s, and its
+/// index.
+fn get_last_link(links: &mut [SummaryItem]) -> Result<(usize, &mut Link)> {
+ links
+ .iter_mut()
+ .enumerate()
+ .filter_map(|(i, item)| item.maybe_link_mut().map(|l| (i, l)))
+ .rev()
+ .next()
+ .ok_or_else(||
+ anyhow::anyhow!("Unable to get last link because the list of SummaryItems doesn't contain any Links")
+ )
+}
+
+/// Removes the styling from a list of Markdown events and returns just the
+/// plain text.
+fn stringify_events(events: Vec<Event<'_>>) -> String {
+ events
+ .into_iter()
+ .filter_map(|t| match t {
+ Event::Text(text) | Event::Code(text) => Some(text.into_string()),
+ Event::SoftBreak => Some(String::from(" ")),
+ _ => None,
+ })
+ .collect()
+}
+
+/// A section number like "1.2.3", basically just a newtype'd `Vec<u32>` with
+/// a pretty `Display` impl.
+#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
+pub struct SectionNumber(pub Vec<u32>);
+
+impl Display for SectionNumber {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ if self.0.is_empty() {
+ write!(f, "0")
+ } else {
+ for item in &self.0 {
+ write!(f, "{}.", item)?;
+ }
+ Ok(())
+ }
+ }
+}
+
+impl Deref for SectionNumber {
+ type Target = Vec<u32>;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for SectionNumber {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+impl FromIterator<u32> for SectionNumber {
+ fn from_iter<I: IntoIterator<Item = u32>>(it: I) -> Self {
+ SectionNumber(it.into_iter().collect())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn section_number_has_correct_dotted_representation() {
+ let inputs = vec![
+ (vec![0], "0."),
+ (vec![1, 3], "1.3."),
+ (vec![1, 2, 3], "1.2.3."),
+ ];
+
+ for (input, should_be) in inputs {
+ let section_number = SectionNumber(input).to_string();
+ assert_eq!(section_number, should_be);
+ }
+ }
+
+ #[test]
+ fn parse_initial_title() {
+ let src = "# Summary";
+ let should_be = String::from("Summary");
+
+ let mut parser = SummaryParser::new(src);
+ let got = parser.parse_title().unwrap();
+
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn no_initial_title() {
+ let src = "[Link]()";
+ let mut parser = SummaryParser::new(src);
+
+ assert!(parser.parse_title().is_none());
+ assert!(matches!(
+ parser.next_event(),
+ Some(Event::Start(Tag::Paragraph))
+ ));
+ }
+
+ #[test]
+ fn parse_title_with_styling() {
+ let src = "# My **Awesome** Summary";
+ let should_be = String::from("My Awesome Summary");
+
+ let mut parser = SummaryParser::new(src);
+ let got = parser.parse_title().unwrap();
+
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn convert_markdown_events_to_a_string() {
+ let src = "Hello *World*, `this` is some text [and a link](./path/to/link)";
+ let should_be = "Hello World, this is some text and a link";
+
+ let events = pulldown_cmark::Parser::new(src).collect();
+ let got = stringify_events(events);
+
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn parse_some_prefix_items() {
+ let src = "[First](./first.md)\n[Second](./second.md)\n";
+ let mut parser = SummaryParser::new(src);
+
+ let should_be = vec![
+ SummaryItem::Link(Link {
+ name: String::from("First"),
+ location: Some(PathBuf::from("./first.md")),
+ ..Default::default()
+ }),
+ SummaryItem::Link(Link {
+ name: String::from("Second"),
+ location: Some(PathBuf::from("./second.md")),
+ ..Default::default()
+ }),
+ ];
+
+ let got = parser.parse_affix(true).unwrap();
+
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn parse_prefix_items_with_a_separator() {
+ let src = "[First](./first.md)\n\n---\n\n[Second](./second.md)\n";
+ let mut parser = SummaryParser::new(src);
+
+ let got = parser.parse_affix(true).unwrap();
+
+ assert_eq!(got.len(), 3);
+ assert_eq!(got[1], SummaryItem::Separator);
+ }
+
+ #[test]
+ fn suffix_items_cannot_be_followed_by_a_list() {
+ let src = "[First](./first.md)\n- [Second](./second.md)\n";
+ let mut parser = SummaryParser::new(src);
+
+ let got = parser.parse_affix(false);
+
+ assert!(got.is_err());
+ }
+
+ #[test]
+ fn parse_a_link() {
+ let src = "[First](./first.md)";
+ let should_be = Link {
+ name: String::from("First"),
+ location: Some(PathBuf::from("./first.md")),
+ ..Default::default()
+ };
+
+ let mut parser = SummaryParser::new(src);
+ let _ = parser.stream.next(); // Discard opening paragraph
+
+ let href = match parser.stream.next() {
+ Some((Event::Start(Tag::Link(_type, href, _title)), _range)) => href.to_string(),
+ other => panic!("Unreachable, {:?}", other),
+ };
+
+ let got = parser.parse_link(href);
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn parse_a_numbered_chapter() {
+ let src = "- [First](./first.md)\n";
+ let link = Link {
+ name: String::from("First"),
+ location: Some(PathBuf::from("./first.md")),
+ number: Some(SectionNumber(vec![1])),
+ ..Default::default()
+ };
+ let should_be = vec![SummaryItem::Link(link)];
+
+ let mut parser = SummaryParser::new(src);
+ let got = parser
+ .parse_numbered(&mut 0, &mut SectionNumber::default())
+ .unwrap();
+
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn parse_nested_numbered_chapters() {
+ let src = "- [First](./first.md)\n - [Nested](./nested.md)\n- [Second](./second.md)";
+
+ let should_be = vec![
+ SummaryItem::Link(Link {
+ name: String::from("First"),
+ location: Some(PathBuf::from("./first.md")),
+ number: Some(SectionNumber(vec![1])),
+ nested_items: vec![SummaryItem::Link(Link {
+ name: String::from("Nested"),
+ location: Some(PathBuf::from("./nested.md")),
+ number: Some(SectionNumber(vec![1, 1])),
+ nested_items: Vec::new(),
+ })],
+ }),
+ SummaryItem::Link(Link {
+ name: String::from("Second"),
+ location: Some(PathBuf::from("./second.md")),
+ number: Some(SectionNumber(vec![2])),
+ nested_items: Vec::new(),
+ }),
+ ];
+
+ let mut parser = SummaryParser::new(src);
+ let got = parser
+ .parse_numbered(&mut 0, &mut SectionNumber::default())
+ .unwrap();
+
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn parse_numbered_chapters_separated_by_comment() {
+ let src = "- [First](./first.md)\n<!-- this is a comment -->\n- [Second](./second.md)";
+
+ let should_be = vec![
+ SummaryItem::Link(Link {
+ name: String::from("First"),
+ location: Some(PathBuf::from("./first.md")),
+ number: Some(SectionNumber(vec![1])),
+ nested_items: Vec::new(),
+ }),
+ SummaryItem::Link(Link {
+ name: String::from("Second"),
+ location: Some(PathBuf::from("./second.md")),
+ number: Some(SectionNumber(vec![2])),
+ nested_items: Vec::new(),
+ }),
+ ];
+
+ let mut parser = SummaryParser::new(src);
+ let got = parser
+ .parse_numbered(&mut 0, &mut SectionNumber::default())
+ .unwrap();
+
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn parse_titled_parts() {
+ let src = "- [First](./first.md)\n- [Second](./second.md)\n\
+ # Title 2\n- [Third](./third.md)\n\t- [Fourth](./fourth.md)";
+
+ let should_be = vec![
+ SummaryItem::Link(Link {
+ name: String::from("First"),
+ location: Some(PathBuf::from("./first.md")),
+ number: Some(SectionNumber(vec![1])),
+ nested_items: Vec::new(),
+ }),
+ SummaryItem::Link(Link {
+ name: String::from("Second"),
+ location: Some(PathBuf::from("./second.md")),
+ number: Some(SectionNumber(vec![2])),
+ nested_items: Vec::new(),
+ }),
+ SummaryItem::PartTitle(String::from("Title 2")),
+ SummaryItem::Link(Link {
+ name: String::from("Third"),
+ location: Some(PathBuf::from("./third.md")),
+ number: Some(SectionNumber(vec![3])),
+ nested_items: vec![SummaryItem::Link(Link {
+ name: String::from("Fourth"),
+ location: Some(PathBuf::from("./fourth.md")),
+ number: Some(SectionNumber(vec![3, 1])),
+ nested_items: Vec::new(),
+ })],
+ }),
+ ];
+
+ let mut parser = SummaryParser::new(src);
+ let got = parser.parse_parts().unwrap();
+
+ assert_eq!(got, should_be);
+ }
+
+ /// This test ensures the book will continue to pass because it breaks the
+ /// `SUMMARY.md` up using level 2 headers ([example]).
+ ///
+ /// [example]: https://github.com/rust-lang/book/blob/2c942dc094f4ddcdc7aba7564f80782801197c99/second-edition/src/SUMMARY.md#basic-rust-literacy
+ #[test]
+ fn can_have_a_subheader_between_nested_items() {
+ let src = "- [First](./first.md)\n\n## Subheading\n\n- [Second](./second.md)\n";
+ let should_be = vec![
+ SummaryItem::Link(Link {
+ name: String::from("First"),
+ location: Some(PathBuf::from("./first.md")),
+ number: Some(SectionNumber(vec![1])),
+ nested_items: Vec::new(),
+ }),
+ SummaryItem::Link(Link {
+ name: String::from("Second"),
+ location: Some(PathBuf::from("./second.md")),
+ number: Some(SectionNumber(vec![2])),
+ nested_items: Vec::new(),
+ }),
+ ];
+
+ let mut parser = SummaryParser::new(src);
+ let got = parser
+ .parse_numbered(&mut 0, &mut SectionNumber::default())
+ .unwrap();
+
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn an_empty_link_location_is_a_draft_chapter() {
+ let src = "- [Empty]()\n";
+ let mut parser = SummaryParser::new(src);
+
+ let got = parser.parse_numbered(&mut 0, &mut SectionNumber::default());
+ let should_be = vec![SummaryItem::Link(Link {
+ name: String::from("Empty"),
+ location: None,
+ number: Some(SectionNumber(vec![1])),
+ nested_items: Vec::new(),
+ })];
+
+ assert!(got.is_ok());
+ assert_eq!(got.unwrap(), should_be);
+ }
+
+ /// Regression test for https://github.com/rust-lang/mdBook/issues/779
+ /// Ensure section numbers are correctly incremented after a horizontal separator.
+ #[test]
+ fn keep_numbering_after_separator() {
+ let src =
+ "- [First](./first.md)\n---\n- [Second](./second.md)\n---\n- [Third](./third.md)\n";
+ let should_be = vec![
+ SummaryItem::Link(Link {
+ name: String::from("First"),
+ location: Some(PathBuf::from("./first.md")),
+ number: Some(SectionNumber(vec![1])),
+ nested_items: Vec::new(),
+ }),
+ SummaryItem::Separator,
+ SummaryItem::Link(Link {
+ name: String::from("Second"),
+ location: Some(PathBuf::from("./second.md")),
+ number: Some(SectionNumber(vec![2])),
+ nested_items: Vec::new(),
+ }),
+ SummaryItem::Separator,
+ SummaryItem::Link(Link {
+ name: String::from("Third"),
+ location: Some(PathBuf::from("./third.md")),
+ number: Some(SectionNumber(vec![3])),
+ nested_items: Vec::new(),
+ }),
+ ];
+
+ let mut parser = SummaryParser::new(src);
+ let got = parser
+ .parse_numbered(&mut 0, &mut SectionNumber::default())
+ .unwrap();
+
+ assert_eq!(got, should_be);
+ }
+
+ /// Regression test for https://github.com/rust-lang/mdBook/issues/1218
+ /// Ensure chapter names spread across multiple lines have spaces between all the words.
+ #[test]
+ fn add_space_for_multi_line_chapter_names() {
+ let src = "- [Chapter\ntitle](./chapter.md)";
+ let should_be = vec![SummaryItem::Link(Link {
+ name: String::from("Chapter title"),
+ location: Some(PathBuf::from("./chapter.md")),
+ number: Some(SectionNumber(vec![1])),
+ nested_items: Vec::new(),
+ })];
+
+ let mut parser = SummaryParser::new(src);
+ let got = parser
+ .parse_numbered(&mut 0, &mut SectionNumber::default())
+ .unwrap();
+
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn allow_space_in_link_destination() {
+ let src = "- [test1](./test%20link1.md)\n- [test2](<./test link2.md>)";
+ let should_be = vec![
+ SummaryItem::Link(Link {
+ name: String::from("test1"),
+ location: Some(PathBuf::from("./test link1.md")),
+ number: Some(SectionNumber(vec![1])),
+ nested_items: Vec::new(),
+ }),
+ SummaryItem::Link(Link {
+ name: String::from("test2"),
+ location: Some(PathBuf::from("./test link2.md")),
+ number: Some(SectionNumber(vec![2])),
+ nested_items: Vec::new(),
+ }),
+ ];
+ let mut parser = SummaryParser::new(src);
+ let got = parser
+ .parse_numbered(&mut 0, &mut SectionNumber::default())
+ .unwrap();
+
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn skip_html_comments() {
+ let src = r#"<!--
+# Title - En
+-->
+# Title - Local
+
+<!--
+[Prefix 00-01 - En](ch00-01.md)
+[Prefix 00-02 - En](ch00-02.md)
+-->
+[Prefix 00-01 - Local](ch00-01.md)
+[Prefix 00-02 - Local](ch00-02.md)
+
+<!--
+## Section Title - En
+-->
+## Section Title - Localized
+
+<!--
+- [Ch 01-00 - En](ch01-00.md)
+ - [Ch 01-01 - En](ch01-01.md)
+ - [Ch 01-02 - En](ch01-02.md)
+-->
+- [Ch 01-00 - Local](ch01-00.md)
+ - [Ch 01-01 - Local](ch01-01.md)
+ - [Ch 01-02 - Local](ch01-02.md)
+
+<!--
+- [Ch 02-00 - En](ch02-00.md)
+-->
+- [Ch 02-00 - Local](ch02-00.md)
+
+<!--
+[Appendix A - En](appendix-01.md)
+[Appendix B - En](appendix-02.md)
+-->`
+[Appendix A - Local](appendix-01.md)
+[Appendix B - Local](appendix-02.md)
+"#;
+
+ let mut parser = SummaryParser::new(src);
+
+ // ---- Title ----
+ let title = parser.parse_title();
+ assert_eq!(title, Some(String::from("Title - Local")));
+
+ // ---- Prefix Chapters ----
+
+ let new_affix_item = |name, location| {
+ SummaryItem::Link(Link {
+ name: String::from(name),
+ location: Some(PathBuf::from(location)),
+ ..Default::default()
+ })
+ };
+
+ let should_be = vec![
+ new_affix_item("Prefix 00-01 - Local", "ch00-01.md"),
+ new_affix_item("Prefix 00-02 - Local", "ch00-02.md"),
+ ];
+
+ let got = parser.parse_affix(true).unwrap();
+ assert_eq!(got, should_be);
+
+ // ---- Numbered Chapters ----
+
+ let new_numbered_item = |name, location, numbers: &[u32], nested_items| {
+ SummaryItem::Link(Link {
+ name: String::from(name),
+ location: Some(PathBuf::from(location)),
+ number: Some(SectionNumber(numbers.to_vec())),
+ nested_items,
+ })
+ };
+
+ let ch01_nested = vec![
+ new_numbered_item("Ch 01-01 - Local", "ch01-01.md", &[1, 1], vec![]),
+ new_numbered_item("Ch 01-02 - Local", "ch01-02.md", &[1, 2], vec![]),
+ ];
+
+ let should_be = vec![
+ new_numbered_item("Ch 01-00 - Local", "ch01-00.md", &[1], ch01_nested),
+ new_numbered_item("Ch 02-00 - Local", "ch02-00.md", &[2], vec![]),
+ ];
+ let got = parser.parse_parts().unwrap();
+ assert_eq!(got, should_be);
+
+ // ---- Suffix Chapters ----
+
+ let should_be = vec![
+ new_affix_item("Appendix A - Local", "appendix-01.md"),
+ new_affix_item("Appendix B - Local", "appendix-02.md"),
+ ];
+
+ let got = parser.parse_affix(false).unwrap();
+ assert_eq!(got, should_be);
+ }
+}
diff --git a/vendor/mdbook/src/cmd/build.rs b/vendor/mdbook/src/cmd/build.rs
new file mode 100644
index 000000000..5fe73236c
--- /dev/null
+++ b/vendor/mdbook/src/cmd/build.rs
@@ -0,0 +1,50 @@
+use crate::{get_book_dir, open};
+use clap::{arg, App, Arg, ArgMatches};
+use mdbook::errors::Result;
+use mdbook::MDBook;
+
+// Create clap subcommand arguments
+pub fn make_subcommand<'help>() -> App<'help> {
+ App::new("build")
+ .about("Builds a book from its markdown files")
+ .arg(
+ Arg::new("dest-dir")
+ .short('d')
+ .long("dest-dir")
+ .value_name("dest-dir")
+ .help(
+ "Output directory for the book{n}\
+ Relative paths are interpreted relative to the book's root directory.{n}\
+ If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
+ ),
+ )
+ .arg(arg!([dir]
+ "Root directory for the book{n}\
+ (Defaults to the Current Directory when omitted)"
+ ))
+ .arg(arg!(-o --open "Opens the compiled book in a web browser"))
+}
+
+// Build command implementation
+pub fn execute(args: &ArgMatches) -> Result<()> {
+ let book_dir = get_book_dir(args);
+ let mut book = MDBook::load(&book_dir)?;
+
+ if let Some(dest_dir) = args.value_of("dest-dir") {
+ book.config.build.build_dir = dest_dir.into();
+ }
+
+ book.build()?;
+
+ if args.is_present("open") {
+ // FIXME: What's the right behaviour if we don't use the HTML renderer?
+ let path = book.build_dir_for("html").join("index.html");
+ if !path.exists() {
+ error!("No chapter available to open");
+ std::process::exit(1)
+ }
+ open(path);
+ }
+
+ Ok(())
+}
diff --git a/vendor/mdbook/src/cmd/clean.rs b/vendor/mdbook/src/cmd/clean.rs
new file mode 100644
index 000000000..0569726e1
--- /dev/null
+++ b/vendor/mdbook/src/cmd/clean.rs
@@ -0,0 +1,44 @@
+use crate::get_book_dir;
+use anyhow::Context;
+use clap::{arg, App, Arg, ArgMatches};
+use mdbook::MDBook;
+use std::fs;
+
+// Create clap subcommand arguments
+pub fn make_subcommand<'help>() -> App<'help> {
+ App::new("clean")
+ .about("Deletes a built book")
+ .arg(
+ Arg::new("dest-dir")
+ .short('d')
+ .long("dest-dir")
+ .value_name("dest-dir")
+ .help(
+ "Output directory for the book{n}\
+ Relative paths are interpreted relative to the book's root directory.{n}\
+ If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
+ ),
+ )
+ .arg(arg!([dir]
+ "Root directory for the book{n}\
+ (Defaults to the Current Directory when omitted)"
+ ))
+}
+
+// Clean command implementation
+pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> {
+ let book_dir = get_book_dir(args);
+ let book = MDBook::load(&book_dir)?;
+
+ let dir_to_remove = match args.value_of("dest-dir") {
+ Some(dest_dir) => dest_dir.into(),
+ None => book.root.join(&book.config.build.build_dir),
+ };
+
+ if dir_to_remove.exists() {
+ fs::remove_dir_all(&dir_to_remove)
+ .with_context(|| "Unable to remove the build directory")?;
+ }
+
+ Ok(())
+}
diff --git a/vendor/mdbook/src/cmd/init.rs b/vendor/mdbook/src/cmd/init.rs
new file mode 100644
index 000000000..c964dcc13
--- /dev/null
+++ b/vendor/mdbook/src/cmd/init.rs
@@ -0,0 +1,126 @@
+use crate::get_book_dir;
+use clap::{arg, App, Arg, ArgMatches};
+use mdbook::config;
+use mdbook::errors::Result;
+use mdbook::MDBook;
+use std::io;
+use std::io::Write;
+use std::process::Command;
+
+// Create clap subcommand arguments
+pub fn make_subcommand<'help>() -> App<'help> {
+ App::new("init")
+ .about("Creates the boilerplate structure and files for a new book")
+ // the {n} denotes a newline which will properly aligned in all help messages
+ .arg(arg!([dir]
+ "Directory to create the book in{n}\
+ (Defaults to the Current Directory when omitted)"
+ ))
+ .arg(arg!(--theme "Copies the default theme into your source folder"))
+ .arg(arg!(--force "Skips confirmation prompts"))
+ .arg(
+ Arg::new("title")
+ .long("title")
+ .takes_value(true)
+ .help("Sets the book title")
+ .required(false),
+ )
+ .arg(
+ Arg::new("ignore")
+ .long("ignore")
+ .takes_value(true)
+ .possible_values(&["none", "git"])
+ .help("Creates a VCS ignore file (i.e. .gitignore)")
+ .required(false),
+ )
+}
+
+// Init command implementation
+pub fn execute(args: &ArgMatches) -> Result<()> {
+ let book_dir = get_book_dir(args);
+ let mut builder = MDBook::init(&book_dir);
+ let mut config = config::Config::default();
+ // If flag `--theme` is present, copy theme to src
+ if args.is_present("theme") {
+ let theme_dir = book_dir.join("theme");
+ println!();
+ println!("Copying the default theme to {}", theme_dir.display());
+ // Skip this if `--force` is present
+ if !args.is_present("force") && theme_dir.exists() {
+ println!("This could potentially overwrite files already present in that directory.");
+ print!("\nAre you sure you want to continue? (y/n) ");
+
+ // Read answer from user and exit if it's not 'yes'
+ if confirm() {
+ builder.copy_theme(true);
+ }
+ } else {
+ builder.copy_theme(true);
+ }
+ }
+
+ if let Some(ignore) = args.value_of("ignore") {
+ match ignore {
+ "git" => builder.create_gitignore(true),
+ _ => builder.create_gitignore(false),
+ };
+ } else {
+ println!("\nDo you want a .gitignore to be created? (y/n)");
+ if confirm() {
+ builder.create_gitignore(true);
+ }
+ }
+
+ config.book.title = if args.is_present("title") {
+ args.value_of("title").map(String::from)
+ } else {
+ request_book_title()
+ };
+
+ if let Some(author) = get_author_name() {
+ debug!("Obtained user name from gitconfig: {:?}", author);
+ config.book.authors.push(author);
+ builder.with_config(config);
+ }
+
+ builder.build()?;
+ println!("\nAll done, no errors...");
+
+ Ok(())
+}
+
+/// Obtains author name from git config file by running the `git config` command.
+fn get_author_name() -> Option<String> {
+ let output = Command::new("git")
+ .args(&["config", "--get", "user.name"])
+ .output()
+ .ok()?;
+
+ if output.status.success() {
+ Some(String::from_utf8_lossy(&output.stdout).trim().to_owned())
+ } else {
+ None
+ }
+}
+
+/// Request book title from user and return if provided.
+fn request_book_title() -> Option<String> {
+ println!("What title would you like to give the book? ");
+ io::stdout().flush().unwrap();
+ let mut resp = String::new();
+ io::stdin().read_line(&mut resp).unwrap();
+ let resp = resp.trim();
+ if resp.is_empty() {
+ None
+ } else {
+ Some(resp.into())
+ }
+}
+
+// Simple function for user confirmation
+fn confirm() -> bool {
+ io::stdout().flush().unwrap();
+ let mut s = String::new();
+ io::stdin().read_line(&mut s).ok();
+ matches!(&*s.trim(), "Y" | "y" | "yes" | "Yes")
+}
diff --git a/vendor/mdbook/src/cmd/mod.rs b/vendor/mdbook/src/cmd/mod.rs
new file mode 100644
index 000000000..c5b6730f1
--- /dev/null
+++ b/vendor/mdbook/src/cmd/mod.rs
@@ -0,0 +1,10 @@
+//! Subcommand modules for the `mdbook` binary.
+
+pub mod build;
+pub mod clean;
+pub mod init;
+#[cfg(feature = "serve")]
+pub mod serve;
+pub mod test;
+#[cfg(feature = "watch")]
+pub mod watch;
diff --git a/vendor/mdbook/src/cmd/serve.rs b/vendor/mdbook/src/cmd/serve.rs
new file mode 100644
index 000000000..bafbfd52e
--- /dev/null
+++ b/vendor/mdbook/src/cmd/serve.rs
@@ -0,0 +1,177 @@
+#[cfg(feature = "watch")]
+use super::watch;
+use crate::{get_book_dir, open};
+use clap::{arg, App, Arg, ArgMatches};
+use futures_util::sink::SinkExt;
+use futures_util::StreamExt;
+use mdbook::errors::*;
+use mdbook::utils;
+use mdbook::utils::fs::get_404_output_file;
+use mdbook::MDBook;
+use std::net::{SocketAddr, ToSocketAddrs};
+use std::path::PathBuf;
+use tokio::sync::broadcast;
+use warp::ws::Message;
+use warp::Filter;
+
+/// The HTTP endpoint for the websocket used to trigger reloads when a file changes.
+const LIVE_RELOAD_ENDPOINT: &str = "__livereload";
+
+// Create clap subcommand arguments
+pub fn make_subcommand<'help>() -> App<'help> {
+ App::new("serve")
+ .about("Serves a book at http://localhost:3000, and rebuilds it on changes")
+ .arg(
+ Arg::new("dest-dir")
+ .short('d')
+ .long("dest-dir")
+ .value_name("dest-dir")
+ .help(
+ "Output directory for the book{n}\
+ Relative paths are interpreted relative to the book's root directory.{n}\
+ If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
+ ),
+ )
+ .arg(arg!([dir]
+ "Root directory for the book{n}\
+ (Defaults to the Current Directory when omitted)"
+ ))
+ .arg(
+ Arg::new("hostname")
+ .short('n')
+ .long("hostname")
+ .takes_value(true)
+ .default_value("localhost")
+ .forbid_empty_values(true)
+ .help("Hostname to listen on for HTTP connections"),
+ )
+ .arg(
+ Arg::new("port")
+ .short('p')
+ .long("port")
+ .takes_value(true)
+ .default_value("3000")
+ .forbid_empty_values(true)
+ .help("Port to use for HTTP connections"),
+ )
+ .arg(arg!(-o --open "Opens the compiled book in a web browser"))
+}
+
+// Serve command implementation
+pub fn execute(args: &ArgMatches) -> Result<()> {
+ let book_dir = get_book_dir(args);
+ let mut book = MDBook::load(&book_dir)?;
+
+ let port = args.value_of("port").unwrap();
+ let hostname = args.value_of("hostname").unwrap();
+ let open_browser = args.is_present("open");
+
+ let address = format!("{}:{}", hostname, port);
+
+ let update_config = |book: &mut MDBook| {
+ book.config
+ .set("output.html.live-reload-endpoint", &LIVE_RELOAD_ENDPOINT)
+ .expect("live-reload-endpoint update failed");
+ if let Some(dest_dir) = args.value_of("dest-dir") {
+ book.config.build.build_dir = dest_dir.into();
+ }
+ // Override site-url for local serving of the 404 file
+ book.config.set("output.html.site-url", "/").unwrap();
+ };
+ update_config(&mut book);
+ book.build()?;
+
+ let sockaddr: SocketAddr = address
+ .to_socket_addrs()?
+ .next()
+ .ok_or_else(|| anyhow::anyhow!("no address found for {}", address))?;
+ let build_dir = book.build_dir_for("html");
+ let input_404 = book
+ .config
+ .get("output.html.input-404")
+ .map(toml::Value::as_str)
+ .and_then(std::convert::identity) // flatten
+ .map(ToString::to_string);
+ let file_404 = get_404_output_file(&input_404);
+
+ // A channel used to broadcast to any websockets to reload when a file changes.
+ let (tx, _rx) = tokio::sync::broadcast::channel::<Message>(100);
+
+ let reload_tx = tx.clone();
+ let thread_handle = std::thread::spawn(move || {
+ serve(build_dir, sockaddr, reload_tx, &file_404);
+ });
+
+ let serving_url = format!("http://{}", address);
+ info!("Serving on: {}", serving_url);
+
+ if open_browser {
+ open(serving_url);
+ }
+
+ #[cfg(feature = "watch")]
+ watch::trigger_on_change(&book, move |paths, book_dir| {
+ info!("Files changed: {:?}", paths);
+ info!("Building book...");
+
+ // FIXME: This area is really ugly because we need to re-set livereload :(
+ let result = MDBook::load(&book_dir).and_then(|mut b| {
+ update_config(&mut b);
+ b.build()
+ });
+
+ if let Err(e) = result {
+ error!("Unable to load the book");
+ utils::log_backtrace(&e);
+ } else {
+ let _ = tx.send(Message::text("reload"));
+ }
+ });
+
+ let _ = thread_handle.join();
+
+ Ok(())
+}
+
+#[tokio::main]
+async fn serve(
+ build_dir: PathBuf,
+ address: SocketAddr,
+ reload_tx: broadcast::Sender<Message>,
+ file_404: &str,
+) {
+ // A warp Filter which captures `reload_tx` and provides an `rx` copy to
+ // receive reload messages.
+ let sender = warp::any().map(move || reload_tx.subscribe());
+
+ // A warp Filter to handle the livereload endpoint. This upgrades to a
+ // websocket, and then waits for any filesystem change notifications, and
+ // relays them over the websocket.
+ let livereload = warp::path(LIVE_RELOAD_ENDPOINT)
+ .and(warp::ws())
+ .and(sender)
+ .map(|ws: warp::ws::Ws, mut rx: broadcast::Receiver<Message>| {
+ ws.on_upgrade(move |ws| async move {
+ let (mut user_ws_tx, _user_ws_rx) = ws.split();
+ trace!("websocket got connection");
+ if let Ok(m) = rx.recv().await {
+ trace!("notify of reload");
+ let _ = user_ws_tx.send(m).await;
+ }
+ })
+ });
+ // A warp Filter that serves from the filesystem.
+ let book_route = warp::fs::dir(build_dir.clone());
+ // The fallback route for 404 errors
+ let fallback_route = warp::fs::file(build_dir.join(file_404))
+ .map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NOT_FOUND));
+ let routes = livereload.or(book_route).or(fallback_route);
+
+ std::panic::set_hook(Box::new(move |panic_info| {
+ // exit if serve panics
+ error!("Unable to serve: {}", panic_info);
+ std::process::exit(1);
+ }));
+
+ warp::serve(routes).run(address).await;
+}
diff --git a/vendor/mdbook/src/cmd/test.rs b/vendor/mdbook/src/cmd/test.rs
new file mode 100644
index 000000000..02f982a49
--- /dev/null
+++ b/vendor/mdbook/src/cmd/test.rs
@@ -0,0 +1,54 @@
+use crate::get_book_dir;
+use clap::{arg, App, Arg, ArgMatches};
+use mdbook::errors::Result;
+use mdbook::MDBook;
+
+// Create clap subcommand arguments
+pub fn make_subcommand<'help>() -> App<'help> {
+ App::new("test")
+ .about("Tests that a book's Rust code samples compile")
+ .arg(
+ Arg::new("dest-dir")
+ .short('d')
+ .long("dest-dir")
+ .value_name("dest-dir")
+ .help(
+ "Output directory for the book{n}\
+ Relative paths are interpreted relative to the book's root directory.{n}\
+ If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
+ ),
+ )
+ .arg(arg!([dir]
+ "Root directory for the book{n}\
+ (Defaults to the Current Directory when omitted)"
+ ))
+ .arg(Arg::new("library-path")
+ .short('L')
+ .long("library-path")
+ .value_name("dir")
+ .takes_value(true)
+ .use_delimiter(true)
+ .require_delimiter(true)
+ .multiple_values(true)
+ .multiple_occurrences(true)
+ .forbid_empty_values(true)
+ .help("A comma-separated list of directories to add to {n}the crate search path when building tests"))
+}
+
+// test command implementation
+pub fn execute(args: &ArgMatches) -> Result<()> {
+ let library_paths: Vec<&str> = args
+ .values_of("library-path")
+ .map(std::iter::Iterator::collect)
+ .unwrap_or_default();
+ let book_dir = get_book_dir(args);
+ let mut book = MDBook::load(&book_dir)?;
+
+ if let Some(dest_dir) = args.value_of("dest-dir") {
+ book.config.build.build_dir = dest_dir.into();
+ }
+
+ book.test(library_paths)?;
+
+ Ok(())
+}
diff --git a/vendor/mdbook/src/cmd/watch.rs b/vendor/mdbook/src/cmd/watch.rs
new file mode 100644
index 000000000..9336af779
--- /dev/null
+++ b/vendor/mdbook/src/cmd/watch.rs
@@ -0,0 +1,175 @@
+use crate::{get_book_dir, open};
+use clap::{arg, App, Arg, ArgMatches};
+use mdbook::errors::Result;
+use mdbook::utils;
+use mdbook::MDBook;
+use notify::Watcher;
+use std::path::{Path, PathBuf};
+use std::sync::mpsc::channel;
+use std::thread::sleep;
+use std::time::Duration;
+
+// Create clap subcommand arguments
+pub fn make_subcommand<'help>() -> App<'help> {
+ App::new("watch")
+ .about("Watches a book's files and rebuilds it on changes")
+ .arg(
+ Arg::new("dest-dir")
+ .short('d')
+ .long("dest-dir")
+ .value_name("dest-dir")
+ .help(
+ "Output directory for the book{n}\
+ Relative paths are interpreted relative to the book's root directory.{n}\
+ If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
+ ),
+ )
+ .arg(arg!([dir]
+ "Root directory for the book{n}\
+ (Defaults to the Current Directory when omitted)"
+ ))
+ .arg(arg!(-o --open "Opens the compiled book in a web browser"))
+}
+
+// Watch command implementation
+pub fn execute(args: &ArgMatches) -> Result<()> {
+ let book_dir = get_book_dir(args);
+ let mut book = MDBook::load(&book_dir)?;
+
+ let update_config = |book: &mut MDBook| {
+ if let Some(dest_dir) = args.value_of("dest-dir") {
+ book.config.build.build_dir = dest_dir.into();
+ }
+ };
+ update_config(&mut book);
+
+ if args.is_present("open") {
+ book.build()?;
+ let path = book.build_dir_for("html").join("index.html");
+ if !path.exists() {
+ error!("No chapter available to open");
+ std::process::exit(1)
+ }
+ open(path);
+ }
+
+ trigger_on_change(&book, |paths, book_dir| {
+ info!("Files changed: {:?}\nBuilding book...\n", paths);
+ let result = MDBook::load(&book_dir).and_then(|mut b| {
+ update_config(&mut b);
+ b.build()
+ });
+
+ if let Err(e) = result {
+ error!("Unable to build the book");
+ utils::log_backtrace(&e);
+ }
+ });
+
+ Ok(())
+}
+
+fn remove_ignored_files(book_root: &Path, paths: &[PathBuf]) -> Vec<PathBuf> {
+ if paths.is_empty() {
+ return vec![];
+ }
+
+ match find_gitignore(book_root) {
+ Some(gitignore_path) => {
+ match gitignore::File::new(gitignore_path.as_path()) {
+ Ok(exclusion_checker) => filter_ignored_files(exclusion_checker, paths),
+ Err(_) => {
+ // We're unable to read the .gitignore file, so we'll silently allow everything.
+ // Please see discussion: https://github.com/rust-lang/mdBook/pull/1051
+ paths.iter().map(|path| path.to_path_buf()).collect()
+ }
+ }
+ }
+ None => {
+ // There is no .gitignore file.
+ paths.iter().map(|path| path.to_path_buf()).collect()
+ }
+ }
+}
+
+fn find_gitignore(book_root: &Path) -> Option<PathBuf> {
+ book_root
+ .ancestors()
+ .map(|p| p.join(".gitignore"))
+ .find(|p| p.exists())
+}
+
+fn filter_ignored_files(exclusion_checker: gitignore::File, paths: &[PathBuf]) -> Vec<PathBuf> {
+ paths
+ .iter()
+ .filter(|path| match exclusion_checker.is_excluded(path) {
+ Ok(exclude) => !exclude,
+ Err(error) => {
+ warn!(
+ "Unable to determine if {:?} is excluded: {:?}. Including it.",
+ &path, error
+ );
+ true
+ }
+ })
+ .map(|path| path.to_path_buf())
+ .collect()
+}
+
+/// Calls the closure when a book source file is changed, blocking indefinitely.
+pub fn trigger_on_change<F>(book: &MDBook, closure: F)
+where
+ F: Fn(Vec<PathBuf>, &Path),
+{
+ use notify::DebouncedEvent::*;
+ use notify::RecursiveMode::*;
+
+ // Create a channel to receive the events.
+ let (tx, rx) = channel();
+
+ let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) {
+ Ok(w) => w,
+ Err(e) => {
+ error!("Error while trying to watch the files:\n\n\t{:?}", e);
+ std::process::exit(1)
+ }
+ };
+
+ // Add the source directory to the watcher
+ if let Err(e) = watcher.watch(book.source_dir(), Recursive) {
+ error!("Error while watching {:?}:\n {:?}", book.source_dir(), e);
+ std::process::exit(1);
+ };
+
+ let _ = watcher.watch(book.theme_dir(), Recursive);
+
+ // Add the book.toml file to the watcher if it exists
+ let _ = watcher.watch(book.root.join("book.toml"), NonRecursive);
+
+ info!("Listening for changes...");
+
+ loop {
+ let first_event = rx.recv().unwrap();
+ sleep(Duration::from_millis(50));
+ let other_events = rx.try_iter();
+
+ let all_events = std::iter::once(first_event).chain(other_events);
+
+ let paths = all_events
+ .filter_map(|event| {
+ debug!("Received filesystem event: {:?}", event);
+
+ match event {
+ Create(path) | Write(path) | Remove(path) | Rename(_, path) => Some(path),
+ _ => None,
+ }
+ })
+ .collect::<Vec<_>>();
+
+ let paths = remove_ignored_files(&book.root, &paths[..]);
+
+ if !paths.is_empty() {
+ closure(paths, &book.root);
+ }
+ }
+}
diff --git a/vendor/mdbook/src/config.rs b/vendor/mdbook/src/config.rs
new file mode 100644
index 000000000..b7d03d1a2
--- /dev/null
+++ b/vendor/mdbook/src/config.rs
@@ -0,0 +1,1190 @@
+//! Mdbook's configuration system.
+//!
+//! The main entrypoint of the `config` module is the `Config` struct. This acts
+//! essentially as a bag of configuration information, with a couple
+//! pre-determined tables ([`BookConfig`] and [`BuildConfig`]) as well as support
+//! for arbitrary data which is exposed to plugins and alternative backends.
+//!
+//!
+//! # Examples
+//!
+//! ```rust
+//! # use mdbook::errors::*;
+//! use std::path::PathBuf;
+//! use std::str::FromStr;
+//! use mdbook::Config;
+//! use toml::Value;
+//!
+//! # fn run() -> Result<()> {
+//! let src = r#"
+//! [book]
+//! title = "My Book"
+//! authors = ["Michael-F-Bryan"]
+//!
+//! [build]
+//! src = "out"
+//!
+//! [other-table.foo]
+//! bar = 123
+//! "#;
+//!
+//! // load the `Config` from a toml string
+//! let mut cfg = Config::from_str(src)?;
+//!
+//! // retrieve a nested value
+//! let bar = cfg.get("other-table.foo.bar").cloned();
+//! assert_eq!(bar, Some(Value::Integer(123)));
+//!
+//! // Set the `output.html.theme` directory
+//! assert!(cfg.get("output.html").is_none());
+//! cfg.set("output.html.theme", "./themes");
+//!
+//! // then load it again, automatically deserializing to a `PathBuf`.
+//! let got: Option<PathBuf> = cfg.get_deserialized_opt("output.html.theme")?;
+//! assert_eq!(got, Some(PathBuf::from("./themes")));
+//! # Ok(())
+//! # }
+//! # run().unwrap()
+//! ```
+
+#![deny(missing_docs)]
+
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
+use std::collections::HashMap;
+use std::env;
+use std::fs::File;
+use std::io::Read;
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+use toml::value::Table;
+use toml::{self, Value};
+
+use crate::errors::*;
+use crate::utils::{self, toml_ext::TomlExt};
+
+/// The overall configuration object for MDBook, essentially an in-memory
+/// representation of `book.toml`.
+#[derive(Debug, Clone, PartialEq)]
+pub struct Config {
+ /// Metadata about the book.
+ pub book: BookConfig,
+ /// Information about the build environment.
+ pub build: BuildConfig,
+ /// Information about Rust language support.
+ pub rust: RustConfig,
+ rest: Value,
+}
+
+impl FromStr for Config {
+ type Err = Error;
+
+ /// Load a `Config` from some string.
+ fn from_str(src: &str) -> Result<Self> {
+ toml::from_str(src).with_context(|| "Invalid configuration file")
+ }
+}
+
+impl Config {
+ /// Load the configuration file from disk.
+ pub fn from_disk<P: AsRef<Path>>(config_file: P) -> Result<Config> {
+ let mut buffer = String::new();
+ File::open(config_file)
+ .with_context(|| "Unable to open the configuration file")?
+ .read_to_string(&mut buffer)
+ .with_context(|| "Couldn't read the file")?;
+
+ Config::from_str(&buffer)
+ }
+
+ /// Updates the `Config` from the available environment variables.
+ ///
+ /// Variables starting with `MDBOOK_` are used for configuration. The key is
+ /// created by removing the `MDBOOK_` prefix and turning the resulting
+ /// string into `kebab-case`. Double underscores (`__`) separate nested
+ /// keys, while a single underscore (`_`) is replaced with a dash (`-`).
+ ///
+ /// For example:
+ ///
+ /// - `MDBOOK_foo` -> `foo`
+ /// - `MDBOOK_FOO` -> `foo`
+ /// - `MDBOOK_FOO__BAR` -> `foo.bar`
+ /// - `MDBOOK_FOO_BAR` -> `foo-bar`
+ /// - `MDBOOK_FOO_bar__baz` -> `foo-bar.baz`
+ ///
+ /// So by setting the `MDBOOK_BOOK__TITLE` environment variable you can
+ /// override the book's title without needing to touch your `book.toml`.
+ ///
+ /// > **Note:** To facilitate setting more complex config items, the value
+ /// > of an environment variable is first parsed as JSON, falling back to a
+ /// > string if the parse fails.
+ /// >
+ /// > This means, if you so desired, you could override all book metadata
+ /// > when building the book with something like
+ /// >
+ /// > ```text
+ /// > $ export MDBOOK_BOOK='{"title": "My Awesome Book", "authors": ["Michael-F-Bryan"]}'
+ /// > $ mdbook build
+ /// > ```
+ ///
+ /// The latter case may be useful in situations where `mdbook` is invoked
+ /// from a script or CI, where it sometimes isn't possible to update the
+ /// `book.toml` before building.
+ pub fn update_from_env(&mut self) {
+ debug!("Updating the config from environment variables");
+
+ let overrides =
+ env::vars().filter_map(|(key, value)| parse_env(&key).map(|index| (index, value)));
+
+ for (key, value) in overrides {
+ trace!("{} => {}", key, value);
+ let parsed_value = serde_json::from_str(&value)
+ .unwrap_or_else(|_| serde_json::Value::String(value.to_string()));
+
+ if key == "book" || key == "build" {
+ if let serde_json::Value::Object(ref map) = parsed_value {
+ // To `set` each `key`, we wrap them as `prefix.key`
+ for (k, v) in map {
+ let full_key = format!("{}.{}", key, k);
+ self.set(&full_key, v).expect("unreachable");
+ }
+ return;
+ }
+ }
+
+ self.set(key, parsed_value).expect("unreachable");
+ }
+ }
+
+ /// Fetch an arbitrary item from the `Config` as a `toml::Value`.
+ ///
+ /// You can use dotted indices to access nested items (e.g.
+ /// `output.html.playground` will fetch the "playground" out of the html output
+ /// table).
+ pub fn get(&self, key: &str) -> Option<&Value> {
+ self.rest.read(key)
+ }
+
+ /// Fetch a value from the `Config` so you can mutate it.
+ pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
+ self.rest.read_mut(key)
+ }
+
+ /// Convenience method for getting the html renderer's configuration.
+ ///
+ /// # Note
+ ///
+ /// This is for compatibility only. It will be removed completely once the
+ /// HTML renderer is refactored to be less coupled to `mdbook` internals.
+ #[doc(hidden)]
+ pub fn html_config(&self) -> Option<HtmlConfig> {
+ match self
+ .get_deserialized_opt("output.html")
+ .with_context(|| "Parsing configuration [output.html]")
+ {
+ Ok(Some(config)) => Some(config),
+ Ok(None) => None,
+ Err(e) => {
+ utils::log_backtrace(&e);
+ None
+ }
+ }
+ }
+
+ /// Deprecated, use get_deserialized_opt instead.
+ #[deprecated = "use get_deserialized_opt instead"]
+ pub fn get_deserialized<'de, T: Deserialize<'de>, S: AsRef<str>>(&self, name: S) -> Result<T> {
+ let name = name.as_ref();
+ match self.get_deserialized_opt(name)? {
+ Some(value) => Ok(value),
+ None => bail!("Key not found, {:?}", name),
+ }
+ }
+
+ /// Convenience function to fetch a value from the config and deserialize it
+ /// into some arbitrary type.
+ pub fn get_deserialized_opt<'de, T: Deserialize<'de>, S: AsRef<str>>(
+ &self,
+ name: S,
+ ) -> Result<Option<T>> {
+ let name = name.as_ref();
+ self.get(name)
+ .map(|value| {
+ value
+ .clone()
+ .try_into()
+ .with_context(|| "Couldn't deserialize the value")
+ })
+ .transpose()
+ }
+
+ /// Set a config key, clobbering any existing values along the way.
+ ///
+ /// The only way this can fail is if we can't serialize `value` into a
+ /// `toml::Value`.
+ pub fn set<S: Serialize, I: AsRef<str>>(&mut self, index: I, value: S) -> Result<()> {
+ let index = index.as_ref();
+
+ let value = Value::try_from(value)
+ .with_context(|| "Unable to represent the item as a JSON Value")?;
+
+ if let Some(key) = index.strip_prefix("book.") {
+ self.book.update_value(key, value);
+ } else if let Some(key) = index.strip_prefix("build.") {
+ self.build.update_value(key, value);
+ } else {
+ self.rest.insert(index, value);
+ }
+
+ Ok(())
+ }
+
+ /// Get the table associated with a particular renderer.
+ pub fn get_renderer<I: AsRef<str>>(&self, index: I) -> Option<&Table> {
+ let key = format!("output.{}", index.as_ref());
+ self.get(&key).and_then(Value::as_table)
+ }
+
+ /// Get the table associated with a particular preprocessor.
+ pub fn get_preprocessor<I: AsRef<str>>(&self, index: I) -> Option<&Table> {
+ let key = format!("preprocessor.{}", index.as_ref());
+ self.get(&key).and_then(Value::as_table)
+ }
+
+ fn from_legacy(mut table: Value) -> Config {
+ let mut cfg = Config::default();
+
+ // we use a macro here instead of a normal loop because the $out
+ // variable can be different types. This way we can make type inference
+ // figure out what try_into() deserializes to.
+ macro_rules! get_and_insert {
+ ($table:expr, $key:expr => $out:expr) => {
+ let got = $table
+ .as_table_mut()
+ .and_then(|t| t.remove($key))
+ .and_then(|v| v.try_into().ok());
+ if let Some(value) = got {
+ $out = value;
+ }
+ };
+ }
+
+ get_and_insert!(table, "title" => cfg.book.title);
+ get_and_insert!(table, "authors" => cfg.book.authors);
+ get_and_insert!(table, "source" => cfg.book.src);
+ get_and_insert!(table, "description" => cfg.book.description);
+
+ if let Some(dest) = table.delete("output.html.destination") {
+ if let Ok(destination) = dest.try_into() {
+ cfg.build.build_dir = destination;
+ }
+ }
+
+ cfg.rest = table;
+ cfg
+ }
+}
+
+impl Default for Config {
+ fn default() -> Config {
+ Config {
+ book: BookConfig::default(),
+ build: BuildConfig::default(),
+ rust: RustConfig::default(),
+ rest: Value::Table(Table::default()),
+ }
+ }
+}
+
+impl<'de> Deserialize<'de> for Config {
+ fn deserialize<D: Deserializer<'de>>(de: D) -> std::result::Result<Self, D::Error> {
+ let raw = Value::deserialize(de)?;
+
+ if is_legacy_format(&raw) {
+ warn!("It looks like you are using the legacy book.toml format.");
+ warn!("We'll parse it for now, but you should probably convert to the new format.");
+ warn!("See the mdbook documentation for more details, although as a rule of thumb");
+ warn!("just move all top level configuration entries like `title`, `author` and");
+ warn!("`description` under a table called `[book]`, move the `destination` entry");
+ warn!("from `[output.html]`, renamed to `build-dir`, under a table called");
+ warn!("`[build]`, and it should all work.");
+ warn!("Documentation: http://rust-lang.github.io/mdBook/format/config.html");
+ return Ok(Config::from_legacy(raw));
+ }
+
+ use serde::de::Error;
+ let mut table = match raw {
+ Value::Table(t) => t,
+ _ => {
+ return Err(D::Error::custom(
+ "A config file should always be a toml table",
+ ));
+ }
+ };
+
+ let book: BookConfig = table
+ .remove("book")
+ .map(|book| book.try_into().map_err(D::Error::custom))
+ .transpose()?
+ .unwrap_or_default();
+
+ let build: BuildConfig = table
+ .remove("build")
+ .map(|build| build.try_into().map_err(D::Error::custom))
+ .transpose()?
+ .unwrap_or_default();
+
+ let rust: RustConfig = table
+ .remove("rust")
+ .map(|rust| rust.try_into().map_err(D::Error::custom))
+ .transpose()?
+ .unwrap_or_default();
+
+ Ok(Config {
+ book,
+ build,
+ rust,
+ rest: Value::Table(table),
+ })
+ }
+}
+
+impl Serialize for Config {
+ fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
+ // TODO: This should probably be removed and use a derive instead.
+ let mut table = self.rest.clone();
+
+ let book_config = Value::try_from(&self.book).expect("should always be serializable");
+ table.insert("book", book_config);
+
+ if self.build != BuildConfig::default() {
+ let build_config = Value::try_from(&self.build).expect("should always be serializable");
+ table.insert("build", build_config);
+ }
+
+ if self.rust != RustConfig::default() {
+ let rust_config = Value::try_from(&self.rust).expect("should always be serializable");
+ table.insert("rust", rust_config);
+ }
+
+ table.serialize(s)
+ }
+}
+
+fn parse_env(key: &str) -> Option<String> {
+ key.strip_prefix("MDBOOK_")
+ .map(|key| key.to_lowercase().replace("__", ".").replace('_', "-"))
+}
+
+fn is_legacy_format(table: &Value) -> bool {
+ let legacy_items = [
+ "title",
+ "authors",
+ "source",
+ "description",
+ "output.html.destination",
+ ];
+
+ for item in &legacy_items {
+ if table.read(item).is_some() {
+ return true;
+ }
+ }
+
+ false
+}
+
+/// Configuration options which are specific to the book and required for
+/// loading it from disk.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(default, rename_all = "kebab-case")]
+pub struct BookConfig {
+ /// The book's title.
+ pub title: Option<String>,
+ /// The book's authors.
+ pub authors: Vec<String>,
+ /// An optional description for the book.
+ pub description: Option<String>,
+ /// Location of the book source relative to the book's root directory.
+ pub src: PathBuf,
+ /// Does this book support more than one language?
+ pub multilingual: bool,
+ /// The main language of the book.
+ pub language: Option<String>,
+}
+
+impl Default for BookConfig {
+ fn default() -> BookConfig {
+ BookConfig {
+ title: None,
+ authors: Vec::new(),
+ description: None,
+ src: PathBuf::from("src"),
+ multilingual: false,
+ language: Some(String::from("en")),
+ }
+ }
+}
+
+/// Configuration for the build procedure.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(default, rename_all = "kebab-case")]
+pub struct BuildConfig {
+ /// Where to put built artefacts relative to the book's root directory.
+ pub build_dir: PathBuf,
+ /// Should non-existent markdown files specified in `SUMMARY.md` be created
+ /// if they don't exist?
+ pub create_missing: bool,
+ /// Should the default preprocessors always be used when they are
+ /// compatible with the renderer?
+ pub use_default_preprocessors: bool,
+}
+
+impl Default for BuildConfig {
+ fn default() -> BuildConfig {
+ BuildConfig {
+ build_dir: PathBuf::from("book"),
+ create_missing: true,
+ use_default_preprocessors: true,
+ }
+ }
+}
+
+/// Configuration for the Rust compiler(e.g., for playground)
+#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(default, rename_all = "kebab-case")]
+pub struct RustConfig {
+ /// Rust edition used in playground
+ pub edition: Option<RustEdition>,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
+/// Rust edition to use for the code.
+pub enum RustEdition {
+ /// The 2021 edition of Rust
+ #[serde(rename = "2021")]
+ E2021,
+ /// The 2018 edition of Rust
+ #[serde(rename = "2018")]
+ E2018,
+ /// The 2015 edition of Rust
+ #[serde(rename = "2015")]
+ E2015,
+}
+
+/// Configuration for the HTML renderer.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(default, rename_all = "kebab-case")]
+pub struct HtmlConfig {
+ /// The theme directory, if specified.
+ pub theme: Option<PathBuf>,
+ /// The default theme to use, defaults to 'light'
+ pub default_theme: Option<String>,
+ /// The theme to use if the browser requests the dark version of the site.
+ /// Defaults to 'navy'.
+ pub preferred_dark_theme: Option<String>,
+ /// Use "smart quotes" instead of the usual `"` character.
+ pub curly_quotes: bool,
+ /// Should mathjax be enabled?
+ pub mathjax_support: bool,
+ /// Whether to fonts.css and respective font files to the output directory.
+ pub copy_fonts: bool,
+ /// An optional google analytics code.
+ pub google_analytics: Option<String>,
+ /// Additional CSS stylesheets to include in the rendered page's `<head>`.
+ pub additional_css: Vec<PathBuf>,
+ /// Additional JS scripts to include at the bottom of the rendered page's
+ /// `<body>`.
+ pub additional_js: Vec<PathBuf>,
+ /// Fold settings.
+ pub fold: Fold,
+ /// Playground settings.
+ #[serde(alias = "playpen")]
+ pub playground: Playground,
+ /// Print settings.
+ pub print: Print,
+ /// Don't render section labels.
+ pub no_section_label: bool,
+ /// Search settings. If `None`, the default will be used.
+ pub search: Option<Search>,
+ /// Git repository url. If `None`, the git button will not be shown.
+ pub git_repository_url: Option<String>,
+ /// FontAwesome icon class to use for the Git repository link.
+ /// Defaults to `fa-github` if `None`.
+ pub git_repository_icon: Option<String>,
+ /// Input path for the 404 file, defaults to 404.md, set to "" to disable 404 file output
+ pub input_404: Option<String>,
+ /// Absolute url to site, used to emit correct paths for the 404 page, which might be accessed in a deeply nested directory
+ pub site_url: Option<String>,
+ /// The DNS subdomain or apex domain at which your book will be hosted. This
+ /// string will be written to a file named CNAME in the root of your site,
+ /// as required by GitHub Pages (see [*Managing a custom domain for your
+ /// GitHub Pages site*][custom domain]).
+ ///
+ /// [custom domain]: https://docs.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site
+ pub cname: Option<String>,
+ /// Edit url template, when set shows a "Suggest an edit" button for
+ /// directly jumping to editing the currently viewed page.
+ /// Contains {path} that is replaced with chapter source file path
+ pub edit_url_template: Option<String>,
+ /// Endpoint of websocket, for livereload usage. Value loaded from .toml file
+ /// is ignored, because our code overrides this field with the value [`LIVE_RELOAD_ENDPOINT`]
+ ///
+ /// [`LIVE_RELOAD_ENDPOINT`]: cmd::serve::LIVE_RELOAD_ENDPOINT
+ ///
+ /// This config item *should not be edited* by the end user.
+ #[doc(hidden)]
+ pub live_reload_endpoint: Option<String>,
+ /// The mapping from old pages to new pages/URLs to use when generating
+ /// redirects.
+ pub redirect: HashMap<String, String>,
+}
+
+impl Default for HtmlConfig {
+ fn default() -> HtmlConfig {
+ HtmlConfig {
+ theme: None,
+ default_theme: None,
+ preferred_dark_theme: None,
+ curly_quotes: false,
+ mathjax_support: false,
+ copy_fonts: true,
+ google_analytics: None,
+ additional_css: Vec::new(),
+ additional_js: Vec::new(),
+ fold: Fold::default(),
+ playground: Playground::default(),
+ print: Print::default(),
+ no_section_label: false,
+ search: None,
+ git_repository_url: None,
+ git_repository_icon: None,
+ edit_url_template: None,
+ input_404: None,
+ site_url: None,
+ cname: None,
+ live_reload_endpoint: None,
+ redirect: HashMap::new(),
+ }
+ }
+}
+
+impl HtmlConfig {
+ /// Returns the directory of theme from the provided root directory. If the
+ /// directory is not present it will append the default directory of "theme"
+ pub fn theme_dir(&self, root: &Path) -> PathBuf {
+ match self.theme {
+ Some(ref d) => root.join(d),
+ None => root.join("theme"),
+ }
+ }
+}
+
+/// Configuration for how to render the print icon, print.html, and print.css.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(default, rename_all = "kebab-case")]
+pub struct Print {
+ /// Whether print support is enabled.
+ pub enable: bool,
+ /// Insert page breaks between chapters. Default: `true`.
+ pub page_break: bool,
+}
+
+impl Default for Print {
+ fn default() -> Self {
+ Self {
+ enable: true,
+ page_break: true,
+ }
+ }
+}
+
+/// Configuration for how to fold chapters of sidebar.
+#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(default, rename_all = "kebab-case")]
+pub struct Fold {
+ /// When off, all folds are open. Default: `false`.
+ pub enable: bool,
+ /// The higher the more folded regions are open. When level is 0, all folds
+ /// are closed.
+ /// Default: `0`.
+ pub level: u8,
+}
+
+/// Configuration for tweaking how the the HTML renderer handles the playground.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(default, rename_all = "kebab-case")]
+pub struct Playground {
+ /// Should playground snippets be editable? Default: `false`.
+ pub editable: bool,
+ /// Display the copy button. Default: `true`.
+ pub copyable: bool,
+ /// Copy JavaScript files for the editor to the output directory?
+ /// Default: `true`.
+ pub copy_js: bool,
+ /// Display line numbers on playground snippets. Default: `false`.
+ pub line_numbers: bool,
+ /// Display the run button. Default: `true`
+ pub runnable: bool,
+}
+
+impl Default for Playground {
+ fn default() -> Playground {
+ Playground {
+ editable: false,
+ copyable: true,
+ copy_js: true,
+ line_numbers: false,
+ runnable: true,
+ }
+ }
+}
+
+/// Configuration of the search functionality of the HTML renderer.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(default, rename_all = "kebab-case")]
+pub struct Search {
+ /// Enable the search feature. Default: `true`.
+ pub enable: bool,
+ /// Maximum number of visible results. Default: `30`.
+ pub limit_results: u32,
+ /// The number of words used for a search result teaser. Default: `30`.
+ pub teaser_word_count: u32,
+ /// Define the logical link between multiple search words.
+ /// If true, all search words must appear in each result. Default: `false`.
+ pub use_boolean_and: bool,
+ /// Boost factor for the search result score if a search word appears in the header.
+ /// Default: `2`.
+ pub boost_title: u8,
+ /// Boost factor for the search result score if a search word appears in the hierarchy.
+ /// The hierarchy contains all titles of the parent documents and all parent headings.
+ /// Default: `1`.
+ pub boost_hierarchy: u8,
+ /// Boost factor for the search result score if a search word appears in the text.
+ /// Default: `1`.
+ pub boost_paragraph: u8,
+ /// True if the searchword `micro` should match `microwave`. Default: `true`.
+ pub expand: bool,
+ /// Documents are split into smaller parts, separated by headings. This defines, until which
+ /// level of heading documents should be split. Default: `3`. (`### This is a level 3 heading`)
+ pub heading_split_level: u8,
+ /// Copy JavaScript files for the search functionality to the output directory?
+ /// Default: `true`.
+ pub copy_js: bool,
+}
+
+impl Default for Search {
+ fn default() -> Search {
+ // Please update the documentation of `Search` when changing values!
+ Search {
+ enable: true,
+ limit_results: 30,
+ teaser_word_count: 30,
+ use_boolean_and: false,
+ boost_title: 2,
+ boost_hierarchy: 1,
+ boost_paragraph: 1,
+ expand: true,
+ heading_split_level: 3,
+ copy_js: true,
+ }
+ }
+}
+
+/// Allows you to "update" any arbitrary field in a struct by round-tripping via
+/// a `toml::Value`.
+///
+/// This is definitely not the most performant way to do things, which means you
+/// should probably keep it away from tight loops...
+trait Updateable<'de>: Serialize + Deserialize<'de> {
+ fn update_value<S: Serialize>(&mut self, key: &str, value: S) {
+ let mut raw = Value::try_from(&self).expect("unreachable");
+
+ if let Ok(value) = Value::try_from(value) {
+ let _ = raw.insert(key, value);
+ } else {
+ return;
+ }
+
+ if let Ok(updated) = raw.try_into() {
+ *self = updated;
+ }
+ }
+}
+
+impl<'de, T> Updateable<'de> for T where T: Serialize + Deserialize<'de> {}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::utils::fs::get_404_output_file;
+
+ const COMPLEX_CONFIG: &str = r#"
+ [book]
+ title = "Some Book"
+ authors = ["Michael-F-Bryan <michaelfbryan@gmail.com>"]
+ description = "A completely useless book"
+ multilingual = true
+ src = "source"
+ language = "ja"
+
+ [build]
+ build-dir = "outputs"
+ create-missing = false
+ use-default-preprocessors = true
+
+ [output.html]
+ theme = "./themedir"
+ default-theme = "rust"
+ curly-quotes = true
+ google-analytics = "123456"
+ additional-css = ["./foo/bar/baz.css"]
+ git-repository-url = "https://foo.com/"
+ git-repository-icon = "fa-code-fork"
+
+ [output.html.playground]
+ editable = true
+ editor = "ace"
+
+ [output.html.redirect]
+ "index.html" = "overview.html"
+ "nexted/page.md" = "https://rust-lang.org/"
+
+ [preprocessor.first]
+
+ [preprocessor.second]
+ "#;
+
+ #[test]
+ fn load_a_complex_config_file() {
+ let src = COMPLEX_CONFIG;
+
+ let book_should_be = BookConfig {
+ title: Some(String::from("Some Book")),
+ authors: vec![String::from("Michael-F-Bryan <michaelfbryan@gmail.com>")],
+ description: Some(String::from("A completely useless book")),
+ multilingual: true,
+ src: PathBuf::from("source"),
+ language: Some(String::from("ja")),
+ };
+ let build_should_be = BuildConfig {
+ build_dir: PathBuf::from("outputs"),
+ create_missing: false,
+ use_default_preprocessors: true,
+ };
+ let rust_should_be = RustConfig { edition: None };
+ let playground_should_be = Playground {
+ editable: true,
+ copyable: true,
+ copy_js: true,
+ line_numbers: false,
+ runnable: true,
+ };
+ let html_should_be = HtmlConfig {
+ curly_quotes: true,
+ google_analytics: Some(String::from("123456")),
+ additional_css: vec![PathBuf::from("./foo/bar/baz.css")],
+ theme: Some(PathBuf::from("./themedir")),
+ default_theme: Some(String::from("rust")),
+ playground: playground_should_be,
+ git_repository_url: Some(String::from("https://foo.com/")),
+ git_repository_icon: Some(String::from("fa-code-fork")),
+ redirect: vec![
+ (String::from("index.html"), String::from("overview.html")),
+ (
+ String::from("nexted/page.md"),
+ String::from("https://rust-lang.org/"),
+ ),
+ ]
+ .into_iter()
+ .collect(),
+ ..Default::default()
+ };
+
+ let got = Config::from_str(src).unwrap();
+
+ assert_eq!(got.book, book_should_be);
+ assert_eq!(got.build, build_should_be);
+ assert_eq!(got.rust, rust_should_be);
+ assert_eq!(got.html_config().unwrap(), html_should_be);
+ }
+
+ #[test]
+ fn disable_runnable() {
+ let src = r#"
+ [book]
+ title = "Some Book"
+ description = "book book book"
+ authors = ["Shogo Takata"]
+
+ [output.html.playground]
+ runnable = false
+ "#;
+
+ let got = Config::from_str(src).unwrap();
+ assert!(!got.html_config().unwrap().playground.runnable);
+ }
+
+ #[test]
+ fn edition_2015() {
+ let src = r#"
+ [book]
+ title = "mdBook Documentation"
+ description = "Create book from markdown files. Like Gitbook but implemented in Rust"
+ authors = ["Mathieu David"]
+ src = "./source"
+ [rust]
+ edition = "2015"
+ "#;
+
+ let book_should_be = BookConfig {
+ title: Some(String::from("mdBook Documentation")),
+ description: Some(String::from(
+ "Create book from markdown files. Like Gitbook but implemented in Rust",
+ )),
+ authors: vec![String::from("Mathieu David")],
+ src: PathBuf::from("./source"),
+ ..Default::default()
+ };
+
+ let got = Config::from_str(src).unwrap();
+ assert_eq!(got.book, book_should_be);
+
+ let rust_should_be = RustConfig {
+ edition: Some(RustEdition::E2015),
+ };
+ let got = Config::from_str(src).unwrap();
+ assert_eq!(got.rust, rust_should_be);
+ }
+
+ #[test]
+ fn edition_2018() {
+ let src = r#"
+ [book]
+ title = "mdBook Documentation"
+ description = "Create book from markdown files. Like Gitbook but implemented in Rust"
+ authors = ["Mathieu David"]
+ src = "./source"
+ [rust]
+ edition = "2018"
+ "#;
+
+ let rust_should_be = RustConfig {
+ edition: Some(RustEdition::E2018),
+ };
+
+ let got = Config::from_str(src).unwrap();
+ assert_eq!(got.rust, rust_should_be);
+ }
+
+ #[test]
+ fn edition_2021() {
+ let src = r#"
+ [book]
+ title = "mdBook Documentation"
+ description = "Create book from markdown files. Like Gitbook but implemented in Rust"
+ authors = ["Mathieu David"]
+ src = "./source"
+ [rust]
+ edition = "2021"
+ "#;
+
+ let rust_should_be = RustConfig {
+ edition: Some(RustEdition::E2021),
+ };
+
+ let got = Config::from_str(src).unwrap();
+ assert_eq!(got.rust, rust_should_be);
+ }
+
+ #[test]
+ fn load_arbitrary_output_type() {
+ #[derive(Debug, Deserialize, PartialEq)]
+ struct RandomOutput {
+ foo: u32,
+ bar: String,
+ baz: Vec<bool>,
+ }
+
+ let src = r#"
+ [output.random]
+ foo = 5
+ bar = "Hello World"
+ baz = [true, true, false]
+ "#;
+
+ let should_be = RandomOutput {
+ foo: 5,
+ bar: String::from("Hello World"),
+ baz: vec![true, true, false],
+ };
+
+ let cfg = Config::from_str(src).unwrap();
+ let got: RandomOutput = cfg.get_deserialized_opt("output.random").unwrap().unwrap();
+
+ assert_eq!(got, should_be);
+
+ let got_baz: Vec<bool> = cfg
+ .get_deserialized_opt("output.random.baz")
+ .unwrap()
+ .unwrap();
+ let baz_should_be = vec![true, true, false];
+
+ assert_eq!(got_baz, baz_should_be);
+ }
+
+ #[test]
+ fn mutate_some_stuff() {
+ // really this is just a sanity check to make sure the borrow checker
+ // is happy...
+ let src = COMPLEX_CONFIG;
+ let mut config = Config::from_str(src).unwrap();
+ let key = "output.html.playground.editable";
+
+ assert_eq!(config.get(key).unwrap(), &Value::Boolean(true));
+ *config.get_mut(key).unwrap() = Value::Boolean(false);
+ assert_eq!(config.get(key).unwrap(), &Value::Boolean(false));
+ }
+
+ /// The config file format has slightly changed (metadata stuff is now under
+ /// the `book` table instead of being at the top level) so we're adding a
+ /// **temporary** compatibility check. You should be able to still load the
+ /// old format, emitting a warning.
+ #[test]
+ fn can_still_load_the_previous_format() {
+ let src = r#"
+ title = "mdBook Documentation"
+ description = "Create book from markdown files. Like Gitbook but implemented in Rust"
+ authors = ["Mathieu David"]
+ source = "./source"
+
+ [output.html]
+ destination = "my-book" # the output files will be generated in `root/my-book` instead of `root/book`
+ theme = "my-theme"
+ curly-quotes = true
+ google-analytics = "123456"
+ additional-css = ["custom.css", "custom2.css"]
+ additional-js = ["custom.js"]
+ "#;
+
+ let book_should_be = BookConfig {
+ title: Some(String::from("mdBook Documentation")),
+ description: Some(String::from(
+ "Create book from markdown files. Like Gitbook but implemented in Rust",
+ )),
+ authors: vec![String::from("Mathieu David")],
+ src: PathBuf::from("./source"),
+ ..Default::default()
+ };
+
+ let build_should_be = BuildConfig {
+ build_dir: PathBuf::from("my-book"),
+ create_missing: true,
+ use_default_preprocessors: true,
+ };
+
+ let html_should_be = HtmlConfig {
+ theme: Some(PathBuf::from("my-theme")),
+ curly_quotes: true,
+ google_analytics: Some(String::from("123456")),
+ additional_css: vec![PathBuf::from("custom.css"), PathBuf::from("custom2.css")],
+ additional_js: vec![PathBuf::from("custom.js")],
+ ..Default::default()
+ };
+
+ let got = Config::from_str(src).unwrap();
+ assert_eq!(got.book, book_should_be);
+ assert_eq!(got.build, build_should_be);
+ assert_eq!(got.html_config().unwrap(), html_should_be);
+ }
+
+ #[test]
+ fn set_a_config_item() {
+ let mut cfg = Config::default();
+ let key = "foo.bar.baz";
+ let value = "Something Interesting";
+
+ assert!(cfg.get(key).is_none());
+ cfg.set(key, value).unwrap();
+
+ let got: String = cfg.get_deserialized_opt(key).unwrap().unwrap();
+ assert_eq!(got, value);
+ }
+
+ #[test]
+ fn parse_env_vars() {
+ let inputs = vec![
+ ("FOO", None),
+ ("MDBOOK_foo", Some("foo")),
+ ("MDBOOK_FOO__bar__baz", Some("foo.bar.baz")),
+ ("MDBOOK_FOO_bar__baz", Some("foo-bar.baz")),
+ ];
+
+ for (src, should_be) in inputs {
+ let got = parse_env(src);
+ let should_be = should_be.map(ToString::to_string);
+
+ assert_eq!(got, should_be);
+ }
+ }
+
+ fn encode_env_var(key: &str) -> String {
+ format!(
+ "MDBOOK_{}",
+ key.to_uppercase().replace('.', "__").replace('-', "_")
+ )
+ }
+
+ #[test]
+ fn update_config_using_env_var() {
+ let mut cfg = Config::default();
+ let key = "foo.bar";
+ let value = "baz";
+
+ assert!(cfg.get(key).is_none());
+
+ let encoded_key = encode_env_var(key);
+ env::set_var(encoded_key, value);
+
+ cfg.update_from_env();
+
+ assert_eq!(
+ cfg.get_deserialized_opt::<String, _>(key).unwrap().unwrap(),
+ value
+ );
+ }
+
+ #[test]
+ fn update_config_using_env_var_and_complex_value() {
+ let mut cfg = Config::default();
+ let key = "foo-bar.baz";
+ let value = json!({"array": [1, 2, 3], "number": 13.37});
+ let value_str = serde_json::to_string(&value).unwrap();
+
+ assert!(cfg.get(key).is_none());
+
+ let encoded_key = encode_env_var(key);
+ env::set_var(encoded_key, value_str);
+
+ cfg.update_from_env();
+
+ assert_eq!(
+ cfg.get_deserialized_opt::<serde_json::Value, _>(key)
+ .unwrap()
+ .unwrap(),
+ value
+ );
+ }
+
+ #[test]
+ fn update_book_title_via_env() {
+ let mut cfg = Config::default();
+ let should_be = "Something else".to_string();
+
+ assert_ne!(cfg.book.title, Some(should_be.clone()));
+
+ env::set_var("MDBOOK_BOOK__TITLE", &should_be);
+ cfg.update_from_env();
+
+ assert_eq!(cfg.book.title, Some(should_be));
+ }
+
+ #[test]
+ fn file_404_default() {
+ let src = r#"
+ [output.html]
+ destination = "my-book"
+ "#;
+
+ let got = Config::from_str(src).unwrap();
+ let html_config = got.html_config().unwrap();
+ assert_eq!(html_config.input_404, None);
+ assert_eq!(&get_404_output_file(&html_config.input_404), "404.html");
+ }
+
+ #[test]
+ fn file_404_custom() {
+ let src = r#"
+ [output.html]
+ input-404= "missing.md"
+ output-404= "missing.html"
+ "#;
+
+ let got = Config::from_str(src).unwrap();
+ let html_config = got.html_config().unwrap();
+ assert_eq!(html_config.input_404, Some("missing.md".to_string()));
+ assert_eq!(&get_404_output_file(&html_config.input_404), "missing.html");
+ }
+
+ #[test]
+ #[should_panic(expected = "Invalid configuration file")]
+ fn invalid_language_type_error() {
+ let src = r#"
+ [book]
+ title = "mdBook Documentation"
+ language = ["en", "pt-br"]
+ description = "Create book from markdown files. Like Gitbook but implemented in Rust"
+ authors = ["Mathieu David"]
+ src = "./source"
+ "#;
+
+ Config::from_str(src).unwrap();
+ }
+
+ #[test]
+ #[should_panic(expected = "Invalid configuration file")]
+ fn invalid_title_type() {
+ let src = r#"
+ [book]
+ title = 20
+ language = "en"
+ description = "Create book from markdown files. Like Gitbook but implemented in Rust"
+ authors = ["Mathieu David"]
+ src = "./source"
+ "#;
+
+ Config::from_str(src).unwrap();
+ }
+
+ #[test]
+ #[should_panic(expected = "Invalid configuration file")]
+ fn invalid_build_dir_type() {
+ let src = r#"
+ [build]
+ build-dir = 99
+ create-missing = false
+ "#;
+
+ Config::from_str(src).unwrap();
+ }
+
+ #[test]
+ #[should_panic(expected = "Invalid configuration file")]
+ fn invalid_rust_edition() {
+ let src = r#"
+ [rust]
+ edition = "1999"
+ "#;
+
+ Config::from_str(src).unwrap();
+ }
+
+ #[test]
+ fn print_config() {
+ let src = r#"
+ [output.html.print]
+ enable = false
+ "#;
+ let got = Config::from_str(src).unwrap();
+ let html_config = got.html_config().unwrap();
+ assert!(!html_config.print.enable);
+ assert!(html_config.print.page_break);
+ let src = r#"
+ [output.html.print]
+ page-break = false
+ "#;
+ let got = Config::from_str(src).unwrap();
+ let html_config = got.html_config().unwrap();
+ assert!(html_config.print.enable);
+ assert!(!html_config.print.page_break);
+ }
+}
diff --git a/vendor/mdbook/src/lib.rs b/vendor/mdbook/src/lib.rs
new file mode 100644
index 000000000..cc62b0abd
--- /dev/null
+++ b/vendor/mdbook/src/lib.rs
@@ -0,0 +1,119 @@
+//! # mdBook
+//!
+//! **mdBook** is a tool for rendering a collection of markdown documents into
+//! a form more suitable for end users like HTML or EPUB. It offers a command
+//! line interface, but this crate can be used if more control is required.
+//!
+//! This is the API doc, the [user guide] is also available if you want
+//! information about the command line tool, format, structure etc. It is also
+//! rendered with mdBook to showcase the features and default theme.
+//!
+//! Some reasons why you would want to use the crate (over the cli):
+//!
+//! - Integrate mdbook in a current project
+//! - Extend the capabilities of mdBook
+//! - Do some processing or test before building your book
+//! - Accessing the public API to help create a new Renderer
+//! - ...
+//!
+//! > **Note:** While we try to ensure `mdbook`'s command-line interface and
+//! > behaviour are backwards compatible, the tool's internals are still
+//! > evolving and being iterated on. If you wish to prevent accidental
+//! > breakages it is recommended to pin any tools building on top of the
+//! > `mdbook` crate to a specific release.
+//!
+//! # Examples
+//!
+//! If creating a new book from scratch, you'll want to get a `BookBuilder` via
+//! the `MDBook::init()` method.
+//!
+//! ```rust,no_run
+//! use mdbook::MDBook;
+//! use mdbook::config::Config;
+//!
+//! let root_dir = "/path/to/book/root";
+//!
+//! // create a default config and change a couple things
+//! let mut cfg = Config::default();
+//! cfg.book.title = Some("My Book".to_string());
+//! cfg.book.authors.push("Michael-F-Bryan".to_string());
+//!
+//! MDBook::init(root_dir)
+//! .create_gitignore(true)
+//! .with_config(cfg)
+//! .build()
+//! .expect("Book generation failed");
+//! ```
+//!
+//! You can also load an existing book and build it.
+//!
+//! ```rust,no_run
+//! use mdbook::MDBook;
+//!
+//! let root_dir = "/path/to/book/root";
+//!
+//! let mut md = MDBook::load(root_dir)
+//! .expect("Unable to load the book");
+//! md.build().expect("Building failed");
+//! ```
+//!
+//! ## Implementing a new Backend
+//!
+//! `mdbook` has a fairly flexible mechanism for creating additional backends
+//! for your book. The general idea is you'll add an extra table in the book's
+//! `book.toml` which specifies an executable to be invoked by `mdbook`. This
+//! executable will then be called during a build, with an in-memory
+//! representation ([`RenderContext`]) of the book being passed to the
+//! subprocess via `stdin`.
+//!
+//! The [`RenderContext`] gives the backend access to the contents of
+//! `book.toml` and lets it know which directory all generated artefacts should
+//! be placed in. For a much more in-depth explanation, consult the [relevant
+//! chapter] in the *For Developers* section of the user guide.
+//!
+//! To make creating a backend easier, the `mdbook` crate can be imported
+//! directly, making deserializing the `RenderContext` easy and giving you
+//! access to the various methods for working with the [`Config`].
+//!
+//! [user guide]: https://rust-lang.github.io/mdBook/
+//! [`RenderContext`]: renderer::RenderContext
+//! [relevant chapter]: https://rust-lang.github.io/mdBook/for_developers/backends.html
+//! [`Config`]: config::Config
+
+#![deny(missing_docs)]
+#![deny(rust_2018_idioms)]
+
+#[macro_use]
+extern crate lazy_static;
+#[macro_use]
+extern crate log;
+#[macro_use]
+extern crate serde_json;
+
+#[cfg(test)]
+#[macro_use]
+extern crate pretty_assertions;
+
+pub mod book;
+pub mod config;
+pub mod preprocess;
+pub mod renderer;
+pub mod theme;
+pub mod utils;
+
+/// The current version of `mdbook`.
+///
+/// This is provided as a way for custom preprocessors and renderers to do
+/// compatibility checks.
+pub const MDBOOK_VERSION: &str = env!("CARGO_PKG_VERSION");
+
+pub use crate::book::BookItem;
+pub use crate::book::MDBook;
+pub use crate::config::Config;
+pub use crate::renderer::Renderer;
+
+/// The error types used through out this crate.
+pub mod errors {
+ pub(crate) use anyhow::{bail, ensure, Context};
+ pub use anyhow::{Error, Result};
+}
diff --git a/vendor/mdbook/src/main.rs b/vendor/mdbook/src/main.rs
new file mode 100644
index 000000000..35562e64b
--- /dev/null
+++ b/vendor/mdbook/src/main.rs
@@ -0,0 +1,150 @@
+#[macro_use]
+extern crate clap;
+#[macro_use]
+extern crate log;
+
+use anyhow::anyhow;
+use chrono::Local;
+use clap::{App, AppSettings, Arg, ArgMatches};
+use clap_complete::Shell;
+use env_logger::Builder;
+use log::LevelFilter;
+use mdbook::utils;
+use std::env;
+use std::ffi::OsStr;
+use std::io::Write;
+use std::path::{Path, PathBuf};
+
+mod cmd;
+
+const VERSION: &str = concat!("v", crate_version!());
+
+fn main() {
+ init_logger();
+
+ let app = create_clap_app();
+
+ // Check which subcomamnd the user ran...
+ let res = match app.get_matches().subcommand() {
+ Some(("init", sub_matches)) => cmd::init::execute(sub_matches),
+ Some(("build", sub_matches)) => cmd::build::execute(sub_matches),
+ Some(("clean", sub_matches)) => cmd::clean::execute(sub_matches),
+ #[cfg(feature = "watch")]
+ Some(("watch", sub_matches)) => cmd::watch::execute(sub_matches),
+ #[cfg(feature = "serve")]
+ Some(("serve", sub_matches)) => cmd::serve::execute(sub_matches),
+ Some(("test", sub_matches)) => cmd::test::execute(sub_matches),
+ Some(("completions", sub_matches)) => (|| {
+ let shell: Shell = sub_matches
+ .value_of("shell")
+ .ok_or_else(|| anyhow!("Shell name missing."))?
+ .parse()
+ .map_err(|s| anyhow!("Invalid shell: {}", s))?;
+
+ let mut complete_app = create_clap_app();
+ clap_complete::generate(
+ shell,
+ &mut complete_app,
+ "mdbook",
+ &mut std::io::stdout().lock(),
+ );
+ Ok(())
+ })(),
+ _ => unreachable!(),
+ };
+
+ if let Err(e) = res {
+ utils::log_backtrace(&e);
+
+ std::process::exit(101);
+ }
+}
+
+/// Create a list of valid arguments and sub-commands
+fn create_clap_app() -> App<'static> {
+ let app = App::new(crate_name!())
+ .about(crate_description!())
+ .author("Mathieu David <mathieudavid@mathieudavid.org>")
+ .version(VERSION)
+ .setting(AppSettings::PropagateVersion)
+ .setting(AppSettings::ArgRequiredElseHelp)
+ .after_help(
+ "For more information about a specific command, try `mdbook <command> --help`\n\
+ The source code for mdBook is available at: https://github.com/rust-lang/mdBook",
+ )
+ .subcommand(cmd::init::make_subcommand())
+ .subcommand(cmd::build::make_subcommand())
+ .subcommand(cmd::test::make_subcommand())
+ .subcommand(cmd::clean::make_subcommand())
+ .subcommand(
+ App::new("completions")
+ .about("Generate shell completions for your shell to stdout")
+ .arg(
+ Arg::new("shell")
+ .takes_value(true)
+ .possible_values(Shell::possible_values())
+ .help("the shell to generate completions for")
+ .value_name("SHELL")
+ .required(true),
+ ),
+ );
+
+ #[cfg(feature = "watch")]
+ let app = app.subcommand(cmd::watch::make_subcommand());
+ #[cfg(feature = "serve")]
+ let app = app.subcommand(cmd::serve::make_subcommand());
+
+ app
+}
+
+fn init_logger() {
+ let mut builder = Builder::new();
+
+ builder.format(|formatter, record| {
+ writeln!(
+ formatter,
+ "{} [{}] ({}): {}",
+ Local::now().format("%Y-%m-%d %H:%M:%S"),
+ record.level(),
+ record.target(),
+ record.args()
+ )
+ });
+
+ if let Ok(var) = env::var("RUST_LOG") {
+ builder.parse_filters(&var);
+ } else {
+ // if no RUST_LOG provided, default to logging at the Info level
+ builder.filter(None, LevelFilter::Info);
+ // Filter extraneous html5ever not-implemented messages
+ builder.filter(Some("html5ever"), LevelFilter::Error);
+ }
+
+ builder.init();
+}
+
+fn get_book_dir(args: &ArgMatches) -> PathBuf {
+ if let Some(dir) = args.value_of("dir") {
+ // Check if path is relative from current dir, or absolute...
+ let p = Path::new(dir);
+ if p.is_relative() {
+ env::current_dir().unwrap().join(dir)
+ } else {
+ p.to_path_buf()
+ }
+ } else {
+ env::current_dir().expect("Unable to determine the current directory")
+ }
+}
+
+fn open<P: AsRef<OsStr>>(path: P) {
+ info!("Opening web browser");
+ if let Err(e) = opener::open(path) {
+ error!("Error opening web browser: {}", e);
+ }
+}
+
+#[test]
+fn verify_app() {
+ create_clap_app().debug_assert();
+}
diff --git a/vendor/mdbook/src/preprocess/cmd.rs b/vendor/mdbook/src/preprocess/cmd.rs
new file mode 100644
index 000000000..c47fd5d22
--- /dev/null
+++ b/vendor/mdbook/src/preprocess/cmd.rs
@@ -0,0 +1,207 @@
+use super::{Preprocessor, PreprocessorContext};
+use crate::book::Book;
+use crate::errors::*;
+use shlex::Shlex;
+use std::io::{self, Read, Write};
+use std::process::{Child, Command, Stdio};
+
+/// A custom preprocessor which will shell out to a 3rd-party program.
+///
+/// # Preprocessing Protocol
+///
+/// When the `supports_renderer()` method is executed, `CmdPreprocessor` will
+/// execute the shell command `$cmd supports $renderer`. If the renderer is
+/// supported, custom preprocessors should exit with a exit code of `0`,
+/// any other exit code be considered as unsupported.
+///
+/// The `run()` method is implemented by passing a `(PreprocessorContext, Book)`
+/// tuple to the spawned command (`$cmd`) as JSON via `stdin`. Preprocessors
+/// should then "return" a processed book by printing it to `stdout` as JSON.
+/// For convenience, the `CmdPreprocessor::parse_input()` function can be used
+/// to parse the input provided by `mdbook`.
+///
+/// Exiting with a non-zero exit code while preprocessing is considered an
+/// error. `stderr` is passed directly through to the user, so it can be used
+/// for logging or emitting warnings if desired.
+///
+/// # Examples
+///
+/// An example preprocessor is available in this project's `examples/`
+/// directory.
+#[derive(Debug, Clone, PartialEq)]
+pub struct CmdPreprocessor {
+ name: String,
+ cmd: String,
+}
+
+impl CmdPreprocessor {
+ /// Create a new `CmdPreprocessor`.
+ pub fn new(name: String, cmd: String) -> CmdPreprocessor {
+ CmdPreprocessor { name, cmd }
+ }
+
+ /// A convenience function custom preprocessors can use to parse the input
+ /// written to `stdin` by a `CmdRenderer`.
+ pub fn parse_input<R: Read>(reader: R) -> Result<(PreprocessorContext, Book)> {
+ serde_json::from_reader(reader).with_context(|| "Unable to parse the input")
+ }
+
+ fn write_input_to_child(&self, child: &mut Child, book: &Book, ctx: &PreprocessorContext) {
+ let stdin = child.stdin.take().expect("Child has stdin");
+
+ if let Err(e) = self.write_input(stdin, book, ctx) {
+ // Looks like the backend hung up before we could finish
+ // sending it the render context. Log the error and keep going
+ warn!("Error writing the RenderContext to the backend, {}", e);
+ }
+ }
+
+ fn write_input<W: Write>(
+ &self,
+ writer: W,
+ book: &Book,
+ ctx: &PreprocessorContext,
+ ) -> Result<()> {
+ serde_json::to_writer(writer, &(ctx, book)).map_err(Into::into)
+ }
+
+ /// The command this `Preprocessor` will invoke.
+ pub fn cmd(&self) -> &str {
+ &self.cmd
+ }
+
+ fn command(&self) -> Result<Command> {
+ let mut words = Shlex::new(&self.cmd);
+ let executable = match words.next() {
+ Some(e) => e,
+ None => bail!("Command string was empty"),
+ };
+
+ let mut cmd = Command::new(executable);
+
+ for arg in words {
+ cmd.arg(arg);
+ }
+
+ Ok(cmd)
+ }
+}
+
+impl Preprocessor for CmdPreprocessor {
+ fn name(&self) -> &str {
+ &self.name
+ }
+
+ fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book> {
+ let mut cmd = self.command()?;
+
+ let mut child = cmd
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::inherit())
+ .spawn()
+ .with_context(|| {
+ format!(
+ "Unable to start the \"{}\" preprocessor. Is it installed?",
+ self.name()
+ )
+ })?;
+
+ self.write_input_to_child(&mut child, &book, ctx);
+
+ let output = child.wait_with_output().with_context(|| {
+ format!(
+ "Error waiting for the \"{}\" preprocessor to complete",
+ self.name
+ )
+ })?;
+
+ trace!("{} exited with output: {:?}", self.cmd, output);
+ ensure!(
+ output.status.success(),
+ format!(
+ "The \"{}\" preprocessor exited unsuccessfully with {} status",
+ self.name, output.status
+ )
+ );
+
+ serde_json::from_slice(&output.stdout).with_context(|| {
+ format!(
+ "Unable to parse the preprocessed book from \"{}\" processor",
+ self.name
+ )
+ })
+ }
+
+ fn supports_renderer(&self, renderer: &str) -> bool {
+ debug!(
+ "Checking if the \"{}\" preprocessor supports \"{}\"",
+ self.name(),
+ renderer
+ );
+
+ let mut cmd = match self.command() {
+ Ok(c) => c,
+ Err(e) => {
+ warn!(
+ "Unable to create the command for the \"{}\" preprocessor, {}",
+ self.name(),
+ e
+ );
+ return false;
+ }
+ };
+
+ let outcome = cmd
+ .arg("supports")
+ .arg(renderer)
+ .stdin(Stdio::null())
+ .stdout(Stdio::inherit())
+ .stderr(Stdio::inherit())
+ .status()
+ .map(|status| status.code() == Some(0));
+
+ if let Err(ref e) = outcome {
+ if e.kind() == io::ErrorKind::NotFound {
+ warn!(
+ "The command wasn't found, is the \"{}\" preprocessor installed?",
+ self.name
+ );
+ warn!("\tCommand: {}", self.cmd);
+ }
+ }
+
+ outcome.unwrap_or(false)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::MDBook;
+ use std::path::Path;
+
+ fn guide() -> MDBook {
+ let example = Path::new(env!("CARGO_MANIFEST_DIR")).join("guide");
+ MDBook::load(example).unwrap()
+ }
+
+ #[test]
+ fn round_trip_write_and_parse_input() {
+ let cmd = CmdPreprocessor::new("test".to_string(), "test".to_string());
+ let md = guide();
+ let ctx = PreprocessorContext::new(
+ md.root.clone(),
+ md.config.clone(),
+ "some-renderer".to_string(),
+ );
+
+ let mut buffer = Vec::new();
+ cmd.write_input(&mut buffer, &md.book, &ctx).unwrap();
+
+ let (got_ctx, got_book) = CmdPreprocessor::parse_input(buffer.as_slice()).unwrap();
+
+ assert_eq!(got_book, md.book);
+ assert_eq!(got_ctx, ctx);
+ }
+}
diff --git a/vendor/mdbook/src/preprocess/index.rs b/vendor/mdbook/src/preprocess/index.rs
new file mode 100644
index 000000000..fd60ad4da
--- /dev/null
+++ b/vendor/mdbook/src/preprocess/index.rs
@@ -0,0 +1,105 @@
+use regex::Regex;
+use std::path::Path;
+
+use crate::errors::*;
+
+use super::{Preprocessor, PreprocessorContext};
+use crate::book::{Book, BookItem};
+
+/// A preprocessor for converting file name `README.md` to `index.md` since
+/// `README.md` is the de facto index file in markdown-based documentation.
+#[derive(Default)]
+pub struct IndexPreprocessor;
+
+impl IndexPreprocessor {
+ pub(crate) const NAME: &'static str = "index";
+
+ /// Create a new `IndexPreprocessor`.
+ pub fn new() -> Self {
+ IndexPreprocessor
+ }
+}
+
+impl Preprocessor for IndexPreprocessor {
+ fn name(&self) -> &str {
+ Self::NAME
+ }
+
+ fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book> {
+ let source_dir = ctx.root.join(&ctx.config.book.src);
+ book.for_each_mut(|section: &mut BookItem| {
+ if let BookItem::Chapter(ref mut ch) = *section {
+ if let Some(ref mut path) = ch.path {
+ if is_readme_file(&path) {
+ let mut index_md = source_dir.join(path.with_file_name("index.md"));
+ if index_md.exists() {
+ warn_readme_name_conflict(&path, &&mut index_md);
+ }
+
+ path.set_file_name("index.md");
+ }
+ }
+ }
+ });
+
+ Ok(book)
+ }
+}
+
+fn warn_readme_name_conflict<P: AsRef<Path>>(readme_path: P, index_path: P) {
+ let file_name = readme_path.as_ref().file_name().unwrap_or_default();
+ let parent_dir = index_path
+ .as_ref()
+ .parent()
+ .unwrap_or_else(|| index_path.as_ref());
+ warn!(
+ "It seems that there are both {:?} and index.md under \"{}\".",
+ file_name,
+ parent_dir.display()
+ );
+ warn!(
+ "mdbook converts {:?} into index.html by default. It may cause",
+ file_name
+ );
+ warn!("unexpected behavior if putting both files under the same directory.");
+ warn!("To solve the warning, try to rearrange the book structure or disable");
+ warn!("\"index\" preprocessor to stop the conversion.");
+}
+
+fn is_readme_file<P: AsRef<Path>>(path: P) -> bool {
+ lazy_static! {
+ static ref RE: Regex = Regex::new(r"(?i)^readme$").unwrap();
+ }
+ RE.is_match(
+ path.as_ref()
+ .file_stem()
+ .and_then(std::ffi::OsStr::to_str)
+ .unwrap_or_default(),
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn file_stem_exactly_matches_readme_case_insensitively() {
+ let path = "path/to/Readme.md";
+ assert!(is_readme_file(path));
+
+ let path = "path/to/README.md";
+ assert!(is_readme_file(path));
+
+ let path = "path/to/rEaDmE.md";
+ assert!(is_readme_file(path));
+
+ let path = "path/to/README.markdown";
+ assert!(is_readme_file(path));
+
+ let path = "path/to/README";
+ assert!(is_readme_file(path));
+
+ let path = "path/to/README-README.md";
+ assert!(!is_readme_file(path));
+ }
+}
diff --git a/vendor/mdbook/src/preprocess/links.rs b/vendor/mdbook/src/preprocess/links.rs
new file mode 100644
index 000000000..7ca6fd345
--- /dev/null
+++ b/vendor/mdbook/src/preprocess/links.rs
@@ -0,0 +1,937 @@
+use crate::errors::*;
+use crate::utils::{
+ take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines,
+ take_rustdoc_include_lines,
+};
+use regex::{CaptureMatches, Captures, Regex};
+use std::fs;
+use std::ops::{Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeTo};
+use std::path::{Path, PathBuf};
+
+use super::{Preprocessor, PreprocessorContext};
+use crate::book::{Book, BookItem};
+
+const ESCAPE_CHAR: char = '\\';
+const MAX_LINK_NESTED_DEPTH: usize = 10;
+
+/// A preprocessor for expanding helpers in a chapter. Supported helpers are:
+///
+/// - `{{# include}}` - Insert an external file of any type. Include the whole file, only particular
+///. lines, or only between the specified anchors.
+/// - `{{# rustdoc_include}}` - Insert an external Rust file, showing the particular lines
+///. specified or the lines between specified anchors, and include the rest of the file behind `#`.
+/// This hides the lines from initial display but shows them when the reader expands the code
+/// block and provides them to Rustdoc for testing.
+/// - `{{# playground}}` - Insert runnable Rust files
+/// - `{{# title}}` - Override \<title\> of a webpage.
+#[derive(Default)]
+pub struct LinkPreprocessor;
+
+impl LinkPreprocessor {
+ pub(crate) const NAME: &'static str = "links";
+
+ /// Create a new `LinkPreprocessor`.
+ pub fn new() -> Self {
+ LinkPreprocessor
+ }
+}
+
+impl Preprocessor for LinkPreprocessor {
+ fn name(&self) -> &str {
+ Self::NAME
+ }
+
+ fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book> {
+ let src_dir = ctx.root.join(&ctx.config.book.src);
+
+ book.for_each_mut(|section: &mut BookItem| {
+ if let BookItem::Chapter(ref mut ch) = *section {
+ if let Some(ref chapter_path) = ch.path {
+ let base = chapter_path
+ .parent()
+ .map(|dir| src_dir.join(dir))
+ .expect("All book items have a parent");
+
+ let mut chapter_title = ch.name.clone();
+ let content =
+ replace_all(&ch.content, base, chapter_path, 0, &mut chapter_title);
+ ch.content = content;
+ if chapter_title != ch.name {
+ ctx.chapter_titles
+ .borrow_mut()
+ .insert(chapter_path.clone(), chapter_title);
+ }
+ }
+ }
+ });
+
+ Ok(book)
+ }
+}
+
+fn replace_all<P1, P2>(
+ s: &str,
+ path: P1,
+ source: P2,
+ depth: usize,
+ chapter_title: &mut String,
+) -> String
+where
+ P1: AsRef<Path>,
+ P2: AsRef<Path>,
+{
+ // When replacing one thing in a string by something with a different length,
+ // the indices after that will not correspond,
+ // we therefore have to store the difference to correct this
+ let path = path.as_ref();
+ let source = source.as_ref();
+ let mut previous_end_index = 0;
+ let mut replaced = String::new();
+
+ for link in find_links(s) {
+ replaced.push_str(&s[previous_end_index..link.start_index]);
+
+ match link.render_with_path(&path, chapter_title) {
+ Ok(new_content) => {
+ if depth < MAX_LINK_NESTED_DEPTH {
+ if let Some(rel_path) = link.link_type.relative_path(path) {
+ replaced.push_str(&replace_all(
+ &new_content,
+ rel_path,
+ source,
+ depth + 1,
+ chapter_title,
+ ));
+ } else {
+ replaced.push_str(&new_content);
+ }
+ } else {
+ error!(
+ "Stack depth exceeded in {}. Check for cyclic includes",
+ source.display()
+ );
+ }
+ previous_end_index = link.end_index;
+ }
+ Err(e) => {
+ error!("Error updating \"{}\", {}", link.link_text, e);
+ for cause in e.chain().skip(1) {
+ warn!("Caused By: {}", cause);
+ }
+
+ // This should make sure we include the raw `{{# ... }}` snippet
+ // in the page content if there are any errors.
+ previous_end_index = link.start_index;
+ }
+ }
+ }
+
+ replaced.push_str(&s[previous_end_index..]);
+ replaced
+}
+
+#[derive(PartialEq, Debug, Clone)]
+enum LinkType<'a> {
+ Escaped,
+ Include(PathBuf, RangeOrAnchor),
+ Playground(PathBuf, Vec<&'a str>),
+ RustdocInclude(PathBuf, RangeOrAnchor),
+ Title(&'a str),
+}
+
+#[derive(PartialEq, Debug, Clone)]
+enum RangeOrAnchor {
+ Range(LineRange),
+ Anchor(String),
+}
+
+// A range of lines specified with some include directive.
+#[allow(clippy::enum_variant_names)] // The prefix can't be removed, and is meant to mirror the contained type
+#[derive(PartialEq, Debug, Clone)]
+enum LineRange {
+ Range(Range<usize>),
+ RangeFrom(RangeFrom<usize>),
+ RangeTo(RangeTo<usize>),
+ RangeFull(RangeFull),
+}
+
+impl RangeBounds<usize> for LineRange {
+ fn start_bound(&self) -> Bound<&usize> {
+ match self {
+ LineRange::Range(r) => r.start_bound(),
+ LineRange::RangeFrom(r) => r.start_bound(),
+ LineRange::RangeTo(r) => r.start_bound(),
+ LineRange::RangeFull(r) => r.start_bound(),
+ }
+ }
+
+ fn end_bound(&self) -> Bound<&usize> {
+ match self {
+ LineRange::Range(r) => r.end_bound(),
+ LineRange::RangeFrom(r) => r.end_bound(),
+ LineRange::RangeTo(r) => r.end_bound(),
+ LineRange::RangeFull(r) => r.end_bound(),
+ }
+ }
+}
+
+impl From<Range<usize>> for LineRange {
+ fn from(r: Range<usize>) -> LineRange {
+ LineRange::Range(r)
+ }
+}
+
+impl From<RangeFrom<usize>> for LineRange {
+ fn from(r: RangeFrom<usize>) -> LineRange {
+ LineRange::RangeFrom(r)
+ }
+}
+
+impl From<RangeTo<usize>> for LineRange {
+ fn from(r: RangeTo<usize>) -> LineRange {
+ LineRange::RangeTo(r)
+ }
+}
+
+impl From<RangeFull> for LineRange {
+ fn from(r: RangeFull) -> LineRange {
+ LineRange::RangeFull(r)
+ }
+}
+
+impl<'a> LinkType<'a> {
+ fn relative_path<P: AsRef<Path>>(self, base: P) -> Option<PathBuf> {
+ let base = base.as_ref();
+ match self {
+ LinkType::Escaped => None,
+ LinkType::Include(p, _) => Some(return_relative_path(base, &p)),
+ LinkType::Playground(p, _) => Some(return_relative_path(base, &p)),
+ LinkType::RustdocInclude(p, _) => Some(return_relative_path(base, &p)),
+ LinkType::Title(_) => None,
+ }
+ }
+}
+fn return_relative_path<P: AsRef<Path>>(base: P, relative: P) -> PathBuf {
+ base.as_ref()
+ .join(relative)
+ .parent()
+ .expect("Included file should not be /")
+ .to_path_buf()
+}
+
+fn parse_range_or_anchor(parts: Option<&str>) -> RangeOrAnchor {
+ let mut parts = parts.unwrap_or("").splitn(3, ':').fuse();
+
+ let next_element = parts.next();
+ let start = if let Some(value) = next_element.and_then(|s| s.parse::<usize>().ok()) {
+ // subtract 1 since line numbers usually begin with 1
+ Some(value.saturating_sub(1))
+ } else if let Some("") = next_element {
+ None
+ } else if let Some(anchor) = next_element {
+ return RangeOrAnchor::Anchor(String::from(anchor));
+ } else {
+ None
+ };
+
+ let end = parts.next();
+ // If `end` is empty string or any other value that can't be parsed as a usize, treat this
+ // include as a range with only a start bound. However, if end isn't specified, include only
+ // the single line specified by `start`.
+ let end = end.map(|s| s.parse::<usize>());
+
+ match (start, end) {
+ (Some(start), Some(Ok(end))) => RangeOrAnchor::Range(LineRange::from(start..end)),
+ (Some(start), Some(Err(_))) => RangeOrAnchor::Range(LineRange::from(start..)),
+ (Some(start), None) => RangeOrAnchor::Range(LineRange::from(start..start + 1)),
+ (None, Some(Ok(end))) => RangeOrAnchor::Range(LineRange::from(..end)),
+ (None, None) | (None, Some(Err(_))) => RangeOrAnchor::Range(LineRange::from(RangeFull)),
+ }
+}
+
+fn parse_include_path(path: &str) -> LinkType<'static> {
+ let mut parts = path.splitn(2, ':');
+
+ let path = parts.next().unwrap().into();
+ let range_or_anchor = parse_range_or_anchor(parts.next());
+
+ LinkType::Include(path, range_or_anchor)
+}
+
+fn parse_rustdoc_include_path(path: &str) -> LinkType<'static> {
+ let mut parts = path.splitn(2, ':');
+
+ let path = parts.next().unwrap().into();
+ let range_or_anchor = parse_range_or_anchor(parts.next());
+
+ LinkType::RustdocInclude(path, range_or_anchor)
+}
+
+#[derive(PartialEq, Debug, Clone)]
+struct Link<'a> {
+ start_index: usize,
+ end_index: usize,
+ link_type: LinkType<'a>,
+ link_text: &'a str,
+}
+
+impl<'a> Link<'a> {
+ fn from_capture(cap: Captures<'a>) -> Option<Link<'a>> {
+ let link_type = match (cap.get(0), cap.get(1), cap.get(2)) {
+ (_, Some(typ), Some(title)) if typ.as_str() == "title" => {
+ Some(LinkType::Title(title.as_str()))
+ }
+ (_, Some(typ), Some(rest)) => {
+ let mut path_props = rest.as_str().split_whitespace();
+ let file_arg = path_props.next();
+ let props: Vec<&str> = path_props.collect();
+
+ match (typ.as_str(), file_arg) {
+ ("include", Some(pth)) => Some(parse_include_path(pth)),
+ ("playground", Some(pth)) => Some(LinkType::Playground(pth.into(), props)),
+ ("playpen", Some(pth)) => {
+ warn!(
+ "the {{{{#playpen}}}} expression has been \
+ renamed to {{{{#playground}}}}, \
+ please update your book to use the new name"
+ );
+ Some(LinkType::Playground(pth.into(), props))
+ }
+ ("rustdoc_include", Some(pth)) => Some(parse_rustdoc_include_path(pth)),
+ _ => None,
+ }
+ }
+ (Some(mat), None, None) if mat.as_str().starts_with(ESCAPE_CHAR) => {
+ Some(LinkType::Escaped)
+ }
+ _ => None,
+ };
+
+ link_type.and_then(|lnk_type| {
+ cap.get(0).map(|mat| Link {
+ start_index: mat.start(),
+ end_index: mat.end(),
+ link_type: lnk_type,
+ link_text: mat.as_str(),
+ })
+ })
+ }
+
+ fn render_with_path<P: AsRef<Path>>(
+ &self,
+ base: P,
+ chapter_title: &mut String,
+ ) -> Result<String> {
+ let base = base.as_ref();
+ match self.link_type {
+ // omit the escape char
+ LinkType::Escaped => Ok((&self.link_text[1..]).to_owned()),
+ LinkType::Include(ref pat, ref range_or_anchor) => {
+ let target = base.join(pat);
+
+ fs::read_to_string(&target)
+ .map(|s| match range_or_anchor {
+ RangeOrAnchor::Range(range) => take_lines(&s, range.clone()),
+ RangeOrAnchor::Anchor(anchor) => take_anchored_lines(&s, anchor),
+ })
+ .with_context(|| {
+ format!(
+ "Could not read file for link {} ({})",
+ self.link_text,
+ target.display(),
+ )
+ })
+ }
+ LinkType::RustdocInclude(ref pat, ref range_or_anchor) => {
+ let target = base.join(pat);
+
+ fs::read_to_string(&target)
+ .map(|s| match range_or_anchor {
+ RangeOrAnchor::Range(range) => {
+ take_rustdoc_include_lines(&s, range.clone())
+ }
+ RangeOrAnchor::Anchor(anchor) => {
+ take_rustdoc_include_anchored_lines(&s, anchor)
+ }
+ })
+ .with_context(|| {
+ format!(
+ "Could not read file for link {} ({})",
+ self.link_text,
+ target.display(),
+ )
+ })
+ }
+ LinkType::Playground(ref pat, ref attrs) => {
+ let target = base.join(pat);
+
+ let mut contents = fs::read_to_string(&target).with_context(|| {
+ format!(
+ "Could not read file for link {} ({})",
+ self.link_text,
+ target.display()
+ )
+ })?;
+ let ftype = if !attrs.is_empty() { "rust," } else { "rust" };
+ if !contents.ends_with('\n') {
+ contents.push('\n');
+ }
+ Ok(format!(
+ "```{}{}\n{}```\n",
+ ftype,
+ attrs.join(","),
+ contents
+ ))
+ }
+ LinkType::Title(title) => {
+ *chapter_title = title.to_owned();
+ Ok(String::new())
+ }
+ }
+ }
+}
+
+struct LinkIter<'a>(CaptureMatches<'a, 'a>);
+
+impl<'a> Iterator for LinkIter<'a> {
+ type Item = Link<'a>;
+ fn next(&mut self) -> Option<Link<'a>> {
+ for cap in &mut self.0 {
+ if let Some(inc) = Link::from_capture(cap) {
+ return Some(inc);
+ }
+ }
+ None
+ }
+}
+
+fn find_links(contents: &str) -> LinkIter<'_> {
+ // lazily compute following regex
+ // r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([^}]+)\}\}")?;
+ lazy_static! {
+ static ref RE: Regex = Regex::new(
+ r"(?x) # insignificant whitespace mode
+ \\\{\{\#.*\}\} # match escaped link
+ | # or
+ \{\{\s* # link opening parens and whitespace
+ \#([a-zA-Z0-9_]+) # link type
+ \s+ # separating whitespace
+ ([^}]+) # link target path and space separated properties
+ \}\} # link closing parens"
+ )
+ .unwrap();
+ }
+ LinkIter(RE.captures_iter(contents))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_replace_all_escaped() {
+ let start = r"
+ Some text over here.
+ ```hbs
+ \{{#include file.rs}} << an escaped link!
+ ```";
+ let end = r"
+ Some text over here.
+ ```hbs
+ {{#include file.rs}} << an escaped link!
+ ```";
+ let mut chapter_title = "test_replace_all_escaped".to_owned();
+ assert_eq!(replace_all(start, "", "", 0, &mut chapter_title), end);
+ }
+
+ #[test]
+ fn test_set_chapter_title() {
+ let start = r"{{#title My Title}}
+ # My Chapter
+ ";
+ let end = r"
+ # My Chapter
+ ";
+ let mut chapter_title = "test_set_chapter_title".to_owned();
+ assert_eq!(replace_all(start, "", "", 0, &mut chapter_title), end);
+ assert_eq!(chapter_title, "My Title");
+ }
+
+ #[test]
+ fn test_find_links_no_link() {
+ let s = "Some random text without link...";
+ assert!(find_links(s).collect::<Vec<_>>() == vec![]);
+ }
+
+ #[test]
+ fn test_find_links_partial_link() {
+ let s = "Some random text with {{#playground...";
+ assert!(find_links(s).collect::<Vec<_>>() == vec![]);
+ let s = "Some random text with {{#include...";
+ assert!(find_links(s).collect::<Vec<_>>() == vec![]);
+ let s = "Some random text with \\{{#include...";
+ assert!(find_links(s).collect::<Vec<_>>() == vec![]);
+ }
+
+ #[test]
+ fn test_find_links_empty_link() {
+ let s = "Some random text with {{#playground}} and {{#playground }} {{}} {{#}}...";
+ assert!(find_links(s).collect::<Vec<_>>() == vec![]);
+ }
+
+ #[test]
+ fn test_find_links_unknown_link_type() {
+ let s = "Some random text with {{#playgroundz ar.rs}} and {{#incn}} {{baz}} {{#bar}}...";
+ assert!(find_links(s).collect::<Vec<_>>() == vec![]);
+ }
+
+ #[test]
+ fn test_find_links_simple_link() {
+ let s = "Some random text with {{#playground file.rs}} and {{#playground test.rs }}...";
+
+ let res = find_links(s).collect::<Vec<_>>();
+ println!("\nOUTPUT: {:?}\n", res);
+
+ assert_eq!(
+ res,
+ vec![
+ Link {
+ start_index: 22,
+ end_index: 45,
+ link_type: LinkType::Playground(PathBuf::from("file.rs"), vec![]),
+ link_text: "{{#playground file.rs}}",
+ },
+ Link {
+ start_index: 50,
+ end_index: 74,
+ link_type: LinkType::Playground(PathBuf::from("test.rs"), vec![]),
+ link_text: "{{#playground test.rs }}",
+ },
+ ]
+ );
+ }
+
+ #[test]
+ fn test_find_links_with_special_characters() {
+ let s = "Some random text with {{#playground foo-bar\\baz/_c++.rs}}...";
+
+ let res = find_links(s).collect::<Vec<_>>();
+ println!("\nOUTPUT: {:?}\n", res);
+
+ assert_eq!(
+ res,
+ vec![Link {
+ start_index: 22,
+ end_index: 57,
+ link_type: LinkType::Playground(PathBuf::from("foo-bar\\baz/_c++.rs"), vec![]),
+ link_text: "{{#playground foo-bar\\baz/_c++.rs}}",
+ },]
+ );
+ }
+
+ #[test]
+ fn test_find_links_with_range() {
+ let s = "Some random text with {{#include file.rs:10:20}}...";
+ let res = find_links(s).collect::<Vec<_>>();
+ println!("\nOUTPUT: {:?}\n", res);
+ assert_eq!(
+ res,
+ vec![Link {
+ start_index: 22,
+ end_index: 48,
+ link_type: LinkType::Include(
+ PathBuf::from("file.rs"),
+ RangeOrAnchor::Range(LineRange::from(9..20))
+ ),
+ link_text: "{{#include file.rs:10:20}}",
+ }]
+ );
+ }
+
+ #[test]
+ fn test_find_links_with_line_number() {
+ let s = "Some random text with {{#include file.rs:10}}...";
+ let res = find_links(s).collect::<Vec<_>>();
+ println!("\nOUTPUT: {:?}\n", res);
+ assert_eq!(
+ res,
+ vec![Link {
+ start_index: 22,
+ end_index: 45,
+ link_type: LinkType::Include(
+ PathBuf::from("file.rs"),
+ RangeOrAnchor::Range(LineRange::from(9..10))
+ ),
+ link_text: "{{#include file.rs:10}}",
+ }]
+ );
+ }
+
+ #[test]
+ fn test_find_links_with_from_range() {
+ let s = "Some random text with {{#include file.rs:10:}}...";
+ let res = find_links(s).collect::<Vec<_>>();
+ println!("\nOUTPUT: {:?}\n", res);
+ assert_eq!(
+ res,
+ vec![Link {
+ start_index: 22,
+ end_index: 46,
+ link_type: LinkType::Include(
+ PathBuf::from("file.rs"),
+ RangeOrAnchor::Range(LineRange::from(9..))
+ ),
+ link_text: "{{#include file.rs:10:}}",
+ }]
+ );
+ }
+
+ #[test]
+ fn test_find_links_with_to_range() {
+ let s = "Some random text with {{#include file.rs::20}}...";
+ let res = find_links(s).collect::<Vec<_>>();
+ println!("\nOUTPUT: {:?}\n", res);
+ assert_eq!(
+ res,
+ vec![Link {
+ start_index: 22,
+ end_index: 46,
+ link_type: LinkType::Include(
+ PathBuf::from("file.rs"),
+ RangeOrAnchor::Range(LineRange::from(..20))
+ ),
+ link_text: "{{#include file.rs::20}}",
+ }]
+ );
+ }
+
+ #[test]
+ fn test_find_links_with_full_range() {
+ let s = "Some random text with {{#include file.rs::}}...";
+ let res = find_links(s).collect::<Vec<_>>();
+ println!("\nOUTPUT: {:?}\n", res);
+ assert_eq!(
+ res,
+ vec![Link {
+ start_index: 22,
+ end_index: 44,
+ link_type: LinkType::Include(
+ PathBuf::from("file.rs"),
+ RangeOrAnchor::Range(LineRange::from(..))
+ ),
+ link_text: "{{#include file.rs::}}",
+ }]
+ );
+ }
+
+ #[test]
+ fn test_find_links_with_no_range_specified() {
+ let s = "Some random text with {{#include file.rs}}...";
+ let res = find_links(s).collect::<Vec<_>>();
+ println!("\nOUTPUT: {:?}\n", res);
+ assert_eq!(
+ res,
+ vec![Link {
+ start_index: 22,
+ end_index: 42,
+ link_type: LinkType::Include(
+ PathBuf::from("file.rs"),
+ RangeOrAnchor::Range(LineRange::from(..))
+ ),
+ link_text: "{{#include file.rs}}",
+ }]
+ );
+ }
+
+ #[test]
+ fn test_find_links_with_anchor() {
+ let s = "Some random text with {{#include file.rs:anchor}}...";
+ let res = find_links(s).collect::<Vec<_>>();
+ println!("\nOUTPUT: {:?}\n", res);
+ assert_eq!(
+ res,
+ vec![Link {
+ start_index: 22,
+ end_index: 49,
+ link_type: LinkType::Include(
+ PathBuf::from("file.rs"),
+ RangeOrAnchor::Anchor(String::from("anchor"))
+ ),
+ link_text: "{{#include file.rs:anchor}}",
+ }]
+ );
+ }
+
+ #[test]
+ fn test_find_links_escaped_link() {
+ let s = "Some random text with escaped playground \\{{#playground file.rs editable}} ...";
+
+ let res = find_links(s).collect::<Vec<_>>();
+ println!("\nOUTPUT: {:?}\n", res);
+
+ assert_eq!(
+ res,
+ vec![Link {
+ start_index: 41,
+ end_index: 74,
+ link_type: LinkType::Escaped,
+ link_text: "\\{{#playground file.rs editable}}",
+ }]
+ );
+ }
+
+ #[test]
+ fn test_find_playgrounds_with_properties() {
+ let s =
+ "Some random text with escaped playground {{#playground file.rs editable }} and some \
+ more\n text {{#playground my.rs editable no_run should_panic}} ...";
+
+ let res = find_links(s).collect::<Vec<_>>();
+ println!("\nOUTPUT: {:?}\n", res);
+ assert_eq!(
+ res,
+ vec![
+ Link {
+ start_index: 41,
+ end_index: 74,
+ link_type: LinkType::Playground(PathBuf::from("file.rs"), vec!["editable"]),
+ link_text: "{{#playground file.rs editable }}",
+ },
+ Link {
+ start_index: 95,
+ end_index: 145,
+ link_type: LinkType::Playground(
+ PathBuf::from("my.rs"),
+ vec!["editable", "no_run", "should_panic"],
+ ),
+ link_text: "{{#playground my.rs editable no_run should_panic}}",
+ },
+ ]
+ );
+ }
+
+ #[test]
+ fn test_find_all_link_types() {
+ let s =
+ "Some random text with escaped playground {{#include file.rs}} and \\{{#contents are \
+ insignifficant in escaped link}} some more\n text {{#playground my.rs editable \
+ no_run should_panic}} ...";
+
+ let res = find_links(s).collect::<Vec<_>>();
+ println!("\nOUTPUT: {:?}\n", res);
+ assert_eq!(res.len(), 3);
+ assert_eq!(
+ res[0],
+ Link {
+ start_index: 41,
+ end_index: 61,
+ link_type: LinkType::Include(
+ PathBuf::from("file.rs"),
+ RangeOrAnchor::Range(LineRange::from(..))
+ ),
+ link_text: "{{#include file.rs}}",
+ }
+ );
+ assert_eq!(
+ res[1],
+ Link {
+ start_index: 66,
+ end_index: 115,
+ link_type: LinkType::Escaped,
+ link_text: "\\{{#contents are insignifficant in escaped link}}",
+ }
+ );
+ assert_eq!(
+ res[2],
+ Link {
+ start_index: 133,
+ end_index: 183,
+ link_type: LinkType::Playground(
+ PathBuf::from("my.rs"),
+ vec!["editable", "no_run", "should_panic"]
+ ),
+ link_text: "{{#playground my.rs editable no_run should_panic}}",
+ }
+ );
+ }
+
+ #[test]
+ fn parse_without_colon_includes_all() {
+ let link_type = parse_include_path("arbitrary");
+ assert_eq!(
+ link_type,
+ LinkType::Include(
+ PathBuf::from("arbitrary"),
+ RangeOrAnchor::Range(LineRange::from(RangeFull))
+ )
+ );
+ }
+
+ #[test]
+ fn parse_with_nothing_after_colon_includes_all() {
+ let link_type = parse_include_path("arbitrary:");
+ assert_eq!(
+ link_type,
+ LinkType::Include(
+ PathBuf::from("arbitrary"),
+ RangeOrAnchor::Range(LineRange::from(RangeFull))
+ )
+ );
+ }
+
+ #[test]
+ fn parse_with_two_colons_includes_all() {
+ let link_type = parse_include_path("arbitrary::");
+ assert_eq!(
+ link_type,
+ LinkType::Include(
+ PathBuf::from("arbitrary"),
+ RangeOrAnchor::Range(LineRange::from(RangeFull))
+ )
+ );
+ }
+
+ #[test]
+ fn parse_with_garbage_after_two_colons_includes_all() {
+ let link_type = parse_include_path("arbitrary::NaN");
+ assert_eq!(
+ link_type,
+ LinkType::Include(
+ PathBuf::from("arbitrary"),
+ RangeOrAnchor::Range(LineRange::from(RangeFull))
+ )
+ );
+ }
+
+ #[test]
+ fn parse_with_one_number_after_colon_only_that_line() {
+ let link_type = parse_include_path("arbitrary:5");
+ assert_eq!(
+ link_type,
+ LinkType::Include(
+ PathBuf::from("arbitrary"),
+ RangeOrAnchor::Range(LineRange::from(4..5))
+ )
+ );
+ }
+
+ #[test]
+ fn parse_with_one_based_start_becomes_zero_based() {
+ let link_type = parse_include_path("arbitrary:1");
+ assert_eq!(
+ link_type,
+ LinkType::Include(
+ PathBuf::from("arbitrary"),
+ RangeOrAnchor::Range(LineRange::from(0..1))
+ )
+ );
+ }
+
+ #[test]
+ fn parse_with_zero_based_start_stays_zero_based_but_is_probably_an_error() {
+ let link_type = parse_include_path("arbitrary:0");
+ assert_eq!(
+ link_type,
+ LinkType::Include(
+ PathBuf::from("arbitrary"),
+ RangeOrAnchor::Range(LineRange::from(0..1))
+ )
+ );
+ }
+
+ #[test]
+ fn parse_start_only_range() {
+ let link_type = parse_include_path("arbitrary:5:");
+ assert_eq!(
+ link_type,
+ LinkType::Include(
+ PathBuf::from("arbitrary"),
+ RangeOrAnchor::Range(LineRange::from(4..))
+ )
+ );
+ }
+
+ #[test]
+ fn parse_start_with_garbage_interpreted_as_start_only_range() {
+ let link_type = parse_include_path("arbitrary:5:NaN");
+ assert_eq!(
+ link_type,
+ LinkType::Include(
+ PathBuf::from("arbitrary"),
+ RangeOrAnchor::Range(LineRange::from(4..))
+ )
+ );
+ }
+
+ #[test]
+ fn parse_end_only_range() {
+ let link_type = parse_include_path("arbitrary::5");
+ assert_eq!(
+ link_type,
+ LinkType::Include(
+ PathBuf::from("arbitrary"),
+ RangeOrAnchor::Range(LineRange::from(..5))
+ )
+ );
+ }
+
+ #[test]
+ fn parse_start_and_end_range() {
+ let link_type = parse_include_path("arbitrary:5:10");
+ assert_eq!(
+ link_type,
+ LinkType::Include(
+ PathBuf::from("arbitrary"),
+ RangeOrAnchor::Range(LineRange::from(4..10))
+ )
+ );
+ }
+
+ #[test]
+ fn parse_with_negative_interpreted_as_anchor() {
+ let link_type = parse_include_path("arbitrary:-5");
+ assert_eq!(
+ link_type,
+ LinkType::Include(
+ PathBuf::from("arbitrary"),
+ RangeOrAnchor::Anchor("-5".to_string())
+ )
+ );
+ }
+
+ #[test]
+ fn parse_with_floating_point_interpreted_as_anchor() {
+ let link_type = parse_include_path("arbitrary:-5.7");
+ assert_eq!(
+ link_type,
+ LinkType::Include(
+ PathBuf::from("arbitrary"),
+ RangeOrAnchor::Anchor("-5.7".to_string())
+ )
+ );
+ }
+
+ #[test]
+ fn parse_with_anchor_followed_by_colon() {
+ let link_type = parse_include_path("arbitrary:some-anchor:this-gets-ignored");
+ assert_eq!(
+ link_type,
+ LinkType::Include(
+ PathBuf::from("arbitrary"),
+ RangeOrAnchor::Anchor("some-anchor".to_string())
+ )
+ );
+ }
+
+ #[test]
+ fn parse_with_more_than_three_colons_ignores_everything_after_third_colon() {
+ let link_type = parse_include_path("arbitrary:5:10:17:anything:");
+ assert_eq!(
+ link_type,
+ LinkType::Include(
+ PathBuf::from("arbitrary"),
+ RangeOrAnchor::Range(LineRange::from(4..10))
+ )
+ );
+ }
+}
diff --git a/vendor/mdbook/src/preprocess/mod.rs b/vendor/mdbook/src/preprocess/mod.rs
new file mode 100644
index 000000000..894e20035
--- /dev/null
+++ b/vendor/mdbook/src/preprocess/mod.rs
@@ -0,0 +1,70 @@
+//! Book preprocessing.
+
+pub use self::cmd::CmdPreprocessor;
+pub use self::index::IndexPreprocessor;
+pub use self::links::LinkPreprocessor;
+
+mod cmd;
+mod index;
+mod links;
+
+use crate::book::Book;
+use crate::config::Config;
+use crate::errors::*;
+
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::path::PathBuf;
+
+use serde::{Deserialize, Serialize};
+
+/// Extra information for a `Preprocessor` to give them more context when
+/// processing a book.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct PreprocessorContext {
+ /// The location of the book directory on disk.
+ pub root: PathBuf,
+ /// The book configuration (`book.toml`).
+ pub config: Config,
+ /// The `Renderer` this preprocessor is being used with.
+ pub renderer: String,
+ /// The calling `mdbook` version.
+ pub mdbook_version: String,
+ #[serde(skip)]
+ pub(crate) chapter_titles: RefCell<HashMap<PathBuf, String>>,
+ #[serde(skip)]
+ __non_exhaustive: (),
+}
+
+impl PreprocessorContext {
+ /// Create a new `PreprocessorContext`.
+ pub(crate) fn new(root: PathBuf, config: Config, renderer: String) -> Self {
+ PreprocessorContext {
+ root,
+ config,
+ renderer,
+ mdbook_version: crate::MDBOOK_VERSION.to_string(),
+ chapter_titles: RefCell::new(HashMap::new()),
+ __non_exhaustive: (),
+ }
+ }
+}
+
+/// An operation which is run immediately after loading a book into memory and
+/// before it gets rendered.
+pub trait Preprocessor {
+ /// Get the `Preprocessor`'s name.
+ fn name(&self) -> &str;
+
+ /// Run this `Preprocessor`, allowing it to update the book before it is
+ /// given to a renderer.
+ fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book>;
+
+ /// A hint to `MDBook` whether this preprocessor is compatible with a
+ /// particular renderer.
+ ///
+ /// By default, always returns `true`.
+ fn supports_renderer(&self, _renderer: &str) -> bool {
+ true
+ }
+}
diff --git a/vendor/mdbook/src/renderer/html_handlebars/hbs_renderer.rs b/vendor/mdbook/src/renderer/html_handlebars/hbs_renderer.rs
new file mode 100644
index 000000000..b933a359a
--- /dev/null
+++ b/vendor/mdbook/src/renderer/html_handlebars/hbs_renderer.rs
@@ -0,0 +1,1106 @@
+use crate::book::{Book, BookItem};
+use crate::config::{BookConfig, Config, HtmlConfig, Playground, RustEdition};
+use crate::errors::*;
+use crate::renderer::html_handlebars::helpers;
+use crate::renderer::{RenderContext, Renderer};
+use crate::theme::{self, playground_editor, Theme};
+use crate::utils;
+
+use std::borrow::Cow;
+use std::collections::BTreeMap;
+use std::collections::HashMap;
+use std::fs::{self, File};
+use std::path::{Path, PathBuf};
+
+use crate::utils::fs::get_404_output_file;
+use handlebars::Handlebars;
+use regex::{Captures, Regex};
+
+#[derive(Default)]
+pub struct HtmlHandlebars;
+
+impl HtmlHandlebars {
+ pub fn new() -> Self {
+ HtmlHandlebars
+ }
+
+ fn render_item(
+ &self,
+ item: &BookItem,
+ mut ctx: RenderItemContext<'_>,
+ print_content: &mut String,
+ ) -> Result<()> {
+ // FIXME: This should be made DRY-er and rely less on mutable state
+
+ let (ch, path) = match item {
+ BookItem::Chapter(ch) if !ch.is_draft_chapter() => (ch, ch.path.as_ref().unwrap()),
+ _ => return Ok(()),
+ };
+
+ if let Some(ref edit_url_template) = ctx.html_config.edit_url_template {
+ let full_path = ctx.book_config.src.to_str().unwrap_or_default().to_owned()
+ + "/"
+ + ch.source_path
+ .clone()
+ .unwrap_or_default()
+ .to_str()
+ .unwrap_or_default();
+
+ let edit_url = edit_url_template.replace("{path}", &full_path);
+ ctx.data
+ .insert("git_repository_edit_url".to_owned(), json!(edit_url));
+ }
+
+ let content = ch.content.clone();
+ let content = utils::render_markdown(&content, ctx.html_config.curly_quotes);
+
+ let fixed_content =
+ utils::render_markdown_with_path(&ch.content, ctx.html_config.curly_quotes, Some(path));
+ if !ctx.is_index && ctx.html_config.print.page_break {
+ // Add page break between chapters
+ // See https://developer.mozilla.org/en-US/docs/Web/CSS/break-before and https://developer.mozilla.org/en-US/docs/Web/CSS/page-break-before
+ // Add both two CSS properties because of the compatibility issue
+ print_content
+ .push_str(r#"<div style="break-before: page; page-break-before: always;"></div>"#);
+ }
+ print_content.push_str(&fixed_content);
+
+ // Update the context with data for this file
+ let ctx_path = path
+ .to_str()
+ .with_context(|| "Could not convert path to str")?;
+ let filepath = Path::new(&ctx_path).with_extension("html");
+
+ // "print.html" is used for the print page.
+ if path == Path::new("print.md") {
+ bail!("{} is reserved for internal use", path.display());
+ };
+
+ let book_title = ctx
+ .data
+ .get("book_title")
+ .and_then(serde_json::Value::as_str)
+ .unwrap_or("");
+
+ let title = if let Some(title) = ctx.chapter_titles.get(path) {
+ title.clone()
+ } else if book_title.is_empty() {
+ ch.name.clone()
+ } else {
+ ch.name.clone() + " - " + book_title
+ };
+
+ ctx.data.insert("path".to_owned(), json!(path));
+ ctx.data.insert("content".to_owned(), json!(content));
+ ctx.data.insert("chapter_title".to_owned(), json!(ch.name));
+ ctx.data.insert("title".to_owned(), json!(title));
+ ctx.data.insert(
+ "path_to_root".to_owned(),
+ json!(utils::fs::path_to_root(&path)),
+ );
+ if let Some(ref section) = ch.number {
+ ctx.data
+ .insert("section".to_owned(), json!(section.to_string()));
+ }
+
+ // Render the handlebars template with the data
+ debug!("Render template");
+ let rendered = ctx.handlebars.render("index", &ctx.data)?;
+
+ let rendered = self.post_process(rendered, &ctx.html_config.playground, ctx.edition);
+
+ // Write to file
+ debug!("Creating {}", filepath.display());
+ utils::fs::write_file(&ctx.destination, &filepath, rendered.as_bytes())?;
+
+ if ctx.is_index {
+ ctx.data.insert("path".to_owned(), json!("index.md"));
+ ctx.data.insert("path_to_root".to_owned(), json!(""));
+ ctx.data.insert("is_index".to_owned(), json!(true));
+ let rendered_index = ctx.handlebars.render("index", &ctx.data)?;
+ let rendered_index =
+ self.post_process(rendered_index, &ctx.html_config.playground, ctx.edition);
+ debug!("Creating index.html from {}", ctx_path);
+ utils::fs::write_file(&ctx.destination, "index.html", rendered_index.as_bytes())?;
+ }
+
+ Ok(())
+ }
+
+ fn render_404(
+ &self,
+ ctx: &RenderContext,
+ html_config: &HtmlConfig,
+ src_dir: &Path,
+ handlebars: &mut Handlebars<'_>,
+ data: &mut serde_json::Map<String, serde_json::Value>,
+ ) -> Result<()> {
+ let destination = &ctx.destination;
+ let content_404 = if let Some(ref filename) = html_config.input_404 {
+ let path = src_dir.join(filename);
+ std::fs::read_to_string(&path)
+ .with_context(|| format!("unable to open 404 input file {:?}", path))?
+ } else {
+ // 404 input not explicitly configured try the default file 404.md
+ let default_404_location = src_dir.join("404.md");
+ if default_404_location.exists() {
+ std::fs::read_to_string(&default_404_location).with_context(|| {
+ format!("unable to open 404 input file {:?}", default_404_location)
+ })?
+ } else {
+ "# Document not found (404)\n\nThis URL is invalid, sorry. Please use the \
+ navigation bar or search to continue."
+ .to_string()
+ }
+ };
+ let html_content_404 = utils::render_markdown(&content_404, html_config.curly_quotes);
+
+ let mut data_404 = data.clone();
+ let base_url = if let Some(site_url) = &html_config.site_url {
+ site_url
+ } else {
+ debug!(
+ "HTML 'site-url' parameter not set, defaulting to '/'. Please configure \
+ this to ensure the 404 page work correctly, especially if your site is hosted in a \
+ subdirectory on the HTTP server."
+ );
+ "/"
+ };
+ data_404.insert("base_url".to_owned(), json!(base_url));
+ // Set a dummy path to ensure other paths (e.g. in the TOC) are generated correctly
+ data_404.insert("path".to_owned(), json!("404.md"));
+ data_404.insert("content".to_owned(), json!(html_content_404));
+
+ let mut title = String::from("Page not found");
+ if let Some(book_title) = &ctx.config.book.title {
+ title.push_str(" - ");
+ title.push_str(book_title);
+ }
+ data_404.insert("title".to_owned(), json!(title));
+ let rendered = handlebars.render("index", &data_404)?;
+
+ let rendered =
+ self.post_process(rendered, &html_config.playground, ctx.config.rust.edition);
+ let output_file = get_404_output_file(&html_config.input_404);
+ utils::fs::write_file(destination, output_file, rendered.as_bytes())?;
+ debug!("Creating 404.html ✓");
+ Ok(())
+ }
+
+ #[cfg_attr(feature = "cargo-clippy", allow(clippy::let_and_return))]
+ fn post_process(
+ &self,
+ rendered: String,
+ playground_config: &Playground,
+ edition: Option<RustEdition>,
+ ) -> String {
+ let rendered = build_header_links(&rendered);
+ let rendered = fix_code_blocks(&rendered);
+ let rendered = add_playground_pre(&rendered, playground_config, edition);
+
+ rendered
+ }
+
+ fn copy_static_files(
+ &self,
+ destination: &Path,
+ theme: &Theme,
+ html_config: &HtmlConfig,
+ ) -> Result<()> {
+ use crate::utils::fs::write_file;
+
+ write_file(
+ destination,
+ ".nojekyll",
+ b"This file makes sure that Github Pages doesn't process mdBook's output.\n",
+ )?;
+
+ if let Some(cname) = &html_config.cname {
+ write_file(destination, "CNAME", format!("{}\n", cname).as_bytes())?;
+ }
+
+ write_file(destination, "book.js", &theme.js)?;
+ write_file(destination, "css/general.css", &theme.general_css)?;
+ write_file(destination, "css/chrome.css", &theme.chrome_css)?;
+ if html_config.print.enable {
+ write_file(destination, "css/print.css", &theme.print_css)?;
+ }
+ write_file(destination, "css/variables.css", &theme.variables_css)?;
+ if let Some(contents) = &theme.favicon_png {
+ write_file(destination, "favicon.png", contents)?;
+ }
+ if let Some(contents) = &theme.favicon_svg {
+ write_file(destination, "favicon.svg", contents)?;
+ }
+ write_file(destination, "highlight.css", &theme.highlight_css)?;
+ write_file(destination, "tomorrow-night.css", &theme.tomorrow_night_css)?;
+ write_file(destination, "ayu-highlight.css", &theme.ayu_highlight_css)?;
+ write_file(destination, "highlight.js", &theme.highlight_js)?;
+ write_file(destination, "clipboard.min.js", &theme.clipboard_js)?;
+ write_file(
+ destination,
+ "FontAwesome/css/font-awesome.css",
+ theme::FONT_AWESOME,
+ )?;
+ write_file(
+ destination,
+ "FontAwesome/fonts/fontawesome-webfont.eot",
+ theme::FONT_AWESOME_EOT,
+ )?;
+ write_file(
+ destination,
+ "FontAwesome/fonts/fontawesome-webfont.svg",
+ theme::FONT_AWESOME_SVG,
+ )?;
+ write_file(
+ destination,
+ "FontAwesome/fonts/fontawesome-webfont.ttf",
+ theme::FONT_AWESOME_TTF,
+ )?;
+ write_file(
+ destination,
+ "FontAwesome/fonts/fontawesome-webfont.woff",
+ theme::FONT_AWESOME_WOFF,
+ )?;
+ write_file(
+ destination,
+ "FontAwesome/fonts/fontawesome-webfont.woff2",
+ theme::FONT_AWESOME_WOFF2,
+ )?;
+ write_file(
+ destination,
+ "FontAwesome/fonts/FontAwesome.ttf",
+ theme::FONT_AWESOME_TTF,
+ )?;
+ if html_config.copy_fonts {
+ write_file(destination, "fonts/fonts.css", theme::fonts::CSS)?;
+ for (file_name, contents) in theme::fonts::LICENSES.iter() {
+ write_file(destination, file_name, contents)?;
+ }
+ for (file_name, contents) in theme::fonts::OPEN_SANS.iter() {
+ write_file(destination, file_name, contents)?;
+ }
+ write_file(
+ destination,
+ theme::fonts::SOURCE_CODE_PRO.0,
+ theme::fonts::SOURCE_CODE_PRO.1,
+ )?;
+ }
+
+ let playground_config = &html_config.playground;
+
+ // Ace is a very large dependency, so only load it when requested
+ if playground_config.editable && playground_config.copy_js {
+ // Load the editor
+ write_file(destination, "editor.js", playground_editor::JS)?;
+ write_file(destination, "ace.js", playground_editor::ACE_JS)?;
+ write_file(destination, "mode-rust.js", playground_editor::MODE_RUST_JS)?;
+ write_file(
+ destination,
+ "theme-dawn.js",
+ playground_editor::THEME_DAWN_JS,
+ )?;
+ write_file(
+ destination,
+ "theme-tomorrow_night.js",
+ playground_editor::THEME_TOMORROW_NIGHT_JS,
+ )?;
+ }
+
+ Ok(())
+ }
+
+ /// Update the context with data for this file
+ fn configure_print_version(
+ &self,
+ data: &mut serde_json::Map<String, serde_json::Value>,
+ print_content: &str,
+ ) {
+ // Make sure that the Print chapter does not display the title from
+ // the last rendered chapter by removing it from its context
+ data.remove("title");
+ data.insert("is_print".to_owned(), json!(true));
+ data.insert("path".to_owned(), json!("print.md"));
+ data.insert("content".to_owned(), json!(print_content));
+ data.insert(
+ "path_to_root".to_owned(),
+ json!(utils::fs::path_to_root(Path::new("print.md"))),
+ );
+ }
+
+ fn register_hbs_helpers(&self, handlebars: &mut Handlebars<'_>, html_config: &HtmlConfig) {
+ handlebars.register_helper(
+ "toc",
+ Box::new(helpers::toc::RenderToc {
+ no_section_label: html_config.no_section_label,
+ }),
+ );
+ handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
+ handlebars.register_helper("next", Box::new(helpers::navigation::next));
+ handlebars.register_helper("theme_option", Box::new(helpers::theme::theme_option));
+ }
+
+ /// Copy across any additional CSS and JavaScript files which the book
+ /// has been configured to use.
+ fn copy_additional_css_and_js(
+ &self,
+ html: &HtmlConfig,
+ root: &Path,
+ destination: &Path,
+ ) -> Result<()> {
+ let custom_files = html.additional_css.iter().chain(html.additional_js.iter());
+
+ debug!("Copying additional CSS and JS");
+
+ for custom_file in custom_files {
+ let input_location = root.join(custom_file);
+ let output_location = destination.join(custom_file);
+ if let Some(parent) = output_location.parent() {
+ fs::create_dir_all(parent)
+ .with_context(|| format!("Unable to create {}", parent.display()))?;
+ }
+ debug!(
+ "Copying {} -> {}",
+ input_location.display(),
+ output_location.display()
+ );
+
+ fs::copy(&input_location, &output_location).with_context(|| {
+ format!(
+ "Unable to copy {} to {}",
+ input_location.display(),
+ output_location.display()
+ )
+ })?;
+ }
+
+ Ok(())
+ }
+
+ fn emit_redirects(
+ &self,
+ root: &Path,
+ handlebars: &Handlebars<'_>,
+ redirects: &HashMap<String, String>,
+ ) -> Result<()> {
+ if redirects.is_empty() {
+ return Ok(());
+ }
+
+ log::debug!("Emitting redirects");
+
+ for (original, new) in redirects {
+ log::debug!("Redirecting \"{}\" → \"{}\"", original, new);
+ // Note: all paths are relative to the build directory, so the
+ // leading slash in an absolute path means nothing (and would mess
+ // up `root.join(original)`).
+ let original = original.trim_start_matches('/');
+ let filename = root.join(original);
+ self.emit_redirect(handlebars, &filename, new)?;
+ }
+
+ Ok(())
+ }
+
+ fn emit_redirect(
+ &self,
+ handlebars: &Handlebars<'_>,
+ original: &Path,
+ destination: &str,
+ ) -> Result<()> {
+ if original.exists() {
+ // sanity check to avoid accidentally overwriting a real file.
+ let msg = format!(
+ "Not redirecting \"{}\" to \"{}\" because it already exists. Are you sure it needs to be redirected?",
+ original.display(),
+ destination,
+ );
+ return Err(Error::msg(msg));
+ }
+
+ if let Some(parent) = original.parent() {
+ std::fs::create_dir_all(parent)
+ .with_context(|| format!("Unable to ensure \"{}\" exists", parent.display()))?;
+ }
+
+ let ctx = json!({
+ "url": destination,
+ });
+ let f = File::create(original)?;
+ handlebars
+ .render_to_write("redirect", &ctx, f)
+ .with_context(|| {
+ format!(
+ "Unable to create a redirect file at \"{}\"",
+ original.display()
+ )
+ })?;
+
+ Ok(())
+ }
+}
+
+// TODO(mattico): Remove some time after the 0.1.8 release
+fn maybe_wrong_theme_dir(dir: &Path) -> Result<bool> {
+ fn entry_is_maybe_book_file(entry: fs::DirEntry) -> Result<bool> {
+ Ok(entry.file_type()?.is_file()
+ && entry.path().extension().map_or(false, |ext| ext == "md"))
+ }
+
+ if dir.is_dir() {
+ for entry in fs::read_dir(dir)? {
+ if entry_is_maybe_book_file(entry?).unwrap_or(false) {
+ return Ok(false);
+ }
+ }
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+}
+
+impl Renderer for HtmlHandlebars {
+ fn name(&self) -> &str {
+ "html"
+ }
+
+ fn render(&self, ctx: &RenderContext) -> Result<()> {
+ let book_config = &ctx.config.book;
+ let html_config = ctx.config.html_config().unwrap_or_default();
+ let src_dir = ctx.root.join(&ctx.config.book.src);
+ let destination = &ctx.destination;
+ let book = &ctx.book;
+ let build_dir = ctx.root.join(&ctx.config.build.build_dir);
+
+ if destination.exists() {
+ utils::fs::remove_dir_content(destination)
+ .with_context(|| "Unable to remove stale HTML output")?;
+ }
+
+ trace!("render");
+ let mut handlebars = Handlebars::new();
+
+ let theme_dir = match html_config.theme {
+ Some(ref theme) => {
+ let dir = ctx.root.join(theme);
+ if !dir.is_dir() {
+ bail!("theme dir {} does not exist", dir.display());
+ }
+ dir
+ }
+ None => ctx.root.join("theme"),
+ };
+
+ if html_config.theme.is_none()
+ && maybe_wrong_theme_dir(&src_dir.join("theme")).unwrap_or(false)
+ {
+ warn!(
+ "Previous versions of mdBook erroneously accepted `./src/theme` as an automatic \
+ theme directory"
+ );
+ warn!("Please move your theme files to `./theme` for them to continue being used");
+ }
+
+ let theme = theme::Theme::new(theme_dir);
+
+ debug!("Register the index handlebars template");
+ handlebars.register_template_string("index", String::from_utf8(theme.index.clone())?)?;
+
+ debug!("Register the head handlebars template");
+ handlebars.register_partial("head", String::from_utf8(theme.head.clone())?)?;
+
+ debug!("Register the redirect handlebars template");
+ handlebars
+ .register_template_string("redirect", String::from_utf8(theme.redirect.clone())?)?;
+
+ debug!("Register the header handlebars template");
+ handlebars.register_partial("header", String::from_utf8(theme.header.clone())?)?;
+
+ debug!("Register handlebars helpers");
+ self.register_hbs_helpers(&mut handlebars, &html_config);
+
+ let mut data = make_data(&ctx.root, book, &ctx.config, &html_config, &theme)?;
+
+ // Print version
+ let mut print_content = String::new();
+
+ fs::create_dir_all(&destination)
+ .with_context(|| "Unexpected error when constructing destination path")?;
+
+ let mut is_index = true;
+ for item in book.iter() {
+ let ctx = RenderItemContext {
+ handlebars: &handlebars,
+ destination: destination.to_path_buf(),
+ data: data.clone(),
+ is_index,
+ book_config: book_config.clone(),
+ html_config: html_config.clone(),
+ edition: ctx.config.rust.edition,
+ chapter_titles: &ctx.chapter_titles,
+ };
+ self.render_item(item, ctx, &mut print_content)?;
+ // Only the first non-draft chapter item should be treated as the "index"
+ is_index &= !matches!(item, BookItem::Chapter(ch) if !ch.is_draft_chapter());
+ }
+
+ // Render 404 page
+ if html_config.input_404 != Some("".to_string()) {
+ self.render_404(ctx, &html_config, &src_dir, &mut handlebars, &mut data)?;
+ }
+
+ // Print version
+ self.configure_print_version(&mut data, &print_content);
+ if let Some(ref title) = ctx.config.book.title {
+ data.insert("title".to_owned(), json!(title));
+ }
+
+ // Render the handlebars template with the data
+ if html_config.print.enable {
+ debug!("Render template");
+ let rendered = handlebars.render("index", &data)?;
+
+ let rendered =
+ self.post_process(rendered, &html_config.playground, ctx.config.rust.edition);
+
+ utils::fs::write_file(destination, "print.html", rendered.as_bytes())?;
+ debug!("Creating print.html ✓");
+ }
+
+ debug!("Copy static files");
+ self.copy_static_files(destination, &theme, &html_config)
+ .with_context(|| "Unable to copy across static files")?;
+ self.copy_additional_css_and_js(&html_config, &ctx.root, destination)
+ .with_context(|| "Unable to copy across additional CSS and JS")?;
+
+ // Render search index
+ #[cfg(feature = "search")]
+ {
+ let search = html_config.search.unwrap_or_default();
+ if search.enable {
+ super::search::create_files(&search, destination, book)?;
+ }
+ }
+
+ self.emit_redirects(&ctx.destination, &handlebars, &html_config.redirect)
+ .context("Unable to emit redirects")?;
+
+ // Copy all remaining files, avoid a recursive copy from/to the book build dir
+ utils::fs::copy_files_except_ext(&src_dir, destination, true, Some(&build_dir), &["md"])?;
+
+ Ok(())
+ }
+}
+
+fn make_data(
+ root: &Path,
+ book: &Book,
+ config: &Config,
+ html_config: &HtmlConfig,
+ theme: &Theme,
+) -> Result<serde_json::Map<String, serde_json::Value>> {
+ trace!("make_data");
+
+ let mut data = serde_json::Map::new();
+ data.insert(
+ "language".to_owned(),
+ json!(config.book.language.clone().unwrap_or_default()),
+ );
+ data.insert(
+ "book_title".to_owned(),
+ json!(config.book.title.clone().unwrap_or_default()),
+ );
+ data.insert(
+ "description".to_owned(),
+ json!(config.book.description.clone().unwrap_or_default()),
+ );
+ if theme.favicon_png.is_some() {
+ data.insert("favicon_png".to_owned(), json!("favicon.png"));
+ }
+ if theme.favicon_svg.is_some() {
+ data.insert("favicon_svg".to_owned(), json!("favicon.svg"));
+ }
+ if let Some(ref live_reload_endpoint) = html_config.live_reload_endpoint {
+ data.insert(
+ "live_reload_endpoint".to_owned(),
+ json!(live_reload_endpoint),
+ );
+ }
+
+ let default_theme = match html_config.default_theme {
+ Some(ref theme) => theme.to_lowercase(),
+ None => "light".to_string(),
+ };
+ data.insert("default_theme".to_owned(), json!(default_theme));
+
+ let preferred_dark_theme = match html_config.preferred_dark_theme {
+ Some(ref theme) => theme.to_lowercase(),
+ None => "navy".to_string(),
+ };
+ data.insert(
+ "preferred_dark_theme".to_owned(),
+ json!(preferred_dark_theme),
+ );
+
+ // Add google analytics tag
+ if let Some(ref ga) = html_config.google_analytics {
+ data.insert("google_analytics".to_owned(), json!(ga));
+ }
+
+ if html_config.mathjax_support {
+ data.insert("mathjax_support".to_owned(), json!(true));
+ }
+
+ if html_config.copy_fonts {
+ data.insert("copy_fonts".to_owned(), json!(true));
+ }
+
+ // Add check to see if there is an additional style
+ if !html_config.additional_css.is_empty() {
+ let mut css = Vec::new();
+ for style in &html_config.additional_css {
+ match style.strip_prefix(root) {
+ Ok(p) => css.push(p.to_str().expect("Could not convert to str")),
+ Err(_) => css.push(style.to_str().expect("Could not convert to str")),
+ }
+ }
+ data.insert("additional_css".to_owned(), json!(css));
+ }
+
+ // Add check to see if there is an additional script
+ if !html_config.additional_js.is_empty() {
+ let mut js = Vec::new();
+ for script in &html_config.additional_js {
+ match script.strip_prefix(root) {
+ Ok(p) => js.push(p.to_str().expect("Could not convert to str")),
+ Err(_) => js.push(script.to_str().expect("Could not convert to str")),
+ }
+ }
+ data.insert("additional_js".to_owned(), json!(js));
+ }
+
+ if html_config.playground.editable && html_config.playground.copy_js {
+ data.insert("playground_js".to_owned(), json!(true));
+ if html_config.playground.line_numbers {
+ data.insert("playground_line_numbers".to_owned(), json!(true));
+ }
+ }
+ if html_config.playground.copyable {
+ data.insert("playground_copyable".to_owned(), json!(true));
+ }
+
+ data.insert("print_enable".to_owned(), json!(html_config.print.enable));
+ data.insert("fold_enable".to_owned(), json!(html_config.fold.enable));
+ data.insert("fold_level".to_owned(), json!(html_config.fold.level));
+
+ let search = html_config.search.clone();
+ if cfg!(feature = "search") {
+ let search = search.unwrap_or_default();
+ data.insert("search_enabled".to_owned(), json!(search.enable));
+ data.insert(
+ "search_js".to_owned(),
+ json!(search.enable && search.copy_js),
+ );
+ } else if search.is_some() {
+ warn!("mdBook compiled without search support, ignoring `output.html.search` table");
+ warn!(
+ "please reinstall with `cargo install mdbook --force --features search`to use the \
+ search feature"
+ )
+ }
+
+ if let Some(ref git_repository_url) = html_config.git_repository_url {
+ data.insert("git_repository_url".to_owned(), json!(git_repository_url));
+ }
+
+ let git_repository_icon = match html_config.git_repository_icon {
+ Some(ref git_repository_icon) => git_repository_icon,
+ None => "fa-github",
+ };
+ data.insert("git_repository_icon".to_owned(), json!(git_repository_icon));
+
+ let mut chapters = vec![];
+
+ for item in book.iter() {
+ // Create the data to inject in the template
+ let mut chapter = BTreeMap::new();
+
+ match *item {
+ BookItem::PartTitle(ref title) => {
+ chapter.insert("part".to_owned(), json!(title));
+ }
+ BookItem::Chapter(ref ch) => {
+ if let Some(ref section) = ch.number {
+ chapter.insert("section".to_owned(), json!(section.to_string()));
+ }
+
+ chapter.insert(
+ "has_sub_items".to_owned(),
+ json!((!ch.sub_items.is_empty()).to_string()),
+ );
+
+ chapter.insert("name".to_owned(), json!(ch.name));
+ if let Some(ref path) = ch.path {
+ let p = path
+ .to_str()
+ .with_context(|| "Could not convert path to str")?;
+ chapter.insert("path".to_owned(), json!(p));
+ }
+ }
+ BookItem::Separator => {
+ chapter.insert("spacer".to_owned(), json!("_spacer_"));
+ }
+ }
+
+ chapters.push(chapter);
+ }
+
+ data.insert("chapters".to_owned(), json!(chapters));
+
+ debug!("[*]: JSON constructed");
+ Ok(data)
+}
+
+/// Goes through the rendered HTML, making sure all header tags have
+/// an anchor respectively so people can link to sections directly.
+fn build_header_links(html: &str) -> String {
+ lazy_static! {
+ static ref BUILD_HEADER_LINKS: Regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();
+ }
+
+ let mut id_counter = HashMap::new();
+
+ BUILD_HEADER_LINKS
+ .replace_all(html, |caps: &Captures<'_>| {
+ let level = caps[1]
+ .parse()
+ .expect("Regex should ensure we only ever get numbers here");
+
+ insert_link_into_header(level, &caps[2], &mut id_counter)
+ })
+ .into_owned()
+}
+
+/// Insert a sinle link into a header, making sure each link gets its own
+/// unique ID by appending an auto-incremented number (if necessary).
+fn insert_link_into_header(
+ level: usize,
+ content: &str,
+ id_counter: &mut HashMap<String, usize>,
+) -> String {
+ let id = utils::unique_id_from_content(content, id_counter);
+
+ format!(
+ r##"<h{level} id="{id}"><a class="header" href="#{id}">{text}</a></h{level}>"##,
+ level = level,
+ id = id,
+ text = content
+ )
+}
+
+// The rust book uses annotations for rustdoc to test code snippets,
+// like the following:
+// ```rust,should_panic
+// fn main() {
+// // Code here
+// }
+// ```
+// This function replaces all commas by spaces in the code block classes
+fn fix_code_blocks(html: &str) -> String {
+ lazy_static! {
+ static ref FIX_CODE_BLOCKS: Regex =
+ Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap();
+ }
+
+ FIX_CODE_BLOCKS
+ .replace_all(html, |caps: &Captures<'_>| {
+ let before = &caps[1];
+ let classes = &caps[2].replace(',', " ");
+ let after = &caps[3];
+
+ format!(
+ r#"<code{before}class="{classes}"{after}>"#,
+ before = before,
+ classes = classes,
+ after = after
+ )
+ })
+ .into_owned()
+}
+
+fn add_playground_pre(
+ html: &str,
+ playground_config: &Playground,
+ edition: Option<RustEdition>,
+) -> String {
+ lazy_static! {
+ static ref ADD_PLAYGROUND_PRE: Regex =
+ Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap();
+ }
+ ADD_PLAYGROUND_PRE
+ .replace_all(html, |caps: &Captures<'_>| {
+ let text = &caps[1];
+ let classes = &caps[2];
+ let code = &caps[3];
+
+ if classes.contains("language-rust") {
+ if (!classes.contains("ignore")
+ && !classes.contains("noplayground")
+ && !classes.contains("noplaypen")
+ && playground_config.runnable)
+ || classes.contains("mdbook-runnable")
+ {
+ let contains_e2015 = classes.contains("edition2015");
+ let contains_e2018 = classes.contains("edition2018");
+ let contains_e2021 = classes.contains("edition2021");
+ let edition_class = if contains_e2015 || contains_e2018 || contains_e2021 {
+ // the user forced edition, we should not overwrite it
+ ""
+ } else {
+ match edition {
+ Some(RustEdition::E2015) => " edition2015",
+ Some(RustEdition::E2018) => " edition2018",
+ Some(RustEdition::E2021) => " edition2021",
+ None => "",
+ }
+ };
+
+ // wrap the contents in an external pre block
+ format!(
+ "<pre class=\"playground\"><code class=\"{}{}\">{}</code></pre>",
+ classes,
+ edition_class,
+ {
+ let content: Cow<'_, str> = if playground_config.editable
+ && classes.contains("editable")
+ || text.contains("fn main")
+ || text.contains("quick_main!")
+ {
+ code.into()
+ } else {
+ // we need to inject our own main
+ let (attrs, code) = partition_source(code);
+
+ format!("# #![allow(unused)]\n{}#fn main() {{\n{}#}}", attrs, code)
+ .into()
+ };
+ hide_lines(&content)
+ }
+ )
+ } else {
+ format!("<code class=\"{}\">{}</code>", classes, hide_lines(code))
+ }
+ } else {
+ // not language-rust, so no-op
+ text.to_owned()
+ }
+ })
+ .into_owned()
+}
+
+fn hide_lines(content: &str) -> String {
+ lazy_static! {
+ static ref BORING_LINES_REGEX: Regex = Regex::new(r"^(\s*)#(.?)(.*)$").unwrap();
+ }
+
+ let mut result = String::with_capacity(content.len());
+ for line in content.lines() {
+ if let Some(caps) = BORING_LINES_REGEX.captures(line) {
+ if &caps[2] == "#" {
+ result += &caps[1];
+ result += &caps[2];
+ result += &caps[3];
+ result += "\n";
+ continue;
+ } else if &caps[2] != "!" && &caps[2] != "[" {
+ result += "<span class=\"boring\">";
+ result += &caps[1];
+ if &caps[2] != " " {
+ result += &caps[2];
+ }
+ result += &caps[3];
+ result += "\n";
+ result += "</span>";
+ continue;
+ }
+ }
+ result += line;
+ result += "\n";
+ }
+ result
+}
+
+fn partition_source(s: &str) -> (String, String) {
+ let mut after_header = false;
+ let mut before = String::new();
+ let mut after = String::new();
+
+ for line in s.lines() {
+ let trimline = line.trim();
+ let header = trimline.chars().all(char::is_whitespace) || trimline.starts_with("#![");
+ if !header || after_header {
+ after_header = true;
+ after.push_str(line);
+ after.push('\n');
+ } else {
+ before.push_str(line);
+ before.push('\n');
+ }
+ }
+
+ (before, after)
+}
+
+struct RenderItemContext<'a> {
+ handlebars: &'a Handlebars<'a>,
+ destination: PathBuf,
+ data: serde_json::Map<String, serde_json::Value>,
+ is_index: bool,
+ book_config: BookConfig,
+ html_config: HtmlConfig,
+ edition: Option<RustEdition>,
+ chapter_titles: &'a HashMap<PathBuf, String>,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn original_build_header_links() {
+ let inputs = vec![
+ (
+ "blah blah <h1>Foo</h1>",
+ r##"blah blah <h1 id="foo"><a class="header" href="#foo">Foo</a></h1>"##,
+ ),
+ (
+ "<h1>Foo</h1>",
+ r##"<h1 id="foo"><a class="header" href="#foo">Foo</a></h1>"##,
+ ),
+ (
+ "<h3>Foo^bar</h3>",
+ r##"<h3 id="foobar"><a class="header" href="#foobar">Foo^bar</a></h3>"##,
+ ),
+ (
+ "<h4></h4>",
+ r##"<h4 id=""><a class="header" href="#"></a></h4>"##,
+ ),
+ (
+ "<h4><em>Hï</em></h4>",
+ r##"<h4 id="hï"><a class="header" href="#hï"><em>Hï</em></a></h4>"##,
+ ),
+ (
+ "<h1>Foo</h1><h3>Foo</h3>",
+ r##"<h1 id="foo"><a class="header" href="#foo">Foo</a></h1><h3 id="foo-1"><a class="header" href="#foo-1">Foo</a></h3>"##,
+ ),
+ ];
+
+ for (src, should_be) in inputs {
+ let got = build_header_links(src);
+ assert_eq!(got, should_be);
+ }
+ }
+
+ #[test]
+ fn add_playground() {
+ let inputs = [
+ ("<code class=\"language-rust\">x()</code>",
+ "<pre class=\"playground\"><code class=\"language-rust\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
+ ("<code class=\"language-rust\">fn main() {}</code>",
+ "<pre class=\"playground\"><code class=\"language-rust\">fn main() {}\n</code></pre>"),
+ ("<code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code>",
+ "<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";\n</code></pre>"),
+ ("<code class=\"language-rust editable\">let s = \"foo\n ## bar\n\";</code>",
+ "<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n\";\n</code></pre>"),
+ ("<code class=\"language-rust editable\">let s = \"foo\n # bar\n#\n\";</code>",
+ "<pre class=\"playground\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span><span class=\"boring\">\n</span>\";\n</code></pre>"),
+ ("<code class=\"language-rust ignore\">let s = \"foo\n # bar\n\";</code>",
+ "<code class=\"language-rust ignore\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";\n</code>"),
+ ("<code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code>",
+ "<pre class=\"playground\"><code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]\n</code></pre>"),
+ ];
+ for (src, should_be) in &inputs {
+ let got = add_playground_pre(
+ src,
+ &Playground {
+ editable: true,
+ ..Playground::default()
+ },
+ None,
+ );
+ assert_eq!(&*got, *should_be);
+ }
+ }
+ #[test]
+ fn add_playground_edition2015() {
+ let inputs = [
+ ("<code class=\"language-rust\">x()</code>",
+ "<pre class=\"playground\"><code class=\"language-rust edition2015\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
+ ("<code class=\"language-rust\">fn main() {}</code>",
+ "<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
+ ("<code class=\"language-rust edition2015\">fn main() {}</code>",
+ "<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
+ ("<code class=\"language-rust edition2018\">fn main() {}</code>",
+ "<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
+ ];
+ for (src, should_be) in &inputs {
+ let got = add_playground_pre(
+ src,
+ &Playground {
+ editable: true,
+ ..Playground::default()
+ },
+ Some(RustEdition::E2015),
+ );
+ assert_eq!(&*got, *should_be);
+ }
+ }
+ #[test]
+ fn add_playground_edition2018() {
+ let inputs = [
+ ("<code class=\"language-rust\">x()</code>",
+ "<pre class=\"playground\"><code class=\"language-rust edition2018\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
+ ("<code class=\"language-rust\">fn main() {}</code>",
+ "<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
+ ("<code class=\"language-rust edition2015\">fn main() {}</code>",
+ "<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
+ ("<code class=\"language-rust edition2018\">fn main() {}</code>",
+ "<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
+ ];
+ for (src, should_be) in &inputs {
+ let got = add_playground_pre(
+ src,
+ &Playground {
+ editable: true,
+ ..Playground::default()
+ },
+ Some(RustEdition::E2018),
+ );
+ assert_eq!(&*got, *should_be);
+ }
+ }
+ #[test]
+ fn add_playground_edition2021() {
+ let inputs = [
+ ("<code class=\"language-rust\">x()</code>",
+ "<pre class=\"playground\"><code class=\"language-rust edition2021\"><span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
+ ("<code class=\"language-rust\">fn main() {}</code>",
+ "<pre class=\"playground\"><code class=\"language-rust edition2021\">fn main() {}\n</code></pre>"),
+ ("<code class=\"language-rust edition2015\">fn main() {}</code>",
+ "<pre class=\"playground\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
+ ("<code class=\"language-rust edition2018\">fn main() {}</code>",
+ "<pre class=\"playground\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
+ ];
+ for (src, should_be) in &inputs {
+ let got = add_playground_pre(
+ src,
+ &Playground {
+ editable: true,
+ ..Playground::default()
+ },
+ Some(RustEdition::E2021),
+ );
+ assert_eq!(&*got, *should_be);
+ }
+ }
+}
diff --git a/vendor/mdbook/src/renderer/html_handlebars/helpers/mod.rs b/vendor/mdbook/src/renderer/html_handlebars/helpers/mod.rs
new file mode 100644
index 000000000..52be6d204
--- /dev/null
+++ b/vendor/mdbook/src/renderer/html_handlebars/helpers/mod.rs
@@ -0,0 +1,3 @@
+pub mod navigation;
+pub mod theme;
+pub mod toc;
diff --git a/vendor/mdbook/src/renderer/html_handlebars/helpers/navigation.rs b/vendor/mdbook/src/renderer/html_handlebars/helpers/navigation.rs
new file mode 100644
index 000000000..65929bbfc
--- /dev/null
+++ b/vendor/mdbook/src/renderer/html_handlebars/helpers/navigation.rs
@@ -0,0 +1,290 @@
+use std::collections::BTreeMap;
+use std::path::Path;
+
+use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError, Renderable};
+
+use crate::utils;
+
+type StringMap = BTreeMap<String, String>;
+
+/// Target for `find_chapter`.
+enum Target {
+ Previous,
+ Next,
+}
+
+impl Target {
+ /// Returns target if found.
+ fn find(
+ &self,
+ base_path: &str,
+ current_path: &str,
+ current_item: &StringMap,
+ previous_item: &StringMap,
+ ) -> Result<Option<StringMap>, RenderError> {
+ match *self {
+ Target::Next => {
+ let previous_path = previous_item
+ .get("path")
+ .ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))?;
+
+ if previous_path == base_path {
+ return Ok(Some(current_item.clone()));
+ }
+ }
+
+ Target::Previous => {
+ if current_path == base_path {
+ return Ok(Some(previous_item.clone()));
+ }
+ }
+ }
+
+ Ok(None)
+ }
+}
+
+fn find_chapter(
+ ctx: &Context,
+ rc: &mut RenderContext<'_, '_>,
+ target: Target,
+) -> Result<Option<StringMap>, RenderError> {
+ debug!("Get data from context");
+
+ let chapters = rc.evaluate(ctx, "@root/chapters").and_then(|c| {
+ serde_json::value::from_value::<Vec<StringMap>>(c.as_json().clone())
+ .map_err(|_| RenderError::new("Could not decode the JSON data"))
+ })?;
+
+ let base_path = rc
+ .evaluate(ctx, "@root/path")?
+ .as_json()
+ .as_str()
+ .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
+ .replace('\"', "");
+
+ if !rc.evaluate(ctx, "@root/is_index")?.is_missing() {
+ // Special case for index.md which may be a synthetic page.
+ // Target::find won't match because there is no page with the path
+ // "index.md" (unless there really is an index.md in SUMMARY.md).
+ match target {
+ Target::Previous => return Ok(None),
+ Target::Next => match chapters
+ .iter()
+ .filter(|chapter| {
+ // Skip things like "spacer"
+ chapter.contains_key("path")
+ })
+ .nth(1)
+ {
+ Some(chapter) => return Ok(Some(chapter.clone())),
+ None => return Ok(None),
+ },
+ }
+ }
+
+ let mut previous: Option<StringMap> = None;
+
+ debug!("Search for chapter");
+
+ for item in chapters {
+ match item.get("path") {
+ Some(path) if !path.is_empty() => {
+ if let Some(previous) = previous {
+ if let Some(item) = target.find(&base_path, path, &item, &previous)? {
+ return Ok(Some(item));
+ }
+ }
+
+ previous = Some(item.clone());
+ }
+ _ => continue,
+ }
+ }
+
+ Ok(None)
+}
+
+fn render(
+ _h: &Helper<'_, '_>,
+ r: &Handlebars<'_>,
+ ctx: &Context,
+ rc: &mut RenderContext<'_, '_>,
+ out: &mut dyn Output,
+ chapter: &StringMap,
+) -> Result<(), RenderError> {
+ trace!("Creating BTreeMap to inject in context");
+
+ let mut context = BTreeMap::new();
+ let base_path = rc
+ .evaluate(ctx, "@root/path")?
+ .as_json()
+ .as_str()
+ .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
+ .replace('\"', "");
+
+ context.insert(
+ "path_to_root".to_owned(),
+ json!(utils::fs::path_to_root(&base_path)),
+ );
+
+ chapter
+ .get("name")
+ .ok_or_else(|| RenderError::new("No title found for chapter in JSON data"))
+ .map(|name| context.insert("title".to_owned(), json!(name)))?;
+
+ chapter
+ .get("path")
+ .ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))
+ .and_then(|p| {
+ Path::new(p)
+ .with_extension("html")
+ .to_str()
+ .ok_or_else(|| RenderError::new("Link could not be converted to str"))
+ .map(|p| context.insert("link".to_owned(), json!(p.replace('\\', "/"))))
+ })?;
+
+ trace!("Render template");
+
+ _h.template()
+ .ok_or_else(|| RenderError::new("Error with the handlebars template"))
+ .and_then(|t| {
+ let local_ctx = Context::wraps(&context)?;
+ let mut local_rc = rc.clone();
+ t.render(r, &local_ctx, &mut local_rc, out)
+ })?;
+
+ Ok(())
+}
+
+pub fn previous(
+ _h: &Helper<'_, '_>,
+ r: &Handlebars<'_>,
+ ctx: &Context,
+ rc: &mut RenderContext<'_, '_>,
+ out: &mut dyn Output,
+) -> Result<(), RenderError> {
+ trace!("previous (handlebars helper)");
+
+ if let Some(previous) = find_chapter(ctx, rc, Target::Previous)? {
+ render(_h, r, ctx, rc, out, &previous)?;
+ }
+
+ Ok(())
+}
+
+pub fn next(
+ _h: &Helper<'_, '_>,
+ r: &Handlebars<'_>,
+ ctx: &Context,
+ rc: &mut RenderContext<'_, '_>,
+ out: &mut dyn Output,
+) -> Result<(), RenderError> {
+ trace!("next (handlebars helper)");
+
+ if let Some(next) = find_chapter(ctx, rc, Target::Next)? {
+ render(_h, r, ctx, rc, out, &next)?;
+ }
+
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ static TEMPLATE: &str =
+ "{{#previous}}{{title}}: {{link}}{{/previous}}|{{#next}}{{title}}: {{link}}{{/next}}";
+
+ #[test]
+ fn test_next_previous() {
+ let data = json!({
+ "name": "two",
+ "path": "two.path",
+ "chapters": [
+ {
+ "name": "one",
+ "path": "one.path"
+ },
+ {
+ "name": "two",
+ "path": "two.path",
+ },
+ {
+ "name": "three",
+ "path": "three.path"
+ }
+ ]
+ });
+
+ let mut h = Handlebars::new();
+ h.register_helper("previous", Box::new(previous));
+ h.register_helper("next", Box::new(next));
+
+ assert_eq!(
+ h.render_template(TEMPLATE, &data).unwrap(),
+ "one: one.html|three: three.html"
+ );
+ }
+
+ #[test]
+ fn test_first() {
+ let data = json!({
+ "name": "one",
+ "path": "one.path",
+ "chapters": [
+ {
+ "name": "one",
+ "path": "one.path"
+ },
+ {
+ "name": "two",
+ "path": "two.path",
+ },
+ {
+ "name": "three",
+ "path": "three.path"
+ }
+ ]
+ });
+
+ let mut h = Handlebars::new();
+ h.register_helper("previous", Box::new(previous));
+ h.register_helper("next", Box::new(next));
+
+ assert_eq!(
+ h.render_template(TEMPLATE, &data).unwrap(),
+ "|two: two.html"
+ );
+ }
+ #[test]
+ fn test_last() {
+ let data = json!({
+ "name": "three",
+ "path": "three.path",
+ "chapters": [
+ {
+ "name": "one",
+ "path": "one.path"
+ },
+ {
+ "name": "two",
+ "path": "two.path",
+ },
+ {
+ "name": "three",
+ "path": "three.path"
+ }
+ ]
+ });
+
+ let mut h = Handlebars::new();
+ h.register_helper("previous", Box::new(previous));
+ h.register_helper("next", Box::new(next));
+
+ assert_eq!(
+ h.render_template(TEMPLATE, &data).unwrap(),
+ "two: two.html|"
+ );
+ }
+}
diff --git a/vendor/mdbook/src/renderer/html_handlebars/helpers/theme.rs b/vendor/mdbook/src/renderer/html_handlebars/helpers/theme.rs
new file mode 100644
index 000000000..809ee1176
--- /dev/null
+++ b/vendor/mdbook/src/renderer/html_handlebars/helpers/theme.rs
@@ -0,0 +1,28 @@
+use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError};
+
+pub fn theme_option(
+ h: &Helper<'_, '_>,
+ _r: &Handlebars<'_>,
+ ctx: &Context,
+ rc: &mut RenderContext<'_, '_>,
+ out: &mut dyn Output,
+) -> Result<(), RenderError> {
+ trace!("theme_option (handlebars helper)");
+
+ let param = h.param(0).and_then(|v| v.value().as_str()).ok_or_else(|| {
+ RenderError::new("Param 0 with String type is required for theme_option helper.")
+ })?;
+
+ let default_theme = rc.evaluate(ctx, "@root/default_theme")?;
+ let default_theme_name = default_theme
+ .as_json()
+ .as_str()
+ .ok_or_else(|| RenderError::new("Type error for `default_theme`, string expected"))?;
+
+ out.write(param)?;
+ if param.to_lowercase() == default_theme_name.to_lowercase() {
+ out.write(" (default)")?;
+ }
+
+ Ok(())
+}
diff --git a/vendor/mdbook/src/renderer/html_handlebars/helpers/toc.rs b/vendor/mdbook/src/renderer/html_handlebars/helpers/toc.rs
new file mode 100644
index 000000000..0884d30ad
--- /dev/null
+++ b/vendor/mdbook/src/renderer/html_handlebars/helpers/toc.rs
@@ -0,0 +1,203 @@
+use std::path::Path;
+use std::{cmp::Ordering, collections::BTreeMap};
+
+use crate::utils;
+use crate::utils::bracket_escape;
+
+use handlebars::{Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError};
+
+// Handlebars helper to construct TOC
+#[derive(Clone, Copy)]
+pub struct RenderToc {
+ pub no_section_label: bool,
+}
+
+impl HelperDef for RenderToc {
+ fn call<'reg: 'rc, 'rc>(
+ &self,
+ _h: &Helper<'reg, 'rc>,
+ _r: &'reg Handlebars<'_>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+ ) -> Result<(), RenderError> {
+ // get value from context data
+ // rc.get_path() is current json parent path, you should always use it like this
+ // param is the key of value you want to display
+ let chapters = rc.evaluate(ctx, "@root/chapters").and_then(|c| {
+ serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.as_json().clone())
+ .map_err(|_| RenderError::new("Could not decode the JSON data"))
+ })?;
+ let current_path = rc
+ .evaluate(ctx, "@root/path")?
+ .as_json()
+ .as_str()
+ .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
+ .replace('\"', "");
+
+ let current_section = rc
+ .evaluate(ctx, "@root/section")?
+ .as_json()
+ .as_str()
+ .map(str::to_owned)
+ .unwrap_or_default();
+
+ let fold_enable = rc
+ .evaluate(ctx, "@root/fold_enable")?
+ .as_json()
+ .as_bool()
+ .ok_or_else(|| RenderError::new("Type error for `fold_enable`, bool expected"))?;
+
+ let fold_level = rc
+ .evaluate(ctx, "@root/fold_level")?
+ .as_json()
+ .as_u64()
+ .ok_or_else(|| RenderError::new("Type error for `fold_level`, u64 expected"))?;
+
+ out.write("<ol class=\"chapter\">")?;
+
+ let mut current_level = 1;
+ // The "index" page, which has this attribute set, is supposed to alias the first chapter in
+ // the book, i.e. the first link. There seems to be no easy way to determine which chapter
+ // the "index" is aliasing from within the renderer, so this is used instead to force the
+ // first link to be active. See further below.
+ let mut is_first_chapter = ctx.data().get("is_index").is_some();
+
+ for item in chapters {
+ // Spacer
+ if item.get("spacer").is_some() {
+ out.write("<li class=\"spacer\"></li>")?;
+ continue;
+ }
+
+ let (section, level) = if let Some(s) = item.get("section") {
+ (s.as_str(), s.matches('.').count())
+ } else {
+ ("", 1)
+ };
+
+ let is_expanded =
+ if !fold_enable || (!section.is_empty() && current_section.starts_with(section)) {
+ // Expand if folding is disabled, or if the section is an
+ // ancestor or the current section itself.
+ true
+ } else {
+ // Levels that are larger than this would be folded.
+ level - 1 < fold_level as usize
+ };
+
+ match level.cmp(&current_level) {
+ Ordering::Greater => {
+ while level > current_level {
+ out.write("<li>")?;
+ out.write("<ol class=\"section\">")?;
+ current_level += 1;
+ }
+ write_li_open_tag(out, is_expanded, false)?;
+ }
+ Ordering::Less => {
+ while level < current_level {
+ out.write("</ol>")?;
+ out.write("</li>")?;
+ current_level -= 1;
+ }
+ write_li_open_tag(out, is_expanded, false)?;
+ }
+ Ordering::Equal => {
+ write_li_open_tag(out, is_expanded, item.get("section").is_none())?;
+ }
+ }
+
+ // Part title
+ if let Some(title) = item.get("part") {
+ out.write("<li class=\"part-title\">")?;
+ out.write(&bracket_escape(title))?;
+ out.write("</li>")?;
+ continue;
+ }
+
+ // Link
+ let path_exists = if let Some(path) =
+ item.get("path")
+ .and_then(|p| if p.is_empty() { None } else { Some(p) })
+ {
+ out.write("<a href=\"")?;
+
+ let tmp = Path::new(item.get("path").expect("Error: path should be Some(_)"))
+ .with_extension("html")
+ .to_str()
+ .unwrap()
+ // Hack for windows who tends to use `\` as separator instead of `/`
+ .replace('\\', "/");
+
+ // Add link
+ out.write(&utils::fs::path_to_root(&current_path))?;
+ out.write(&tmp)?;
+ out.write("\"")?;
+
+ if path == &current_path || is_first_chapter {
+ is_first_chapter = false;
+ out.write(" class=\"active\"")?;
+ }
+
+ out.write(">")?;
+ true
+ } else {
+ out.write("<div>")?;
+ false
+ };
+
+ if !self.no_section_label {
+ // Section does not necessarily exist
+ if let Some(section) = item.get("section") {
+ out.write("<strong aria-hidden=\"true\">")?;
+ out.write(section)?;
+ out.write("</strong> ")?;
+ }
+ }
+
+ if let Some(name) = item.get("name") {
+ out.write(&bracket_escape(name))?
+ }
+
+ if path_exists {
+ out.write("</a>")?;
+ } else {
+ out.write("</div>")?;
+ }
+
+ // Render expand/collapse toggle
+ if let Some(flag) = item.get("has_sub_items") {
+ let has_sub_items = flag.parse::<bool>().unwrap_or_default();
+ if fold_enable && has_sub_items {
+ out.write("<a class=\"toggle\"><div>❱</div></a>")?;
+ }
+ }
+ out.write("</li>")?;
+ }
+ while current_level > 1 {
+ out.write("</ol>")?;
+ out.write("</li>")?;
+ current_level -= 1;
+ }
+
+ out.write("</ol>")?;
+ Ok(())
+ }
+}
+
+fn write_li_open_tag(
+ out: &mut dyn Output,
+ is_expanded: bool,
+ is_affix: bool,
+) -> Result<(), std::io::Error> {
+ let mut li = String::from("<li class=\"chapter-item ");
+ if is_expanded {
+ li.push_str("expanded ");
+ }
+ if is_affix {
+ li.push_str("affix ");
+ }
+ li.push_str("\">");
+ out.write(&li)
+}
diff --git a/vendor/mdbook/src/renderer/html_handlebars/mod.rs b/vendor/mdbook/src/renderer/html_handlebars/mod.rs
new file mode 100644
index 000000000..f1155ed75
--- /dev/null
+++ b/vendor/mdbook/src/renderer/html_handlebars/mod.rs
@@ -0,0 +1,9 @@
+#![allow(missing_docs)] // FIXME: Document this
+
+pub use self::hbs_renderer::HtmlHandlebars;
+
+mod hbs_renderer;
+mod helpers;
+
+#[cfg(feature = "search")]
+mod search;
diff --git a/vendor/mdbook/src/renderer/html_handlebars/search.rs b/vendor/mdbook/src/renderer/html_handlebars/search.rs
new file mode 100644
index 000000000..c3b944c9d
--- /dev/null
+++ b/vendor/mdbook/src/renderer/html_handlebars/search.rs
@@ -0,0 +1,286 @@
+use std::borrow::Cow;
+use std::collections::{HashMap, HashSet};
+use std::path::Path;
+
+use elasticlunr::{Index, IndexBuilder};
+use pulldown_cmark::*;
+
+use crate::book::{Book, BookItem};
+use crate::config::Search;
+use crate::errors::*;
+use crate::theme::searcher;
+use crate::utils;
+
+use serde::Serialize;
+
+const MAX_WORD_LENGTH_TO_INDEX: usize = 80;
+
+/// Tokenizes in the same way as elasticlunr-rs (for English), but also drops long tokens.
+fn tokenize(text: &str) -> Vec<String> {
+ text.split(|c: char| c.is_whitespace() || c == '-')
+ .filter(|s| !s.is_empty())
+ .map(|s| s.trim().to_lowercase())
+ .filter(|s| s.len() <= MAX_WORD_LENGTH_TO_INDEX)
+ .collect()
+}
+
+/// Creates all files required for search.
+pub fn create_files(search_config: &Search, destination: &Path, book: &Book) -> Result<()> {
+ let mut index = IndexBuilder::new()
+ .add_field_with_tokenizer("title", Box::new(&tokenize))
+ .add_field_with_tokenizer("body", Box::new(&tokenize))
+ .add_field_with_tokenizer("breadcrumbs", Box::new(&tokenize))
+ .build();
+
+ let mut doc_urls = Vec::with_capacity(book.sections.len());
+
+ for item in book.iter() {
+ render_item(&mut index, search_config, &mut doc_urls, item)?;
+ }
+
+ let index = write_to_json(index, search_config, doc_urls)?;
+ debug!("Writing search index ✓");
+ if index.len() > 10_000_000 {
+ warn!("searchindex.json is very large ({} bytes)", index.len());
+ }
+
+ if search_config.copy_js {
+ utils::fs::write_file(destination, "searchindex.json", index.as_bytes())?;
+ utils::fs::write_file(
+ destination,
+ "searchindex.js",
+ format!("Object.assign(window.search, {});", index).as_bytes(),
+ )?;
+ utils::fs::write_file(destination, "searcher.js", searcher::JS)?;
+ utils::fs::write_file(destination, "mark.min.js", searcher::MARK_JS)?;
+ utils::fs::write_file(destination, "elasticlunr.min.js", searcher::ELASTICLUNR_JS)?;
+ debug!("Copying search files ✓");
+ }
+
+ Ok(())
+}
+
+/// Uses the given arguments to construct a search document, then inserts it to the given index.
+fn add_doc(
+ index: &mut Index,
+ doc_urls: &mut Vec<String>,
+ anchor_base: &str,
+ section_id: &Option<String>,
+ items: &[&str],
+) {
+ let url = if let Some(ref id) = *section_id {
+ Cow::Owned(format!("{}#{}", anchor_base, id))
+ } else {
+ Cow::Borrowed(anchor_base)
+ };
+ let url = utils::collapse_whitespace(url.trim());
+ let doc_ref = doc_urls.len().to_string();
+ doc_urls.push(url.into());
+
+ let items = items.iter().map(|&x| utils::collapse_whitespace(x.trim()));
+ index.add_doc(&doc_ref, items);
+}
+
+/// Renders markdown into flat unformatted text and adds it to the search index.
+fn render_item(
+ index: &mut Index,
+ search_config: &Search,
+ doc_urls: &mut Vec<String>,
+ item: &BookItem,
+) -> Result<()> {
+ let chapter = match *item {
+ BookItem::Chapter(ref ch) if !ch.is_draft_chapter() => ch,
+ _ => return Ok(()),
+ };
+
+ let chapter_path = chapter
+ .path
+ .as_ref()
+ .expect("Checked that path exists above");
+ let filepath = Path::new(&chapter_path).with_extension("html");
+ let filepath = filepath
+ .to_str()
+ .with_context(|| "Could not convert HTML path to str")?;
+ let anchor_base = utils::fs::normalize_path(filepath);
+
+ let mut p = utils::new_cmark_parser(&chapter.content, false).peekable();
+
+ let mut in_heading = false;
+ let max_section_depth = u32::from(search_config.heading_split_level);
+ let mut section_id = None;
+ let mut heading = String::new();
+ let mut body = String::new();
+ let mut breadcrumbs = chapter.parent_names.clone();
+ let mut footnote_numbers = HashMap::new();
+
+ breadcrumbs.push(chapter.name.clone());
+
+ let mut id_counter = HashMap::new();
+ while let Some(event) = p.next() {
+ match event {
+ Event::Start(Tag::Heading(i, ..)) if i as u32 <= max_section_depth => {
+ if !heading.is_empty() {
+ // Section finished, the next heading is following now
+ // Write the data to the index, and clear it for the next section
+ add_doc(
+ index,
+ doc_urls,
+ &anchor_base,
+ &section_id,
+ &[&heading, &body, &breadcrumbs.join(" » ")],
+ );
+ section_id = None;
+ heading.clear();
+ body.clear();
+ breadcrumbs.pop();
+ }
+
+ in_heading = true;
+ }
+ Event::End(Tag::Heading(i, ..)) if i as u32 <= max_section_depth => {
+ in_heading = false;
+ section_id = Some(utils::unique_id_from_content(&heading, &mut id_counter));
+ breadcrumbs.push(heading.clone());
+ }
+ Event::Start(Tag::FootnoteDefinition(name)) => {
+ let number = footnote_numbers.len() + 1;
+ footnote_numbers.entry(name).or_insert(number);
+ }
+ Event::Html(html) => {
+ let mut html_block = html.into_string();
+
+ // As of pulldown_cmark 0.6, html events are no longer contained
+ // in an HtmlBlock tag. We must collect consecutive Html events
+ // into a block ourselves.
+ while let Some(Event::Html(html)) = p.peek() {
+ html_block.push_str(html);
+ p.next();
+ }
+
+ body.push_str(&clean_html(&html_block));
+ }
+ Event::Start(_) | Event::End(_) | Event::Rule | Event::SoftBreak | Event::HardBreak => {
+ // Insert spaces where HTML output would usually separate text
+ // to ensure words don't get merged together
+ if in_heading {
+ heading.push(' ');
+ } else {
+ body.push(' ');
+ }
+ }
+ Event::Text(text) | Event::Code(text) => {
+ if in_heading {
+ heading.push_str(&text);
+ } else {
+ body.push_str(&text);
+ }
+ }
+ Event::FootnoteReference(name) => {
+ let len = footnote_numbers.len() + 1;
+ let number = footnote_numbers.entry(name).or_insert(len);
+ body.push_str(&format!(" [{}] ", number));
+ }
+ Event::TaskListMarker(_checked) => {}
+ }
+ }
+
+ if !body.is_empty() || !heading.is_empty() {
+ if heading.is_empty() {
+ if let Some(chapter) = breadcrumbs.first() {
+ heading = chapter.clone();
+ }
+ }
+ // Make sure the last section is added to the index
+ add_doc(
+ index,
+ doc_urls,
+ &anchor_base,
+ &section_id,
+ &[&heading, &body, &breadcrumbs.join(" » ")],
+ );
+ }
+
+ Ok(())
+}
+
+fn write_to_json(index: Index, search_config: &Search, doc_urls: Vec<String>) -> Result<String> {
+ use elasticlunr::config::{SearchBool, SearchOptions, SearchOptionsField};
+ use std::collections::BTreeMap;
+
+ #[derive(Serialize)]
+ struct ResultsOptions {
+ limit_results: u32,
+ teaser_word_count: u32,
+ }
+
+ #[derive(Serialize)]
+ struct SearchindexJson {
+ /// The options used for displaying search results
+ results_options: ResultsOptions,
+ /// The searchoptions for elasticlunr.js
+ search_options: SearchOptions,
+ /// Used to lookup a document's URL from an integer document ref.
+ doc_urls: Vec<String>,
+ /// The index for elasticlunr.js
+ index: elasticlunr::Index,
+ }
+
+ let mut fields = BTreeMap::new();
+ let mut opt = SearchOptionsField::default();
+ let mut insert_boost = |key: &str, boost| {
+ opt.boost = Some(boost);
+ fields.insert(key.into(), opt);
+ };
+ insert_boost("title", search_config.boost_title);
+ insert_boost("body", search_config.boost_paragraph);
+ insert_boost("breadcrumbs", search_config.boost_hierarchy);
+
+ let search_options = SearchOptions {
+ bool: if search_config.use_boolean_and {
+ SearchBool::And
+ } else {
+ SearchBool::Or
+ },
+ expand: search_config.expand,
+ fields,
+ };
+
+ let results_options = ResultsOptions {
+ limit_results: search_config.limit_results,
+ teaser_word_count: search_config.teaser_word_count,
+ };
+
+ let json_contents = SearchindexJson {
+ results_options,
+ search_options,
+ doc_urls,
+ index,
+ };
+
+ // By converting to serde_json::Value as an intermediary, we use a
+ // BTreeMap internally and can force a stable ordering of map keys.
+ let json_contents = serde_json::to_value(&json_contents)?;
+ let json_contents = serde_json::to_string(&json_contents)?;
+
+ Ok(json_contents)
+}
+
+fn clean_html(html: &str) -> String {
+ lazy_static! {
+ static ref AMMONIA: ammonia::Builder<'static> = {
+ let mut clean_content = HashSet::new();
+ clean_content.insert("script");
+ clean_content.insert("style");
+ let mut builder = ammonia::Builder::new();
+ builder
+ .tags(HashSet::new())
+ .tag_attributes(HashMap::new())
+ .generic_attributes(HashSet::new())
+ .link_rel(None)
+ .allowed_classes(HashMap::new())
+ .clean_content_tags(clean_content);
+ builder
+ };
+ }
+ AMMONIA.clean(html).to_string()
+}
diff --git a/vendor/mdbook/src/renderer/markdown_renderer.rs b/vendor/mdbook/src/renderer/markdown_renderer.rs
new file mode 100644
index 000000000..bd5def1f4
--- /dev/null
+++ b/vendor/mdbook/src/renderer/markdown_renderer.rs
@@ -0,0 +1,52 @@
+use crate::book::BookItem;
+use crate::errors::*;
+use crate::renderer::{RenderContext, Renderer};
+use crate::utils;
+
+use std::fs;
+
+#[derive(Default)]
+/// A renderer to output the Markdown after the preprocessors have run. Mostly useful
+/// when debugging preprocessors.
+pub struct MarkdownRenderer;
+
+impl MarkdownRenderer {
+ /// Create a new `MarkdownRenderer` instance.
+ pub fn new() -> Self {
+ MarkdownRenderer
+ }
+}
+
+impl Renderer for MarkdownRenderer {
+ fn name(&self) -> &str {
+ "markdown"
+ }
+
+ fn render(&self, ctx: &RenderContext) -> Result<()> {
+ let destination = &ctx.destination;
+ let book = &ctx.book;
+
+ if destination.exists() {
+ utils::fs::remove_dir_content(destination)
+ .with_context(|| "Unable to remove stale Markdown output")?;
+ }
+
+ trace!("markdown render");
+ for item in book.iter() {
+ if let BookItem::Chapter(ref ch) = *item {
+ if !ch.is_draft_chapter() {
+ utils::fs::write_file(
+ &ctx.destination,
+ &ch.path.as_ref().expect("Checked path exists before"),
+ ch.content.as_bytes(),
+ )?;
+ }
+ }
+ }
+
+ fs::create_dir_all(&destination)
+ .with_context(|| "Unexpected error when constructing destination path")?;
+
+ Ok(())
+ }
+}
diff --git a/vendor/mdbook/src/renderer/mod.rs b/vendor/mdbook/src/renderer/mod.rs
new file mode 100644
index 000000000..15465fbce
--- /dev/null
+++ b/vendor/mdbook/src/renderer/mod.rs
@@ -0,0 +1,265 @@
+//! `mdbook`'s low level rendering interface.
+//!
+//! # Note
+//!
+//! You usually don't need to work with this module directly. If you want to
+//! implement your own backend, then check out the [For Developers] section of
+//! the user guide.
+//!
+//! The definition for [RenderContext] may be useful though.
+//!
+//! [For Developers]: https://rust-lang.github.io/mdBook/for_developers/index.html
+//! [RenderContext]: struct.RenderContext.html
+
+pub use self::html_handlebars::HtmlHandlebars;
+pub use self::markdown_renderer::MarkdownRenderer;
+
+mod html_handlebars;
+mod markdown_renderer;
+
+use shlex::Shlex;
+use std::collections::HashMap;
+use std::fs;
+use std::io::{self, ErrorKind, Read};
+use std::path::{Path, PathBuf};
+use std::process::{Command, Stdio};
+
+use crate::book::Book;
+use crate::config::Config;
+use crate::errors::*;
+use toml::Value;
+
+use serde::{Deserialize, Serialize};
+
+/// An arbitrary `mdbook` backend.
+///
+/// Although it's quite possible for you to import `mdbook` as a library and
+/// provide your own renderer, there are two main renderer implementations that
+/// 99% of users will ever use:
+///
+/// - [`HtmlHandlebars`] - the built-in HTML renderer
+/// - [`CmdRenderer`] - a generic renderer which shells out to a program to do the
+/// actual rendering
+pub trait Renderer {
+ /// The `Renderer`'s name.
+ fn name(&self) -> &str;
+
+ /// Invoke the `Renderer`, passing in all the necessary information for
+ /// describing a book.
+ fn render(&self, ctx: &RenderContext) -> Result<()>;
+}
+
+/// The context provided to all renderers.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct RenderContext {
+ /// Which version of `mdbook` did this come from (as written in `mdbook`'s
+ /// `Cargo.toml`). Useful if you know the renderer is only compatible with
+ /// certain versions of `mdbook`.
+ pub version: String,
+ /// The book's root directory.
+ pub root: PathBuf,
+ /// A loaded representation of the book itself.
+ pub book: Book,
+ /// The loaded configuration file.
+ pub config: Config,
+ /// Where the renderer *must* put any build artefacts generated. To allow
+ /// renderers to cache intermediate results, this directory is not
+ /// guaranteed to be empty or even exist.
+ pub destination: PathBuf,
+ #[serde(skip)]
+ pub(crate) chapter_titles: HashMap<PathBuf, String>,
+ #[serde(skip)]
+ __non_exhaustive: (),
+}
+
+impl RenderContext {
+ /// Create a new `RenderContext`.
+ pub fn new<P, Q>(root: P, book: Book, config: Config, destination: Q) -> RenderContext
+ where
+ P: Into<PathBuf>,
+ Q: Into<PathBuf>,
+ {
+ RenderContext {
+ book,
+ config,
+ version: crate::MDBOOK_VERSION.to_string(),
+ root: root.into(),
+ destination: destination.into(),
+ chapter_titles: HashMap::new(),
+ __non_exhaustive: (),
+ }
+ }
+
+ /// Get the source directory's (absolute) path on disk.
+ pub fn source_dir(&self) -> PathBuf {
+ self.root.join(&self.config.book.src)
+ }
+
+ /// Load a `RenderContext` from its JSON representation.
+ pub fn from_json<R: Read>(reader: R) -> Result<RenderContext> {
+ serde_json::from_reader(reader).with_context(|| "Unable to deserialize the `RenderContext`")
+ }
+}
+
+/// A generic renderer which will shell out to an arbitrary executable.
+///
+/// # Rendering Protocol
+///
+/// When the renderer's `render()` method is invoked, `CmdRenderer` will spawn
+/// the `cmd` as a subprocess. The `RenderContext` is passed to the subprocess
+/// as a JSON string (using `serde_json`).
+///
+/// > **Note:** The command used doesn't necessarily need to be a single
+/// > executable (i.e. `/path/to/renderer`). The `cmd` string lets you pass
+/// > in command line arguments, so there's no reason why it couldn't be
+/// > `python /path/to/renderer --from mdbook --to epub`.
+///
+/// Anything the subprocess writes to `stdin` or `stdout` will be passed through
+/// to the user. While this gives the renderer maximum flexibility to output
+/// whatever it wants, to avoid spamming users it is recommended to avoid
+/// unnecessary output.
+///
+/// To help choose the appropriate output level, the `RUST_LOG` environment
+/// variable will be passed through to the subprocess, if set.
+///
+/// If the subprocess wishes to indicate that rendering failed, it should exit
+/// with a non-zero return code.
+#[derive(Debug, Clone, PartialEq)]
+pub struct CmdRenderer {
+ name: String,
+ cmd: String,
+}
+
+impl CmdRenderer {
+ /// Create a new `CmdRenderer` which will invoke the provided `cmd` string.
+ pub fn new(name: String, cmd: String) -> CmdRenderer {
+ CmdRenderer { name, cmd }
+ }
+
+ fn compose_command(&self, root: &Path, destination: &Path) -> Result<Command> {
+ let mut words = Shlex::new(&self.cmd);
+ let exe = match words.next() {
+ Some(e) => PathBuf::from(e),
+ None => bail!("Command string was empty"),
+ };
+
+ let exe = if exe.components().count() == 1 {
+ // Search PATH for the executable.
+ exe
+ } else {
+ // Relative paths are preferred to be relative to the book root.
+ let abs_exe = root.join(&exe);
+ if abs_exe.exists() {
+ abs_exe
+ } else {
+ // Historically paths were relative to the destination, but
+ // this is not the preferred way.
+ let legacy_path = destination.join(&exe);
+ if legacy_path.exists() {
+ warn!(
+ "Renderer command `{}` uses a path relative to the \
+ renderer output directory `{}`. This was previously \
+ accepted, but has been deprecated. Relative executable \
+ paths should be relative to the book root.",
+ exe.display(),
+ destination.display()
+ );
+ legacy_path
+ } else {
+ // Let this bubble through to later be handled by
+ // handle_render_command_error.
+ abs_exe
+ }
+ }
+ };
+
+ let mut cmd = Command::new(exe);
+
+ for arg in words {
+ cmd.arg(arg);
+ }
+
+ Ok(cmd)
+ }
+}
+
+impl CmdRenderer {
+ fn handle_render_command_error(&self, ctx: &RenderContext, error: io::Error) -> Result<()> {
+ if let ErrorKind::NotFound = error.kind() {
+ // Look for "output.{self.name}.optional".
+ // If it exists and is true, treat this as a warning.
+ // Otherwise, fail the build.
+
+ let optional_key = format!("output.{}.optional", self.name);
+
+ let is_optional = match ctx.config.get(&optional_key) {
+ Some(Value::Boolean(value)) => *value,
+ _ => false,
+ };
+
+ if is_optional {
+ warn!(
+ "The command `{}` for backend `{}` was not found, \
+ but was marked as optional.",
+ self.cmd, self.name
+ );
+ return Ok(());
+ } else {
+ error!(
+ "The command `{0}` wasn't found, is the \"{1}\" backend installed? \
+ If you want to ignore this error when the \"{1}\" backend is not installed, \
+ set `optional = true` in the `[output.{1}]` section of the book.toml configuration file.",
+ self.cmd, self.name
+ );
+ }
+ }
+ Err(error).with_context(|| "Unable to start the backend")?
+ }
+}
+
+impl Renderer for CmdRenderer {
+ fn name(&self) -> &str {
+ &self.name
+ }
+
+ fn render(&self, ctx: &RenderContext) -> Result<()> {
+ info!("Invoking the \"{}\" renderer", self.name);
+
+ let _ = fs::create_dir_all(&ctx.destination);
+
+ let mut child = match self
+ .compose_command(&ctx.root, &ctx.destination)?
+ .stdin(Stdio::piped())
+ .stdout(Stdio::inherit())
+ .stderr(Stdio::inherit())
+ .current_dir(&ctx.destination)
+ .spawn()
+ {
+ Ok(c) => c,
+ Err(e) => return self.handle_render_command_error(ctx, e),
+ };
+
+ let mut stdin = child.stdin.take().expect("Child has stdin");
+ if let Err(e) = serde_json::to_writer(&mut stdin, &ctx) {
+ // Looks like the backend hung up before we could finish
+ // sending it the render context. Log the error and keep going
+ warn!("Error writing the RenderContext to the backend, {}", e);
+ }
+
+ // explicitly close the `stdin` file handle
+ drop(stdin);
+
+ let status = child
+ .wait()
+ .with_context(|| "Error waiting for the backend to complete")?;
+
+ trace!("{} exited with output: {:?}", self.cmd, status);
+
+ if !status.success() {
+ error!("Renderer exited with non-zero return code.");
+ bail!("The \"{}\" renderer failed", self.name);
+ } else {
+ Ok(())
+ }
+ }
+}
diff --git a/vendor/mdbook/src/theme/ayu-highlight.css b/vendor/mdbook/src/theme/ayu-highlight.css
new file mode 100644
index 000000000..32c943222
--- /dev/null
+++ b/vendor/mdbook/src/theme/ayu-highlight.css
@@ -0,0 +1,78 @@
+/*
+Based off of the Ayu theme
+Original by Dempfi (https://github.com/dempfi/ayu)
+*/
+
+.hljs {
+ display: block;
+ overflow-x: auto;
+ background: #191f26;
+ color: #e6e1cf;
+}
+
+.hljs-comment,
+.hljs-quote {
+ color: #5c6773;
+ font-style: italic;
+}
+
+.hljs-variable,
+.hljs-template-variable,
+.hljs-attribute,
+.hljs-attr,
+.hljs-regexp,
+.hljs-link,
+.hljs-selector-id,
+.hljs-selector-class {
+ color: #ff7733;
+}
+
+.hljs-number,
+.hljs-meta,
+.hljs-builtin-name,
+.hljs-literal,
+.hljs-type,
+.hljs-params {
+ color: #ffee99;
+}
+
+.hljs-string,
+.hljs-bullet {
+ color: #b8cc52;
+}
+
+.hljs-title,
+.hljs-built_in,
+.hljs-section {
+ color: #ffb454;
+}
+
+.hljs-keyword,
+.hljs-selector-tag,
+.hljs-symbol {
+ color: #ff7733;
+}
+
+.hljs-name {
+ color: #36a3d9;
+}
+
+.hljs-tag {
+ color: #00568d;
+}
+
+.hljs-emphasis {
+ font-style: italic;
+}
+
+.hljs-strong {
+ font-weight: bold;
+}
+
+.hljs-addition {
+ color: #91b362;
+}
+
+.hljs-deletion {
+ color: #d96c75;
+}
diff --git a/vendor/mdbook/src/theme/book.js b/vendor/mdbook/src/theme/book.js
new file mode 100644
index 000000000..d40440c72
--- /dev/null
+++ b/vendor/mdbook/src/theme/book.js
@@ -0,0 +1,679 @@
+"use strict";
+
+// Fix back button cache problem
+window.onunload = function () { };
+
+// Global variable, shared between modules
+function playground_text(playground) {
+ let code_block = playground.querySelector("code");
+
+ if (window.ace && code_block.classList.contains("editable")) {
+ let editor = window.ace.edit(code_block);
+ return editor.getValue();
+ } else {
+ return code_block.textContent;
+ }
+}
+
+(function codeSnippets() {
+ function fetch_with_timeout(url, options, timeout = 6000) {
+ return Promise.race([
+ fetch(url, options),
+ new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
+ ]);
+ }
+
+ var playgrounds = Array.from(document.querySelectorAll(".playground"));
+ if (playgrounds.length > 0) {
+ fetch_with_timeout("https://play.rust-lang.org/meta/crates", {
+ headers: {
+ 'Content-Type': "application/json",
+ },
+ method: 'POST',
+ mode: 'cors',
+ })
+ .then(response => response.json())
+ .then(response => {
+ // get list of crates available in the rust playground
+ let playground_crates = response.crates.map(item => item["id"]);
+ playgrounds.forEach(block => handle_crate_list_update(block, playground_crates));
+ });
+ }
+
+ function handle_crate_list_update(playground_block, playground_crates) {
+ // update the play buttons after receiving the response
+ update_play_button(playground_block, playground_crates);
+
+ // and install on change listener to dynamically update ACE editors
+ if (window.ace) {
+ let code_block = playground_block.querySelector("code");
+ if (code_block.classList.contains("editable")) {
+ let editor = window.ace.edit(code_block);
+ editor.addEventListener("change", function (e) {
+ update_play_button(playground_block, playground_crates);
+ });
+ // add Ctrl-Enter command to execute rust code
+ editor.commands.addCommand({
+ name: "run",
+ bindKey: {
+ win: "Ctrl-Enter",
+ mac: "Ctrl-Enter"
+ },
+ exec: _editor => run_rust_code(playground_block)
+ });
+ }
+ }
+ }
+
+ // updates the visibility of play button based on `no_run` class and
+ // used crates vs ones available on http://play.rust-lang.org
+ function update_play_button(pre_block, playground_crates) {
+ var play_button = pre_block.querySelector(".play-button");
+
+ // skip if code is `no_run`
+ if (pre_block.querySelector('code').classList.contains("no_run")) {
+ play_button.classList.add("hidden");
+ return;
+ }
+
+ // get list of `extern crate`'s from snippet
+ var txt = playground_text(pre_block);
+ var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
+ var snippet_crates = [];
+ var item;
+ while (item = re.exec(txt)) {
+ snippet_crates.push(item[1]);
+ }
+
+ // check if all used crates are available on play.rust-lang.org
+ var all_available = snippet_crates.every(function (elem) {
+ return playground_crates.indexOf(elem) > -1;
+ });
+
+ if (all_available) {
+ play_button.classList.remove("hidden");
+ } else {
+ play_button.classList.add("hidden");
+ }
+ }
+
+ function run_rust_code(code_block) {
+ var result_block = code_block.querySelector(".result");
+ if (!result_block) {
+ result_block = document.createElement('code');
+ result_block.className = 'result hljs language-bash';
+
+ code_block.append(result_block);
+ }
+
+ let text = playground_text(code_block);
+ let classes = code_block.querySelector('code').classList;
+ let edition = "2015";
+ if(classes.contains("edition2018")) {
+ edition = "2018";
+ } else if(classes.contains("edition2021")) {
+ edition = "2021";
+ }
+ var params = {
+ version: "stable",
+ optimize: "0",
+ code: text,
+ edition: edition
+ };
+
+ if (text.indexOf("#![feature") !== -1) {
+ params.version = "nightly";
+ }
+
+ result_block.innerText = "Running...";
+
+ fetch_with_timeout("https://play.rust-lang.org/evaluate.json", {
+ headers: {
+ 'Content-Type': "application/json",
+ },
+ method: 'POST',
+ mode: 'cors',
+ body: JSON.stringify(params)
+ })
+ .then(response => response.json())
+ .then(response => {
+ if (response.result.trim() === '') {
+ result_block.innerText = "No output";
+ result_block.classList.add("result-no-output");
+ } else {
+ result_block.innerText = response.result;
+ result_block.classList.remove("result-no-output");
+ }
+ })
+ .catch(error => result_block.innerText = "Playground Communication: " + error.message);
+ }
+
+ // Syntax highlighting Configuration
+ hljs.configure({
+ tabReplace: ' ', // 4 spaces
+ languages: [], // Languages used for auto-detection
+ });
+
+ let code_nodes = Array
+ .from(document.querySelectorAll('code'))
+ // Don't highlight `inline code` blocks in headers.
+ .filter(function (node) {return !node.parentElement.classList.contains("header"); });
+
+ if (window.ace) {
+ // language-rust class needs to be removed for editable
+ // blocks or highlightjs will capture events
+ code_nodes
+ .filter(function (node) {return node.classList.contains("editable"); })
+ .forEach(function (block) { block.classList.remove('language-rust'); });
+
+ Array
+ code_nodes
+ .filter(function (node) {return !node.classList.contains("editable"); })
+ .forEach(function (block) { hljs.highlightBlock(block); });
+ } else {
+ code_nodes.forEach(function (block) { hljs.highlightBlock(block); });
+ }
+
+ // Adding the hljs class gives code blocks the color css
+ // even if highlighting doesn't apply
+ code_nodes.forEach(function (block) { block.classList.add('hljs'); });
+
+ Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) {
+
+ var lines = Array.from(block.querySelectorAll('.boring'));
+ // If no lines were hidden, return
+ if (!lines.length) { return; }
+ block.classList.add("hide-boring");
+
+ var buttons = document.createElement('div');
+ buttons.className = 'buttons';
+ buttons.innerHTML = "<button class=\"fa fa-eye\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>";
+
+ // add expand button
+ var pre_block = block.parentNode;
+ pre_block.insertBefore(buttons, pre_block.firstChild);
+
+ pre_block.querySelector('.buttons').addEventListener('click', function (e) {
+ if (e.target.classList.contains('fa-eye')) {
+ e.target.classList.remove('fa-eye');
+ e.target.classList.add('fa-eye-slash');
+ e.target.title = 'Hide lines';
+ e.target.setAttribute('aria-label', e.target.title);
+
+ block.classList.remove('hide-boring');
+ } else if (e.target.classList.contains('fa-eye-slash')) {
+ e.target.classList.remove('fa-eye-slash');
+ e.target.classList.add('fa-eye');
+ e.target.title = 'Show hidden lines';
+ e.target.setAttribute('aria-label', e.target.title);
+
+ block.classList.add('hide-boring');
+ }
+ });
+ });
+
+ if (window.playground_copyable) {
+ Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
+ var pre_block = block.parentNode;
+ if (!pre_block.classList.contains('playground')) {
+ var buttons = pre_block.querySelector(".buttons");
+ if (!buttons) {
+ buttons = document.createElement('div');
+ buttons.className = 'buttons';
+ pre_block.insertBefore(buttons, pre_block.firstChild);
+ }
+
+ var clipButton = document.createElement('button');
+ clipButton.className = 'fa fa-copy clip-button';
+ clipButton.title = 'Copy to clipboard';
+ clipButton.setAttribute('aria-label', clipButton.title);
+ clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
+
+ buttons.insertBefore(clipButton, buttons.firstChild);
+ }
+ });
+ }
+
+ // Process playground code blocks
+ Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) {
+ // Add play button
+ var buttons = pre_block.querySelector(".buttons");
+ if (!buttons) {
+ buttons = document.createElement('div');
+ buttons.className = 'buttons';
+ pre_block.insertBefore(buttons, pre_block.firstChild);
+ }
+
+ var runCodeButton = document.createElement('button');
+ runCodeButton.className = 'fa fa-play play-button';
+ runCodeButton.hidden = true;
+ runCodeButton.title = 'Run this code';
+ runCodeButton.setAttribute('aria-label', runCodeButton.title);
+
+ buttons.insertBefore(runCodeButton, buttons.firstChild);
+ runCodeButton.addEventListener('click', function (e) {
+ run_rust_code(pre_block);
+ });
+
+ if (window.playground_copyable) {
+ var copyCodeClipboardButton = document.createElement('button');
+ copyCodeClipboardButton.className = 'fa fa-copy clip-button';
+ copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
+ copyCodeClipboardButton.title = 'Copy to clipboard';
+ copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
+
+ buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
+ }
+
+ let code_block = pre_block.querySelector("code");
+ if (window.ace && code_block.classList.contains("editable")) {
+ var undoChangesButton = document.createElement('button');
+ undoChangesButton.className = 'fa fa-history reset-button';
+ undoChangesButton.title = 'Undo changes';
+ undoChangesButton.setAttribute('aria-label', undoChangesButton.title);
+
+ buttons.insertBefore(undoChangesButton, buttons.firstChild);
+
+ undoChangesButton.addEventListener('click', function () {
+ let editor = window.ace.edit(code_block);
+ editor.setValue(editor.originalCode);
+ editor.clearSelection();
+ });
+ }
+ });
+})();
+
+(function themes() {
+ var html = document.querySelector('html');
+ var themeToggleButton = document.getElementById('theme-toggle');
+ var themePopup = document.getElementById('theme-list');
+ var themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
+ var stylesheets = {
+ ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"),
+ tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"),
+ highlight: document.querySelector("[href$='highlight.css']"),
+ };
+
+ function showThemes() {
+ themePopup.style.display = 'block';
+ themeToggleButton.setAttribute('aria-expanded', true);
+ themePopup.querySelector("button#" + get_theme()).focus();
+ }
+
+ function hideThemes() {
+ themePopup.style.display = 'none';
+ themeToggleButton.setAttribute('aria-expanded', false);
+ themeToggleButton.focus();
+ }
+
+ function get_theme() {
+ var theme;
+ try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { }
+ if (theme === null || theme === undefined) {
+ return default_theme;
+ } else {
+ return theme;
+ }
+ }
+
+ function set_theme(theme, store = true) {
+ let ace_theme;
+
+ if (theme == 'coal' || theme == 'navy') {
+ stylesheets.ayuHighlight.disabled = true;
+ stylesheets.tomorrowNight.disabled = false;
+ stylesheets.highlight.disabled = true;
+
+ ace_theme = "ace/theme/tomorrow_night";
+ } else if (theme == 'ayu') {
+ stylesheets.ayuHighlight.disabled = false;
+ stylesheets.tomorrowNight.disabled = true;
+ stylesheets.highlight.disabled = true;
+ ace_theme = "ace/theme/tomorrow_night";
+ } else {
+ stylesheets.ayuHighlight.disabled = true;
+ stylesheets.tomorrowNight.disabled = true;
+ stylesheets.highlight.disabled = false;
+ ace_theme = "ace/theme/dawn";
+ }
+
+ setTimeout(function () {
+ themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor;
+ }, 1);
+
+ if (window.ace && window.editors) {
+ window.editors.forEach(function (editor) {
+ editor.setTheme(ace_theme);
+ });
+ }
+
+ var previousTheme = get_theme();
+
+ if (store) {
+ try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
+ }
+
+ html.classList.remove(previousTheme);
+ html.classList.add(theme);
+ }
+
+ // Set theme
+ var theme = get_theme();
+
+ set_theme(theme, false);
+
+ themeToggleButton.addEventListener('click', function () {
+ if (themePopup.style.display === 'block') {
+ hideThemes();
+ } else {
+ showThemes();
+ }
+ });
+
+ themePopup.addEventListener('click', function (e) {
+ var theme;
+ if (e.target.className === "theme") {
+ theme = e.target.id;
+ } else if (e.target.parentElement.className === "theme") {
+ theme = e.target.parentElement.id;
+ } else {
+ return;
+ }
+ set_theme(theme);
+ });
+
+ themePopup.addEventListener('focusout', function(e) {
+ // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
+ if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) {
+ hideThemes();
+ }
+ });
+
+ // Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628
+ document.addEventListener('click', function(e) {
+ if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {
+ hideThemes();
+ }
+ });
+
+ document.addEventListener('keydown', function (e) {
+ if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
+ if (!themePopup.contains(e.target)) { return; }
+
+ switch (e.key) {
+ case 'Escape':
+ e.preventDefault();
+ hideThemes();
+ break;
+ case 'ArrowUp':
+ e.preventDefault();
+ var li = document.activeElement.parentElement;
+ if (li && li.previousElementSibling) {
+ li.previousElementSibling.querySelector('button').focus();
+ }
+ break;
+ case 'ArrowDown':
+ e.preventDefault();
+ var li = document.activeElement.parentElement;
+ if (li && li.nextElementSibling) {
+ li.nextElementSibling.querySelector('button').focus();
+ }
+ break;
+ case 'Home':
+ e.preventDefault();
+ themePopup.querySelector('li:first-child button').focus();
+ break;
+ case 'End':
+ e.preventDefault();
+ themePopup.querySelector('li:last-child button').focus();
+ break;
+ }
+ });
+})();
+
+(function sidebar() {
+ var html = document.querySelector("html");
+ var sidebar = document.getElementById("sidebar");
+ var sidebarLinks = document.querySelectorAll('#sidebar a');
+ var sidebarToggleButton = document.getElementById("sidebar-toggle");
+ var sidebarResizeHandle = document.getElementById("sidebar-resize-handle");
+ var firstContact = null;
+
+ function showSidebar() {
+ html.classList.remove('sidebar-hidden')
+ html.classList.add('sidebar-visible');
+ Array.from(sidebarLinks).forEach(function (link) {
+ link.setAttribute('tabIndex', 0);
+ });
+ sidebarToggleButton.setAttribute('aria-expanded', true);
+ sidebar.setAttribute('aria-hidden', false);
+ try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }
+ }
+
+
+ var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle');
+
+ function toggleSection(ev) {
+ ev.currentTarget.parentElement.classList.toggle('expanded');
+ }
+
+ Array.from(sidebarAnchorToggles).forEach(function (el) {
+ el.addEventListener('click', toggleSection);
+ });
+
+ function hideSidebar() {
+ html.classList.remove('sidebar-visible')
+ html.classList.add('sidebar-hidden');
+ Array.from(sidebarLinks).forEach(function (link) {
+ link.setAttribute('tabIndex', -1);
+ });
+ sidebarToggleButton.setAttribute('aria-expanded', false);
+ sidebar.setAttribute('aria-hidden', true);
+ try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { }
+ }
+
+ // Toggle sidebar
+ sidebarToggleButton.addEventListener('click', function sidebarToggle() {
+ if (html.classList.contains("sidebar-hidden")) {
+ var current_width = parseInt(
+ document.documentElement.style.getPropertyValue('--sidebar-width'), 10);
+ if (current_width < 150) {
+ document.documentElement.style.setProperty('--sidebar-width', '150px');
+ }
+ showSidebar();
+ } else if (html.classList.contains("sidebar-visible")) {
+ hideSidebar();
+ } else {
+ if (getComputedStyle(sidebar)['transform'] === 'none') {
+ hideSidebar();
+ } else {
+ showSidebar();
+ }
+ }
+ });
+
+ sidebarResizeHandle.addEventListener('mousedown', initResize, false);
+
+ function initResize(e) {
+ window.addEventListener('mousemove', resize, false);
+ window.addEventListener('mouseup', stopResize, false);
+ html.classList.add('sidebar-resizing');
+ }
+ function resize(e) {
+ var pos = (e.clientX - sidebar.offsetLeft);
+ if (pos < 20) {
+ hideSidebar();
+ } else {
+ if (html.classList.contains("sidebar-hidden")) {
+ showSidebar();
+ }
+ pos = Math.min(pos, window.innerWidth - 100);
+ document.documentElement.style.setProperty('--sidebar-width', pos + 'px');
+ }
+ }
+ //on mouseup remove windows functions mousemove & mouseup
+ function stopResize(e) {
+ html.classList.remove('sidebar-resizing');
+ window.removeEventListener('mousemove', resize, false);
+ window.removeEventListener('mouseup', stopResize, false);
+ }
+
+ document.addEventListener('touchstart', function (e) {
+ firstContact = {
+ x: e.touches[0].clientX,
+ time: Date.now()
+ };
+ }, { passive: true });
+
+ document.addEventListener('touchmove', function (e) {
+ if (!firstContact)
+ return;
+
+ var curX = e.touches[0].clientX;
+ var xDiff = curX - firstContact.x,
+ tDiff = Date.now() - firstContact.time;
+
+ if (tDiff < 250 && Math.abs(xDiff) >= 150) {
+ if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300))
+ showSidebar();
+ else if (xDiff < 0 && curX < 300)
+ hideSidebar();
+
+ firstContact = null;
+ }
+ }, { passive: true });
+
+ // Scroll sidebar to current active section
+ var activeSection = document.getElementById("sidebar").querySelector(".active");
+ if (activeSection) {
+ // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
+ activeSection.scrollIntoView({ block: 'center' });
+ }
+})();
+
+(function chapterNavigation() {
+ document.addEventListener('keydown', function (e) {
+ if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
+ if (window.search && window.search.hasFocus()) { return; }
+
+ switch (e.key) {
+ case 'ArrowRight':
+ e.preventDefault();
+ var nextButton = document.querySelector('.nav-chapters.next');
+ if (nextButton) {
+ window.location.href = nextButton.href;
+ }
+ break;
+ case 'ArrowLeft':
+ e.preventDefault();
+ var previousButton = document.querySelector('.nav-chapters.previous');
+ if (previousButton) {
+ window.location.href = previousButton.href;
+ }
+ break;
+ }
+ });
+})();
+
+(function clipboard() {
+ var clipButtons = document.querySelectorAll('.clip-button');
+
+ function hideTooltip(elem) {
+ elem.firstChild.innerText = "";
+ elem.className = 'fa fa-copy clip-button';
+ }
+
+ function showTooltip(elem, msg) {
+ elem.firstChild.innerText = msg;
+ elem.className = 'fa fa-copy tooltipped';
+ }
+
+ var clipboardSnippets = new ClipboardJS('.clip-button', {
+ text: function (trigger) {
+ hideTooltip(trigger);
+ let playground = trigger.closest("pre");
+ return playground_text(playground);
+ }
+ });
+
+ Array.from(clipButtons).forEach(function (clipButton) {
+ clipButton.addEventListener('mouseout', function (e) {
+ hideTooltip(e.currentTarget);
+ });
+ });
+
+ clipboardSnippets.on('success', function (e) {
+ e.clearSelection();
+ showTooltip(e.trigger, "Copied!");
+ });
+
+ clipboardSnippets.on('error', function (e) {
+ showTooltip(e.trigger, "Clipboard error!");
+ });
+})();
+
+(function scrollToTop () {
+ var menuTitle = document.querySelector('.menu-title');
+
+ menuTitle.addEventListener('click', function () {
+ document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' });
+ });
+})();
+
+(function controllMenu() {
+ var menu = document.getElementById('menu-bar');
+
+ (function controllPosition() {
+ var scrollTop = document.scrollingElement.scrollTop;
+ var prevScrollTop = scrollTop;
+ var minMenuY = -menu.clientHeight - 50;
+ // When the script loads, the page can be at any scroll (e.g. if you reforesh it).
+ menu.style.top = scrollTop + 'px';
+ // Same as parseInt(menu.style.top.slice(0, -2), but faster
+ var topCache = menu.style.top.slice(0, -2);
+ menu.classList.remove('sticky');
+ var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster
+ document.addEventListener('scroll', function () {
+ scrollTop = Math.max(document.scrollingElement.scrollTop, 0);
+ // `null` means that it doesn't need to be updated
+ var nextSticky = null;
+ var nextTop = null;
+ var scrollDown = scrollTop > prevScrollTop;
+ var menuPosAbsoluteY = topCache - scrollTop;
+ if (scrollDown) {
+ nextSticky = false;
+ if (menuPosAbsoluteY > 0) {
+ nextTop = prevScrollTop;
+ }
+ } else {
+ if (menuPosAbsoluteY > 0) {
+ nextSticky = true;
+ } else if (menuPosAbsoluteY < minMenuY) {
+ nextTop = prevScrollTop + minMenuY;
+ }
+ }
+ if (nextSticky === true && stickyCache === false) {
+ menu.classList.add('sticky');
+ stickyCache = true;
+ } else if (nextSticky === false && stickyCache === true) {
+ menu.classList.remove('sticky');
+ stickyCache = false;
+ }
+ if (nextTop !== null) {
+ menu.style.top = nextTop + 'px';
+ topCache = nextTop;
+ }
+ prevScrollTop = scrollTop;
+ }, { passive: true });
+ })();
+ (function controllBorder() {
+ menu.classList.remove('bordered');
+ document.addEventListener('scroll', function () {
+ if (menu.offsetTop === 0) {
+ menu.classList.remove('bordered');
+ } else {
+ menu.classList.add('bordered');
+ }
+ }, { passive: true });
+ })();
+})();
diff --git a/vendor/mdbook/src/theme/css/chrome.css b/vendor/mdbook/src/theme/css/chrome.css
new file mode 100644
index 000000000..10fa4b365
--- /dev/null
+++ b/vendor/mdbook/src/theme/css/chrome.css
@@ -0,0 +1,534 @@
+/* CSS for UI elements (a.k.a. chrome) */
+
+@import 'variables.css';
+
+::-webkit-scrollbar {
+ background: var(--bg);
+}
+::-webkit-scrollbar-thumb {
+ background: var(--scrollbar);
+}
+html {
+ scrollbar-color: var(--scrollbar) var(--bg);
+}
+#searchresults a,
+.content a:link,
+a:visited,
+a > .hljs {
+ color: var(--links);
+}
+
+/* Menu Bar */
+
+#menu-bar,
+#menu-bar-hover-placeholder {
+ z-index: 101;
+ margin: auto calc(0px - var(--page-padding));
+}
+#menu-bar {
+ position: relative;
+ display: flex;
+ flex-wrap: wrap;
+ background-color: var(--bg);
+ border-bottom-color: var(--bg);
+ border-bottom-width: 1px;
+ border-bottom-style: solid;
+}
+#menu-bar.sticky,
+.js #menu-bar-hover-placeholder:hover + #menu-bar,
+.js #menu-bar:hover,
+.js.sidebar-visible #menu-bar {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 0 !important;
+}
+#menu-bar-hover-placeholder {
+ position: sticky;
+ position: -webkit-sticky;
+ top: 0;
+ height: var(--menu-bar-height);
+}
+#menu-bar.bordered {
+ border-bottom-color: var(--table-border-color);
+}
+#menu-bar i, #menu-bar .icon-button {
+ position: relative;
+ padding: 0 8px;
+ z-index: 10;
+ line-height: var(--menu-bar-height);
+ cursor: pointer;
+ transition: color 0.5s;
+}
+@media only screen and (max-width: 420px) {
+ #menu-bar i, #menu-bar .icon-button {
+ padding: 0 5px;
+ }
+}
+
+.icon-button {
+ border: none;
+ background: none;
+ padding: 0;
+ color: inherit;
+}
+.icon-button i {
+ margin: 0;
+}
+
+.right-buttons {
+ margin: 0 15px;
+}
+.right-buttons a {
+ text-decoration: none;
+}
+
+.left-buttons {
+ display: flex;
+ margin: 0 5px;
+}
+.no-js .left-buttons {
+ display: none;
+}
+
+.menu-title {
+ display: inline-block;
+ font-weight: 200;
+ font-size: 2.4rem;
+ line-height: var(--menu-bar-height);
+ text-align: center;
+ margin: 0;
+ flex: 1;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.js .menu-title {
+ cursor: pointer;
+}
+
+.menu-bar,
+.menu-bar:visited,
+.nav-chapters,
+.nav-chapters:visited,
+.mobile-nav-chapters,
+.mobile-nav-chapters:visited,
+.menu-bar .icon-button,
+.menu-bar a i {
+ color: var(--icons);
+}
+
+.menu-bar i:hover,
+.menu-bar .icon-button:hover,
+.nav-chapters:hover,
+.mobile-nav-chapters i:hover {
+ color: var(--icons-hover);
+}
+
+/* Nav Icons */
+
+.nav-chapters {
+ font-size: 2.5em;
+ text-align: center;
+ text-decoration: none;
+
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ margin: 0;
+ max-width: 150px;
+ min-width: 90px;
+
+ display: flex;
+ justify-content: center;
+ align-content: center;
+ flex-direction: column;
+
+ transition: color 0.5s, background-color 0.5s;
+}
+
+.nav-chapters:hover {
+ text-decoration: none;
+ background-color: var(--theme-hover);
+ transition: background-color 0.15s, color 0.15s;
+}
+
+.nav-wrapper {
+ margin-top: 50px;
+ display: none;
+}
+
+.mobile-nav-chapters {
+ font-size: 2.5em;
+ text-align: center;
+ text-decoration: none;
+ width: 90px;
+ border-radius: 5px;
+ background-color: var(--sidebar-bg);
+}
+
+.previous {
+ float: left;
+}
+
+.next {
+ float: right;
+ right: var(--page-padding);
+}
+
+@media only screen and (max-width: 1080px) {
+ .nav-wide-wrapper { display: none; }
+ .nav-wrapper { display: block; }
+}
+
+@media only screen and (max-width: 1380px) {
+ .sidebar-visible .nav-wide-wrapper { display: none; }
+ .sidebar-visible .nav-wrapper { display: block; }
+}
+
+/* Inline code */
+
+:not(pre) > .hljs {
+ display: inline;
+ padding: 0.1em 0.3em;
+ border-radius: 3px;
+}
+
+:not(pre):not(a) > .hljs {
+ color: var(--inline-code-color);
+ overflow-x: initial;
+}
+
+a:hover > .hljs {
+ text-decoration: underline;
+}
+
+pre {
+ position: relative;
+}
+pre > .buttons {
+ position: absolute;
+ z-index: 100;
+ right: 0px;
+ top: 2px;
+ margin: 0px;
+ padding: 2px 0px;
+
+ color: var(--sidebar-fg);
+ cursor: pointer;
+ visibility: hidden;
+ opacity: 0;
+ transition: visibility 0.1s linear, opacity 0.1s linear;
+}
+pre:hover > .buttons {
+ visibility: visible;
+ opacity: 1
+}
+pre > .buttons :hover {
+ color: var(--sidebar-active);
+ border-color: var(--icons-hover);
+ background-color: var(--theme-hover);
+}
+pre > .buttons i {
+ margin-left: 8px;
+}
+pre > .buttons button {
+ cursor: inherit;
+ margin: 0px 5px;
+ padding: 3px 5px;
+ font-size: 14px;
+
+ border-style: solid;
+ border-width: 1px;
+ border-radius: 4px;
+ border-color: var(--icons);
+ background-color: var(--theme-popup-bg);
+ transition: 100ms;
+ transition-property: color,border-color,background-color;
+ color: var(--icons);
+}
+@media (pointer: coarse) {
+ pre > .buttons button {
+ /* On mobile, make it easier to tap buttons. */
+ padding: 0.3rem 1rem;
+ }
+}
+pre > code {
+ padding: 1rem;
+}
+
+/* FIXME: ACE editors overlap their buttons because ACE does absolute
+ positioning within the code block which breaks padding. The only solution I
+ can think of is to move the padding to the outer pre tag (or insert a div
+ wrapper), but that would require fixing a whole bunch of CSS rules.
+*/
+.hljs.ace_editor {
+ padding: 0rem 0rem;
+}
+
+pre > .result {
+ margin-top: 10px;
+}
+
+/* Search */
+
+#searchresults a {
+ text-decoration: none;
+}
+
+mark {
+ border-radius: 2px;
+ padding: 0 3px 1px 3px;
+ margin: 0 -3px -1px -3px;
+ background-color: var(--search-mark-bg);
+ transition: background-color 300ms linear;
+ cursor: pointer;
+}
+
+mark.fade-out {
+ background-color: rgba(0,0,0,0) !important;
+ cursor: auto;
+}
+
+.searchbar-outer {
+ margin-left: auto;
+ margin-right: auto;
+ max-width: var(--content-max-width);
+}
+
+#searchbar {
+ width: 100%;
+ margin: 5px auto 0px auto;
+ padding: 10px 16px;
+ transition: box-shadow 300ms ease-in-out;
+ border: 1px solid var(--searchbar-border-color);
+ border-radius: 3px;
+ background-color: var(--searchbar-bg);
+ color: var(--searchbar-fg);
+}
+#searchbar:focus,
+#searchbar.active {
+ box-shadow: 0 0 3px var(--searchbar-shadow-color);
+}
+
+.searchresults-header {
+ font-weight: bold;
+ font-size: 1em;
+ padding: 18px 0 0 5px;
+ color: var(--searchresults-header-fg);
+}
+
+.searchresults-outer {
+ margin-left: auto;
+ margin-right: auto;
+ max-width: var(--content-max-width);
+ border-bottom: 1px dashed var(--searchresults-border-color);
+}
+
+ul#searchresults {
+ list-style: none;
+ padding-left: 20px;
+}
+ul#searchresults li {
+ margin: 10px 0px;
+ padding: 2px;
+ border-radius: 2px;
+}
+ul#searchresults li.focus {
+ background-color: var(--searchresults-li-bg);
+}
+ul#searchresults span.teaser {
+ display: block;
+ clear: both;
+ margin: 5px 0 0 20px;
+ font-size: 0.8em;
+}
+ul#searchresults span.teaser em {
+ font-weight: bold;
+ font-style: normal;
+}
+
+/* Sidebar */
+
+.sidebar {
+ position: fixed;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: var(--sidebar-width);
+ font-size: 0.875em;
+ box-sizing: border-box;
+ -webkit-overflow-scrolling: touch;
+ overscroll-behavior-y: contain;
+ background-color: var(--sidebar-bg);
+ color: var(--sidebar-fg);
+}
+.sidebar-resizing {
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+.js:not(.sidebar-resizing) .sidebar {
+ transition: transform 0.3s; /* Animation: slide away */
+}
+.sidebar code {
+ line-height: 2em;
+}
+.sidebar .sidebar-scrollbox {
+ overflow-y: auto;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding: 10px 10px;
+}
+.sidebar .sidebar-resize-handle {
+ position: absolute;
+ cursor: col-resize;
+ width: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+}
+.js .sidebar .sidebar-resize-handle {
+ cursor: col-resize;
+ width: 5px;
+}
+.sidebar-hidden .sidebar {
+ transform: translateX(calc(0px - var(--sidebar-width)));
+}
+.sidebar::-webkit-scrollbar {
+ background: var(--sidebar-bg);
+}
+.sidebar::-webkit-scrollbar-thumb {
+ background: var(--scrollbar);
+}
+
+.sidebar-visible .page-wrapper {
+ transform: translateX(var(--sidebar-width));
+}
+@media only screen and (min-width: 620px) {
+ .sidebar-visible .page-wrapper {
+ transform: none;
+ margin-left: var(--sidebar-width);
+ }
+}
+
+.chapter {
+ list-style: none outside none;
+ padding-left: 0;
+ line-height: 2.2em;
+}
+
+.chapter ol {
+ width: 100%;
+}
+
+.chapter li {
+ display: flex;
+ color: var(--sidebar-non-existant);
+}
+.chapter li a {
+ display: block;
+ padding: 0;
+ text-decoration: none;
+ color: var(--sidebar-fg);
+}
+
+.chapter li a:hover {
+ color: var(--sidebar-active);
+}
+
+.chapter li a.active {
+ color: var(--sidebar-active);
+}
+
+.chapter li > a.toggle {
+ cursor: pointer;
+ display: block;
+ margin-left: auto;
+ padding: 0 10px;
+ user-select: none;
+ opacity: 0.68;
+}
+
+.chapter li > a.toggle div {
+ transition: transform 0.5s;
+}
+
+/* collapse the section */
+.chapter li:not(.expanded) + li > ol {
+ display: none;
+}
+
+.chapter li.chapter-item {
+ line-height: 1.5em;
+ margin-top: 0.6em;
+}
+
+.chapter li.expanded > a.toggle div {
+ transform: rotate(90deg);
+}
+
+.spacer {
+ width: 100%;
+ height: 3px;
+ margin: 5px 0px;
+}
+.chapter .spacer {
+ background-color: var(--sidebar-spacer);
+}
+
+@media (-moz-touch-enabled: 1), (pointer: coarse) {
+ .chapter li a { padding: 5px 0; }
+ .spacer { margin: 10px 0; }
+}
+
+.section {
+ list-style: none outside none;
+ padding-left: 20px;
+ line-height: 1.9em;
+}
+
+/* Theme Menu Popup */
+
+.theme-popup {
+ position: absolute;
+ left: 10px;
+ top: var(--menu-bar-height);
+ z-index: 1000;
+ border-radius: 4px;
+ font-size: 0.7em;
+ color: var(--fg);
+ background: var(--theme-popup-bg);
+ border: 1px solid var(--theme-popup-border);
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ display: none;
+}
+.theme-popup .default {
+ color: var(--icons);
+}
+.theme-popup .theme {
+ width: 100%;
+ border: 0;
+ margin: 0;
+ padding: 2px 10px;
+ line-height: 25px;
+ white-space: nowrap;
+ text-align: left;
+ cursor: pointer;
+ color: inherit;
+ background: inherit;
+ font-size: inherit;
+}
+.theme-popup .theme:hover {
+ background-color: var(--theme-hover);
+}
+.theme-popup .theme:hover:first-child,
+.theme-popup .theme:hover:last-child {
+ border-top-left-radius: inherit;
+ border-top-right-radius: inherit;
+}
diff --git a/vendor/mdbook/src/theme/css/general.css b/vendor/mdbook/src/theme/css/general.css
new file mode 100644
index 000000000..0e4f07a50
--- /dev/null
+++ b/vendor/mdbook/src/theme/css/general.css
@@ -0,0 +1,191 @@
+/* Base styles and content styles */
+
+@import 'variables.css';
+
+:root {
+ /* Browser default font-size is 16px, this way 1 rem = 10px */
+ font-size: 62.5%;
+}
+
+html {
+ font-family: "Open Sans", sans-serif;
+ color: var(--fg);
+ background-color: var(--bg);
+ text-size-adjust: none;
+ -webkit-text-size-adjust: none;
+}
+
+body {
+ margin: 0;
+ font-size: 1.6rem;
+ overflow-x: hidden;
+}
+
+code {
+ font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important;
+ font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */
+}
+
+/* make long words/inline code not x overflow */
+main {
+ overflow-wrap: break-word;
+}
+
+/* make wide tables scroll if they overflow */
+.table-wrapper {
+ overflow-x: auto;
+}
+
+/* Don't change font size in headers. */
+h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
+ font-size: unset;
+}
+
+.left { float: left; }
+.right { float: right; }
+.boring { opacity: 0.6; }
+.hide-boring .boring { display: none; }
+.hidden { display: none !important; }
+
+h2, h3 { margin-top: 2.5em; }
+h4, h5 { margin-top: 2em; }
+
+.header + .header h3,
+.header + .header h4,
+.header + .header h5 {
+ margin-top: 1em;
+}
+
+h1:target::before,
+h2:target::before,
+h3:target::before,
+h4:target::before,
+h5:target::before,
+h6:target::before {
+ display: inline-block;
+ content: "»";
+ margin-left: -30px;
+ width: 30px;
+}
+
+/* This is broken on Safari as of version 14, but is fixed
+ in Safari Technology Preview 117 which I think will be Safari 14.2.
+ https://bugs.webkit.org/show_bug.cgi?id=218076
+*/
+:target {
+ scroll-margin-top: calc(var(--menu-bar-height) + 0.5em);
+}
+
+.page {
+ outline: 0;
+ padding: 0 var(--page-padding);
+ margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */
+}
+.page-wrapper {
+ box-sizing: border-box;
+}
+.js:not(.sidebar-resizing) .page-wrapper {
+ transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */
+}
+
+.content {
+ overflow-y: auto;
+ padding: 0 5px 50px 5px;
+}
+.content main {
+ margin-left: auto;
+ margin-right: auto;
+ max-width: var(--content-max-width);
+}
+.content p { line-height: 1.45em; }
+.content ol { line-height: 1.45em; }
+.content ul { line-height: 1.45em; }
+.content a { text-decoration: none; }
+.content a:hover { text-decoration: underline; }
+.content img, .content video { max-width: 100%; }
+.content .header:link,
+.content .header:visited {
+ color: var(--fg);
+}
+.content .header:link,
+.content .header:visited:hover {
+ text-decoration: none;
+}
+
+table {
+ margin: 0 auto;
+ border-collapse: collapse;
+}
+table td {
+ padding: 3px 20px;
+ border: 1px var(--table-border-color) solid;
+}
+table thead {
+ background: var(--table-header-bg);
+}
+table thead td {
+ font-weight: 700;
+ border: none;
+}
+table thead th {
+ padding: 3px 20px;
+}
+table thead tr {
+ border: 1px var(--table-header-bg) solid;
+}
+/* Alternate background colors for rows */
+table tbody tr:nth-child(2n) {
+ background: var(--table-alternate-bg);
+}
+
+
+blockquote {
+ margin: 20px 0;
+ padding: 0 20px;
+ color: var(--fg);
+ background-color: var(--quote-bg);
+ border-top: .1em solid var(--quote-border);
+ border-bottom: .1em solid var(--quote-border);
+}
+
+
+:not(.footnote-definition) + .footnote-definition,
+.footnote-definition + :not(.footnote-definition) {
+ margin-top: 2em;
+}
+.footnote-definition {
+ font-size: 0.9em;
+ margin: 0.5em 0;
+}
+.footnote-definition p {
+ display: inline;
+}
+
+.tooltiptext {
+ position: absolute;
+ visibility: hidden;
+ color: #fff;
+ background-color: #333;
+ transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */
+ left: -8px; /* Half of the width of the icon */
+ top: -35px;
+ font-size: 0.8em;
+ text-align: center;
+ border-radius: 6px;
+ padding: 5px 8px;
+ margin: 5px;
+ z-index: 1000;
+}
+.tooltipped .tooltiptext {
+ visibility: visible;
+}
+
+.chapter li.part-title {
+ color: var(--sidebar-fg);
+ margin: 5px 0px;
+ font-weight: bold;
+}
+
+.result-no-output {
+ font-style: italic;
+}
diff --git a/vendor/mdbook/src/theme/css/print.css b/vendor/mdbook/src/theme/css/print.css
new file mode 100644
index 000000000..5e690f755
--- /dev/null
+++ b/vendor/mdbook/src/theme/css/print.css
@@ -0,0 +1,54 @@
+
+#sidebar,
+#menu-bar,
+.nav-chapters,
+.mobile-nav-chapters {
+ display: none;
+}
+
+#page-wrapper.page-wrapper {
+ transform: none;
+ margin-left: 0px;
+ overflow-y: initial;
+}
+
+#content {
+ max-width: none;
+ margin: 0;
+ padding: 0;
+}
+
+.page {
+ overflow-y: initial;
+}
+
+code {
+ background-color: #666666;
+ border-radius: 5px;
+
+ /* Force background to be printed in Chrome */
+ -webkit-print-color-adjust: exact;
+}
+
+pre > .buttons {
+ z-index: 2;
+}
+
+a, a:visited, a:active, a:hover {
+ color: #4183c4;
+ text-decoration: none;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ page-break-inside: avoid;
+ page-break-after: avoid;
+}
+
+pre, code {
+ page-break-inside: avoid;
+ white-space: pre-wrap;
+}
+
+.fa {
+ display: none !important;
+}
diff --git a/vendor/mdbook/src/theme/css/variables.css b/vendor/mdbook/src/theme/css/variables.css
new file mode 100644
index 000000000..56b634bc3
--- /dev/null
+++ b/vendor/mdbook/src/theme/css/variables.css
@@ -0,0 +1,253 @@
+
+/* Globals */
+
+:root {
+ --sidebar-width: 300px;
+ --page-padding: 15px;
+ --content-max-width: 750px;
+ --menu-bar-height: 50px;
+}
+
+/* Themes */
+
+.ayu {
+ --bg: hsl(210, 25%, 8%);
+ --fg: #c5c5c5;
+
+ --sidebar-bg: #14191f;
+ --sidebar-fg: #c8c9db;
+ --sidebar-non-existant: #5c6773;
+ --sidebar-active: #ffb454;
+ --sidebar-spacer: #2d334f;
+
+ --scrollbar: var(--sidebar-fg);
+
+ --icons: #737480;
+ --icons-hover: #b7b9cc;
+
+ --links: #0096cf;
+
+ --inline-code-color: #ffb454;
+
+ --theme-popup-bg: #14191f;
+ --theme-popup-border: #5c6773;
+ --theme-hover: #191f26;
+
+ --quote-bg: hsl(226, 15%, 17%);
+ --quote-border: hsl(226, 15%, 22%);
+
+ --table-border-color: hsl(210, 25%, 13%);
+ --table-header-bg: hsl(210, 25%, 28%);
+ --table-alternate-bg: hsl(210, 25%, 11%);
+
+ --searchbar-border-color: #848484;
+ --searchbar-bg: #424242;
+ --searchbar-fg: #fff;
+ --searchbar-shadow-color: #d4c89f;
+ --searchresults-header-fg: #666;
+ --searchresults-border-color: #888;
+ --searchresults-li-bg: #252932;
+ --search-mark-bg: #e3b171;
+}
+
+.coal {
+ --bg: hsl(200, 7%, 8%);
+ --fg: #98a3ad;
+
+ --sidebar-bg: #292c2f;
+ --sidebar-fg: #a1adb8;
+ --sidebar-non-existant: #505254;
+ --sidebar-active: #3473ad;
+ --sidebar-spacer: #393939;
+
+ --scrollbar: var(--sidebar-fg);
+
+ --icons: #43484d;
+ --icons-hover: #b3c0cc;
+
+ --links: #2b79a2;
+
+ --inline-code-color: #c5c8c6;
+
+ --theme-popup-bg: #141617;
+ --theme-popup-border: #43484d;
+ --theme-hover: #1f2124;
+
+ --quote-bg: hsl(234, 21%, 18%);
+ --quote-border: hsl(234, 21%, 23%);
+
+ --table-border-color: hsl(200, 7%, 13%);
+ --table-header-bg: hsl(200, 7%, 28%);
+ --table-alternate-bg: hsl(200, 7%, 11%);
+
+ --searchbar-border-color: #aaa;
+ --searchbar-bg: #b7b7b7;
+ --searchbar-fg: #000;
+ --searchbar-shadow-color: #aaa;
+ --searchresults-header-fg: #666;
+ --searchresults-border-color: #98a3ad;
+ --searchresults-li-bg: #2b2b2f;
+ --search-mark-bg: #355c7d;
+}
+
+.light {
+ --bg: hsl(0, 0%, 100%);
+ --fg: hsl(0, 0%, 0%);
+
+ --sidebar-bg: #fafafa;
+ --sidebar-fg: hsl(0, 0%, 0%);
+ --sidebar-non-existant: #aaaaaa;
+ --sidebar-active: #1f1fff;
+ --sidebar-spacer: #f4f4f4;
+
+ --scrollbar: #8F8F8F;
+
+ --icons: #747474;
+ --icons-hover: #000000;
+
+ --links: #20609f;
+
+ --inline-code-color: #301900;
+
+ --theme-popup-bg: #fafafa;
+ --theme-popup-border: #cccccc;
+ --theme-hover: #e6e6e6;
+
+ --quote-bg: hsl(197, 37%, 96%);
+ --quote-border: hsl(197, 37%, 91%);
+
+ --table-border-color: hsl(0, 0%, 95%);
+ --table-header-bg: hsl(0, 0%, 80%);
+ --table-alternate-bg: hsl(0, 0%, 97%);
+
+ --searchbar-border-color: #aaa;
+ --searchbar-bg: #fafafa;
+ --searchbar-fg: #000;
+ --searchbar-shadow-color: #aaa;
+ --searchresults-header-fg: #666;
+ --searchresults-border-color: #888;
+ --searchresults-li-bg: #e4f2fe;
+ --search-mark-bg: #a2cff5;
+}
+
+.navy {
+ --bg: hsl(226, 23%, 11%);
+ --fg: #bcbdd0;
+
+ --sidebar-bg: #282d3f;
+ --sidebar-fg: #c8c9db;
+ --sidebar-non-existant: #505274;
+ --sidebar-active: #2b79a2;
+ --sidebar-spacer: #2d334f;
+
+ --scrollbar: var(--sidebar-fg);
+
+ --icons: #737480;
+ --icons-hover: #b7b9cc;
+
+ --links: #2b79a2;
+
+ --inline-code-color: #c5c8c6;
+
+ --theme-popup-bg: #161923;
+ --theme-popup-border: #737480;
+ --theme-hover: #282e40;
+
+ --quote-bg: hsl(226, 15%, 17%);
+ --quote-border: hsl(226, 15%, 22%);
+
+ --table-border-color: hsl(226, 23%, 16%);
+ --table-header-bg: hsl(226, 23%, 31%);
+ --table-alternate-bg: hsl(226, 23%, 14%);
+
+ --searchbar-border-color: #aaa;
+ --searchbar-bg: #aeaec6;
+ --searchbar-fg: #000;
+ --searchbar-shadow-color: #aaa;
+ --searchresults-header-fg: #5f5f71;
+ --searchresults-border-color: #5c5c68;
+ --searchresults-li-bg: #242430;
+ --search-mark-bg: #a2cff5;
+}
+
+.rust {
+ --bg: hsl(60, 9%, 87%);
+ --fg: #262625;
+
+ --sidebar-bg: #3b2e2a;
+ --sidebar-fg: #c8c9db;
+ --sidebar-non-existant: #505254;
+ --sidebar-active: #e69f67;
+ --sidebar-spacer: #45373a;
+
+ --scrollbar: var(--sidebar-fg);
+
+ --icons: #737480;
+ --icons-hover: #262625;
+
+ --links: #2b79a2;
+
+ --inline-code-color: #6e6b5e;
+
+ --theme-popup-bg: #e1e1db;
+ --theme-popup-border: #b38f6b;
+ --theme-hover: #99908a;
+
+ --quote-bg: hsl(60, 5%, 75%);
+ --quote-border: hsl(60, 5%, 70%);
+
+ --table-border-color: hsl(60, 9%, 82%);
+ --table-header-bg: #b3a497;
+ --table-alternate-bg: hsl(60, 9%, 84%);
+
+ --searchbar-border-color: #aaa;
+ --searchbar-bg: #fafafa;
+ --searchbar-fg: #000;
+ --searchbar-shadow-color: #aaa;
+ --searchresults-header-fg: #666;
+ --searchresults-border-color: #888;
+ --searchresults-li-bg: #dec2a2;
+ --search-mark-bg: #e69f67;
+}
+
+@media (prefers-color-scheme: dark) {
+ .light.no-js {
+ --bg: hsl(200, 7%, 8%);
+ --fg: #98a3ad;
+
+ --sidebar-bg: #292c2f;
+ --sidebar-fg: #a1adb8;
+ --sidebar-non-existant: #505254;
+ --sidebar-active: #3473ad;
+ --sidebar-spacer: #393939;
+
+ --scrollbar: var(--sidebar-fg);
+
+ --icons: #43484d;
+ --icons-hover: #b3c0cc;
+
+ --links: #2b79a2;
+
+ --inline-code-color: #c5c8c6;
+
+ --theme-popup-bg: #141617;
+ --theme-popup-border: #43484d;
+ --theme-hover: #1f2124;
+
+ --quote-bg: hsl(234, 21%, 18%);
+ --quote-border: hsl(234, 21%, 23%);
+
+ --table-border-color: hsl(200, 7%, 13%);
+ --table-header-bg: hsl(200, 7%, 28%);
+ --table-alternate-bg: hsl(200, 7%, 11%);
+
+ --searchbar-border-color: #aaa;
+ --searchbar-bg: #b7b7b7;
+ --searchbar-fg: #000;
+ --searchbar-shadow-color: #aaa;
+ --searchresults-header-fg: #666;
+ --searchresults-border-color: #98a3ad;
+ --searchresults-li-bg: #2b2b2f;
+ --search-mark-bg: #355c7d;
+ }
+}
diff --git a/vendor/mdbook/src/theme/favicon.png b/vendor/mdbook/src/theme/favicon.png
new file mode 100644
index 000000000..a5b1aa16c
--- /dev/null
+++ b/vendor/mdbook/src/theme/favicon.png
Binary files differ
diff --git a/vendor/mdbook/src/theme/favicon.svg b/vendor/mdbook/src/theme/favicon.svg
new file mode 100755
index 000000000..90e0ea58b
--- /dev/null
+++ b/vendor/mdbook/src/theme/favicon.svg
@@ -0,0 +1,22 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 199.7 184.2">
+ <style>
+ @media (prefers-color-scheme: dark) {
+ svg { fill: white; }
+ }
+ </style>
+<path d="M189.5,36.8c0.2,2.8,0,5.1-0.6,6.8L153,162c-0.6,2.1-2,3.7-4.2,5c-2.2,1.2-4.4,1.9-6.7,1.9H31.4c-9.6,0-15.3-2.8-17.3-8.4
+ c-0.8-2.2-0.8-3.9,0.1-5.2c0.9-1.2,2.4-1.8,4.6-1.8H123c7.4,0,12.6-1.4,15.4-4.1s5.7-8.9,8.6-18.4l32.9-108.6
+ c1.8-5.9,1-11.1-2.2-15.6S169.9,0,164,0H72.7c-1,0-3.1,0.4-6.1,1.1l0.1-0.4C64.5,0.2,62.6,0,61,0.1s-3,0.5-4.3,1.4
+ c-1.3,0.9-2.4,1.8-3.2,2.8S52,6.5,51.2,8.1c-0.8,1.6-1.4,3-1.9,4.3s-1.1,2.7-1.8,4.2c-0.7,1.5-1.3,2.7-2,3.7c-0.5,0.6-1.2,1.5-2,2.5
+ s-1.6,2-2.2,2.8s-0.9,1.5-1.1,2.2c-0.2,0.7-0.1,1.8,0.2,3.2c0.3,1.4,0.4,2.4,0.4,3.1c-0.3,3-1.4,6.9-3.3,11.6
+ c-1.9,4.7-3.6,8.1-5.1,10.1c-0.3,0.4-1.2,1.3-2.6,2.7c-1.4,1.4-2.3,2.6-2.6,3.7c-0.3,0.4-0.3,1.5-0.1,3.4c0.3,1.8,0.4,3.1,0.3,3.8
+ c-0.3,2.7-1.3,6.3-3,10.8c-1.7,4.5-3.4,8.2-5,11c-0.2,0.5-0.9,1.4-2,2.8c-1.1,1.4-1.8,2.5-2,3.4c-0.2,0.6-0.1,1.8,0.1,3.4
+ c0.2,1.6,0.2,2.8-0.1,3.6c-0.6,3-1.8,6.7-3.6,11c-1.8,4.3-3.6,7.9-5.4,11c-0.5,0.8-1.1,1.7-2,2.8c-0.8,1.1-1.5,2-2,2.8
+ s-0.8,1.6-1,2.5c-0.1,0.5,0,1.3,0.4,2.3c0.3,1.1,0.4,1.9,0.4,2.6c-0.1,1.1-0.2,2.6-0.5,4.4c-0.2,1.8-0.4,2.9-0.4,3.2
+ c-1.8,4.8-1.7,9.9,0.2,15.2c2.2,6.2,6.2,11.5,11.9,15.8c5.7,4.3,11.7,6.4,17.8,6.4h110.7c5.2,0,10.1-1.7,14.7-5.2s7.7-7.8,9.2-12.9
+ l33-108.6c1.8-5.8,1-10.9-2.2-15.5C194.9,39.7,192.6,38,189.5,36.8z M59.6,122.8L73.8,80c0,0,7,0,10.8,0s28.8-1.7,25.4,17.5
+ c-3.4,19.2-18.8,25.2-36.8,25.4S59.6,122.8,59.6,122.8z M78.6,116.8c4.7-0.1,18.9-2.9,22.1-17.1S89.2,86.3,89.2,86.3l-8.9,0
+ l-10.2,30.5C70.2,116.9,74,116.9,78.6,116.8z M75.3,68.7L89,26.2h9.8l0.8,34l23.6-34h9.9l-13.6,42.5h-7.1l12.5-35.4l-24.5,35.4h-6.8
+ l-0.8-35L82,68.7H75.3z"/>
+</svg>
+<!-- Original image Copyright Dave Gandy — CC BY 4.0 License -->
diff --git a/vendor/mdbook/src/theme/head.hbs b/vendor/mdbook/src/theme/head.hbs
new file mode 100644
index 000000000..cb1be1876
--- /dev/null
+++ b/vendor/mdbook/src/theme/head.hbs
@@ -0,0 +1 @@
+{{!-- Put your head HTML text here --}}
diff --git a/vendor/mdbook/src/theme/header.hbs b/vendor/mdbook/src/theme/header.hbs
new file mode 100644
index 000000000..26fa2d2ef
--- /dev/null
+++ b/vendor/mdbook/src/theme/header.hbs
@@ -0,0 +1 @@
+{{!-- Put your header HTML text here --}} \ No newline at end of file
diff --git a/vendor/mdbook/src/theme/index.hbs b/vendor/mdbook/src/theme/index.hbs
new file mode 100644
index 000000000..18d984a2b
--- /dev/null
+++ b/vendor/mdbook/src/theme/index.hbs
@@ -0,0 +1,314 @@
+<!DOCTYPE HTML>
+<html lang="{{ language }}" class="sidebar-visible no-js {{ default_theme }}">
+ <head>
+ <!-- Book generated using mdBook -->
+ <meta charset="UTF-8">
+ <title>{{ title }}</title>
+ {{#if is_print }}
+ <meta name="robots" content="noindex" />
+ {{/if}}
+ {{#if base_url}}
+ <base href="{{ base_url }}">
+ {{/if}}
+
+
+ <!-- Custom HTML head -->
+ {{> head}}
+
+ <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
+ <meta name="description" content="{{ description }}">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta name="theme-color" content="#ffffff" />
+
+ {{#if favicon_svg}}
+ <link rel="icon" href="{{ path_to_root }}favicon.svg">
+ {{/if}}
+ {{#if favicon_png}}
+ <link rel="shortcut icon" href="{{ path_to_root }}favicon.png">
+ {{/if}}
+ <link rel="stylesheet" href="{{ path_to_root }}css/variables.css">
+ <link rel="stylesheet" href="{{ path_to_root }}css/general.css">
+ <link rel="stylesheet" href="{{ path_to_root }}css/chrome.css">
+ {{#if print_enable}}
+ <link rel="stylesheet" href="{{ path_to_root }}css/print.css" media="print">
+ {{/if}}
+
+ <!-- Fonts -->
+ <link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
+ {{#if copy_fonts}}
+ <link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
+ {{/if}}
+
+ <!-- Highlight.js Stylesheets -->
+ <link rel="stylesheet" href="{{ path_to_root }}highlight.css">
+ <link rel="stylesheet" href="{{ path_to_root }}tomorrow-night.css">
+ <link rel="stylesheet" href="{{ path_to_root }}ayu-highlight.css">
+
+ <!-- Custom theme stylesheets -->
+ {{#each additional_css}}
+ <link rel="stylesheet" href="{{ ../path_to_root }}{{ this }}">
+ {{/each}}
+
+ {{#if mathjax_support}}
+ <!-- MathJax -->
+ <script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
+ {{/if}}
+ </head>
+ <body>
+ <!-- Provide site root to javascript -->
+ <script type="text/javascript">
+ var path_to_root = "{{ path_to_root }}";
+ var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
+ </script>
+
+ <!-- Work around some values being stored in localStorage wrapped in quotes -->
+ <script type="text/javascript">
+ try {
+ var theme = localStorage.getItem('mdbook-theme');
+ var sidebar = localStorage.getItem('mdbook-sidebar');
+
+ if (theme.startsWith('"') && theme.endsWith('"')) {
+ localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
+ }
+
+ if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
+ localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
+ }
+ } catch (e) { }
+ </script>
+
+ <!-- Set the theme before any content is loaded, prevents flash -->
+ <script type="text/javascript">
+ var theme;
+ try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
+ if (theme === null || theme === undefined) { theme = default_theme; }
+ var html = document.querySelector('html');
+ html.classList.remove('no-js')
+ html.classList.remove('{{ default_theme }}')
+ html.classList.add(theme);
+ html.classList.add('js');
+ </script>
+
+ <!-- Hide / unhide sidebar before it is displayed -->
+ <script type="text/javascript">
+ var html = document.querySelector('html');
+ var sidebar = 'hidden';
+ if (document.body.clientWidth >= 1080) {
+ try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
+ sidebar = sidebar || 'visible';
+ }
+ html.classList.remove('sidebar-visible');
+ html.classList.add("sidebar-" + sidebar);
+ </script>
+
+ <nav id="sidebar" class="sidebar" aria-label="Table of contents">
+ <div class="sidebar-scrollbox">
+ {{#toc}}{{/toc}}
+ </div>
+ <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
+ </nav>
+
+ <div id="page-wrapper" class="page-wrapper">
+
+ <div class="page">
+ {{> header}}
+ <div id="menu-bar-hover-placeholder"></div>
+ <div id="menu-bar" class="menu-bar sticky bordered">
+ <div class="left-buttons">
+ <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
+ <i class="fa fa-bars"></i>
+ </button>
+ <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
+ <i class="fa fa-paint-brush"></i>
+ </button>
+ <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
+ <li role="none"><button role="menuitem" class="theme" id="light">{{ theme_option "Light" }}</button></li>
+ <li role="none"><button role="menuitem" class="theme" id="rust">{{ theme_option "Rust" }}</button></li>
+ <li role="none"><button role="menuitem" class="theme" id="coal">{{ theme_option "Coal" }}</button></li>
+ <li role="none"><button role="menuitem" class="theme" id="navy">{{ theme_option "Navy" }}</button></li>
+ <li role="none"><button role="menuitem" class="theme" id="ayu">{{ theme_option "Ayu" }}</button></li>
+ </ul>
+ {{#if search_enabled}}
+ <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
+ <i class="fa fa-search"></i>
+ </button>
+ {{/if}}
+ </div>
+
+ <h1 class="menu-title">{{ book_title }}</h1>
+
+ <div class="right-buttons">
+ {{#if print_enable}}
+ <a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
+ <i id="print-button" class="fa fa-print"></i>
+ </a>
+ {{/if}}
+ {{#if git_repository_url}}
+ <a href="{{git_repository_url}}" title="Git repository" aria-label="Git repository">
+ <i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
+ </a>
+ {{/if}}
+ {{#if git_repository_edit_url}}
+ <a href="{{git_repository_edit_url}}" title="Suggest an edit" aria-label="Suggest an edit">
+ <i id="git-edit-button" class="fa fa-edit"></i>
+ </a>
+ {{/if}}
+
+ </div>
+ </div>
+
+ {{#if search_enabled}}
+ <div id="search-wrapper" class="hidden">
+ <form id="searchbar-outer" class="searchbar-outer">
+ <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
+ </form>
+ <div id="searchresults-outer" class="searchresults-outer hidden">
+ <div id="searchresults-header" class="searchresults-header"></div>
+ <ul id="searchresults">
+ </ul>
+ </div>
+ </div>
+ {{/if}}
+
+ <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
+ <script type="text/javascript">
+ document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
+ document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
+ Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
+ link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
+ });
+ </script>
+
+ <div id="content" class="content">
+ <main>
+ {{{ content }}}
+ </main>
+
+ <nav class="nav-wrapper" aria-label="Page navigation">
+ <!-- Mobile navigation buttons -->
+ {{#previous}}
+ <a rel="prev" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
+ <i class="fa fa-angle-left"></i>
+ </a>
+ {{/previous}}
+
+ {{#next}}
+ <a rel="next" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
+ <i class="fa fa-angle-right"></i>
+ </a>
+ {{/next}}
+
+ <div style="clear: both"></div>
+ </nav>
+ </div>
+ </div>
+
+ <nav class="nav-wide-wrapper" aria-label="Page navigation">
+ {{#previous}}
+ <a rel="prev" href="{{ path_to_root }}{{link}}" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
+ <i class="fa fa-angle-left"></i>
+ </a>
+ {{/previous}}
+
+ {{#next}}
+ <a rel="next" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
+ <i class="fa fa-angle-right"></i>
+ </a>
+ {{/next}}
+ </nav>
+
+ </div>
+
+ {{#if live_reload_endpoint}}
+ <!-- Livereload script (if served using the cli tool) -->
+ <script type="text/javascript">
+ const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
+ const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
+ const socket = new WebSocket(wsAddress);
+ socket.onmessage = function (event) {
+ if (event.data === "reload") {
+ socket.close();
+ location.reload();
+ }
+ };
+
+ window.onbeforeunload = function() {
+ socket.close();
+ }
+ </script>
+ {{/if}}
+
+ {{#if google_analytics}}
+ <!-- Google Analytics Tag -->
+ <script type="text/javascript">
+ var localAddrs = ["localhost", "127.0.0.1", ""];
+
+ // make sure we don't activate google analytics if the developer is
+ // inspecting the book locally...
+ if (localAddrs.indexOf(document.location.hostname) === -1) {
+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+ })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
+
+ ga('create', '{{google_analytics}}', 'auto');
+ ga('send', 'pageview');
+ }
+ </script>
+ {{/if}}
+
+ {{#if playground_line_numbers}}
+ <script type="text/javascript">
+ window.playground_line_numbers = true;
+ </script>
+ {{/if}}
+
+ {{#if playground_copyable}}
+ <script type="text/javascript">
+ window.playground_copyable = true;
+ </script>
+ {{/if}}
+
+ {{#if playground_js}}
+ <script src="{{ path_to_root }}ace.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{ path_to_root }}editor.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{ path_to_root }}mode-rust.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{ path_to_root }}theme-dawn.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{ path_to_root }}theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
+ {{/if}}
+
+ {{#if search_js}}
+ <script src="{{ path_to_root }}elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{ path_to_root }}mark.min.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{ path_to_root }}searcher.js" type="text/javascript" charset="utf-8"></script>
+ {{/if}}
+
+ <script src="{{ path_to_root }}clipboard.min.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{ path_to_root }}highlight.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{ path_to_root }}book.js" type="text/javascript" charset="utf-8"></script>
+
+ <!-- Custom JS scripts -->
+ {{#each additional_js}}
+ <script type="text/javascript" src="{{ ../path_to_root }}{{this}}"></script>
+ {{/each}}
+
+ {{#if is_print}}
+ {{#if mathjax_support}}
+ <script type="text/javascript">
+ window.addEventListener('load', function() {
+ MathJax.Hub.Register.StartupHook('End', function() {
+ window.setTimeout(window.print, 100);
+ });
+ });
+ </script>
+ {{else}}
+ <script type="text/javascript">
+ window.addEventListener('load', function() {
+ window.setTimeout(window.print, 100);
+ });
+ </script>
+ {{/if}}
+ {{/if}}
+
+ </body>
+</html>
diff --git a/vendor/mdbook/src/theme/mod.rs b/vendor/mdbook/src/theme/mod.rs
new file mode 100644
index 000000000..a1ee18aff
--- /dev/null
+++ b/vendor/mdbook/src/theme/mod.rs
@@ -0,0 +1,270 @@
+#![allow(missing_docs)]
+
+pub mod playground_editor;
+
+pub mod fonts;
+
+#[cfg(feature = "search")]
+pub mod searcher;
+
+use std::fs::File;
+use std::io::Read;
+use std::path::Path;
+
+use crate::errors::*;
+
+pub static INDEX: &[u8] = include_bytes!("index.hbs");
+pub static HEAD: &[u8] = include_bytes!("head.hbs");
+pub static REDIRECT: &[u8] = include_bytes!("redirect.hbs");
+pub static HEADER: &[u8] = include_bytes!("header.hbs");
+pub static CHROME_CSS: &[u8] = include_bytes!("css/chrome.css");
+pub static GENERAL_CSS: &[u8] = include_bytes!("css/general.css");
+pub static PRINT_CSS: &[u8] = include_bytes!("css/print.css");
+pub static VARIABLES_CSS: &[u8] = include_bytes!("css/variables.css");
+pub static FAVICON_PNG: &[u8] = include_bytes!("favicon.png");
+pub static FAVICON_SVG: &[u8] = include_bytes!("favicon.svg");
+pub static JS: &[u8] = include_bytes!("book.js");
+pub static HIGHLIGHT_JS: &[u8] = include_bytes!("highlight.js");
+pub static TOMORROW_NIGHT_CSS: &[u8] = include_bytes!("tomorrow-night.css");
+pub static HIGHLIGHT_CSS: &[u8] = include_bytes!("highlight.css");
+pub static AYU_HIGHLIGHT_CSS: &[u8] = include_bytes!("ayu-highlight.css");
+pub static CLIPBOARD_JS: &[u8] = include_bytes!("clipboard.min.js");
+pub static FONT_AWESOME: &[u8] = include_bytes!("FontAwesome/css/font-awesome.min.css");
+pub static FONT_AWESOME_EOT: &[u8] = include_bytes!("FontAwesome/fonts/fontawesome-webfont.eot");
+pub static FONT_AWESOME_SVG: &[u8] = include_bytes!("FontAwesome/fonts/fontawesome-webfont.svg");
+pub static FONT_AWESOME_TTF: &[u8] = include_bytes!("FontAwesome/fonts/fontawesome-webfont.ttf");
+pub static FONT_AWESOME_WOFF: &[u8] = include_bytes!("FontAwesome/fonts/fontawesome-webfont.woff");
+pub static FONT_AWESOME_WOFF2: &[u8] =
+ include_bytes!("FontAwesome/fonts/fontawesome-webfont.woff2");
+pub static FONT_AWESOME_OTF: &[u8] = include_bytes!("FontAwesome/fonts/FontAwesome.otf");
+
+/// The `Theme` struct should be used instead of the static variables because
+/// the `new()` method will look if the user has a theme directory in their
+/// source folder and use the users theme instead of the default.
+///
+/// You should only ever use the static variables directly if you want to
+/// override the user's theme with the defaults.
+#[derive(Debug, PartialEq)]
+pub struct Theme {
+ pub index: Vec<u8>,
+ pub head: Vec<u8>,
+ pub redirect: Vec<u8>,
+ pub header: Vec<u8>,
+ pub chrome_css: Vec<u8>,
+ pub general_css: Vec<u8>,
+ pub print_css: Vec<u8>,
+ pub variables_css: Vec<u8>,
+ pub favicon_png: Option<Vec<u8>>,
+ pub favicon_svg: Option<Vec<u8>>,
+ pub js: Vec<u8>,
+ pub highlight_css: Vec<u8>,
+ pub tomorrow_night_css: Vec<u8>,
+ pub ayu_highlight_css: Vec<u8>,
+ pub highlight_js: Vec<u8>,
+ pub clipboard_js: Vec<u8>,
+}
+
+impl Theme {
+ /// Creates a `Theme` from the given `theme_dir`.
+ /// If a file is found in the theme dir, it will override the default version.
+ pub fn new<P: AsRef<Path>>(theme_dir: P) -> Self {
+ let theme_dir = theme_dir.as_ref();
+ let mut theme = Theme::default();
+
+ // If the theme directory doesn't exist there's no point continuing...
+ if !theme_dir.exists() || !theme_dir.is_dir() {
+ return theme;
+ }
+
+ // Check for individual files, if they exist copy them across
+ {
+ let files = vec![
+ (theme_dir.join("index.hbs"), &mut theme.index),
+ (theme_dir.join("head.hbs"), &mut theme.head),
+ (theme_dir.join("redirect.hbs"), &mut theme.redirect),
+ (theme_dir.join("header.hbs"), &mut theme.header),
+ (theme_dir.join("book.js"), &mut theme.js),
+ (theme_dir.join("css/chrome.css"), &mut theme.chrome_css),
+ (theme_dir.join("css/general.css"), &mut theme.general_css),
+ (theme_dir.join("css/print.css"), &mut theme.print_css),
+ (
+ theme_dir.join("css/variables.css"),
+ &mut theme.variables_css,
+ ),
+ (theme_dir.join("highlight.js"), &mut theme.highlight_js),
+ (theme_dir.join("clipboard.min.js"), &mut theme.clipboard_js),
+ (theme_dir.join("highlight.css"), &mut theme.highlight_css),
+ (
+ theme_dir.join("tomorrow-night.css"),
+ &mut theme.tomorrow_night_css,
+ ),
+ (
+ theme_dir.join("ayu-highlight.css"),
+ &mut theme.ayu_highlight_css,
+ ),
+ ];
+
+ let load_with_warn = |filename: &Path, dest| {
+ if !filename.exists() {
+ // Don't warn if the file doesn't exist.
+ return false;
+ }
+ if let Err(e) = load_file_contents(filename, dest) {
+ warn!("Couldn't load custom file, {}: {}", filename.display(), e);
+ false
+ } else {
+ true
+ }
+ };
+
+ for (filename, dest) in files {
+ load_with_warn(&filename, dest);
+ }
+
+ // If the user overrides one favicon, but not the other, do not
+ // copy the default for the other.
+ let favicon_png = &mut theme.favicon_png.as_mut().unwrap();
+ let png = load_with_warn(&theme_dir.join("favicon.png"), favicon_png);
+ let favicon_svg = &mut theme.favicon_svg.as_mut().unwrap();
+ let svg = load_with_warn(&theme_dir.join("favicon.svg"), favicon_svg);
+ match (png, svg) {
+ (true, true) | (false, false) => {}
+ (true, false) => {
+ theme.favicon_svg = None;
+ }
+ (false, true) => {
+ theme.favicon_png = None;
+ }
+ }
+ }
+
+ theme
+ }
+}
+
+impl Default for Theme {
+ fn default() -> Theme {
+ Theme {
+ index: INDEX.to_owned(),
+ head: HEAD.to_owned(),
+ redirect: REDIRECT.to_owned(),
+ header: HEADER.to_owned(),
+ chrome_css: CHROME_CSS.to_owned(),
+ general_css: GENERAL_CSS.to_owned(),
+ print_css: PRINT_CSS.to_owned(),
+ variables_css: VARIABLES_CSS.to_owned(),
+ favicon_png: Some(FAVICON_PNG.to_owned()),
+ favicon_svg: Some(FAVICON_SVG.to_owned()),
+ js: JS.to_owned(),
+ highlight_css: HIGHLIGHT_CSS.to_owned(),
+ tomorrow_night_css: TOMORROW_NIGHT_CSS.to_owned(),
+ ayu_highlight_css: AYU_HIGHLIGHT_CSS.to_owned(),
+ highlight_js: HIGHLIGHT_JS.to_owned(),
+ clipboard_js: CLIPBOARD_JS.to_owned(),
+ }
+ }
+}
+
+/// Checks if a file exists, if so, the destination buffer will be filled with
+/// its contents.
+fn load_file_contents<P: AsRef<Path>>(filename: P, dest: &mut Vec<u8>) -> Result<()> {
+ let filename = filename.as_ref();
+
+ let mut buffer = Vec::new();
+ File::open(filename)?.read_to_end(&mut buffer)?;
+
+ // We needed the buffer so we'd only overwrite the existing content if we
+ // could successfully load the file into memory.
+ dest.clear();
+ dest.append(&mut buffer);
+
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::fs;
+ use std::path::PathBuf;
+ use tempfile::Builder as TempFileBuilder;
+
+ #[test]
+ fn theme_uses_defaults_with_nonexistent_src_dir() {
+ let non_existent = PathBuf::from("/non/existent/directory/");
+ assert!(!non_existent.exists());
+
+ let should_be = Theme::default();
+ let got = Theme::new(&non_existent);
+
+ assert_eq!(got, should_be);
+ }
+
+ #[test]
+ fn theme_dir_overrides_defaults() {
+ let files = [
+ "index.hbs",
+ "head.hbs",
+ "redirect.hbs",
+ "header.hbs",
+ "favicon.png",
+ "favicon.svg",
+ "css/chrome.css",
+ "css/fonts.css",
+ "css/general.css",
+ "css/print.css",
+ "css/variables.css",
+ "book.js",
+ "highlight.js",
+ "tomorrow-night.css",
+ "highlight.css",
+ "ayu-highlight.css",
+ "clipboard.min.js",
+ ];
+
+ let temp = TempFileBuilder::new().prefix("mdbook-").tempdir().unwrap();
+ fs::create_dir(temp.path().join("css")).unwrap();
+
+ // "touch" all of the special files so we have empty copies
+ for file in &files {
+ File::create(&temp.path().join(file)).unwrap();
+ }
+
+ let got = Theme::new(temp.path());
+
+ let empty = Theme {
+ index: Vec::new(),
+ head: Vec::new(),
+ redirect: Vec::new(),
+ header: Vec::new(),
+ chrome_css: Vec::new(),
+ general_css: Vec::new(),
+ print_css: Vec::new(),
+ variables_css: Vec::new(),
+ favicon_png: Some(Vec::new()),
+ favicon_svg: Some(Vec::new()),
+ js: Vec::new(),
+ highlight_css: Vec::new(),
+ tomorrow_night_css: Vec::new(),
+ ayu_highlight_css: Vec::new(),
+ highlight_js: Vec::new(),
+ clipboard_js: Vec::new(),
+ };
+
+ assert_eq!(got, empty);
+ }
+
+ #[test]
+ fn favicon_override() {
+ let temp = TempFileBuilder::new().prefix("mdbook-").tempdir().unwrap();
+ fs::write(temp.path().join("favicon.png"), "1234").unwrap();
+ let got = Theme::new(temp.path());
+ assert_eq!(got.favicon_png.as_ref().unwrap(), b"1234");
+ assert_eq!(got.favicon_svg, None);
+
+ let temp = TempFileBuilder::new().prefix("mdbook-").tempdir().unwrap();
+ fs::write(temp.path().join("favicon.svg"), "4567").unwrap();
+ let got = Theme::new(temp.path());
+ assert_eq!(got.favicon_png, None);
+ assert_eq!(got.favicon_svg.as_ref().unwrap(), b"4567");
+ }
+}
diff --git a/vendor/mdbook/src/theme/redirect.hbs b/vendor/mdbook/src/theme/redirect.hbs
new file mode 100644
index 000000000..9f49e6d09
--- /dev/null
+++ b/vendor/mdbook/src/theme/redirect.hbs
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Redirecting...</title>
+ <meta http-equiv="refresh" content="0;URL='{{url}}'">
+ <meta rel="canonical" href="{{url}}">
+ </head>
+ <body>
+ <p>Redirecting to... <a href="{{url}}">{{url}}</a>.</p>
+ </body>
+</html>
diff --git a/vendor/mdbook/src/theme/searcher/mod.rs b/vendor/mdbook/src/theme/searcher/mod.rs
new file mode 100644
index 000000000..d5029db16
--- /dev/null
+++ b/vendor/mdbook/src/theme/searcher/mod.rs
@@ -0,0 +1,6 @@
+//! Theme dependencies for in-browser search. Not included in mdbook when
+//! the "search" cargo feature is disabled.
+
+pub static JS: &[u8] = include_bytes!("searcher.js");
+pub static MARK_JS: &[u8] = include_bytes!("mark.min.js");
+pub static ELASTICLUNR_JS: &[u8] = include_bytes!("elasticlunr.min.js");
diff --git a/vendor/mdbook/src/theme/searcher/searcher.js b/vendor/mdbook/src/theme/searcher/searcher.js
new file mode 100644
index 000000000..d2b0aeed3
--- /dev/null
+++ b/vendor/mdbook/src/theme/searcher/searcher.js
@@ -0,0 +1,483 @@
+"use strict";
+window.search = window.search || {};
+(function search(search) {
+ // Search functionality
+ //
+ // You can use !hasFocus() to prevent keyhandling in your key
+ // event handlers while the user is typing their search.
+
+ if (!Mark || !elasticlunr) {
+ return;
+ }
+
+ //IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
+ if (!String.prototype.startsWith) {
+ String.prototype.startsWith = function(search, pos) {
+ return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search;
+ };
+ }
+
+ var search_wrap = document.getElementById('search-wrapper'),
+ searchbar = document.getElementById('searchbar'),
+ searchbar_outer = document.getElementById('searchbar-outer'),
+ searchresults = document.getElementById('searchresults'),
+ searchresults_outer = document.getElementById('searchresults-outer'),
+ searchresults_header = document.getElementById('searchresults-header'),
+ searchicon = document.getElementById('search-toggle'),
+ content = document.getElementById('content'),
+
+ searchindex = null,
+ doc_urls = [],
+ results_options = {
+ teaser_word_count: 30,
+ limit_results: 30,
+ },
+ search_options = {
+ bool: "AND",
+ expand: true,
+ fields: {
+ title: {boost: 1},
+ body: {boost: 1},
+ breadcrumbs: {boost: 0}
+ }
+ },
+ mark_exclude = [],
+ marker = new Mark(content),
+ current_searchterm = "",
+ URL_SEARCH_PARAM = 'search',
+ URL_MARK_PARAM = 'highlight',
+ teaser_count = 0,
+
+ SEARCH_HOTKEY_KEYCODE = 83,
+ ESCAPE_KEYCODE = 27,
+ DOWN_KEYCODE = 40,
+ UP_KEYCODE = 38,
+ SELECT_KEYCODE = 13;
+
+ function hasFocus() {
+ return searchbar === document.activeElement;
+ }
+
+ function removeChildren(elem) {
+ while (elem.firstChild) {
+ elem.removeChild(elem.firstChild);
+ }
+ }
+
+ // Helper to parse a url into its building blocks.
+ function parseURL(url) {
+ var a = document.createElement('a');
+ a.href = url;
+ return {
+ source: url,
+ protocol: a.protocol.replace(':',''),
+ host: a.hostname,
+ port: a.port,
+ params: (function(){
+ var ret = {};
+ var seg = a.search.replace(/^\?/,'').split('&');
+ var len = seg.length, i = 0, s;
+ for (;i<len;i++) {
+ if (!seg[i]) { continue; }
+ s = seg[i].split('=');
+ ret[s[0]] = s[1];
+ }
+ return ret;
+ })(),
+ file: (a.pathname.match(/\/([^/?#]+)$/i) || [,''])[1],
+ hash: a.hash.replace('#',''),
+ path: a.pathname.replace(/^([^/])/,'/$1')
+ };
+ }
+
+ // Helper to recreate a url string from its building blocks.
+ function renderURL(urlobject) {
+ var url = urlobject.protocol + "://" + urlobject.host;
+ if (urlobject.port != "") {
+ url += ":" + urlobject.port;
+ }
+ url += urlobject.path;
+ var joiner = "?";
+ for(var prop in urlobject.params) {
+ if(urlobject.params.hasOwnProperty(prop)) {
+ url += joiner + prop + "=" + urlobject.params[prop];
+ joiner = "&";
+ }
+ }
+ if (urlobject.hash != "") {
+ url += "#" + urlobject.hash;
+ }
+ return url;
+ }
+
+ // Helper to escape html special chars for displaying the teasers
+ var escapeHTML = (function() {
+ var MAP = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '"': '&#34;',
+ "'": '&#39;'
+ };
+ var repl = function(c) { return MAP[c]; };
+ return function(s) {
+ return s.replace(/[&<>'"]/g, repl);
+ };
+ })();
+
+ function formatSearchMetric(count, searchterm) {
+ if (count == 1) {
+ return count + " search result for '" + searchterm + "':";
+ } else if (count == 0) {
+ return "No search results for '" + searchterm + "'.";
+ } else {
+ return count + " search results for '" + searchterm + "':";
+ }
+ }
+
+ function formatSearchResult(result, searchterms) {
+ var teaser = makeTeaser(escapeHTML(result.doc.body), searchterms);
+ teaser_count++;
+
+ // The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor
+ var url = doc_urls[result.ref].split("#");
+ if (url.length == 1) { // no anchor found
+ url.push("");
+ }
+
+ // encodeURIComponent escapes all chars that could allow an XSS except
+ // for '. Due to that we also manually replace ' with its url-encoded
+ // representation (%27).
+ var searchterms = encodeURIComponent(searchterms.join(" ")).replace(/\'/g, "%27");
+
+ return '<a href="' + path_to_root + url[0] + '?' + URL_MARK_PARAM + '=' + searchterms + '#' + url[1]
+ + '" aria-details="teaser_' + teaser_count + '">' + result.doc.breadcrumbs + '</a>'
+ + '<span class="teaser" id="teaser_' + teaser_count + '" aria-label="Search Result Teaser">'
+ + teaser + '</span>';
+ }
+
+ function makeTeaser(body, searchterms) {
+ // The strategy is as follows:
+ // First, assign a value to each word in the document:
+ // Words that correspond to search terms (stemmer aware): 40
+ // Normal words: 2
+ // First word in a sentence: 8
+ // Then use a sliding window with a constant number of words and count the
+ // sum of the values of the words within the window. Then use the window that got the
+ // maximum sum. If there are multiple maximas, then get the last one.
+ // Enclose the terms in <em>.
+ var stemmed_searchterms = searchterms.map(function(w) {
+ return elasticlunr.stemmer(w.toLowerCase());
+ });
+ var searchterm_weight = 40;
+ var weighted = []; // contains elements of ["word", weight, index_in_document]
+ // split in sentences, then words
+ var sentences = body.toLowerCase().split('. ');
+ var index = 0;
+ var value = 0;
+ var searchterm_found = false;
+ for (var sentenceindex in sentences) {
+ var words = sentences[sentenceindex].split(' ');
+ value = 8;
+ for (var wordindex in words) {
+ var word = words[wordindex];
+ if (word.length > 0) {
+ for (var searchtermindex in stemmed_searchterms) {
+ if (elasticlunr.stemmer(word).startsWith(stemmed_searchterms[searchtermindex])) {
+ value = searchterm_weight;
+ searchterm_found = true;
+ }
+ };
+ weighted.push([word, value, index]);
+ value = 2;
+ }
+ index += word.length;
+ index += 1; // ' ' or '.' if last word in sentence
+ };
+ index += 1; // because we split at a two-char boundary '. '
+ };
+
+ if (weighted.length == 0) {
+ return body;
+ }
+
+ var window_weight = [];
+ var window_size = Math.min(weighted.length, results_options.teaser_word_count);
+
+ var cur_sum = 0;
+ for (var wordindex = 0; wordindex < window_size; wordindex++) {
+ cur_sum += weighted[wordindex][1];
+ };
+ window_weight.push(cur_sum);
+ for (var wordindex = 0; wordindex < weighted.length - window_size; wordindex++) {
+ cur_sum -= weighted[wordindex][1];
+ cur_sum += weighted[wordindex + window_size][1];
+ window_weight.push(cur_sum);
+ };
+
+ if (searchterm_found) {
+ var max_sum = 0;
+ var max_sum_window_index = 0;
+ // backwards
+ for (var i = window_weight.length - 1; i >= 0; i--) {
+ if (window_weight[i] > max_sum) {
+ max_sum = window_weight[i];
+ max_sum_window_index = i;
+ }
+ };
+ } else {
+ max_sum_window_index = 0;
+ }
+
+ // add <em/> around searchterms
+ var teaser_split = [];
+ var index = weighted[max_sum_window_index][2];
+ for (var i = max_sum_window_index; i < max_sum_window_index+window_size; i++) {
+ var word = weighted[i];
+ if (index < word[2]) {
+ // missing text from index to start of `word`
+ teaser_split.push(body.substring(index, word[2]));
+ index = word[2];
+ }
+ if (word[1] == searchterm_weight) {
+ teaser_split.push("<em>")
+ }
+ index = word[2] + word[0].length;
+ teaser_split.push(body.substring(word[2], index));
+ if (word[1] == searchterm_weight) {
+ teaser_split.push("</em>")
+ }
+ };
+
+ return teaser_split.join('');
+ }
+
+ function init(config) {
+ results_options = config.results_options;
+ search_options = config.search_options;
+ searchbar_outer = config.searchbar_outer;
+ doc_urls = config.doc_urls;
+ searchindex = elasticlunr.Index.load(config.index);
+
+ // Set up events
+ searchicon.addEventListener('click', function(e) { searchIconClickHandler(); }, false);
+ searchbar.addEventListener('keyup', function(e) { searchbarKeyUpHandler(); }, false);
+ document.addEventListener('keydown', function(e) { globalKeyHandler(e); }, false);
+ // If the user uses the browser buttons, do the same as if a reload happened
+ window.onpopstate = function(e) { doSearchOrMarkFromUrl(); };
+ // Suppress "submit" events so the page doesn't reload when the user presses Enter
+ document.addEventListener('submit', function(e) { e.preventDefault(); }, false);
+
+ // If reloaded, do the search or mark again, depending on the current url parameters
+ doSearchOrMarkFromUrl();
+ }
+
+ function unfocusSearchbar() {
+ // hacky, but just focusing a div only works once
+ var tmp = document.createElement('input');
+ tmp.setAttribute('style', 'position: absolute; opacity: 0;');
+ searchicon.appendChild(tmp);
+ tmp.focus();
+ tmp.remove();
+ }
+
+ // On reload or browser history backwards/forwards events, parse the url and do search or mark
+ function doSearchOrMarkFromUrl() {
+ // Check current URL for search request
+ var url = parseURL(window.location.href);
+ if (url.params.hasOwnProperty(URL_SEARCH_PARAM)
+ && url.params[URL_SEARCH_PARAM] != "") {
+ showSearch(true);
+ searchbar.value = decodeURIComponent(
+ (url.params[URL_SEARCH_PARAM]+'').replace(/\+/g, '%20'));
+ searchbarKeyUpHandler(); // -> doSearch()
+ } else {
+ showSearch(false);
+ }
+
+ if (url.params.hasOwnProperty(URL_MARK_PARAM)) {
+ var words = decodeURIComponent(url.params[URL_MARK_PARAM]).split(' ');
+ marker.mark(words, {
+ exclude: mark_exclude
+ });
+
+ var markers = document.querySelectorAll("mark");
+ function hide() {
+ for (var i = 0; i < markers.length; i++) {
+ markers[i].classList.add("fade-out");
+ window.setTimeout(function(e) { marker.unmark(); }, 300);
+ }
+ }
+ for (var i = 0; i < markers.length; i++) {
+ markers[i].addEventListener('click', hide);
+ }
+ }
+ }
+
+ // Eventhandler for keyevents on `document`
+ function globalKeyHandler(e) {
+ if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea' || e.target.type === 'text') { return; }
+
+ if (e.keyCode === ESCAPE_KEYCODE) {
+ e.preventDefault();
+ searchbar.classList.remove("active");
+ setSearchUrlParameters("",
+ (searchbar.value.trim() !== "") ? "push" : "replace");
+ if (hasFocus()) {
+ unfocusSearchbar();
+ }
+ showSearch(false);
+ marker.unmark();
+ } else if (!hasFocus() && e.keyCode === SEARCH_HOTKEY_KEYCODE) {
+ e.preventDefault();
+ showSearch(true);
+ window.scrollTo(0, 0);
+ searchbar.select();
+ } else if (hasFocus() && e.keyCode === DOWN_KEYCODE) {
+ e.preventDefault();
+ unfocusSearchbar();
+ searchresults.firstElementChild.classList.add("focus");
+ } else if (!hasFocus() && (e.keyCode === DOWN_KEYCODE
+ || e.keyCode === UP_KEYCODE
+ || e.keyCode === SELECT_KEYCODE)) {
+ // not `:focus` because browser does annoying scrolling
+ var focused = searchresults.querySelector("li.focus");
+ if (!focused) return;
+ e.preventDefault();
+ if (e.keyCode === DOWN_KEYCODE) {
+ var next = focused.nextElementSibling;
+ if (next) {
+ focused.classList.remove("focus");
+ next.classList.add("focus");
+ }
+ } else if (e.keyCode === UP_KEYCODE) {
+ focused.classList.remove("focus");
+ var prev = focused.previousElementSibling;
+ if (prev) {
+ prev.classList.add("focus");
+ } else {
+ searchbar.select();
+ }
+ } else { // SELECT_KEYCODE
+ window.location.assign(focused.querySelector('a'));
+ }
+ }
+ }
+
+ function showSearch(yes) {
+ if (yes) {
+ search_wrap.classList.remove('hidden');
+ searchicon.setAttribute('aria-expanded', 'true');
+ } else {
+ search_wrap.classList.add('hidden');
+ searchicon.setAttribute('aria-expanded', 'false');
+ var results = searchresults.children;
+ for (var i = 0; i < results.length; i++) {
+ results[i].classList.remove("focus");
+ }
+ }
+ }
+
+ function showResults(yes) {
+ if (yes) {
+ searchresults_outer.classList.remove('hidden');
+ } else {
+ searchresults_outer.classList.add('hidden');
+ }
+ }
+
+ // Eventhandler for search icon
+ function searchIconClickHandler() {
+ if (search_wrap.classList.contains('hidden')) {
+ showSearch(true);
+ window.scrollTo(0, 0);
+ searchbar.select();
+ } else {
+ showSearch(false);
+ }
+ }
+
+ // Eventhandler for keyevents while the searchbar is focused
+ function searchbarKeyUpHandler() {
+ var searchterm = searchbar.value.trim();
+ if (searchterm != "") {
+ searchbar.classList.add("active");
+ doSearch(searchterm);
+ } else {
+ searchbar.classList.remove("active");
+ showResults(false);
+ removeChildren(searchresults);
+ }
+
+ setSearchUrlParameters(searchterm, "push_if_new_search_else_replace");
+
+ // Remove marks
+ marker.unmark();
+ }
+
+ // Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and #heading-anchor .
+ // `action` can be one of "push", "replace", "push_if_new_search_else_replace"
+ // and replaces or pushes a new browser history item.
+ // "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet.
+ function setSearchUrlParameters(searchterm, action) {
+ var url = parseURL(window.location.href);
+ var first_search = ! url.params.hasOwnProperty(URL_SEARCH_PARAM);
+ if (searchterm != "" || action == "push_if_new_search_else_replace") {
+ url.params[URL_SEARCH_PARAM] = searchterm;
+ delete url.params[URL_MARK_PARAM];
+ url.hash = "";
+ } else {
+ delete url.params[URL_MARK_PARAM];
+ delete url.params[URL_SEARCH_PARAM];
+ }
+ // A new search will also add a new history item, so the user can go back
+ // to the page prior to searching. A updated search term will only replace
+ // the url.
+ if (action == "push" || (action == "push_if_new_search_else_replace" && first_search) ) {
+ history.pushState({}, document.title, renderURL(url));
+ } else if (action == "replace" || (action == "push_if_new_search_else_replace" && !first_search) ) {
+ history.replaceState({}, document.title, renderURL(url));
+ }
+ }
+
+ function doSearch(searchterm) {
+
+ // Don't search the same twice
+ if (current_searchterm == searchterm) { return; }
+ else { current_searchterm = searchterm; }
+
+ if (searchindex == null) { return; }
+
+ // Do the actual search
+ var results = searchindex.search(searchterm, search_options);
+ var resultcount = Math.min(results.length, results_options.limit_results);
+
+ // Display search metrics
+ searchresults_header.innerText = formatSearchMetric(resultcount, searchterm);
+
+ // Clear and insert results
+ var searchterms = searchterm.split(' ');
+ removeChildren(searchresults);
+ for(var i = 0; i < resultcount ; i++){
+ var resultElem = document.createElement('li');
+ resultElem.innerHTML = formatSearchResult(results[i], searchterms);
+ searchresults.appendChild(resultElem);
+ }
+
+ // Display results
+ showResults(true);
+ }
+
+ fetch(path_to_root + 'searchindex.json')
+ .then(response => response.json())
+ .then(json => init(json))
+ .catch(error => { // Try to load searchindex.js if fetch failed
+ var script = document.createElement('script');
+ script.src = path_to_root + 'searchindex.js';
+ script.onload = () => init(window.search);
+ document.head.appendChild(script);
+ });
+
+ // Exported functions
+ search.hasFocus = hasFocus;
+})(window.search);
diff --git a/vendor/mdbook/src/theme/tomorrow-night.css b/vendor/mdbook/src/theme/tomorrow-night.css
new file mode 100644
index 000000000..5b4aca77c
--- /dev/null
+++ b/vendor/mdbook/src/theme/tomorrow-night.css
@@ -0,0 +1,102 @@
+/* Tomorrow Night Theme */
+/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
+/* Original theme - https://github.com/chriskempson/tomorrow-theme */
+/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
+
+/* Tomorrow Comment */
+.hljs-comment {
+ color: #969896;
+}
+
+/* Tomorrow Red */
+.hljs-variable,
+.hljs-attribute,
+.hljs-tag,
+.hljs-regexp,
+.ruby .hljs-constant,
+.xml .hljs-tag .hljs-title,
+.xml .hljs-pi,
+.xml .hljs-doctype,
+.html .hljs-doctype,
+.css .hljs-id,
+.css .hljs-class,
+.css .hljs-pseudo {
+ color: #cc6666;
+}
+
+/* Tomorrow Orange */
+.hljs-number,
+.hljs-preprocessor,
+.hljs-pragma,
+.hljs-built_in,
+.hljs-literal,
+.hljs-params,
+.hljs-constant {
+ color: #de935f;
+}
+
+/* Tomorrow Yellow */
+.ruby .hljs-class .hljs-title,
+.css .hljs-rule .hljs-attribute {
+ color: #f0c674;
+}
+
+/* Tomorrow Green */
+.hljs-string,
+.hljs-value,
+.hljs-inheritance,
+.hljs-header,
+.hljs-name,
+.ruby .hljs-symbol,
+.xml .hljs-cdata {
+ color: #b5bd68;
+}
+
+/* Tomorrow Aqua */
+.hljs-title,
+.css .hljs-hexcolor {
+ color: #8abeb7;
+}
+
+/* Tomorrow Blue */
+.hljs-function,
+.python .hljs-decorator,
+.python .hljs-title,
+.ruby .hljs-function .hljs-title,
+.ruby .hljs-title .hljs-keyword,
+.perl .hljs-sub,
+.javascript .hljs-title,
+.coffeescript .hljs-title {
+ color: #81a2be;
+}
+
+/* Tomorrow Purple */
+.hljs-keyword,
+.javascript .hljs-function {
+ color: #b294bb;
+}
+
+.hljs {
+ display: block;
+ overflow-x: auto;
+ background: #1d1f21;
+ color: #c5c8c6;
+}
+
+.coffeescript .javascript,
+.javascript .xml,
+.tex .hljs-formula,
+.xml .javascript,
+.xml .vbscript,
+.xml .css,
+.xml .hljs-cdata {
+ opacity: 0.5;
+}
+
+.hljs-addition {
+ color: #718c00;
+}
+
+.hljs-deletion {
+ color: #c82829;
+}
diff --git a/vendor/mdbook/src/utils/fs.rs b/vendor/mdbook/src/utils/fs.rs
new file mode 100644
index 000000000..a933d548a
--- /dev/null
+++ b/vendor/mdbook/src/utils/fs.rs
@@ -0,0 +1,275 @@
+use crate::errors::*;
+use std::convert::Into;
+use std::fs::{self, File};
+use std::io::Write;
+use std::path::{Component, Path, PathBuf};
+
+/// Naively replaces any path separator with a forward-slash '/'
+pub fn normalize_path(path: &str) -> String {
+ use std::path::is_separator;
+ path.chars()
+ .map(|ch| if is_separator(ch) { '/' } else { ch })
+ .collect::<String>()
+}
+
+/// Write the given data to a file, creating it first if necessary
+pub fn write_file<P: AsRef<Path>>(build_dir: &Path, filename: P, content: &[u8]) -> Result<()> {
+ let path = build_dir.join(filename);
+
+ create_file(&path)?.write_all(content).map_err(Into::into)
+}
+
+/// Takes a path and returns a path containing just enough `../` to point to
+/// the root of the given path.
+///
+/// This is mostly interesting for a relative path to point back to the
+/// directory from where the path starts.
+///
+/// ```rust
+/// # use std::path::Path;
+/// # use mdbook::utils::fs::path_to_root;
+/// let path = Path::new("some/relative/path");
+/// assert_eq!(path_to_root(path), "../../");
+/// ```
+///
+/// **note:** it's not very fool-proof, if you find a situation where
+/// it doesn't return the correct path.
+/// Consider [submitting a new issue](https://github.com/rust-lang/mdBook/issues)
+/// or a [pull-request](https://github.com/rust-lang/mdBook/pulls) to improve it.
+pub fn path_to_root<P: Into<PathBuf>>(path: P) -> String {
+ debug!("path_to_root");
+ // Remove filename and add "../" for every directory
+
+ path.into()
+ .parent()
+ .expect("")
+ .components()
+ .fold(String::new(), |mut s, c| {
+ match c {
+ Component::Normal(_) => s.push_str("../"),
+ _ => {
+ debug!("Other path component... {:?}", c);
+ }
+ }
+ s
+ })
+}
+
+/// This function creates a file and returns it. But before creating the file
+/// it checks every directory in the path to see if it exists,
+/// and if it does not it will be created.
+pub fn create_file(path: &Path) -> Result<File> {
+ debug!("Creating {}", path.display());
+
+ // Construct path
+ if let Some(p) = path.parent() {
+ trace!("Parent directory is: {:?}", p);
+
+ fs::create_dir_all(p)?;
+ }
+
+ File::create(path).map_err(Into::into)
+}
+
+/// Removes all the content of a directory but not the directory itself
+pub fn remove_dir_content(dir: &Path) -> Result<()> {
+ for item in fs::read_dir(dir)? {
+ if let Ok(item) = item {
+ let item = item.path();
+ if item.is_dir() {
+ fs::remove_dir_all(item)?;
+ } else {
+ fs::remove_file(item)?;
+ }
+ }
+ }
+ Ok(())
+}
+
+/// Copies all files of a directory to another one except the files
+/// with the extensions given in the `ext_blacklist` array
+pub fn copy_files_except_ext(
+ from: &Path,
+ to: &Path,
+ recursive: bool,
+ avoid_dir: Option<&PathBuf>,
+ ext_blacklist: &[&str],
+) -> Result<()> {
+ debug!(
+ "Copying all files from {} to {} (blacklist: {:?}), avoiding {:?}",
+ from.display(),
+ to.display(),
+ ext_blacklist,
+ avoid_dir
+ );
+
+ // Check that from and to are different
+ if from == to {
+ return Ok(());
+ }
+
+ for entry in fs::read_dir(from)? {
+ let entry = entry?;
+ let metadata = entry
+ .path()
+ .metadata()
+ .with_context(|| format!("Failed to read {:?}", entry.path()))?;
+
+ // If the entry is a dir and the recursive option is enabled, call itself
+ if metadata.is_dir() && recursive {
+ if entry.path() == to.to_path_buf() {
+ continue;
+ }
+
+ if let Some(avoid) = avoid_dir {
+ if entry.path() == *avoid {
+ continue;
+ }
+ }
+
+ // check if output dir already exists
+ if !to.join(entry.file_name()).exists() {
+ fs::create_dir(&to.join(entry.file_name()))?;
+ }
+
+ copy_files_except_ext(
+ &from.join(entry.file_name()),
+ &to.join(entry.file_name()),
+ true,
+ avoid_dir,
+ ext_blacklist,
+ )?;
+ } else if metadata.is_file() {
+ // Check if it is in the blacklist
+ if let Some(ext) = entry.path().extension() {
+ if ext_blacklist.contains(&ext.to_str().unwrap()) {
+ continue;
+ }
+ }
+ debug!(
+ "creating path for file: {:?}",
+ &to.join(
+ entry
+ .path()
+ .file_name()
+ .expect("a file should have a file name...")
+ )
+ );
+
+ debug!(
+ "Copying {:?} to {:?}",
+ entry.path(),
+ &to.join(
+ entry
+ .path()
+ .file_name()
+ .expect("a file should have a file name...")
+ )
+ );
+ fs::copy(
+ entry.path(),
+ &to.join(
+ entry
+ .path()
+ .file_name()
+ .expect("a file should have a file name..."),
+ ),
+ )?;
+ }
+ }
+ Ok(())
+}
+
+pub fn get_404_output_file(input_404: &Option<String>) -> String {
+ input_404
+ .as_ref()
+ .unwrap_or(&"404.md".to_string())
+ .replace(".md", ".html")
+}
+
+#[cfg(test)]
+mod tests {
+ use super::copy_files_except_ext;
+ use std::{fs, io::Result, path::Path};
+
+ #[cfg(target_os = "windows")]
+ fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> {
+ std::os::windows::fs::symlink_file(src, dst)
+ }
+
+ #[cfg(not(target_os = "windows"))]
+ fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> {
+ std::os::unix::fs::symlink(src, dst)
+ }
+
+ #[test]
+ fn copy_files_except_ext_test() {
+ let tmp = match tempfile::TempDir::new() {
+ Ok(t) => t,
+ Err(e) => panic!("Could not create a temp dir: {}", e),
+ };
+
+ // Create a couple of files
+ if let Err(err) = fs::File::create(&tmp.path().join("file.txt")) {
+ panic!("Could not create file.txt: {}", err);
+ }
+ if let Err(err) = fs::File::create(&tmp.path().join("file.md")) {
+ panic!("Could not create file.md: {}", err);
+ }
+ if let Err(err) = fs::File::create(&tmp.path().join("file.png")) {
+ panic!("Could not create file.png: {}", err);
+ }
+ if let Err(err) = fs::create_dir(&tmp.path().join("sub_dir")) {
+ panic!("Could not create sub_dir: {}", err);
+ }
+ if let Err(err) = fs::File::create(&tmp.path().join("sub_dir/file.png")) {
+ panic!("Could not create sub_dir/file.png: {}", err);
+ }
+ if let Err(err) = fs::create_dir(&tmp.path().join("sub_dir_exists")) {
+ panic!("Could not create sub_dir_exists: {}", err);
+ }
+ if let Err(err) = fs::File::create(&tmp.path().join("sub_dir_exists/file.txt")) {
+ panic!("Could not create sub_dir_exists/file.txt: {}", err);
+ }
+ if let Err(err) = symlink(
+ &tmp.path().join("file.png"),
+ &tmp.path().join("symlink.png"),
+ ) {
+ panic!("Could not symlink file.png: {}", err);
+ }
+
+ // Create output dir
+ if let Err(err) = fs::create_dir(&tmp.path().join("output")) {
+ panic!("Could not create output: {}", err);
+ }
+ if let Err(err) = fs::create_dir(&tmp.path().join("output/sub_dir_exists")) {
+ panic!("Could not create output/sub_dir_exists: {}", err);
+ }
+
+ if let Err(e) =
+ copy_files_except_ext(tmp.path(), &tmp.path().join("output"), true, None, &["md"])
+ {
+ panic!("Error while executing the function:\n{:?}", e);
+ }
+
+ // Check if the correct files where created
+ if !(&tmp.path().join("output/file.txt")).exists() {
+ panic!("output/file.txt should exist")
+ }
+ if (&tmp.path().join("output/file.md")).exists() {
+ panic!("output/file.md should not exist")
+ }
+ if !(&tmp.path().join("output/file.png")).exists() {
+ panic!("output/file.png should exist")
+ }
+ if !(&tmp.path().join("output/sub_dir/file.png")).exists() {
+ panic!("output/sub_dir/file.png should exist")
+ }
+ if !(&tmp.path().join("output/sub_dir_exists/file.txt")).exists() {
+ panic!("output/sub_dir/file.png should exist")
+ }
+ if !(&tmp.path().join("output/symlink.png")).exists() {
+ panic!("output/symlink.png should exist")
+ }
+ }
+}
diff --git a/vendor/mdbook/src/utils/mod.rs b/vendor/mdbook/src/utils/mod.rs
new file mode 100644
index 000000000..a205633f9
--- /dev/null
+++ b/vendor/mdbook/src/utils/mod.rs
@@ -0,0 +1,494 @@
+#![allow(missing_docs)] // FIXME: Document this
+
+pub mod fs;
+mod string;
+pub(crate) mod toml_ext;
+use crate::errors::Error;
+use regex::Regex;
+
+use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag};
+
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::fmt::Write;
+use std::path::Path;
+
+pub use self::string::{
+ take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines,
+ take_rustdoc_include_lines,
+};
+
+/// Replaces multiple consecutive whitespace characters with a single space character.
+pub fn collapse_whitespace(text: &str) -> Cow<'_, str> {
+ lazy_static! {
+ static ref RE: Regex = Regex::new(r"\s\s+").unwrap();
+ }
+ RE.replace_all(text, " ")
+}
+
+/// Convert the given string to a valid HTML element ID.
+/// The only restriction is that the ID must not contain any ASCII whitespace.
+pub fn normalize_id(content: &str) -> String {
+ content
+ .chars()
+ .filter_map(|ch| {
+ if ch.is_alphanumeric() || ch == '_' || ch == '-' {
+ Some(ch.to_ascii_lowercase())
+ } else if ch.is_whitespace() {
+ Some('-')
+ } else {
+ None
+ }
+ })
+ .collect::<String>()
+}
+
+/// Generate an ID for use with anchors which is derived from a "normalised"
+/// string.
+// This function should be made private when the deprecation expires.
+#[deprecated(since = "0.4.16", note = "use unique_id_from_content instead")]
+pub fn id_from_content(content: &str) -> String {
+ let mut content = content.to_string();
+
+ // Skip any tags or html-encoded stuff
+ lazy_static! {
+ static ref HTML: Regex = Regex::new(r"(<.*?>)").unwrap();
+ }
+ content = HTML.replace_all(&content, "").into();
+ const REPL_SUB: &[&str] = &["&lt;", "&gt;", "&amp;", "&#39;", "&quot;"];
+ for sub in REPL_SUB {
+ content = content.replace(sub, "");
+ }
+
+ // Remove spaces and hashes indicating a header
+ let trimmed = content.trim().trim_start_matches('#').trim();
+ normalize_id(trimmed)
+}
+
+/// Generate an ID for use with anchors which is derived from a "normalised"
+/// string.
+///
+/// Each ID returned will be unique, if the same `id_counter` is provided on
+/// each call.
+pub fn unique_id_from_content(content: &str, id_counter: &mut HashMap<String, usize>) -> String {
+ let id = {
+ #[allow(deprecated)]
+ id_from_content(content)
+ };
+
+ // If we have headers with the same normalized id, append an incrementing counter
+ let id_count = id_counter.entry(id.clone()).or_insert(0);
+ let unique_id = match *id_count {
+ 0 => id,
+ id_count => format!("{}-{}", id, id_count),
+ };
+ *id_count += 1;
+ unique_id
+}
+
+/// Fix links to the correct location.
+///
+/// This adjusts links, such as turning `.md` extensions to `.html`.
+///
+/// `path` is the path to the page being rendered relative to the root of the
+/// book. This is used for the `print.html` page so that links on the print
+/// page go to the original location. Normal page rendering sets `path` to
+/// None. Ideally, print page links would link to anchors on the print page,
+/// but that is very difficult.
+fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
+ lazy_static! {
+ static ref SCHEME_LINK: Regex = Regex::new(r"^[a-z][a-z0-9+.-]*:").unwrap();
+ static ref MD_LINK: Regex = Regex::new(r"(?P<link>.*)\.md(?P<anchor>#.*)?").unwrap();
+ }
+
+ fn fix<'a>(dest: CowStr<'a>, path: Option<&Path>) -> CowStr<'a> {
+ if dest.starts_with('#') {
+ // Fragment-only link.
+ if let Some(path) = path {
+ let mut base = path.display().to_string();
+ if base.ends_with(".md") {
+ base.replace_range(base.len() - 3.., ".html");
+ }
+ return format!("{}{}", base, dest).into();
+ } else {
+ return dest;
+ }
+ }
+ // Don't modify links with schemes like `https`.
+ if !SCHEME_LINK.is_match(&dest) {
+ // This is a relative link, adjust it as necessary.
+ let mut fixed_link = String::new();
+ if let Some(path) = path {
+ let base = path
+ .parent()
+ .expect("path can't be empty")
+ .to_str()
+ .expect("utf-8 paths only");
+ if !base.is_empty() {
+ write!(fixed_link, "{}/", base).unwrap();
+ }
+ }
+
+ if let Some(caps) = MD_LINK.captures(&dest) {
+ fixed_link.push_str(&caps["link"]);
+ fixed_link.push_str(".html");
+ if let Some(anchor) = caps.name("anchor") {
+ fixed_link.push_str(anchor.as_str());
+ }
+ } else {
+ fixed_link.push_str(&dest);
+ };
+ return CowStr::from(fixed_link);
+ }
+ dest
+ }
+
+ fn fix_html<'a>(html: CowStr<'a>, path: Option<&Path>) -> CowStr<'a> {
+ // This is a terrible hack, but should be reasonably reliable. Nobody
+ // should ever parse a tag with a regex. However, there isn't anything
+ // in Rust that I know of that is suitable for handling partial html
+ // fragments like those generated by pulldown_cmark.
+ //
+ // There are dozens of HTML tags/attributes that contain paths, so
+ // feel free to add more tags if desired; these are the only ones I
+ // care about right now.
+ lazy_static! {
+ static ref HTML_LINK: Regex =
+ Regex::new(r#"(<(?:a|img) [^>]*?(?:src|href)=")([^"]+?)""#).unwrap();
+ }
+
+ HTML_LINK
+ .replace_all(&html, |caps: &regex::Captures<'_>| {
+ let fixed = fix(caps[2].into(), path);
+ format!("{}{}\"", &caps[1], fixed)
+ })
+ .into_owned()
+ .into()
+ }
+
+ match event {
+ Event::Start(Tag::Link(link_type, dest, title)) => {
+ Event::Start(Tag::Link(link_type, fix(dest, path), title))
+ }
+ Event::Start(Tag::Image(link_type, dest, title)) => {
+ Event::Start(Tag::Image(link_type, fix(dest, path), title))
+ }
+ Event::Html(html) => Event::Html(fix_html(html, path)),
+ _ => event,
+ }
+}
+
+/// Wrapper around the pulldown-cmark parser for rendering markdown to HTML.
+pub fn render_markdown(text: &str, curly_quotes: bool) -> String {
+ render_markdown_with_path(text, curly_quotes, None)
+}
+
+pub fn new_cmark_parser(text: &str, curly_quotes: bool) -> Parser<'_, '_> {
+ let mut opts = Options::empty();
+ opts.insert(Options::ENABLE_TABLES);
+ opts.insert(Options::ENABLE_FOOTNOTES);
+ opts.insert(Options::ENABLE_STRIKETHROUGH);
+ opts.insert(Options::ENABLE_TASKLISTS);
+ if curly_quotes {
+ opts.insert(Options::ENABLE_SMART_PUNCTUATION);
+ }
+ Parser::new_ext(text, opts)
+}
+
+pub fn render_markdown_with_path(text: &str, curly_quotes: bool, path: Option<&Path>) -> String {
+ let mut s = String::with_capacity(text.len() * 3 / 2);
+ let p = new_cmark_parser(text, curly_quotes);
+ let events = p
+ .map(clean_codeblock_headers)
+ .map(|event| adjust_links(event, path))
+ .flat_map(|event| {
+ let (a, b) = wrap_tables(event);
+ a.into_iter().chain(b)
+ });
+
+ html::push_html(&mut s, events);
+ s
+}
+
+/// Wraps tables in a `.table-wrapper` class to apply overflow-x rules to.
+fn wrap_tables(event: Event<'_>) -> (Option<Event<'_>>, Option<Event<'_>>) {
+ match event {
+ Event::Start(Tag::Table(_)) => (
+ Some(Event::Html(r#"<div class="table-wrapper">"#.into())),
+ Some(event),
+ ),
+ Event::End(Tag::Table(_)) => (Some(event), Some(Event::Html(r#"</div>"#.into()))),
+ _ => (Some(event), None),
+ }
+}
+
+fn clean_codeblock_headers(event: Event<'_>) -> Event<'_> {
+ match event {
+ Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(ref info))) => {
+ let info: String = info
+ .chars()
+ .map(|x| match x {
+ ' ' | '\t' => ',',
+ _ => x,
+ })
+ .filter(|ch| !ch.is_whitespace())
+ .collect();
+
+ Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::from(info))))
+ }
+ _ => event,
+ }
+}
+
+/// Prints a "backtrace" of some `Error`.
+pub fn log_backtrace(e: &Error) {
+ error!("Error: {}", e);
+
+ for cause in e.chain().skip(1) {
+ error!("\tCaused By: {}", cause);
+ }
+}
+
+pub(crate) fn bracket_escape(mut s: &str) -> String {
+ let mut escaped = String::with_capacity(s.len());
+ let needs_escape: &[char] = &['<', '>'];
+ while let Some(next) = s.find(needs_escape) {
+ escaped.push_str(&s[..next]);
+ match s.as_bytes()[next] {
+ b'<' => escaped.push_str("&lt;"),
+ b'>' => escaped.push_str("&gt;"),
+ _ => unreachable!(),
+ }
+ s = &s[next + 1..];
+ }
+ escaped.push_str(s);
+ escaped
+}
+
+#[cfg(test)]
+mod tests {
+ use super::bracket_escape;
+
+ mod render_markdown {
+ use super::super::render_markdown;
+
+ #[test]
+ fn preserves_external_links() {
+ assert_eq!(
+ render_markdown("[example](https://www.rust-lang.org/)", false),
+ "<p><a href=\"https://www.rust-lang.org/\">example</a></p>\n"
+ );
+ }
+
+ #[test]
+ fn it_can_adjust_markdown_links() {
+ assert_eq!(
+ render_markdown("[example](example.md)", false),
+ "<p><a href=\"example.html\">example</a></p>\n"
+ );
+ assert_eq!(
+ render_markdown("[example_anchor](example.md#anchor)", false),
+ "<p><a href=\"example.html#anchor\">example_anchor</a></p>\n"
+ );
+
+ // this anchor contains 'md' inside of it
+ assert_eq!(
+ render_markdown("[phantom data](foo.html#phantomdata)", false),
+ "<p><a href=\"foo.html#phantomdata\">phantom data</a></p>\n"
+ );
+ }
+
+ #[test]
+ fn it_can_wrap_tables() {
+ let src = r#"
+| Original | Punycode | Punycode + Encoding |
+|-----------------|-----------------|---------------------|
+| føø | f-5gaa | f_5gaa |
+"#;
+ let out = r#"
+<div class="table-wrapper"><table><thead><tr><th>Original</th><th>Punycode</th><th>Punycode + Encoding</th></tr></thead><tbody>
+<tr><td>føø</td><td>f-5gaa</td><td>f_5gaa</td></tr>
+</tbody></table>
+</div>
+"#.trim();
+ assert_eq!(render_markdown(src, false), out);
+ }
+
+ #[test]
+ fn it_can_keep_quotes_straight() {
+ assert_eq!(render_markdown("'one'", false), "<p>'one'</p>\n");
+ }
+
+ #[test]
+ fn it_can_make_quotes_curly_except_when_they_are_in_code() {
+ let input = r#"
+'one'
+```
+'two'
+```
+`'three'` 'four'"#;
+ let expected = r#"<p>‘one’</p>
+<pre><code>'two'
+</code></pre>
+<p><code>'three'</code> ‘four’</p>
+"#;
+ assert_eq!(render_markdown(input, true), expected);
+ }
+
+ #[test]
+ fn whitespace_outside_of_codeblock_header_is_preserved() {
+ let input = r#"
+some text with spaces
+```rust
+fn main() {
+// code inside is unchanged
+}
+```
+more text with spaces
+"#;
+
+ let expected = r#"<p>some text with spaces</p>
+<pre><code class="language-rust">fn main() {
+// code inside is unchanged
+}
+</code></pre>
+<p>more text with spaces</p>
+"#;
+ assert_eq!(render_markdown(input, false), expected);
+ assert_eq!(render_markdown(input, true), expected);
+ }
+
+ #[test]
+ fn rust_code_block_properties_are_passed_as_space_delimited_class() {
+ let input = r#"
+```rust,no_run,should_panic,property_3
+```
+"#;
+
+ let expected = r#"<pre><code class="language-rust,no_run,should_panic,property_3"></code></pre>
+"#;
+ assert_eq!(render_markdown(input, false), expected);
+ assert_eq!(render_markdown(input, true), expected);
+ }
+
+ #[test]
+ fn rust_code_block_properties_with_whitespace_are_passed_as_space_delimited_class() {
+ let input = r#"
+```rust, no_run,,,should_panic , ,property_3
+```
+"#;
+
+ let expected = r#"<pre><code class="language-rust,,,,,no_run,,,should_panic,,,,property_3"></code></pre>
+"#;
+ assert_eq!(render_markdown(input, false), expected);
+ assert_eq!(render_markdown(input, true), expected);
+ }
+
+ #[test]
+ fn rust_code_block_without_properties_has_proper_html_class() {
+ let input = r#"
+```rust
+```
+"#;
+
+ let expected = r#"<pre><code class="language-rust"></code></pre>
+"#;
+ assert_eq!(render_markdown(input, false), expected);
+ assert_eq!(render_markdown(input, true), expected);
+
+ let input = r#"
+```rust
+```
+"#;
+ assert_eq!(render_markdown(input, false), expected);
+ assert_eq!(render_markdown(input, true), expected);
+ }
+ }
+
+ #[allow(deprecated)]
+ mod id_from_content {
+ use super::super::id_from_content;
+
+ #[test]
+ fn it_generates_anchors() {
+ assert_eq!(
+ id_from_content("## Method-call expressions"),
+ "method-call-expressions"
+ );
+ assert_eq!(id_from_content("## **Bold** title"), "bold-title");
+ assert_eq!(id_from_content("## `Code` title"), "code-title");
+ assert_eq!(
+ id_from_content("## title <span dir=rtl>foo</span>"),
+ "title-foo"
+ );
+ }
+
+ #[test]
+ fn it_generates_anchors_from_non_ascii_initial() {
+ assert_eq!(
+ id_from_content("## `--passes`: add more rustdoc passes"),
+ "--passes-add-more-rustdoc-passes"
+ );
+ assert_eq!(
+ id_from_content("## 中文標題 CJK title"),
+ "中文標題-cjk-title"
+ );
+ assert_eq!(id_from_content("## Über"), "Über");
+ }
+ }
+
+ mod html_munging {
+ use super::super::{normalize_id, unique_id_from_content};
+
+ #[test]
+ fn it_normalizes_ids() {
+ assert_eq!(
+ normalize_id("`--passes`: add more rustdoc passes"),
+ "--passes-add-more-rustdoc-passes"
+ );
+ assert_eq!(
+ normalize_id("Method-call 🐙 expressions \u{1f47c}"),
+ "method-call--expressions-"
+ );
+ assert_eq!(normalize_id("_-_12345"), "_-_12345");
+ assert_eq!(normalize_id("12345"), "12345");
+ assert_eq!(normalize_id("中文"), "中文");
+ assert_eq!(normalize_id("にほんご"), "にほんご");
+ assert_eq!(normalize_id("한국어"), "한국어");
+ assert_eq!(normalize_id(""), "");
+ }
+
+ #[test]
+ fn it_generates_unique_ids_from_content() {
+ // Same id if not given shared state
+ assert_eq!(
+ unique_id_from_content("## 中文標題 CJK title", &mut Default::default()),
+ "中文標題-cjk-title"
+ );
+ assert_eq!(
+ unique_id_from_content("## 中文標題 CJK title", &mut Default::default()),
+ "中文標題-cjk-title"
+ );
+
+ // Different id if given shared state
+ let mut id_counter = Default::default();
+ assert_eq!(unique_id_from_content("## Über", &mut id_counter), "Über");
+ assert_eq!(
+ unique_id_from_content("## 中文標題 CJK title", &mut id_counter),
+ "中文標題-cjk-title"
+ );
+ assert_eq!(unique_id_from_content("## Über", &mut id_counter), "Über-1");
+ assert_eq!(unique_id_from_content("## Über", &mut id_counter), "Über-2");
+ }
+ }
+
+ #[test]
+ fn escaped_brackets() {
+ assert_eq!(bracket_escape(""), "");
+ assert_eq!(bracket_escape("<"), "&lt;");
+ assert_eq!(bracket_escape(">"), "&gt;");
+ assert_eq!(bracket_escape("<>"), "&lt;&gt;");
+ assert_eq!(bracket_escape("<test>"), "&lt;test&gt;");
+ assert_eq!(bracket_escape("a<test>b"), "a&lt;test&gt;b");
+ }
+}
diff --git a/vendor/mdbook/src/utils/string.rs b/vendor/mdbook/src/utils/string.rs
new file mode 100644
index 000000000..97485d7b6
--- /dev/null
+++ b/vendor/mdbook/src/utils/string.rs
@@ -0,0 +1,255 @@
+use regex::Regex;
+use std::ops::Bound::{Excluded, Included, Unbounded};
+use std::ops::RangeBounds;
+
+/// Take a range of lines from a string.
+pub fn take_lines<R: RangeBounds<usize>>(s: &str, range: R) -> String {
+ let start = match range.start_bound() {
+ Excluded(&n) => n + 1,
+ Included(&n) => n,
+ Unbounded => 0,
+ };
+ let lines = s.lines().skip(start);
+ match range.end_bound() {
+ Excluded(end) => lines
+ .take(end.saturating_sub(start))
+ .collect::<Vec<_>>()
+ .join("\n"),
+ Included(end) => lines
+ .take((end + 1).saturating_sub(start))
+ .collect::<Vec<_>>()
+ .join("\n"),
+ Unbounded => lines.collect::<Vec<_>>().join("\n"),
+ }
+}
+
+lazy_static! {
+ static ref ANCHOR_START: Regex = Regex::new(r"ANCHOR:\s*(?P<anchor_name>[\w_-]+)").unwrap();
+ static ref ANCHOR_END: Regex = Regex::new(r"ANCHOR_END:\s*(?P<anchor_name>[\w_-]+)").unwrap();
+}
+
+/// Take anchored lines from a string.
+/// Lines containing anchor are ignored.
+pub fn take_anchored_lines(s: &str, anchor: &str) -> String {
+ let mut retained = Vec::<&str>::new();
+ let mut anchor_found = false;
+
+ for l in s.lines() {
+ if anchor_found {
+ match ANCHOR_END.captures(l) {
+ Some(cap) => {
+ if &cap["anchor_name"] == anchor {
+ break;
+ }
+ }
+ None => {
+ if !ANCHOR_START.is_match(l) {
+ retained.push(l);
+ }
+ }
+ }
+ } else if let Some(cap) = ANCHOR_START.captures(l) {
+ if &cap["anchor_name"] == anchor {
+ anchor_found = true;
+ }
+ }
+ }
+
+ retained.join("\n")
+}
+
+/// Keep lines contained within the range specified as-is.
+/// For any lines not in the range, include them but use `#` at the beginning. This will hide the
+/// lines from initial display but include them when expanding the code snippet or testing with
+/// rustdoc.
+pub fn take_rustdoc_include_lines<R: RangeBounds<usize>>(s: &str, range: R) -> String {
+ let mut output = String::with_capacity(s.len());
+
+ for (index, line) in s.lines().enumerate() {
+ if !range.contains(&index) {
+ output.push_str("# ");
+ }
+ output.push_str(line);
+ output.push('\n');
+ }
+ output.pop();
+ output
+}
+
+/// Keep lines between the anchor comments specified as-is.
+/// For any lines not between the anchors, include them but use `#` at the beginning. This will
+/// hide the lines from initial display but include them when expanding the code snippet or testing
+/// with rustdoc.
+pub fn take_rustdoc_include_anchored_lines(s: &str, anchor: &str) -> String {
+ let mut output = String::with_capacity(s.len());
+ let mut within_anchored_section = false;
+
+ for l in s.lines() {
+ if within_anchored_section {
+ match ANCHOR_END.captures(l) {
+ Some(cap) => {
+ if &cap["anchor_name"] == anchor {
+ within_anchored_section = false;
+ }
+ }
+ None => {
+ if !ANCHOR_START.is_match(l) {
+ output.push_str(l);
+ output.push('\n');
+ }
+ }
+ }
+ } else if let Some(cap) = ANCHOR_START.captures(l) {
+ if &cap["anchor_name"] == anchor {
+ within_anchored_section = true;
+ }
+ } else if !ANCHOR_END.is_match(l) {
+ output.push_str("# ");
+ output.push_str(l);
+ output.push('\n');
+ }
+ }
+
+ output.pop();
+ output
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{
+ take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines,
+ take_rustdoc_include_lines,
+ };
+
+ #[test]
+ #[allow(clippy::reversed_empty_ranges)] // Intentionally checking that those are correctly handled
+ fn take_lines_test() {
+ let s = "Lorem\nipsum\ndolor\nsit\namet";
+ assert_eq!(take_lines(s, 1..3), "ipsum\ndolor");
+ assert_eq!(take_lines(s, 3..), "sit\namet");
+ assert_eq!(take_lines(s, ..3), "Lorem\nipsum\ndolor");
+ assert_eq!(take_lines(s, ..), s);
+ // corner cases
+ assert_eq!(take_lines(s, 4..3), "");
+ assert_eq!(take_lines(s, ..100), s);
+ }
+
+ #[test]
+ fn take_anchored_lines_test() {
+ let s = "Lorem\nipsum\ndolor\nsit\namet";
+ assert_eq!(take_anchored_lines(s, "test"), "");
+
+ let s = "Lorem\nipsum\ndolor\nANCHOR_END: test\nsit\namet";
+ assert_eq!(take_anchored_lines(s, "test"), "");
+
+ let s = "Lorem\nipsum\nANCHOR: test\ndolor\nsit\namet";
+ assert_eq!(take_anchored_lines(s, "test"), "dolor\nsit\namet");
+ assert_eq!(take_anchored_lines(s, "something"), "");
+
+ let s = "Lorem\nipsum\nANCHOR: test\ndolor\nsit\namet\nANCHOR_END: test\nlorem\nipsum";
+ assert_eq!(take_anchored_lines(s, "test"), "dolor\nsit\namet");
+ assert_eq!(take_anchored_lines(s, "something"), "");
+
+ let s = "Lorem\nANCHOR: test\nipsum\nANCHOR: test\ndolor\nsit\namet\nANCHOR_END: test\nlorem\nipsum";
+ assert_eq!(take_anchored_lines(s, "test"), "ipsum\ndolor\nsit\namet");
+ assert_eq!(take_anchored_lines(s, "something"), "");
+
+ let s = "Lorem\nANCHOR: test2\nipsum\nANCHOR: test\ndolor\nsit\namet\nANCHOR_END: test\nlorem\nANCHOR_END:test2\nipsum";
+ assert_eq!(
+ take_anchored_lines(s, "test2"),
+ "ipsum\ndolor\nsit\namet\nlorem"
+ );
+ assert_eq!(take_anchored_lines(s, "test"), "dolor\nsit\namet");
+ assert_eq!(take_anchored_lines(s, "something"), "");
+ }
+
+ #[test]
+ #[allow(clippy::reversed_empty_ranges)] // Intentionally checking that those are correctly handled
+ fn take_rustdoc_include_lines_test() {
+ let s = "Lorem\nipsum\ndolor\nsit\namet";
+ assert_eq!(
+ take_rustdoc_include_lines(s, 1..3),
+ "# Lorem\nipsum\ndolor\n# sit\n# amet"
+ );
+ assert_eq!(
+ take_rustdoc_include_lines(s, 3..),
+ "# Lorem\n# ipsum\n# dolor\nsit\namet"
+ );
+ assert_eq!(
+ take_rustdoc_include_lines(s, ..3),
+ "Lorem\nipsum\ndolor\n# sit\n# amet"
+ );
+ assert_eq!(take_rustdoc_include_lines(s, ..), s);
+ // corner cases
+ assert_eq!(
+ take_rustdoc_include_lines(s, 4..3),
+ "# Lorem\n# ipsum\n# dolor\n# sit\n# amet"
+ );
+ assert_eq!(take_rustdoc_include_lines(s, ..100), s);
+ }
+
+ #[test]
+ fn take_rustdoc_include_anchored_lines_test() {
+ let s = "Lorem\nipsum\ndolor\nsit\namet";
+ assert_eq!(
+ take_rustdoc_include_anchored_lines(s, "test"),
+ "# Lorem\n# ipsum\n# dolor\n# sit\n# amet"
+ );
+
+ let s = "Lorem\nipsum\ndolor\nANCHOR_END: test\nsit\namet";
+ assert_eq!(
+ take_rustdoc_include_anchored_lines(s, "test"),
+ "# Lorem\n# ipsum\n# dolor\n# sit\n# amet"
+ );
+
+ let s = "Lorem\nipsum\nANCHOR: test\ndolor\nsit\namet";
+ assert_eq!(
+ take_rustdoc_include_anchored_lines(s, "test"),
+ "# Lorem\n# ipsum\ndolor\nsit\namet"
+ );
+ assert_eq!(
+ take_rustdoc_include_anchored_lines(s, "something"),
+ "# Lorem\n# ipsum\n# dolor\n# sit\n# amet"
+ );
+
+ let s = "Lorem\nipsum\nANCHOR: test\ndolor\nsit\namet\nANCHOR_END: test\nlorem\nipsum";
+ assert_eq!(
+ take_rustdoc_include_anchored_lines(s, "test"),
+ "# Lorem\n# ipsum\ndolor\nsit\namet\n# lorem\n# ipsum"
+ );
+ assert_eq!(
+ take_rustdoc_include_anchored_lines(s, "something"),
+ "# Lorem\n# ipsum\n# dolor\n# sit\n# amet\n# lorem\n# ipsum"
+ );
+
+ let s = "Lorem\nANCHOR: test\nipsum\nANCHOR: test\ndolor\nsit\namet\nANCHOR_END: test\nlorem\nipsum";
+ assert_eq!(
+ take_rustdoc_include_anchored_lines(s, "test"),
+ "# Lorem\nipsum\ndolor\nsit\namet\n# lorem\n# ipsum"
+ );
+ assert_eq!(
+ take_rustdoc_include_anchored_lines(s, "something"),
+ "# Lorem\n# ipsum\n# dolor\n# sit\n# amet\n# lorem\n# ipsum"
+ );
+
+ let s = "Lorem\nANCHOR: test2\nipsum\nANCHOR: test\ndolor\nsit\namet\nANCHOR_END: test\nlorem\nANCHOR_END:test2\nipsum";
+ assert_eq!(
+ take_rustdoc_include_anchored_lines(s, "test2"),
+ "# Lorem\nipsum\ndolor\nsit\namet\nlorem\n# ipsum"
+ );
+ assert_eq!(
+ take_rustdoc_include_anchored_lines(s, "test"),
+ "# Lorem\n# ipsum\ndolor\nsit\namet\n# lorem\n# ipsum"
+ );
+ assert_eq!(
+ take_rustdoc_include_anchored_lines(s, "something"),
+ "# Lorem\n# ipsum\n# dolor\n# sit\n# amet\n# lorem\n# ipsum"
+ );
+
+ let s = "Lorem\nANCHOR: test\nipsum\nANCHOR_END: test\ndolor\nANCHOR: test\nsit\nANCHOR_END: test\namet";
+ assert_eq!(
+ take_rustdoc_include_anchored_lines(s, "test"),
+ "# Lorem\nipsum\n# dolor\nsit\n# amet"
+ );
+ }
+}
diff --git a/vendor/mdbook/src/utils/toml_ext.rs b/vendor/mdbook/src/utils/toml_ext.rs
new file mode 100644
index 000000000..bf25ad11b
--- /dev/null
+++ b/vendor/mdbook/src/utils/toml_ext.rs
@@ -0,0 +1,130 @@
+use toml::value::{Table, Value};
+
+pub(crate) trait TomlExt {
+ fn read(&self, key: &str) -> Option<&Value>;
+ fn read_mut(&mut self, key: &str) -> Option<&mut Value>;
+ fn insert(&mut self, key: &str, value: Value);
+ fn delete(&mut self, key: &str) -> Option<Value>;
+}
+
+impl TomlExt for Value {
+ fn read(&self, key: &str) -> Option<&Value> {
+ if let Some((head, tail)) = split(key) {
+ self.get(head)?.read(tail)
+ } else {
+ self.get(key)
+ }
+ }
+
+ fn read_mut(&mut self, key: &str) -> Option<&mut Value> {
+ if let Some((head, tail)) = split(key) {
+ self.get_mut(head)?.read_mut(tail)
+ } else {
+ self.get_mut(key)
+ }
+ }
+
+ fn insert(&mut self, key: &str, value: Value) {
+ if !self.is_table() {
+ *self = Value::Table(Table::new());
+ }
+
+ let table = self.as_table_mut().expect("unreachable");
+
+ if let Some((head, tail)) = split(key) {
+ table
+ .entry(head)
+ .or_insert_with(|| Value::Table(Table::new()))
+ .insert(tail, value);
+ } else {
+ table.insert(key.to_string(), value);
+ }
+ }
+
+ fn delete(&mut self, key: &str) -> Option<Value> {
+ if let Some((head, tail)) = split(key) {
+ self.get_mut(head)?.delete(tail)
+ } else if let Some(table) = self.as_table_mut() {
+ table.remove(key)
+ } else {
+ None
+ }
+ }
+}
+
+fn split(key: &str) -> Option<(&str, &str)> {
+ let ix = key.find('.')?;
+
+ let (head, tail) = key.split_at(ix);
+ // splitting will leave the "."
+ let tail = &tail[1..];
+
+ Some((head, tail))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::str::FromStr;
+
+ #[test]
+ fn read_simple_table() {
+ let src = "[table]";
+ let value = Value::from_str(src).unwrap();
+
+ let got = value.read("table").unwrap();
+
+ assert!(got.is_table());
+ }
+
+ #[test]
+ fn read_nested_item() {
+ let src = "[table]\nnested=true";
+ let value = Value::from_str(src).unwrap();
+
+ let got = value.read("table.nested").unwrap();
+
+ assert_eq!(got, &Value::Boolean(true));
+ }
+
+ #[test]
+ fn insert_item_at_top_level() {
+ let mut value = Value::Table(Table::default());
+ let item = Value::Boolean(true);
+
+ value.insert("first", item.clone());
+
+ assert_eq!(value.get("first").unwrap(), &item);
+ }
+
+ #[test]
+ fn insert_nested_item() {
+ let mut value = Value::Table(Table::default());
+ let item = Value::Boolean(true);
+
+ value.insert("first.second", item.clone());
+
+ let inserted = value.read("first.second").unwrap();
+ assert_eq!(inserted, &item);
+ }
+
+ #[test]
+ fn delete_a_top_level_item() {
+ let src = "top = true";
+ let mut value = Value::from_str(src).unwrap();
+
+ let got = value.delete("top").unwrap();
+
+ assert_eq!(got, Value::Boolean(true));
+ }
+
+ #[test]
+ fn delete_a_nested_item() {
+ let src = "[table]\n nested = true";
+ let mut value = Value::from_str(src).unwrap();
+
+ let got = value.delete("table.nested").unwrap();
+
+ assert_eq!(got, Value::Boolean(true));
+ }
+}
diff --git a/vendor/mdbook/test_book/book.toml b/vendor/mdbook/test_book/book.toml
new file mode 100644
index 000000000..a30500763
--- /dev/null
+++ b/vendor/mdbook/test_book/book.toml
@@ -0,0 +1,27 @@
+[book]
+title = "mdBook test book"
+description = "A demo book to test and validate changes"
+authors = ["YJDoc2"]
+language = "en"
+
+[rust]
+edition = "2018"
+
+[output.html]
+mathjax-support = true
+
+[output.html.playground]
+editable = true
+line-numbers = true
+
+[output.html.search]
+limit-results = 20
+use-boolean-and = true
+boost-title = 2
+boost-hierarchy = 2
+boost-paragraph = 1
+expand = true
+heading-split-level = 2
+
+[output.html.redirect]
+"/format/config.html" = "configuration/index.html"
diff --git a/vendor/mdbook/test_book/src/README.md b/vendor/mdbook/test_book/src/README.md
new file mode 100644
index 000000000..04d5afdc9
--- /dev/null
+++ b/vendor/mdbook/test_book/src/README.md
@@ -0,0 +1,12 @@
+# Demo Book
+
+This is a simple demo book, which is intended to be used for verifying and validating style changes in mdBook.
+This contains dummy examples of various markdown elements and code languages, so that one can check changes made in mdBook styles.
+
+This rough outline is :
+
+- individual : contains basic markdown elements such as headings, paragraphs, links etc.
+- languages : contains a `hello world` in each of supported language to see changes in syntax highlighting
+- rust : contains language examples specific to rust, such as play pen, runnable examples etc.
+
+This is more for checking and fixing style, rather than verifying that correct code is generated for given markdown, that is better handled in tests.
diff --git a/vendor/mdbook/test_book/src/SUMMARY.md b/vendor/mdbook/test_book/src/SUMMARY.md
new file mode 100644
index 000000000..6292fd22d
--- /dev/null
+++ b/vendor/mdbook/test_book/src/SUMMARY.md
@@ -0,0 +1,33 @@
+# Summary
+
+[Prefix Chapter](prefix.md)
+
+---
+
+- [Introduction](README.md)
+- [Draft Chapter]()
+
+# Actual Markdown Tag Examples
+
+- [Markdown Individual tags](individual/README.md)
+ - [Heading](individual/heading.md)
+ - [Paragraphs](individual/paragraph.md)
+ - [Line Break](individual/linebreak.md)
+ - [Emphasis](individual/emphasis.md)
+ - [Blockquote](individual/blockquote.md)
+ - [List](individual/list.md)
+ - [Code](individual/code.md)
+ - [Image](individual/image.md)
+ - [Links and Horizontal Rule](individual/link_hr.md)
+ - [Tables](individual/table.md)
+ - [Tasks](individual/task.md)
+ - [Strikethrough](individual/strikethrough.md)
+ - [Mixed](individual/mixed.md)
+- [Languages](languages/README.md)
+ - [Syntax Highlight](languages/highlight.md)
+- [Rust Specific](rust/README.md)
+ - [Rust Codeblocks](rust/rust_codeblock.md)
+
+---
+
+[Suffix Chapter](suffix.md)
diff --git a/vendor/mdbook/test_book/src/individual/README.md b/vendor/mdbook/test_book/src/individual/README.md
new file mode 100644
index 000000000..67a1e9dcd
--- /dev/null
+++ b/vendor/mdbook/test_book/src/individual/README.md
@@ -0,0 +1,17 @@
+# Individual Common mark tags
+
+This contains following tags:
+
+- Headings
+- Paragraphs
+- Line breaks
+- Emphasis
+- Blockquotes
+- Lists
+- Code blocks
+- Images
+- Links and Horizontal rules
+- Github tables
+- Github Task Lists
+- Strikethrough
+- Mixed
diff --git a/vendor/mdbook/test_book/src/individual/blockquote.md b/vendor/mdbook/test_book/src/individual/blockquote.md
new file mode 100644
index 000000000..9f8be54a4
--- /dev/null
+++ b/vendor/mdbook/test_book/src/individual/blockquote.md
@@ -0,0 +1,30 @@
+# Blockquote
+
+> This is a quoted sentence.
+
+> This is a quoted paragraph
+>
+> separated lines
+> here
+
+> Nested
+>
+> > Quoted
+> > Paragraph
+
+> ### And now,
+>
+> **Let us _introduce_**
+> All kinds of
+>
+> - tags
+> - etc
+> - stuff
+>
+> 1. In
+> 2. The
+> 3. blockquote
+>
+> > cause we can
+> >
+> > > Cause we can
diff --git a/vendor/mdbook/test_book/src/individual/code.md b/vendor/mdbook/test_book/src/individual/code.md
new file mode 100644
index 000000000..26f2d564c
--- /dev/null
+++ b/vendor/mdbook/test_book/src/individual/code.md
@@ -0,0 +1,31 @@
+# Code
+
+This section only does simple code blocks and inline code, detailed syntax highlight and stuff is in the languages section
+
+---
+
+```
+This is a codeblock
+```
+
+---
+
+This line contains `inline code`
+
+---
+
+````
+escaping ``` in ```, fun, isn't is?
+````
+
+---
+
+```bash,editable
+This is an editable codeblock
+```
+
+---
+
+```rust
+// This links to a playpen
+```
diff --git a/vendor/mdbook/test_book/src/individual/emphasis.md b/vendor/mdbook/test_book/src/individual/emphasis.md
new file mode 100644
index 000000000..deac5307b
--- /dev/null
+++ b/vendor/mdbook/test_book/src/individual/emphasis.md
@@ -0,0 +1,13 @@
+# Emphasis
+
+This has **bold text** in between normal.
+
+This has _italic text_ in between normal.
+
+A **line** having _both_, bold and italic text.
+
+**A bold line _having_ italic text**
+
+_An Italic line having **bold** text_
+
+Now this is going **_out of hands_**.
diff --git a/vendor/mdbook/test_book/src/individual/heading.md b/vendor/mdbook/test_book/src/individual/heading.md
new file mode 100644
index 000000000..df96d74c3
--- /dev/null
+++ b/vendor/mdbook/test_book/src/individual/heading.md
@@ -0,0 +1,15 @@
+# Chapter Heading
+
+---
+
+# Really Big Heading
+
+## Big Heading
+
+### Normal-ish Heading
+
+#### Small Heading...?
+
+##### Really Small Heading
+
+###### Is it even a heading anymore - heading
diff --git a/vendor/mdbook/test_book/src/individual/image.md b/vendor/mdbook/test_book/src/individual/image.md
new file mode 100644
index 000000000..c73db95ac
--- /dev/null
+++ b/vendor/mdbook/test_book/src/individual/image.md
@@ -0,0 +1,27 @@
+# Images
+
+For copyright and trademark information on these images, please check [rust-artwork repository](https://github.com/rust-lang/rust-artworkhttps://github.com/rust-lang/rust-artwork)
+
+## A 16x16 image
+
+![16x16 rust-lang logo](http://rust-lang.org/logos/rust-logo-16x16.png)
+
+## A 32x32 image
+
+![32x32 rust-lang logo](http://rust-lang.org/logos/rust-logo-32x32-blk.png)
+
+## A 256x256 image
+
+![256x256 rust-lang logo](http://rust-lang.org/logos/rust-logo-256x256.png)
+
+## A 512x512 image
+
+![512x512 rust-lang logo](http://rust-lang.org/logos/rust-logo-512x512-blk.png)
+
+## A large image
+
+![2018 rust-conf art](https://raw.githubusercontent.com/rust-lang/rust-artwork/master/2018-RustConf/lucy-mountain-climber.png)
+
+## A SVG image
+
+![2018 rust-conf art svg](https://raw.githubusercontent.com/rust-lang/rust-artwork/461afe27d8e02451cf9f46e507f2c2a71d2b276b/2018-RustConf/lucy-mountain-climber.svg)
diff --git a/vendor/mdbook/test_book/src/individual/linebreak.md b/vendor/mdbook/test_book/src/individual/linebreak.md
new file mode 100644
index 000000000..4d3535942
--- /dev/null
+++ b/vendor/mdbook/test_book/src/individual/linebreak.md
@@ -0,0 +1,8 @@
+# Line breaks
+
+This is a long
+line with a couple of
+line breaks in <br/>
+between : both with two
+spaces and return, <br/>
+and with HTML tags.
diff --git a/vendor/mdbook/test_book/src/individual/link_hr.md b/vendor/mdbook/test_book/src/individual/link_hr.md
new file mode 100644
index 000000000..cc939a267
--- /dev/null
+++ b/vendor/mdbook/test_book/src/individual/link_hr.md
@@ -0,0 +1,15 @@
+# Links and Horizontal Rule
+
+This is followed by a Horizontal rule
+
+---
+
+And this is preceded by a horizontal rule.
+
+[This](www.rust-lang.org) should link to rust-lang website
+[So should this][rl].
+**[This][rl]** is a strong link.
+_[This][rl]_ is italic.
+**_[This][rl]_** is both.
+
+[rl]: www.rust-lang.org
diff --git a/vendor/mdbook/test_book/src/individual/list.md b/vendor/mdbook/test_book/src/individual/list.md
new file mode 100644
index 000000000..a1431f942
--- /dev/null
+++ b/vendor/mdbook/test_book/src/individual/list.md
@@ -0,0 +1,35 @@
+# Lists
+
+1. A
+2. Normal
+3. Ordered
+4. List
+
+---
+
+1. A
+ 1. Nested
+ 2. List
+2. But
+3. Still
+4. Normal
+
+---
+
+- An
+- Unordered
+- Normal
+- List
+
+---
+
+- Nested
+ - Unordered
+- List
+
+---
+
+- This
+ 1. Is
+ 2. Normal
+- ?!
diff --git a/vendor/mdbook/test_book/src/individual/mixed.md b/vendor/mdbook/test_book/src/individual/mixed.md
new file mode 100644
index 000000000..d7c502b1a
--- /dev/null
+++ b/vendor/mdbook/test_book/src/individual/mixed.md
@@ -0,0 +1,61 @@
+# Mixed
+
+This contains all tags randomly mixed together, to make sure style changes in one does not affect others.
+
+### A heading
+
+**Quite a Strong statement , to make**
+
+~~No, cross that~~
+
+> Whose **quote** is this
+>
+> > And ~~this~~
+> >
+> > > - and
+> > > - this
+> > > - also
+
+```
+You encountered a wild codepen
+```
+
+```rust,editable
+// The codepen is editable and runnable
+fn main(){
+ println!("Hello world!");
+}
+```
+
+A random image sprinkled in between
+
+![16x16 rust-lang logo](http://rust-lang.org/logos/rust-logo-16x16.png)
+
+---
+
+- ~~An unordered list~~
+- **Hello**
+- _World_
+- What
+ 1. Should
+ 2. be
+ 3. `put`
+ 4. here?
+
+| col1 | col2 | col 3 | col 4 | col 5 | col 6 |
+| ---- | ---- | ----- | ----- | ----- | ----- |
+| val1 | val2 | val3 | val5 | val4 | val6 |
+
+| col1 | col2 | col 3 | An Questionable table header | col 5 | col 6 |
+| ---- | ---- | ----- | ---------------------------- | ----- | ---------------------------------------- |
+| val1 | val2 | val3 | val5 | val4 | An equally Questionable long table value |
+
+### Things to do
+
+- [x] Add individual tags
+- [ ] Add language examples
+- [ ] Add rust specific examples
+
+And another image
+
+![2018 rust-conf art svg](https://raw.githubusercontent.com/rust-lang/rust-artwork/461afe27d8e02451cf9f46e507f2c2a71d2b276b/2018-RustConf/lucy-mountain-climber.svg)
diff --git a/vendor/mdbook/test_book/src/individual/paragraph.md b/vendor/mdbook/test_book/src/individual/paragraph.md
new file mode 100644
index 000000000..032dd6eac
--- /dev/null
+++ b/vendor/mdbook/test_book/src/individual/paragraph.md
@@ -0,0 +1,25 @@
+Just a simple paragraph.
+
+Let's stress test this.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer elit lorem, eleifend eu leo sit amet, suscipit feugiat libero. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Proin congue lectus sit amet lacus venenatis, ac sollicitudin purus condimentum. Suspendisse pretium volutpat sapien at gravida. In tincidunt, sem non accumsan consectetur, leo libero porttitor dolor, at imperdiet erat nibh quis leo. Cras dictum erat augue, quis pharetra justo porttitor posuere. Aenean sed lacinia justo, vel suscipit nisl. Etiam eleifend id mauris at gravida. Aliquam molestie cursus lorem pulvinar sollicitudin. Nam et ex dignissim, posuere sem non, pellentesque lacus. Morbi vulputate sed lorem et convallis. Duis non turpis eget elit posuere volutpat. Donec accumsan euismod enim, id consequat ex rhoncus ac. Pellentesque ac felis nisl. Duis imperdiet vel tellus ac iaculis.
+
+Vivamus nec tempus enim. Integer in ligula eget elit ornare vulputate id et est. Proin mi elit, sagittis nec urna et, iaculis imperdiet neque. Vestibulum placerat cursus dolor. Donec eu sodales nulla. Praesent ac tellus eros. Donec venenatis ligula id ex porttitor malesuada. Aliquam maximus, nisi in fringilla finibus, ante elit rhoncus dui, placerat semper nisl tellus quis odio. Cras luctus magna ultrices dolor pharetra volutpat. Maecenas non enim vitae ligula efficitur aliquet id quis quam. In sagittis mollis magna eu porta. Morbi at nulla et ante elementum pharetra in sed est. Nam commodo purus enim.
+
+Ut non elit sit amet urna luctus facilisis vel et sapien. Morbi nec metus at libero imperdiet sollicitudin eget quis lacus. Donec in ipsum at enim accumsan tempor vel sed magna. Aliquam non imperdiet neque. Etiam pharetra neque sed pretium interdum. Suspendisse potenti. Phasellus varius, lectus quis dapibus faucibus, purus mauris accumsan nibh, vel tempor quam metus nec sem. Nunc sagittis suscipit lorem eu finibus. Nullam augue leo, imperdiet vel diam et, vulputate scelerisque turpis. Nullam ut volutpat diam. Praesent cursus accumsan dui a commodo. Vivamus sed libero sed turpis facilisis rutrum id sed ligula. Ut id sollicitudin dui. Nulla pulvinar commodo lectus. Cras ut quam congue, consectetur dolor ac, consequat ante.
+
+Curabitur scelerisque sed leo eu facilisis. Nam faucibus neque eget dictum hendrerit. Duis efficitur ex sed vulputate volutpat. Praesent condimentum nisl ac sapien efficitur laoreet. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut ut nibh elit. Nunc a neque lobortis, tempus diam vitae, interdum magna. Aenean eget nisl sed justo volutpat interdum. Mauris malesuada ex nisl, a dignissim dui elementum eget. Suspendisse potenti.
+
+Praesent congue fringilla sem sed faucibus. Vivamus malesuada eget mauris at molestie. In sed faucibus nulla. Vivamus elementum accumsan metus quis suscipit. Maecenas interdum est nulla. Cras volutpat cursus nibh quis sollicitudin. Morbi vitae massa laoreet, aliquet tellus quis, consectetur ipsum. Mauris euismod congue purus non condimentum. Etiam laoreet mi vel sem consectetur gravida. Vestibulum volutpat magna nunc, vitae ultrices risus commodo eu.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer elit lorem, eleifend eu leo sit amet, suscipit feugiat libero. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Proin congue lectus sit amet lacus venenatis, ac sollicitudin purus condimentum. Suspendisse pretium volutpat sapien at gravida. In tincidunt, sem non accumsan consectetur, leo libero porttitor dolor, at imperdiet erat nibh quis leo. Cras dictum erat augue, quis pharetra justo porttitor posuere. Aenean sed lacinia justo, vel suscipit nisl. Etiam eleifend id mauris at gravida. Aliquam molestie cursus lorem pulvinar sollicitudin. Nam et ex dignissim, posuere sem non, pellentesque lacus. Morbi vulputate sed lorem et convallis. Duis non turpis eget elit posuere volutpat. Donec accumsan euismod enim, id consequat ex rhoncus ac. Pellentesque ac felis nisl. Duis imperdiet vel tellus ac iaculis.
+
+Vivamus nec tempus enim. Integer in ligula eget elit ornare vulputate id et est. Proin mi elit, sagittis nec urna et, iaculis imperdiet neque. Vestibulum placerat cursus dolor. Donec eu sodales nulla. Praesent ac tellus eros. Donec venenatis ligula id ex porttitor malesuada. Aliquam maximus, nisi in fringilla finibus, ante elit rhoncus dui, placerat semper nisl tellus quis odio. Cras luctus magna ultrices dolor pharetra volutpat. Maecenas non enim vitae ligula efficitur aliquet id quis quam. In sagittis mollis magna eu porta. Morbi at nulla et ante elementum pharetra in sed est. Nam commodo purus enim.
+
+Ut non elit sit amet urna luctus facilisis vel et sapien. Morbi nec metus at libero imperdiet sollicitudin eget quis lacus. Donec in ipsum at enim accumsan tempor vel sed magna. Aliquam non imperdiet neque. Etiam pharetra neque sed pretium interdum. Suspendisse potenti. Phasellus varius, lectus quis dapibus faucibus, purus mauris accumsan nibh, vel tempor quam metus nec sem. Nunc sagittis suscipit lorem eu finibus. Nullam augue leo, imperdiet vel diam et, vulputate scelerisque turpis. Nullam ut volutpat diam. Praesent cursus accumsan dui a commodo. Vivamus sed libero sed turpis facilisis rutrum id sed ligula. Ut id sollicitudin dui. Nulla pulvinar commodo lectus. Cras ut quam congue, consectetur dolor ac, consequat ante.
+
+Curabitur scelerisque sed leo eu facilisis. Nam faucibus neque eget dictum hendrerit. Duis efficitur ex sed vulputate volutpat. Praesent condimentum nisl ac sapien efficitur laoreet. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut ut nibh elit. Nunc a neque lobortis, tempus diam vitae, interdum magna. Aenean eget nisl sed justo volutpat interdum. Mauris malesuada ex nisl, a dignissim dui elementum eget. Suspendisse potenti.
+
+Praesent congue fringilla sem sed faucibus. Vivamus malesuada eget mauris at molestie. In sed faucibus nulla. Vivamus elementum accumsan metus quis suscipit. Maecenas interdum est nulla. Cras volutpat cursus nibh quis sollicitudin. Morbi vitae massa laoreet, aliquet tellus quis, consectetur ipsum. Mauris euismod congue purus non condimentum. Etiam laoreet mi vel sem consectetur gravida. Vestibulum volutpat magna nunc, vitae ultrices risus commodo eu.
+
+Hopefully everything above was rendered nicely, on both desktop and mobile.
diff --git a/vendor/mdbook/test_book/src/individual/strikethrough.md b/vendor/mdbook/test_book/src/individual/strikethrough.md
new file mode 100644
index 000000000..4ce62cbdf
--- /dev/null
+++ b/vendor/mdbook/test_book/src/individual/strikethrough.md
@@ -0,0 +1,5 @@
+# Strikethrough
+
+~~This is Striked~~
+
+~~This is **strong**, _italic_ , **_both_** and striked~~
diff --git a/vendor/mdbook/test_book/src/individual/table.md b/vendor/mdbook/test_book/src/individual/table.md
new file mode 100644
index 000000000..43580012f
--- /dev/null
+++ b/vendor/mdbook/test_book/src/individual/table.md
@@ -0,0 +1,28 @@
+# Tables
+
+| col1 | col2 |
+| ---- | ---- |
+
+---
+
+| col1 | col2 |
+| ---- | ---- |
+| val1 | val2 |
+
+---
+
+| col1 | col2 | col 3 | col 4 | col 5 | col 6 |
+| ---- | ---- | ----- | ----- | ----- | ----- |
+| val1 | val2 | val3 | val5 | val4 | val6 |
+| val1 | val2 | val3 | val5 | val4 | val6 |
+| val1 | val2 | val3 | val5 | val4 | val6 |
+| val1 | val2 | val3 | val5 | val4 | val6 |
+
+---
+
+| col1 | col2 | col 3 | col 4 | col 5 | col 6 |
+| -------------------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ----- | -------------------------------------------------------------------------------------------------------------- |
+| This is a simple demo book, which is intended to be used for verifying and validating style changes in mdBook. | val2 | val3 | val5 | val4 | val6 |
+| val1 | val2 | val3 | val5 | val4 | val6 |
+| val1 | val2 | val3 | val5 | val4 | This is a simple demo book, which is intended to be used for verifying and validating style changes in mdBook. |
+| val1 | val2 | This is a simple demo book, which is intended to be used for verifying and validating style changes in mdBook. | This is a simple demo book, which is intended to be used for verifying and validating style changes in mdBook. | val4 | val6 |
diff --git a/vendor/mdbook/test_book/src/individual/task.md b/vendor/mdbook/test_book/src/individual/task.md
new file mode 100644
index 000000000..84327723e
--- /dev/null
+++ b/vendor/mdbook/test_book/src/individual/task.md
@@ -0,0 +1,11 @@
+# Tasks
+
+- [ ] Task 1
+- [ ] Task 2
+- [x] Completed Task 1
+- [x] Completed Task 2
+
+---
+
+- [ ] **Important Task**
+- [x] _Completed Important task_
diff --git a/vendor/mdbook/test_book/src/languages/README.md b/vendor/mdbook/test_book/src/languages/README.md
new file mode 100644
index 000000000..fc1723358
--- /dev/null
+++ b/vendor/mdbook/test_book/src/languages/README.md
@@ -0,0 +1,47 @@
+# Syntax Highlighting
+
+This Currently contains following languages
+
+- apache
+- armasm
+- bash
+- c
+- coffeescript
+- cpp
+- csharp
+- css
+- d
+- diff
+- go
+- handlebars
+- haskell
+- http
+- ini
+- java
+- javascript
+- json
+- julia
+- kotlin
+- less
+- lua
+- makefile
+- markdown
+- nginx
+- objectivec
+- perl
+- php
+- plaintext
+- properties
+- python
+- r
+- ruby
+- rust
+- scala
+- scss
+- shell
+- sql
+- swift
+- typescript
+- x86asm
+- xml
+- yaml
diff --git a/vendor/mdbook/test_book/src/languages/highlight.md b/vendor/mdbook/test_book/src/languages/highlight.md
new file mode 100644
index 000000000..af0f34005
--- /dev/null
+++ b/vendor/mdbook/test_book/src/languages/highlight.md
@@ -0,0 +1,927 @@
+# Syntax Highlights
+
+## apache
+
+```apache
+# rewrite`s rules for wordpress pretty url
+LoadModule rewrite_module modules/mod_rewrite.so
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule . index.php [NC,L]
+
+ExpiresActive On
+ExpiresByType application/x-javascript "access plus 1 days"
+
+Order Deny,Allow
+Allow from All
+
+<Location /maps/>
+ RewriteMap map txt:map.txt
+ RewriteMap lower int:tolower
+ RewriteCond %{REQUEST_URI} ^/([^/.]+)\.html$ [NC]
+ RewriteCond ${map:${lower:%1}|NOT_FOUND} !NOT_FOUND
+ RewriteRule .? /index.php?q=${map:${lower:%1}} [NC,L]
+</Location>
+
+20.164.151.111 - - [20/Aug/2015:22:20:18 -0400] "GET /mywebpage/index.php HTTP/1.1" 403 772 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1"
+```
+
+## armasm
+
+```armasm
+.data
+
+/* Data segment: define our message string and calculate its length. */
+msg:
+ .ascii "Hello, ARM!\n"
+len = . - msg
+
+.text
+
+/* Our application's entry point. */
+.globl _start
+_start:
+ /* syscall write(int fd, const void *buf, size_t count) */
+ mov %r0, $1 /* fd := STDOUT_FILENO */
+ ldr %r1, =msg /* buf := msg */
+ ldr %r2, =len /* count := len */
+ mov %r7, $4 /* write is syscall #4 */
+ swi $0 /* invoke syscall */
+
+ /* syscall exit(int status) */
+ mov %r0, $0 /* status := 0 */
+ mov %r7, $1 /* exit is syscall #1 */
+ swi $0 /* invoke syscall */
+
+```
+
+## bash
+
+```
+#!/bin/bash
+
+###### CONFIG
+ACCEPTED_HOSTS="/root/.hag_accepted.conf"
+BE_VERBOSE=false
+
+if [ "$UID" -ne 0 ]
+then
+ echo "Superuser rights required"
+ exit 2
+fi
+
+genApacheConf(){
+ echo -e "# Host ${HOME_DIR}$1/$2 :"
+}
+
+echo '"quoted"' | tr -d \" > text.txt
+
+```
+
+## c
+
+```c
+#include <stdio.h>
+void main(int argc,char ** argv){
+ printf("Hello World!");
+}
+
+```
+
+## coffeescript
+
+```coffeescript
+grade = (student, period=(if b? then 7 else 6)) ->
+ if student.excellentWork
+ "A+"
+ else if student.okayStuff
+ if student.triedHard then "B" else "B-"
+ else
+ "C"
+
+class Animal extends Being
+ constructor: (@name) ->
+
+ move: (meters) ->
+ alert @name + " moved #{meters}m."
+```
+
+## cpp
+
+```cpp
+#include <iostream>
+using namespace std;
+int main() {
+ cout << "Hello, World!" << endl; // This prints Hello, World!
+ return 0;
+}
+```
+
+## csharp
+
+```csharp
+using System;
+class App
+{
+ static void Main()
+ {
+ Console.WriteLine("Hello World!");
+ }
+}
+```
+
+## css
+
+```css
+@font-face {
+ font-family: Chunkfive;
+ src: url('Chunkfive.otf');
+}
+
+body,
+.usertext {
+ color: #f0f0f0;
+ background: #600;
+ font-family: Chunkfive, sans;
+ --heading-1: 30px/32px Helvetica, sans-serif;
+}
+
+@import url(print.css);
+@media print {
+ a[href^='http']::after {
+ content: attr(href);
+ }
+}
+```
+
+## d
+
+```d
+/* This program prints a
+ hello world message
+ to the console. */
+
+import std.stdio;
+
+void main()
+{
+ writeln("Hello, World!");
+}
+```
+
+## diff
+
+```diff
+Index: languages/ini.js
+===================================================================
+--- languages/ini.js (revision 199)
++++ languages/ini.js (revision 200)
+@@ -1,8 +1,7 @@
+ hljs.LANGUAGES.ini =
+ {
+ case_insensitive: true,
+- defaultMode:
+- {
++ defaultMode: {
+ contains: ['comment', 'title', 'setting'],
+ illegal: '[^\\s]'
+ },
+
+*** /path/to/original timestamp
+--- /path/to/new timestamp
+***************
+*** 1,3 ****
+--- 1,9 ----
++ This is an important
++ notice! It should
++ therefore be located at
++ the beginning of this
++ document!
+
+! compress the size of the
+! changes.
+
+ It is important to spell
+```
+
+## go
+
+```go
+package main
+import "fmt"
+
+func main() {
+ fmt.Println("Hello World!")
+}
+```
+
+## handlebars
+
+```handlebars
+<div class='entry'>
+ {{! only show if author exists }}
+ {{#if author}}
+ <h1>{{firstName}} {{lastName}}</h1>
+ {{/if}}
+</div>
+```
+
+## haskell
+
+```haskell
+main :: IO ()
+main = putStrLn "Hello World!"
+
+```
+
+## http
+
+```http
+POST /task?id=1 HTTP/1.1
+Host: example.org
+Content-Type: application/json; charset=utf-8
+Content-Length: 137
+
+{
+ "status": "ok",
+ "extended": true,
+ "results": [
+ {"value": 0, "type": "int64"},
+ {"value": 1.0e+3, "type": "decimal"}
+ ]
+}
+
+```
+
+## ini
+
+```ini
+; boilerplate
+[package]
+name = "some_name"
+authors = ["Author"]
+description = "This is \
+a description"
+
+[[lib]]
+name = ${NAME}
+default = True
+auto = no
+counter = 1_000
+```
+
+## java
+
+```java
+class Main {
+ public static void main(String[] args) {
+ System.out.println("Hello World!");
+ }
+}
+```
+
+## javascript
+
+```javascript
+function $initHighlight(block, cls) {
+ try {
+ if (cls.search(/\bno\-highlight\b/) != -1)
+ return process(block, true, 0x0F) +
+ ` class="${cls}"`;
+ } catch (e) {
+ /* handle exception */
+ }
+ for (var i = 0 / 2; i < classes.length; i++) {
+ if (checkCondition(classes[i]) === undefined)
+ console.log('undefined');
+ }
+
+ return (
+ <div>
+ <web-component>{block}</web-component>
+ </div>
+ )
+}
+
+export $initHighlight;
+```
+
+## json
+
+```json
+[
+ {
+ "title": "apples",
+ "count": [12000, 20000],
+ "description": { "text": "...", "sensitive": false }
+ },
+ {
+ "title": "oranges",
+ "count": [17500, null],
+ "description": { "text": "...", "sensitive": false }
+ }
+]
+```
+
+## julia
+
+```julia
+# function to calculate the volume of a sphere
+function sphere_vol(r)
+ # julia allows Unicode names (in UTF-8 encoding)
+ # so either "pi" or the symbol π can be used
+ return 4/3*pi*r^3
+end
+
+# functions can also be defined more succinctly
+quadratic(a, sqr_term, b) = (-b + sqr_term) / 2a
+
+# calculates x for 0 = a*x^2+b*x+c, arguments types can be defined in function definitions
+function quadratic2(a::Float64, b::Float64, c::Float64)
+ # unlike other languages 2a is equivalent to 2*a
+ # a^2 is used instead of a**2 or pow(a,2)
+ sqr_term = sqrt(b^2-4a*c)
+ r1 = quadratic(a, sqr_term, b)
+ r2 = quadratic(a, -sqr_term, b)
+ # multiple values can be returned from a function using tuples
+ # if the return keyword is omitted, the last term is returned
+ r1, r2
+end
+
+vol = sphere_vol(3)
+```
+
+## kotlin
+
+```kotlin
+package org.kotlinlang.play
+
+fun main() {
+ println("Hello, World!")
+}
+```
+
+## less
+
+```less
+@import 'fruits';
+
+@rhythm: 1.5em;
+
+@media screen and (min-resolution: 2dppx) {
+ body {
+ font-size: 125%;
+ }
+}
+
+section > .foo + #bar:hover [href*='less'] {
+ margin: @rhythm 0 0 @rhythm;
+ padding: calc(5% + 20px);
+ background: #f00ba7 url(http://placehold.alpha-centauri/42.png) no-repeat;
+ background-image: linear-gradient(-135deg, wheat, fuchsia) !important ;
+ background-blend-mode: multiply;
+}
+
+@font-face {
+ font-family: /* ? */ 'Omega';
+ src: url('../fonts/omega-webfont.woff?v=2.0.2');
+}
+
+.icon-baz::before {
+ display: inline-block;
+ font-family: 'Omega', Alpha, sans-serif;
+ content: '\f085';
+ color: rgba(98, 76 /* or 54 */, 231, 0.75);
+}
+```
+
+## lua
+
+```lua
+--[[
+Simple signal/slot implementation
+]]
+local signal_mt = {
+ __index = {
+ register = table.insert
+ }
+}
+function signal_mt.__index:emit(... --[[ Comment in params ]])
+ for _, slot in ipairs(self) do
+ slot(self, ...)
+ end
+end
+local function create_signal()
+ return setmetatable({}, signal_mt)
+end
+
+-- Signal test
+local signal = create_signal()
+signal:register(function(signal, ...)
+ print(...)
+end)
+signal:emit('Answer to Life, the Universe, and Everything:', 42)
+
+--[==[ [=[ [[
+Nested ]]
+multi-line ]=]
+comment ]==]
+[==[ Nested
+[=[ multi-line
+[[ string
+]] ]=] ]==]
+```
+
+## makefile
+
+```makefile
+# Makefile
+
+BUILDDIR = _build
+EXTRAS ?= $(BUILDDIR)/extras
+
+.PHONY: main clean
+
+main:
+ @echo "Building main facility..."
+ build_main $(BUILDDIR)
+
+clean:
+ rm -rf $(BUILDDIR)/*
+
+```
+
+## markdown
+
+```markdown
+# hello world
+
+you can write text [with links](http://example.com) inline or [link references][1].
+
+- one _thing_ has *em*phasis
+- two **things** are **bold**
+
+[1]: http://example.com
+
+---
+
+# hello world
+
+<this_is inline="xml"></this_is>
+
+> markdown is so cool
+
+ so are code segments
+
+1. one thing (yeah!)
+2. two thing `i can write code`, and `more` wipee!
+```
+
+## nginx
+
+```nginx
+user www www;
+worker_processes 2;
+pid /var/run/nginx.pid;
+error_log /var/log/nginx.error_log debug | info | notice | warn | error | crit;
+
+events {
+ connections 2000;
+ use kqueue | rtsig | epoll | /dev/poll | select | poll;
+}
+
+http {
+ log_format main '$remote_addr - $remote_user [$time_local] '
+ '"$request" $status $bytes_sent '
+ '"$http_referer" "$http_user_agent" '
+ '"$gzip_ratio"';
+
+ send_timeout 3m;
+ client_header_buffer_size 1k;
+
+ gzip on;
+ gzip_min_length 1100;
+
+ #lingering_time 30;
+
+ server {
+ server_name one.example.com www.one.example.com;
+ access_log /var/log/nginx.access_log main;
+
+ rewrite (.*) /index.php?page=$1 break;
+
+ location / {
+ proxy_pass http://127.0.0.1/;
+ proxy_redirect off;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ charset koi8-r;
+ }
+
+ location /api/ {
+ fastcgi_pass 127.0.0.1:9000;
+ }
+
+ location ~* \.(jpg|jpeg|gif)$ {
+ root /spool/www;
+ }
+ }
+}
+```
+
+## objectivec
+
+```objectivec
+#import <Foundation/Foundation.h>
+
+int main(int argc, const char * argv[]) {
+ @mylak {
+ NSLog(@"Hello World!");
+ }
+ return 0;
+}
+
+```
+
+## perl
+
+```perl
+print "Hello World!\n";
+```
+
+## php
+
+```php
+<?php
+echo "Hello World!";
+?>
+```
+
+## plaintext
+
+```plaintext
+I think this is simply plain text?
+Hello World!
+```
+
+## properties
+
+```properties
+# .properties
+! Exclamation mark = comments, too
+
+key1 = value1
+key2 : value2
+key3 value3
+key\ spaces multiline\
+ value4
+empty_key
+! Key can contain escaped chars
+\:\= = value5
+```
+
+## python
+
+```python
+@requires_authorization(roles=["ADMIN"])
+def somefunc(param1='', param2=0):
+ r'''A docstring'''
+ if param1 > param2: # interesting
+ print 'Gre\'ater'
+ return (param2 - param1 + 1 + 0b10l) or None
+
+class SomeClass:
+ pass
+
+>>> message = '''interpreter
+... prompt'''
+```
+
+## r
+
+```r
+require(stats)
+
+#' Compute different averages
+#'
+#' @param x \code{numeric} vector of sample data
+#' @param type \code{character} vector of length 1 specifying the average type
+#' @return \code{centre} returns the sample average according to the chosen method.
+#' @examples
+#' centre(rcauchy(10), "mean")
+#' @export
+centre <- function(x, type) {
+ switch(type,
+ mean = mean(x),
+ median = median(x),
+ trimmed = mean(x, trim = .1))
+}
+x <- rcauchy(10)
+centre(x, "mean")
+
+library(ggplot2)
+
+models <- tibble::tribble(
+ ~model_name, ~ formula,
+ "length-width", Sepal.Length ~ Petal.Width + Petal.Length,
+ "interaction", Sepal.Length ~ Petal.Width * Petal.Length
+)
+
+iris %>%
+ nest_by(Species) %>%
+ left_join(models, by = character()) %>%
+ rowwise(Species, model_name) %>%
+ mutate(model = list(lm(formula, data = data))) %>%
+ summarise(broom::glance(model))
+```
+
+## ruby
+
+```ruby
+# The Greeter class
+class Greeter
+ def initialize(name)
+ @name = name.capitalize
+ end
+
+ def salute
+ puts "Hello #{@name}!"
+ end
+end
+
+g = Greeter.new("world")
+g.salute
+```
+
+## rust
+
+```rust
+fn main()->(){
+ println!("Hello World!");
+}
+```
+
+## scala
+
+```scala
+/**
+ * A person has a name and an age.
+ */
+case class Person(name: String, age: Int)
+
+abstract class Vertical extends CaseJeu
+case class Haut(a: Int) extends Vertical
+case class Bas(name: String, b: Double) extends Vertical
+
+sealed trait Ior[+A, +B]
+case class Left[A](a: A) extends Ior[A, Nothing]
+case class Right[B](b: B) extends Ior[Nothing, B]
+case class Both[A, B](a: A, b: B) extends Ior[A, B]
+
+trait Functor[F[_]] {
+ def map[A, B](fa: F[A], f: A => B): F[B]
+}
+
+// beware Int.MinValue
+def absoluteValue(n: Int): Int =
+ if (n < 0) -n else n
+
+def interp(n: Int): String =
+ s"there are $n ${color} balloons.\n"
+
+type ξ[A] = (A, A)
+
+trait Hist { lhs =>
+ def ⊕(rhs: Hist): Hist
+}
+
+def gsum[A: Ring](as: Seq[A]): A =
+ as.foldLeft(Ring[A].zero)(_ + _)
+
+val actions: List[Symbol] =
+ 'init :: 'read :: 'write :: 'close :: Nil
+```
+
+## scss
+
+```scss
+import "compass/reset";
+
+// variables
+$colorGreen: #008000;
+$colorGreenDark: darken($colorGreen, 10);
+
+@mixin container {
+ max-width: 980px;
+}
+
+// mixins with parameters
+@mixin button($color:green) {
+ @if ($color == green) {
+ background-color: #008000;
+ }
+ @else if ($color == red) {
+ background-color: #B22222;
+ }
+}
+
+button {
+ @include button(red);
+}
+
+div,
+.navbar,
+#header,
+input[type="input"] {
+ font-family: "Helvetica Neue", Arial, sans-serif;
+ width: auto;
+ margin: 0 auto;
+ display: block;
+}
+
+.row-12 > [class*="spans"] {
+ border-left: 1px solid #B5C583;
+}
+
+```
+
+## shell
+
+```shell
+$ echo $EDITOR
+vim
+$ git checkout main
+Switched to branch 'main'
+Your branch is up-to-date with 'origin/main'.
+$ git push
+Everything up-to-date
+$ echo 'All
+> done!'
+All
+done!
+
+```
+
+## sql
+
+```sql
+CREATE TABLE "topic" (
+ "id" integer NOT NULL PRIMARY KEY,
+ "forum_id" integer NOT NULL,
+ "subject" varchar(255) NOT NULL
+);
+ALTER TABLE "topic"
+ADD CONSTRAINT forum_id FOREIGN KEY ("forum_id")
+REFERENCES "forum" ("id");
+
+-- Initials
+insert into "topic" ("forum_id", "subject")
+values (2, 'D''artagnian');
+```
+
+## swift
+
+```swift
+import Foundation
+
+@objc class Person: Entity {
+ var name: String!
+ var age: Int!
+
+ init(name: String, age: Int) {
+ /* /* ... */ */
+ }
+
+ // Return a descriptive string for this person
+ func description(offset: Int = 0) -> String {
+ return "\(name) is \(age + offset) years old"
+ }
+}
+```
+
+## typescript
+
+```typescript
+class MyClass {
+ public static myValue: string;
+ constructor(init: string) {
+ this.myValue = init;
+ }
+}
+import fs = require("fs");
+module MyModule {
+ export interface MyInterface extends Other {
+ myProperty: any;
+ }
+}
+declare magicNumber number;
+myArray.forEach(() => { }); // fat arrow syntax
+
+```
+
+## x86asm
+
+```x86asm
+section .text
+extern _MessageBoxA@16
+%if __NASM_VERSION_ID__ >= 0x02030000
+safeseh handler ; register handler as "safe handler"
+%endif
+
+handler:
+ push dword 1 ; MB_OKCANCEL
+ push dword caption
+ push dword text
+ push dword 0
+ call _MessageBoxA@16
+ sub eax,1 ; incidentally suits as return value
+ ; for exception handler
+ ret
+
+global _main
+_main: push dword handler
+ push dword [fs:0]
+ mov dword [fs:0], esp
+ xor eax,eax
+ mov eax, dword[eax] ; cause exception
+ pop dword [fs:0] ; disengage exception handler
+ add esp, 4
+ ret
+
+avx2: vzeroupper
+ push rbx
+ mov rbx, rsp
+ sub rsp, 0h20
+ vmovdqa ymm0, [rcx]
+ vpaddb ymm0, [rdx]
+ leave
+ ret
+
+text: db 'OK to rethrow, CANCEL to generate core dump',0
+caption:db 'SEGV',0
+
+section .drectve info
+ db '/defaultlib:user32.lib /defaultlib:msvcrt.lib '
+```
+
+## xml
+
+```xml
+<!DOCTYPE html>
+<title>Title</title>
+
+<style>body {width: 500px;}</style>
+
+<script type="application/javascript">
+ function $init() {return true;}
+</script>
+
+<body>
+ <p checked class="title" id='title'>Title</p>
+ <!-- here goes the rest of the page -->
+</body>
+```
+
+## yaml
+
+```yaml
+---
+# comment
+string_1: "Bar"
+string_2: 'bar'
+string_3: bar
+inline_keys_ignored: sompath/name/file.jpg
+keywords_in_yaml:
+ - true
+ - false
+ - TRUE
+ - FALSE
+ - 21
+ - 21.0
+ - !!str 123
+"quoted_key": &foobar
+ bar: foo
+ foo:
+ "foo": bar
+
+reference: *foobar
+
+multiline_1: |
+ Multiline
+ String
+multiline_2: >
+ Multiline
+ String
+multiline_3: "
+ Multiline string
+ "
+
+ansible_variables: "foo {{variable}}"
+
+array_nested:
+- a
+- b: 1
+ c: 2
+- b
+- comment
+```
diff --git a/vendor/mdbook/test_book/src/prefix.md b/vendor/mdbook/test_book/src/prefix.md
new file mode 100644
index 000000000..1c40c2cb4
--- /dev/null
+++ b/vendor/mdbook/test_book/src/prefix.md
@@ -0,0 +1,3 @@
+# Prefix Chapter
+
+This is to verify the placement and style of prefix chapter in book index.
diff --git a/vendor/mdbook/test_book/src/rust/README.md b/vendor/mdbook/test_book/src/rust/README.md
new file mode 100644
index 000000000..e3bb350f0
--- /dev/null
+++ b/vendor/mdbook/test_book/src/rust/README.md
@@ -0,0 +1 @@
+# Rust specific code examples
diff --git a/vendor/mdbook/test_book/src/rust/rust_codeblock.md b/vendor/mdbook/test_book/src/rust/rust_codeblock.md
new file mode 100644
index 000000000..5fb2b7249
--- /dev/null
+++ b/vendor/mdbook/test_book/src/rust/rust_codeblock.md
@@ -0,0 +1,27 @@
+## Rust codeblocks
+
+This contains various examples of codeblocks, specific to rust
+
+## Simple
+
+```rust
+fn main(){
+ println!("Hello world!");
+}
+```
+
+## With Hidden lines
+
+```rust
+# fn main(){
+ println!("Hello world!");
+# }
+```
+
+## Editable
+
+```rust,editable
+fn main(){
+ println!("Hello world!");
+}
+```
diff --git a/vendor/mdbook/test_book/src/suffix.md b/vendor/mdbook/test_book/src/suffix.md
new file mode 100644
index 000000000..9d86853cf
--- /dev/null
+++ b/vendor/mdbook/test_book/src/suffix.md
@@ -0,0 +1,3 @@
+# Suffix Chapter
+
+This is to verify the placement and style of suffix chapter in book index.
diff --git a/vendor/mdbook/tests/alternative_backends.rs b/vendor/mdbook/tests/alternative_backends.rs
new file mode 100644
index 000000000..cc7bfc7d9
--- /dev/null
+++ b/vendor/mdbook/tests/alternative_backends.rs
@@ -0,0 +1,164 @@
+//! Integration tests to make sure alternative backends work.
+
+use mdbook::config::Config;
+use mdbook::MDBook;
+use std::fs;
+use std::path::Path;
+use tempfile::{Builder as TempFileBuilder, TempDir};
+
+#[test]
+fn passing_alternate_backend() {
+ let (md, _temp) = dummy_book_with_backend("passing", success_cmd(), false);
+
+ md.build().unwrap();
+}
+
+#[test]
+fn failing_alternate_backend() {
+ let (md, _temp) = dummy_book_with_backend("failing", fail_cmd(), false);
+
+ md.build().unwrap_err();
+}
+
+#[test]
+fn missing_backends_are_fatal() {
+ let (md, _temp) = dummy_book_with_backend("missing", "trduyvbhijnorgevfuhn", false);
+ assert!(md.build().is_err());
+}
+
+#[test]
+fn missing_optional_backends_are_not_fatal() {
+ let (md, _temp) = dummy_book_with_backend("missing", "trduyvbhijnorgevfuhn", true);
+ assert!(md.build().is_ok());
+}
+
+#[test]
+fn alternate_backend_with_arguments() {
+ let (md, _temp) = dummy_book_with_backend("arguments", "echo Hello World!", false);
+
+ md.build().unwrap();
+}
+
+/// Get a command which will pipe `stdin` to the provided file.
+#[cfg(not(windows))]
+fn tee_command<P: AsRef<Path>>(out_file: P) -> String {
+ let out_file = out_file.as_ref();
+
+ if cfg!(windows) {
+ format!("cmd.exe /c \"type > {}\"", out_file.display())
+ } else {
+ format!("tee {}", out_file.display())
+ }
+}
+
+#[test]
+#[cfg(not(windows))]
+fn backends_receive_render_context_via_stdin() {
+ use mdbook::renderer::RenderContext;
+ use std::fs::File;
+
+ let temp = TempFileBuilder::new().prefix("output").tempdir().unwrap();
+ let out_file = temp.path().join("out.txt");
+ let cmd = tee_command(&out_file);
+
+ let (md, _temp) = dummy_book_with_backend("cat-to-file", &cmd, false);
+
+ assert!(!out_file.exists());
+ md.build().unwrap();
+ assert!(out_file.exists());
+
+ let got = RenderContext::from_json(File::open(&out_file).unwrap());
+ assert!(got.is_ok());
+}
+
+#[test]
+fn relative_command_path() {
+ // Checks behavior of relative paths for the `command` setting.
+ let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
+ let renderers = temp.path().join("renderers");
+ fs::create_dir(&renderers).unwrap();
+ rust_exe(
+ &renderers,
+ "myrenderer",
+ r#"fn main() {
+ std::fs::write("output", "test").unwrap();
+ }"#,
+ );
+ let do_test = |cmd_path| {
+ let mut config = Config::default();
+ config
+ .set("output.html", toml::value::Table::new())
+ .unwrap();
+ config.set("output.myrenderer.command", cmd_path).unwrap();
+ let md = MDBook::init(&temp.path())
+ .with_config(config)
+ .build()
+ .unwrap();
+ let output = temp.path().join("book/myrenderer/output");
+ assert!(!output.exists());
+ md.build().unwrap();
+ assert!(output.exists());
+ fs::remove_file(output).unwrap();
+ };
+ // Legacy paths work, relative to the output directory.
+ if cfg!(windows) {
+ do_test("../../renderers/myrenderer.exe");
+ } else {
+ do_test("../../renderers/myrenderer");
+ }
+ // Modern path, relative to the book directory.
+ do_test("renderers/myrenderer");
+}
+
+fn dummy_book_with_backend(
+ name: &str,
+ command: &str,
+ backend_is_optional: bool,
+) -> (MDBook, TempDir) {
+ let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
+
+ let mut config = Config::default();
+ config
+ .set(format!("output.{}.command", name), command)
+ .unwrap();
+
+ if backend_is_optional {
+ config
+ .set(format!("output.{}.optional", name), true)
+ .unwrap();
+ }
+
+ let md = MDBook::init(temp.path())
+ .with_config(config)
+ .build()
+ .unwrap();
+
+ (md, temp)
+}
+
+fn fail_cmd() -> &'static str {
+ if cfg!(windows) {
+ r#"cmd.exe /c "exit 1""#
+ } else {
+ "false"
+ }
+}
+
+fn success_cmd() -> &'static str {
+ if cfg!(windows) {
+ r#"cmd.exe /c "exit 0""#
+ } else {
+ "true"
+ }
+}
+
+fn rust_exe(temp: &Path, name: &str, src: &str) {
+ let rs = temp.join(name).with_extension("rs");
+ fs::write(&rs, src).unwrap();
+ let status = std::process::Command::new("rustc")
+ .arg(rs)
+ .current_dir(temp)
+ .status()
+ .expect("rustc should run");
+ assert!(status.success());
+}
diff --git a/vendor/mdbook/tests/build_process.rs b/vendor/mdbook/tests/build_process.rs
new file mode 100644
index 000000000..10d0b4a9a
--- /dev/null
+++ b/vendor/mdbook/tests/build_process.rs
@@ -0,0 +1,78 @@
+mod dummy_book;
+
+use crate::dummy_book::DummyBook;
+use mdbook::book::Book;
+use mdbook::config::Config;
+use mdbook::errors::*;
+use mdbook::preprocess::{Preprocessor, PreprocessorContext};
+use mdbook::renderer::{RenderContext, Renderer};
+use mdbook::MDBook;
+use std::sync::{Arc, Mutex};
+
+struct Spy(Arc<Mutex<Inner>>);
+
+#[derive(Debug, Default)]
+struct Inner {
+ run_count: usize,
+ rendered_with: Vec<String>,
+}
+
+impl Preprocessor for Spy {
+ fn name(&self) -> &str {
+ "dummy"
+ }
+
+ fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book> {
+ let mut inner = self.0.lock().unwrap();
+ inner.run_count += 1;
+ inner.rendered_with.push(ctx.renderer.clone());
+ Ok(book)
+ }
+}
+
+impl Renderer for Spy {
+ fn name(&self) -> &str {
+ "dummy"
+ }
+
+ fn render(&self, _ctx: &RenderContext) -> Result<()> {
+ let mut inner = self.0.lock().unwrap();
+ inner.run_count += 1;
+ Ok(())
+ }
+}
+
+#[test]
+fn mdbook_runs_preprocessors() {
+ let spy: Arc<Mutex<Inner>> = Default::default();
+
+ let temp = DummyBook::new().build().unwrap();
+ let cfg = Config::default();
+
+ let mut book = MDBook::load_with_config(temp.path(), cfg).unwrap();
+ book.with_preprocessor(Spy(Arc::clone(&spy)));
+ book.build().unwrap();
+
+ let inner = spy.lock().unwrap();
+ assert_eq!(inner.run_count, 1);
+ assert_eq!(inner.rendered_with.len(), 1);
+ assert_eq!(
+ "html", inner.rendered_with[0],
+ "We should have been run with the default HTML renderer"
+ );
+}
+
+#[test]
+fn mdbook_runs_renderers() {
+ let spy: Arc<Mutex<Inner>> = Default::default();
+
+ let temp = DummyBook::new().build().unwrap();
+ let cfg = Config::default();
+
+ let mut book = MDBook::load_with_config(temp.path(), cfg).unwrap();
+ book.with_renderer(Spy(Arc::clone(&spy)));
+ book.build().unwrap();
+
+ let inner = spy.lock().unwrap();
+ assert_eq!(inner.run_count, 1);
+}
diff --git a/vendor/mdbook/tests/cli/build.rs b/vendor/mdbook/tests/cli/build.rs
new file mode 100644
index 000000000..0daba9d72
--- /dev/null
+++ b/vendor/mdbook/tests/cli/build.rs
@@ -0,0 +1,28 @@
+use crate::cli::cmd::mdbook_cmd;
+use crate::dummy_book::DummyBook;
+
+#[test]
+fn mdbook_cli_dummy_book_generates_index_html() {
+ let temp = DummyBook::new().build().unwrap();
+
+ // doesn't exist before
+ assert!(!temp.path().join("book").exists());
+
+ let mut cmd = mdbook_cmd();
+ cmd.arg("build").current_dir(temp.path());
+ cmd.assert()
+ .success()
+ .stderr(
+ predicates::str::is_match(r##"Stack depth exceeded in first[\\/]recursive.md."##)
+ .unwrap(),
+ )
+ .stderr(predicates::str::contains(
+ r##"[INFO] (mdbook::book): Running the html backend"##,
+ ));
+
+ // exists afterward
+ assert!(temp.path().join("book").exists());
+
+ let index_file = temp.path().join("book/index.html");
+ assert!(index_file.exists());
+}
diff --git a/vendor/mdbook/tests/cli/cmd.rs b/vendor/mdbook/tests/cli/cmd.rs
new file mode 100644
index 000000000..a612f3ad2
--- /dev/null
+++ b/vendor/mdbook/tests/cli/cmd.rs
@@ -0,0 +1,7 @@
+use assert_cmd::Command;
+
+pub(crate) fn mdbook_cmd() -> Command {
+ let mut cmd = Command::cargo_bin("mdbook").unwrap();
+ cmd.env_remove("RUST_LOG");
+ cmd
+}
diff --git a/vendor/mdbook/tests/cli/mod.rs b/vendor/mdbook/tests/cli/mod.rs
new file mode 100644
index 000000000..989f443f0
--- /dev/null
+++ b/vendor/mdbook/tests/cli/mod.rs
@@ -0,0 +1,3 @@
+mod build;
+mod cmd;
+mod test;
diff --git a/vendor/mdbook/tests/cli/test.rs b/vendor/mdbook/tests/cli/test.rs
new file mode 100644
index 000000000..bc525d9a9
--- /dev/null
+++ b/vendor/mdbook/tests/cli/test.rs
@@ -0,0 +1,34 @@
+use crate::cli::cmd::mdbook_cmd;
+use crate::dummy_book::DummyBook;
+
+use predicates::boolean::PredicateBooleanExt;
+
+#[test]
+fn mdbook_cli_can_correctly_test_a_passing_book() {
+ let temp = DummyBook::new().with_passing_test(true).build().unwrap();
+
+ let mut cmd = mdbook_cmd();
+ cmd.arg("test").current_dir(temp.path());
+ cmd.assert().success()
+ .stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]README.md""##).unwrap())
+ .stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]intro.md""##).unwrap())
+ .stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]index.md""##).unwrap())
+ .stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]nested.md""##).unwrap())
+ .stderr(predicates::str::is_match(r##"rustdoc returned an error:\n\n"##).unwrap().not())
+ .stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap().not());
+}
+
+#[test]
+fn mdbook_cli_detects_book_with_failing_tests() {
+ let temp = DummyBook::new().with_passing_test(false).build().unwrap();
+
+ let mut cmd = mdbook_cmd();
+ cmd.arg("test").current_dir(temp.path());
+ cmd.assert().failure()
+ .stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]README.md""##).unwrap())
+ .stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]intro.md""##).unwrap())
+ .stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]index.md""##).unwrap())
+ .stderr(predicates::str::is_match(r##"Testing file: "([^"]+)[\\/]first[\\/]nested.md""##).unwrap())
+ .stderr(predicates::str::is_match(r##"rustdoc returned an error:\n\n"##).unwrap())
+ .stderr(predicates::str::is_match(r##"Nested_Chapter::Rustdoc_include_works_with_anchors_too \(line \d+\) ... FAILED"##).unwrap());
+}
diff --git a/vendor/mdbook/tests/cli_tests.rs b/vendor/mdbook/tests/cli_tests.rs
new file mode 100644
index 000000000..0b005d019
--- /dev/null
+++ b/vendor/mdbook/tests/cli_tests.rs
@@ -0,0 +1,2 @@
+mod cli;
+mod dummy_book;
diff --git a/vendor/mdbook/tests/custom_preprocessors.rs b/vendor/mdbook/tests/custom_preprocessors.rs
new file mode 100644
index 000000000..8237602de
--- /dev/null
+++ b/vendor/mdbook/tests/custom_preprocessors.rs
@@ -0,0 +1,56 @@
+mod dummy_book;
+
+use crate::dummy_book::DummyBook;
+use mdbook::preprocess::{CmdPreprocessor, Preprocessor};
+use mdbook::MDBook;
+
+fn example() -> CmdPreprocessor {
+ CmdPreprocessor::new(
+ "nop-preprocessor".to_string(),
+ "cargo run --example nop-preprocessor --".to_string(),
+ )
+}
+
+#[test]
+fn example_supports_whatever() {
+ let cmd = example();
+
+ let got = cmd.supports_renderer("whatever");
+
+ assert_eq!(got, true);
+}
+
+#[test]
+fn example_doesnt_support_not_supported() {
+ let cmd = example();
+
+ let got = cmd.supports_renderer("not-supported");
+
+ assert_eq!(got, false);
+}
+
+#[test]
+fn ask_the_preprocessor_to_blow_up() {
+ let dummy_book = DummyBook::new();
+ let temp = dummy_book.build().unwrap();
+ let mut md = MDBook::load(temp.path()).unwrap();
+ md.with_preprocessor(example());
+
+ md.config
+ .set("preprocessor.nop-preprocessor.blow-up", true)
+ .unwrap();
+
+ let got = md.build();
+
+ assert!(got.is_err());
+}
+
+#[test]
+fn process_the_dummy_book() {
+ let dummy_book = DummyBook::new();
+ let temp = dummy_book.build().unwrap();
+ let mut md = MDBook::load(temp.path()).unwrap();
+ md.with_preprocessor(example());
+
+ md.build().unwrap();
+}
diff --git a/vendor/mdbook/tests/dummy_book/index_html_test/SUMMARY.md b/vendor/mdbook/tests/dummy_book/index_html_test/SUMMARY.md
new file mode 100644
index 000000000..37bf68cde
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/index_html_test/SUMMARY.md
@@ -0,0 +1,11 @@
+# Summary
+
+---
+
+- [None of these should be treated as the "index chapter"]()
+
+# Part 1
+
+- [Not this either]()
+- [Chapter 1](./chapter_1.md)
+- [And not this]()
diff --git a/vendor/mdbook/tests/dummy_book/index_html_test/chapter_1.md b/vendor/mdbook/tests/dummy_book/index_html_test/chapter_1.md
new file mode 100644
index 000000000..b743fda35
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/index_html_test/chapter_1.md
@@ -0,0 +1 @@
+# Chapter 1
diff --git a/vendor/mdbook/tests/dummy_book/mod.rs b/vendor/mdbook/tests/dummy_book/mod.rs
new file mode 100644
index 000000000..d9d9a068d
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/mod.rs
@@ -0,0 +1,145 @@
+//! This will create an entire book in a temporary directory using some
+//! dummy contents from the `tests/dummy-book/` directory.
+
+// Not all features are used in all test crates, so...
+#![allow(dead_code, unused_variables, unused_imports, unused_extern_crates)]
+
+use anyhow::Context;
+use mdbook::errors::*;
+use mdbook::MDBook;
+use std::fs::{self, File};
+use std::io::{Read, Write};
+use std::path::Path;
+use tempfile::{Builder as TempFileBuilder, TempDir};
+use walkdir::WalkDir;
+
+/// Create a dummy book in a temporary directory, using the contents of
+/// `SUMMARY_MD` as a guide.
+///
+/// The "Nested Chapter" file contains a code block with a single
+/// `assert!($TEST_STATUS)`. If you want to check MDBook's testing
+/// functionality, `$TEST_STATUS` can be substitute for either `true` or
+/// `false`. This is done using the `passing_test` parameter.
+#[derive(Clone, Debug, PartialEq)]
+pub struct DummyBook {
+ passing_test: bool,
+}
+
+impl DummyBook {
+ /// Create a new `DummyBook` with all the defaults.
+ pub fn new() -> DummyBook {
+ DummyBook { passing_test: true }
+ }
+
+ /// Whether the doc-test included in the "Nested Chapter" should pass or
+ /// fail (it passes by default).
+ pub fn with_passing_test(&mut self, test_passes: bool) -> &mut DummyBook {
+ self.passing_test = test_passes;
+ self
+ }
+
+ /// Write a book to a temporary directory using the provided settings.
+ pub fn build(&self) -> Result<TempDir> {
+ let temp = TempFileBuilder::new()
+ .prefix("dummy_book-")
+ .tempdir()
+ .with_context(|| "Unable to create temp directory")?;
+
+ let dummy_book_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/dummy_book");
+ recursive_copy(&dummy_book_root, temp.path()).with_context(|| {
+ "Couldn't copy files into a \
+ temporary directory"
+ })?;
+
+ let sub_pattern = if self.passing_test { "true" } else { "false" };
+ let files_containing_tests = [
+ "src/first/nested.md",
+ "src/first/nested-test.rs",
+ "src/first/nested-test-with-anchors.rs",
+ "src/first/partially-included-test.rs",
+ "src/first/partially-included-test-with-anchors.rs",
+ ];
+ for file in &files_containing_tests {
+ let path_containing_tests = temp.path().join(file);
+ replace_pattern_in_file(&path_containing_tests, "$TEST_STATUS", sub_pattern)?;
+ }
+
+ Ok(temp)
+ }
+}
+
+fn replace_pattern_in_file(filename: &Path, from: &str, to: &str) -> Result<()> {
+ let contents = fs::read_to_string(filename)?;
+ File::create(filename)?.write_all(contents.replace(from, to).as_bytes())?;
+
+ Ok(())
+}
+
+/// Read the contents of the provided file into memory and then iterate through
+/// the list of strings asserting that the file contains all of them.
+pub fn assert_contains_strings<P: AsRef<Path>>(filename: P, strings: &[&str]) {
+ let filename = filename.as_ref();
+ let content = fs::read_to_string(filename).expect("Couldn't read the file's contents");
+
+ for s in strings {
+ assert!(
+ content.contains(s),
+ "Searching for {:?} in {}\n\n{}",
+ s,
+ filename.display(),
+ content
+ );
+ }
+}
+
+pub fn assert_doesnt_contain_strings<P: AsRef<Path>>(filename: P, strings: &[&str]) {
+ let filename = filename.as_ref();
+ let content = fs::read_to_string(filename).expect("Couldn't read the file's contents");
+
+ for s in strings {
+ assert!(
+ !content.contains(s),
+ "Found {:?} in {}\n\n{}",
+ s,
+ filename.display(),
+ content
+ );
+ }
+}
+
+/// Recursively copy an entire directory tree to somewhere else (a la `cp -r`).
+fn recursive_copy<A: AsRef<Path>, B: AsRef<Path>>(from: A, to: B) -> Result<()> {
+ let from = from.as_ref();
+ let to = to.as_ref();
+
+ for entry in WalkDir::new(&from) {
+ let entry = entry.with_context(|| "Unable to inspect directory entry")?;
+
+ let original_location = entry.path();
+ let relative = original_location
+ .strip_prefix(&from)
+ .expect("`original_location` is inside the `from` directory");
+ let new_location = to.join(relative);
+
+ if original_location.is_file() {
+ if let Some(parent) = new_location.parent() {
+ fs::create_dir_all(parent).with_context(|| "Couldn't create directory")?;
+ }
+
+ fs::copy(&original_location, &new_location)
+ .with_context(|| "Unable to copy file contents")?;
+ }
+ }
+
+ Ok(())
+}
+
+pub fn new_copy_of_example_book() -> Result<TempDir> {
+ let temp = TempFileBuilder::new().prefix("guide").tempdir()?;
+
+ let guide = Path::new(env!("CARGO_MANIFEST_DIR")).join("guide");
+
+ recursive_copy(guide, temp.path())?;
+
+ Ok(temp)
+}
diff --git a/vendor/mdbook/tests/dummy_book/src/README.md b/vendor/mdbook/tests/dummy_book/src/README.md
new file mode 100644
index 000000000..7d946e35d
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/README.md
@@ -0,0 +1,5 @@
+# Dummy Book
+
+This file is just here to cause the index preprocessor to run.
+
+Does a pretty good job, too. \ No newline at end of file
diff --git a/vendor/mdbook/tests/dummy_book/src/SUMMARY.md b/vendor/mdbook/tests/dummy_book/src/SUMMARY.md
new file mode 100644
index 000000000..49b64a545
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/SUMMARY.md
@@ -0,0 +1,22 @@
+# Summary
+
+[Dummy Book](README.md)
+
+---
+
+[Introduction](intro.md)
+
+- [First Chapter](first/index.md)
+ - [Nested Chapter](first/nested.md)
+ - [Includes](first/includes.md)
+ - [Recursive](first/recursive.md)
+ - [Markdown](first/markdown.md)
+ - [Unicode](first/unicode.md)
+ - [No Headers](first/no-headers.md)
+ - [Duplicate Headers](first/duplicate-headers.md)
+- [Second Chapter](second.md)
+ - [Nested Chapter](second/nested.md)
+
+---
+
+[Conclusion](conclusion.md)
diff --git a/vendor/mdbook/tests/dummy_book/src/conclusion.md b/vendor/mdbook/tests/dummy_book/src/conclusion.md
new file mode 100644
index 000000000..ba121c1fd
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/conclusion.md
@@ -0,0 +1,20 @@
+# Conclusion
+
+<p>
+<!--secret secret-->
+I put &lt;HTML&gt; in here!<br/>
+</p>
+<script type="text/javascript" >
+// I probably shouldn't do this
+if (3 < 5 > 10)
+{
+ alert("The sky is falling!");
+}
+</script >
+<style >
+/*
+css looks, like this {
+ foo: < 3 <bar >
+}
+*/
+</style>
diff --git a/vendor/mdbook/tests/dummy_book/src/example.rs b/vendor/mdbook/tests/dummy_book/src/example.rs
new file mode 100644
index 000000000..6b49705c1
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/example.rs
@@ -0,0 +1,6 @@
+fn main() {
+ println!("Hello World!");
+#
+# // You can even hide lines! :D
+# println!("I am hidden! Expand the code snippet to see me");
+}
diff --git a/vendor/mdbook/tests/dummy_book/src/first/duplicate-headers.md b/vendor/mdbook/tests/dummy_book/src/first/duplicate-headers.md
new file mode 100644
index 000000000..83522b440
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/first/duplicate-headers.md
@@ -0,0 +1,9 @@
+# Duplicate headers
+
+This page validates behaviour of duplicate headers.
+
+# Header Text
+
+# Header Text
+
+# header-text
diff --git a/vendor/mdbook/tests/dummy_book/src/first/includes.md b/vendor/mdbook/tests/dummy_book/src/first/includes.md
new file mode 100644
index 000000000..a5a2fef1f
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/first/includes.md
@@ -0,0 +1,3 @@
+# Includes
+
+{{#include ../SUMMARY.md::}} \ No newline at end of file
diff --git a/vendor/mdbook/tests/dummy_book/src/first/index.md b/vendor/mdbook/tests/dummy_book/src/first/index.md
new file mode 100644
index 000000000..200672b9c
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/first/index.md
@@ -0,0 +1,5 @@
+# First Chapter
+
+more text.
+
+## Some Section
diff --git a/vendor/mdbook/tests/dummy_book/src/first/markdown.md b/vendor/mdbook/tests/dummy_book/src/first/markdown.md
new file mode 100644
index 000000000..d65d3d389
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/first/markdown.md
@@ -0,0 +1,29 @@
+# Markdown tests
+
+Tests for some markdown output.
+
+## Tables
+
+| foo | bar |
+| --- | --- |
+| baz | bim |
+
+## Footnotes
+
+Footnote example[^1], or with a word[^word].
+
+[^1]: This is a footnote.
+
+[^word]: A longer footnote.
+ With multiple lines.
+ Third line.
+
+## Strikethrough
+
+~~strikethrough example~~
+
+## Tasklisks
+
+- [X] Apples
+- [X] Broccoli
+- [ ] Carrots
diff --git a/vendor/mdbook/tests/dummy_book/src/first/nested-test-with-anchors.rs b/vendor/mdbook/tests/dummy_book/src/first/nested-test-with-anchors.rs
new file mode 100644
index 000000000..783ab14de
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/first/nested-test-with-anchors.rs
@@ -0,0 +1,11 @@
+// The next line will cause a `testing` test to fail if the anchor feature is broken in such a way
+// that the whole file gets mistakenly included.
+assert!(!$TEST_STATUS);
+
+// ANCHOR: myanchor
+// ANCHOR: unendinganchor
+// The next line will cause a `rendered_output` test to fail if the anchor feature is broken in
+// such a way that the content between anchors isn't included.
+// unique-string-for-anchor-test
+assert!($TEST_STATUS);
+// ANCHOR_END: myanchor
diff --git a/vendor/mdbook/tests/dummy_book/src/first/nested-test.rs b/vendor/mdbook/tests/dummy_book/src/first/nested-test.rs
new file mode 100644
index 000000000..2bc46e01a
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/first/nested-test.rs
@@ -0,0 +1 @@
+assert!($TEST_STATUS);
diff --git a/vendor/mdbook/tests/dummy_book/src/first/nested.md b/vendor/mdbook/tests/dummy_book/src/first/nested.md
new file mode 100644
index 000000000..ae90763a0
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/first/nested.md
@@ -0,0 +1,31 @@
+# Nested Chapter
+
+This file has some testable code.
+
+```rust
+assert!($TEST_STATUS);
+```
+
+## Some Section
+
+```rust
+{{#include nested-test.rs}}
+```
+
+## Anchors include the part of a file between special comments
+
+```rust
+{{#include nested-test-with-anchors.rs:myanchor}}
+```
+
+## Rustdoc include adds the rest of the file as hidden
+
+```rust
+{{#rustdoc_include partially-included-test.rs:5:7}}
+```
+
+## Rustdoc include works with anchors too
+
+```rust
+{{#rustdoc_include partially-included-test-with-anchors.rs:rustdoc-include-anchor}}
+```
diff --git a/vendor/mdbook/tests/dummy_book/src/first/no-headers.md b/vendor/mdbook/tests/dummy_book/src/first/no-headers.md
new file mode 100644
index 000000000..5d799aa68
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/first/no-headers.md
@@ -0,0 +1,5 @@
+Capybara capybara capybara.
+
+Capybara capybara capybara.
+
+ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex.
diff --git a/vendor/mdbook/tests/dummy_book/src/first/partially-included-test-with-anchors.rs b/vendor/mdbook/tests/dummy_book/src/first/partially-included-test-with-anchors.rs
new file mode 100644
index 000000000..17b6afeff
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/first/partially-included-test-with-anchors.rs
@@ -0,0 +1,11 @@
+fn some_other_function() {
+ // ANCHOR: unused-anchor-that-should-be-stripped
+ assert!($TEST_STATUS);
+ // ANCHOR_END: unused-anchor-that-should-be-stripped
+}
+
+// ANCHOR: rustdoc-include-anchor
+fn main() {
+ some_other_function();
+}
+// ANCHOR_END: rustdoc-include-anchor
diff --git a/vendor/mdbook/tests/dummy_book/src/first/partially-included-test.rs b/vendor/mdbook/tests/dummy_book/src/first/partially-included-test.rs
new file mode 100644
index 000000000..1f8421b54
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/first/partially-included-test.rs
@@ -0,0 +1,7 @@
+fn some_function() {
+ assert!($TEST_STATUS);
+}
+
+fn main() {
+ some_function();
+}
diff --git a/vendor/mdbook/tests/dummy_book/src/first/recursive.md b/vendor/mdbook/tests/dummy_book/src/first/recursive.md
new file mode 100644
index 000000000..cb82a52f1
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/first/recursive.md
@@ -0,0 +1,2 @@
+Around the world, around the world
+{{#include recursive.md}}
diff --git a/vendor/mdbook/tests/dummy_book/src/first/unicode.md b/vendor/mdbook/tests/dummy_book/src/first/unicode.md
new file mode 100644
index 000000000..160cc367d
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/first/unicode.md
@@ -0,0 +1,21 @@
+# Unicode stress tests
+
+Please be careful editing, this contains carefully crafted characters.
+
+Two byte character: spatiëring
+
+Combining character: spatiëring
+
+Three byte character: 书こんにちは
+
+Four byte character: 𐌀‮𐌁‮𐌂‮𐌃‮𐌄‮𐌅‮𐌆‮𐌇‮𐌈‬
+
+Right-to-left: مرحبا
+
+Emoticons: 🔊 😍 💜 1️⃣
+
+right-to-left mark: hello באמת!‏
+
+
+Zalgo: ǫ̛̖̱̗̝͈̋͒͋̏ͥͫ̒̆ͩ̏͌̾͊͐ͪ̾̚
+
diff --git a/vendor/mdbook/tests/dummy_book/src/intro.md b/vendor/mdbook/tests/dummy_book/src/intro.md
new file mode 100644
index 000000000..1990ef58c
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/intro.md
@@ -0,0 +1,3 @@
+# Introduction
+
+Here's some interesting text... \ No newline at end of file
diff --git a/vendor/mdbook/tests/dummy_book/src/second.md b/vendor/mdbook/tests/dummy_book/src/second.md
new file mode 100644
index 000000000..adf4fca62
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/second.md
@@ -0,0 +1,5 @@
+# Second Chapter
+
+This makes sure you can insert runnable Rust files.
+
+{{#playground example.rs}}
diff --git a/vendor/mdbook/tests/dummy_book/src/second/nested.md b/vendor/mdbook/tests/dummy_book/src/second/nested.md
new file mode 100644
index 000000000..faf1187ff
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src/second/nested.md
@@ -0,0 +1,16 @@
+# Testing relative links for the print page
+
+When we link to [the first section](../first/nested.md), it should work on
+both the print page and the non-print page.
+
+A [fragment link](#some-section) should work.
+
+Link [outside](../../std/foo/bar.html).
+
+![Some image](../images/picture.png)
+
+<a href="../first/markdown.md">HTML Link</a>
+
+<img src="../images/picture.png" alt="raw html">
+
+## Some section
diff --git a/vendor/mdbook/tests/dummy_book/src2/README.md b/vendor/mdbook/tests/dummy_book/src2/README.md
new file mode 100644
index 000000000..ba23d94ef
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src2/README.md
@@ -0,0 +1 @@
+# Root README
diff --git a/vendor/mdbook/tests/dummy_book/src2/SUMMARY.md b/vendor/mdbook/tests/dummy_book/src2/SUMMARY.md
new file mode 100644
index 000000000..6b279fc48
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src2/SUMMARY.md
@@ -0,0 +1,7 @@
+# This dummy book is for testing the conversion of README.md to index.html by IndexPreprocessor
+
+[Root README](README.md)
+
+- [1st README](first/README.md)
+- [2nd README](second/README.md)
+ - [2nd index](second/index.md)
diff --git a/vendor/mdbook/tests/dummy_book/src2/first/README.md b/vendor/mdbook/tests/dummy_book/src2/first/README.md
new file mode 100644
index 000000000..d062d2ccd
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src2/first/README.md
@@ -0,0 +1 @@
+# First README
diff --git a/vendor/mdbook/tests/dummy_book/src2/second/README.md b/vendor/mdbook/tests/dummy_book/src2/second/README.md
new file mode 100644
index 000000000..be81f856f
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src2/second/README.md
@@ -0,0 +1 @@
+# Second README
diff --git a/vendor/mdbook/tests/dummy_book/src2/second/index.md b/vendor/mdbook/tests/dummy_book/src2/second/index.md
new file mode 100644
index 000000000..f23274504
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/src2/second/index.md
@@ -0,0 +1 @@
+# Second index
diff --git a/vendor/mdbook/tests/dummy_book/summary-formatting/SUMMARY.md b/vendor/mdbook/tests/dummy_book/summary-formatting/SUMMARY.md
new file mode 100644
index 000000000..336218d82
--- /dev/null
+++ b/vendor/mdbook/tests/dummy_book/summary-formatting/SUMMARY.md
@@ -0,0 +1,6 @@
+# Summary formatting tests
+
+- [*Italic* `code` \*escape\* \`escape2\`](formatted-summary.md)
+- [Soft
+line break](soft.md)
+- [\<escaped tag\>](escaped-tag.md)
diff --git a/vendor/mdbook/tests/init.rs b/vendor/mdbook/tests/init.rs
new file mode 100644
index 000000000..4deb84019
--- /dev/null
+++ b/vendor/mdbook/tests/init.rs
@@ -0,0 +1,144 @@
+use mdbook::config::Config;
+use mdbook::MDBook;
+use std::fs;
+use std::fs::File;
+use std::io::prelude::*;
+use std::path::PathBuf;
+use tempfile::Builder as TempFileBuilder;
+
+/// Run `mdbook init` in an empty directory and make sure the default files
+/// are created.
+#[test]
+fn base_mdbook_init_should_create_default_content() {
+ let created_files = vec!["book", "src", "src/SUMMARY.md", "src/chapter_1.md"];
+
+ let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
+ for file in &created_files {
+ assert!(!temp.path().join(file).exists());
+ }
+
+ MDBook::init(temp.path()).build().unwrap();
+
+ for file in &created_files {
+ let target = temp.path().join(file);
+ println!("{}", target.display());
+ assert!(target.exists(), "{} doesn't exist", file);
+ }
+
+ let contents = fs::read_to_string(temp.path().join("book.toml")).unwrap();
+ assert_eq!(
+ contents,
+ "[book]\nauthors = []\nlanguage = \"en\"\nmultilingual = false\nsrc = \"src\"\n"
+ );
+}
+
+/// Run `mdbook init` in a directory containing a SUMMARY.md should create the
+/// files listed in the summary.
+#[test]
+fn run_mdbook_init_should_create_content_from_summary() {
+ let created_files = vec!["intro.md", "first.md", "outro.md"];
+
+ let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
+ let src_dir = temp.path().join("src");
+ fs::create_dir_all(src_dir.clone()).unwrap();
+ static SUMMARY: &str = r#"# Summary
+
+[intro](intro.md)
+
+- [First chapter](first.md)
+
+[outro](outro.md)
+
+"#;
+
+ let mut summary = File::create(src_dir.join("SUMMARY.md")).unwrap();
+ summary.write_all(SUMMARY.as_bytes()).unwrap();
+ MDBook::init(temp.path()).build().unwrap();
+
+ for file in &created_files {
+ let target = src_dir.join(file);
+ println!("{}", target.display());
+ assert!(target.exists(), "{} doesn't exist", file);
+ }
+}
+
+/// Set some custom arguments for where to place the source and destination
+/// files, then call `mdbook init`.
+#[test]
+fn run_mdbook_init_with_custom_book_and_src_locations() {
+ let created_files = vec!["out", "in", "in/SUMMARY.md", "in/chapter_1.md"];
+
+ let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
+ for file in &created_files {
+ assert!(
+ !temp.path().join(file).exists(),
+ "{} shouldn't exist yet!",
+ file
+ );
+ }
+
+ let mut cfg = Config::default();
+ cfg.book.src = PathBuf::from("in");
+ cfg.build.build_dir = PathBuf::from("out");
+
+ MDBook::init(temp.path()).with_config(cfg).build().unwrap();
+
+ for file in &created_files {
+ let target = temp.path().join(file);
+ assert!(
+ target.exists(),
+ "{} should have been created by `mdbook init`",
+ file
+ );
+ }
+
+ let contents = fs::read_to_string(temp.path().join("book.toml")).unwrap();
+ assert_eq!(
+ contents,
+ "[book]\nauthors = []\nlanguage = \"en\"\nmultilingual = false\nsrc = \"in\"\n\n[build]\nbuild-dir = \"out\"\ncreate-missing = true\nuse-default-preprocessors = true\n"
+ );
+}
+
+#[test]
+fn book_toml_isnt_required() {
+ let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
+ let md = MDBook::init(temp.path()).build().unwrap();
+
+ let _ = fs::remove_file(temp.path().join("book.toml"));
+
+ md.build().unwrap();
+}
+
+#[test]
+fn copy_theme() {
+ let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
+ MDBook::init(temp.path()).copy_theme(true).build().unwrap();
+ let expected = vec![
+ "book.js",
+ "css/chrome.css",
+ "css/general.css",
+ "css/print.css",
+ "css/variables.css",
+ "favicon.png",
+ "favicon.svg",
+ "highlight.css",
+ "highlight.js",
+ "index.hbs",
+ ];
+ let theme_dir = temp.path().join("theme");
+ let mut actual: Vec<_> = walkdir::WalkDir::new(&theme_dir)
+ .into_iter()
+ .filter_map(|e| e.ok())
+ .filter(|e| !e.file_type().is_dir())
+ .map(|e| {
+ e.path()
+ .strip_prefix(&theme_dir)
+ .unwrap()
+ .to_str()
+ .unwrap()
+ .replace('\\', "/")
+ })
+ .collect();
+ actual.sort();
+ assert_eq!(actual, expected);
+}
diff --git a/vendor/mdbook/tests/parse_existing_summary_files.rs b/vendor/mdbook/tests/parse_existing_summary_files.rs
new file mode 100644
index 000000000..418ec31fe
--- /dev/null
+++ b/vendor/mdbook/tests/parse_existing_summary_files.rs
@@ -0,0 +1,43 @@
+//! Some integration tests to make sure the `SUMMARY.md` parser can deal with
+//! some real-life examples.
+
+use mdbook::book;
+use std::fs::File;
+use std::io::Read;
+use std::path::Path;
+
+macro_rules! summary_md_test {
+ ($name:ident, $filename:expr) => {
+ #[test]
+ fn $name() {
+ env_logger::try_init().ok();
+
+ let filename = Path::new(env!("CARGO_MANIFEST_DIR"))
+ .join("tests")
+ .join("summary_md_files")
+ .join($filename);
+
+ if !filename.exists() {
+ panic!("{} Doesn't exist", filename.display());
+ }
+
+ let mut content = String::new();
+ File::open(&filename)
+ .unwrap()
+ .read_to_string(&mut content)
+ .unwrap();
+
+ if let Err(e) = book::parse_summary(&content) {
+ eprintln!("Error parsing {}", filename.display());
+ eprintln!();
+ eprintln!("{:?}", e);
+ panic!();
+ }
+ }
+ };
+}
+
+summary_md_test!(rust_by_example, "rust_by_example.md");
+summary_md_test!(rust_ffi_guide, "rust_ffi_guide.md");
+summary_md_test!(example_book, "example_book.md");
+summary_md_test!(the_book_2nd_edition, "the_book-2nd_edition.md");
diff --git a/vendor/mdbook/tests/rendered_output.rs b/vendor/mdbook/tests/rendered_output.rs
new file mode 100644
index 000000000..9750a35e2
--- /dev/null
+++ b/vendor/mdbook/tests/rendered_output.rs
@@ -0,0 +1,844 @@
+#[macro_use]
+extern crate pretty_assertions;
+
+mod dummy_book;
+
+use crate::dummy_book::{assert_contains_strings, assert_doesnt_contain_strings, DummyBook};
+
+use anyhow::Context;
+use mdbook::config::Config;
+use mdbook::errors::*;
+use mdbook::utils::fs::write_file;
+use mdbook::MDBook;
+use select::document::Document;
+use select::predicate::{Class, Name, Predicate};
+use std::collections::HashMap;
+use std::ffi::OsStr;
+use std::fs;
+use std::io::Write;
+use std::path::{Component, Path, PathBuf};
+use std::str::FromStr;
+use tempfile::Builder as TempFileBuilder;
+use walkdir::{DirEntry, WalkDir};
+
+const BOOK_ROOT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/dummy_book");
+const TOC_TOP_LEVEL: &[&str] = &[
+ "1. First Chapter",
+ "2. Second Chapter",
+ "Conclusion",
+ "Dummy Book",
+ "Introduction",
+];
+const TOC_SECOND_LEVEL: &[&str] = &[
+ "1.1. Nested Chapter",
+ "1.2. Includes",
+ "1.3. Recursive",
+ "1.4. Markdown",
+ "1.5. Unicode",
+ "1.6. No Headers",
+ "1.7. Duplicate Headers",
+ "2.1. Nested Chapter",
+];
+
+/// Make sure you can load the dummy book and build it without panicking.
+#[test]
+fn build_the_dummy_book() {
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+
+ md.build().unwrap();
+}
+
+#[test]
+fn by_default_mdbook_generates_rendered_content_in_the_book_directory() {
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+
+ assert!(!temp.path().join("book").exists());
+ md.build().unwrap();
+
+ assert!(temp.path().join("book").exists());
+ let index_file = md.build_dir_for("html").join("index.html");
+ assert!(index_file.exists());
+}
+
+#[test]
+fn make_sure_bottom_level_files_contain_links_to_chapters() {
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let dest = temp.path().join("book");
+ let links = vec![
+ r#"href="intro.html""#,
+ r#"href="first/index.html""#,
+ r#"href="first/nested.html""#,
+ r#"href="second.html""#,
+ r#"href="conclusion.html""#,
+ ];
+
+ let files_in_bottom_dir = vec!["index.html", "intro.html", "second.html", "conclusion.html"];
+
+ for filename in files_in_bottom_dir {
+ assert_contains_strings(dest.join(filename), &links);
+ }
+}
+
+#[test]
+fn check_correct_cross_links_in_nested_dir() {
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let first = temp.path().join("book").join("first");
+ let links = vec![
+ r#"href="../intro.html""#,
+ r#"href="../first/index.html""#,
+ r#"href="../first/nested.html""#,
+ r#"href="../second.html""#,
+ r#"href="../conclusion.html""#,
+ ];
+
+ let files_in_nested_dir = vec!["index.html", "nested.html"];
+
+ for filename in files_in_nested_dir {
+ assert_contains_strings(first.join(filename), &links);
+ }
+
+ assert_contains_strings(
+ first.join("index.html"),
+ &[r##"<h2 id="some-section"><a class="header" href="#some-section">"##],
+ );
+
+ assert_contains_strings(
+ first.join("nested.html"),
+ &[r##"<h2 id="some-section"><a class="header" href="#some-section">"##],
+ );
+}
+
+#[test]
+fn check_correct_relative_links_in_print_page() {
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let first = temp.path().join("book");
+
+ assert_contains_strings(
+ first.join("print.html"),
+ &[
+ r##"<a href="second/../first/nested.html">the first section</a>,"##,
+ r##"<a href="second/../../std/foo/bar.html">outside</a>"##,
+ r##"<img src="second/../images/picture.png" alt="Some image" />"##,
+ r##"<a href="second/nested.html#some-section">fragment link</a>"##,
+ r##"<a href="second/../first/markdown.html">HTML Link</a>"##,
+ r##"<img src="second/../images/picture.png" alt="raw html">"##,
+ ],
+ );
+}
+
+#[test]
+fn rendered_code_has_playground_stuff() {
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let nested = temp.path().join("book/first/nested.html");
+ let playground_class = vec![r#"class="playground""#];
+
+ assert_contains_strings(nested, &playground_class);
+
+ let book_js = temp.path().join("book/book.js");
+ assert_contains_strings(book_js, &[".playground"]);
+}
+
+#[test]
+fn rendered_code_does_not_have_playground_stuff_in_html_when_disabled_in_config() {
+ let temp = DummyBook::new().build().unwrap();
+ let config = Config::from_str(
+ "
+ [output.html.playground]
+ runnable = false
+ ",
+ )
+ .unwrap();
+ let md = MDBook::load_with_config(temp.path(), config).unwrap();
+ md.build().unwrap();
+
+ let nested = temp.path().join("book/first/nested.html");
+ let playground_class = vec![r#"class="playground""#];
+
+ assert_doesnt_contain_strings(nested, &playground_class);
+}
+
+#[test]
+fn anchors_include_text_between_but_not_anchor_comments() {
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let nested = temp.path().join("book/first/nested.html");
+ let text_between_anchors = vec!["unique-string-for-anchor-test"];
+ let anchor_text = vec!["ANCHOR"];
+
+ assert_contains_strings(nested.clone(), &text_between_anchors);
+ assert_doesnt_contain_strings(nested, &anchor_text);
+}
+
+#[test]
+fn rustdoc_include_hides_the_unspecified_part_of_the_file() {
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let nested = temp.path().join("book/first/nested.html");
+ let text = vec![
+ "<span class=\"boring\">fn some_function() {",
+ "<span class=\"boring\">fn some_other_function() {",
+ ];
+
+ assert_contains_strings(nested, &text);
+}
+
+#[test]
+fn chapter_content_appears_in_rendered_document() {
+ let content = vec![
+ ("index.html", "This file is just here to cause the"),
+ ("intro.html", "Here's some interesting text"),
+ ("second.html", "Second Chapter"),
+ ("first/nested.html", "testable code"),
+ ("first/index.html", "more text"),
+ ("conclusion.html", "Conclusion"),
+ ];
+
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let destination = temp.path().join("book");
+
+ for (filename, text) in content {
+ let path = destination.join(filename);
+ assert_contains_strings(path, &[text]);
+ }
+}
+
+/// Apply a series of predicates to some root predicate, where each
+/// successive predicate is the descendant of the last one. Similar to how you
+/// might do `ul.foo li a` in CSS to access all anchor tags in the `foo` list.
+macro_rules! descendants {
+ ($root:expr, $($child:expr),*) => {
+ $root
+ $(
+ .descendant($child)
+ )*
+ };
+}
+
+/// Make sure that all `*.md` files (excluding `SUMMARY.md`) were rendered
+/// and placed in the `book` directory with their extensions set to `*.html`.
+#[test]
+fn chapter_files_were_rendered_to_html() {
+ let temp = DummyBook::new().build().unwrap();
+ let src = Path::new(BOOK_ROOT).join("src");
+
+ let chapter_files = WalkDir::new(&src)
+ .into_iter()
+ .filter_entry(|entry| entry_ends_with(entry, ".md"))
+ .filter_map(std::result::Result::ok)
+ .map(|entry| entry.path().to_path_buf())
+ .filter(|path| path.file_name().and_then(OsStr::to_str) != Some("SUMMARY.md"));
+
+ for chapter in chapter_files {
+ let rendered_location = temp
+ .path()
+ .join(chapter.strip_prefix(&src).unwrap())
+ .with_extension("html");
+ assert!(
+ rendered_location.exists(),
+ "{} doesn't exits",
+ rendered_location.display()
+ );
+ }
+}
+
+fn entry_ends_with(entry: &DirEntry, ending: &str) -> bool {
+ entry.file_name().to_string_lossy().ends_with(ending)
+}
+
+/// Read the main page (`book/index.html`) and expose it as a DOM which we
+/// can search with the `select` crate
+fn root_index_html() -> Result<Document> {
+ let temp = DummyBook::new()
+ .build()
+ .with_context(|| "Couldn't create the dummy book")?;
+ MDBook::load(temp.path())?
+ .build()
+ .with_context(|| "Book building failed")?;
+
+ let index_page = temp.path().join("book").join("index.html");
+ let html = fs::read_to_string(&index_page).with_context(|| "Unable to read index.html")?;
+
+ Ok(Document::from(html.as_str()))
+}
+
+#[test]
+fn check_second_toc_level() {
+ let doc = root_index_html().unwrap();
+ let mut should_be = Vec::from(TOC_SECOND_LEVEL);
+ should_be.sort_unstable();
+
+ let pred = descendants!(
+ Class("chapter"),
+ Name("li"),
+ Name("li"),
+ Name("a").and(Class("toggle").not())
+ );
+
+ let mut children_of_children: Vec<_> = doc
+ .find(pred)
+ .map(|elem| elem.text().trim().to_string())
+ .collect();
+ children_of_children.sort();
+
+ assert_eq!(children_of_children, should_be);
+}
+
+#[test]
+fn check_first_toc_level() {
+ let doc = root_index_html().unwrap();
+ let mut should_be = Vec::from(TOC_TOP_LEVEL);
+
+ should_be.extend(TOC_SECOND_LEVEL);
+ should_be.sort_unstable();
+
+ let pred = descendants!(
+ Class("chapter"),
+ Name("li"),
+ Name("a").and(Class("toggle").not())
+ );
+
+ let mut children: Vec<_> = doc
+ .find(pred)
+ .map(|elem| elem.text().trim().to_string())
+ .collect();
+ children.sort();
+
+ assert_eq!(children, should_be);
+}
+
+#[test]
+fn check_spacers() {
+ let doc = root_index_html().unwrap();
+ let should_be = 2;
+
+ let num_spacers = doc
+ .find(Class("chapter").descendant(Name("li").and(Class("spacer"))))
+ .count();
+ assert_eq!(num_spacers, should_be);
+}
+
+/// Ensure building fails if `create-missing` is false and one of the files does
+/// not exist.
+#[test]
+fn failure_on_missing_file() {
+ let temp = DummyBook::new().build().unwrap();
+ fs::remove_file(temp.path().join("src").join("intro.md")).unwrap();
+
+ let mut cfg = Config::default();
+ cfg.build.create_missing = false;
+
+ let got = MDBook::load_with_config(temp.path(), cfg);
+ assert!(got.is_err());
+}
+
+/// Ensure a missing file is created if `create-missing` is true.
+#[test]
+fn create_missing_file_with_config() {
+ let temp = DummyBook::new().build().unwrap();
+ fs::remove_file(temp.path().join("src").join("intro.md")).unwrap();
+
+ let mut cfg = Config::default();
+ cfg.build.create_missing = true;
+
+ assert!(!temp.path().join("src").join("intro.md").exists());
+ let _md = MDBook::load_with_config(temp.path(), cfg).unwrap();
+ assert!(temp.path().join("src").join("intro.md").exists());
+}
+
+/// This makes sure you can include a Rust file with `{{#playground example.rs}}`.
+/// Specification is in `guide/src/format/rust.md`
+#[test]
+fn able_to_include_playground_files_in_chapters() {
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let second = temp.path().join("book/second.html");
+
+ let playground_strings = &[
+ r#"class="playground""#,
+ r#"println!(&quot;Hello World!&quot;);"#,
+ ];
+
+ assert_contains_strings(&second, playground_strings);
+ assert_doesnt_contain_strings(&second, &["{{#playground example.rs}}"]);
+}
+
+/// This makes sure you can include a Rust file with `{{#include ../SUMMARY.md}}`.
+#[test]
+fn able_to_include_files_in_chapters() {
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let includes = temp.path().join("book/first/includes.html");
+
+ let summary_strings = &[
+ r##"<h1 id="summary"><a class="header" href="#summary">Summary</a></h1>"##,
+ ">First Chapter</a>",
+ ];
+ assert_contains_strings(&includes, summary_strings);
+
+ assert_doesnt_contain_strings(&includes, &["{{#include ../SUMMARY.md::}}"]);
+}
+
+/// Ensure cyclic includes are capped so that no exceptions occur
+#[test]
+fn recursive_includes_are_capped() {
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let recursive = temp.path().join("book/first/recursive.html");
+ let content = &["Around the world, around the world
+Around the world, around the world
+Around the world, around the world"];
+ assert_contains_strings(&recursive, content);
+}
+
+#[test]
+fn example_book_can_build() {
+ let example_book_dir = dummy_book::new_copy_of_example_book().unwrap();
+
+ let md = MDBook::load(example_book_dir.path()).unwrap();
+
+ md.build().unwrap();
+}
+
+#[test]
+fn book_with_a_reserved_filename_does_not_build() {
+ let tmp_dir = TempFileBuilder::new().prefix("mdBook").tempdir().unwrap();
+ let src_path = tmp_dir.path().join("src");
+ fs::create_dir(&src_path).unwrap();
+
+ let summary_path = src_path.join("SUMMARY.md");
+ let print_path = src_path.join("print.md");
+
+ fs::File::create(print_path).unwrap();
+ let mut summary_file = fs::File::create(summary_path).unwrap();
+ writeln!(summary_file, "[print](print.md)").unwrap();
+
+ let md = MDBook::load(tmp_dir.path()).unwrap();
+ let got = md.build();
+ assert!(got.is_err());
+}
+
+#[test]
+fn by_default_mdbook_use_index_preprocessor_to_convert_readme_to_index() {
+ let temp = DummyBook::new().build().unwrap();
+ let mut cfg = Config::default();
+ cfg.set("book.src", "src2")
+ .expect("Couldn't set config.book.src to \"src2\".");
+ let md = MDBook::load_with_config(temp.path(), cfg).unwrap();
+ md.build().unwrap();
+
+ let first_index = temp.path().join("book").join("first").join("index.html");
+ let expected_strings = vec![
+ r#"href="../first/index.html""#,
+ r#"href="../second/index.html""#,
+ "First README",
+ ];
+ assert_contains_strings(&first_index, &expected_strings);
+ assert_doesnt_contain_strings(&first_index, &["README.html"]);
+
+ let second_index = temp.path().join("book").join("second").join("index.html");
+ let unexpected_strings = vec!["Second README"];
+ assert_doesnt_contain_strings(&second_index, &unexpected_strings);
+}
+
+#[test]
+fn first_chapter_is_copied_as_index_even_if_not_first_elem() {
+ let temp = DummyBook::new().build().unwrap();
+ let mut cfg = Config::default();
+ cfg.set("book.src", "index_html_test")
+ .expect("Couldn't set config.book.src to \"index_html_test\"");
+ let md = MDBook::load_with_config(temp.path(), cfg).unwrap();
+ md.build().unwrap();
+
+ let root = temp.path().join("book");
+ let chapter = fs::read_to_string(root.join("chapter_1.html")).expect("read chapter 1");
+ let index = fs::read_to_string(root.join("index.html")).expect("read index");
+ pretty_assertions::assert_eq!(chapter, index);
+}
+
+#[test]
+fn theme_dir_overrides_work_correctly() {
+ let book_dir = dummy_book::new_copy_of_example_book().unwrap();
+ let book_dir = book_dir.path();
+ let theme_dir = book_dir.join("theme");
+
+ let mut index = mdbook::theme::INDEX.to_vec();
+ index.extend_from_slice(b"\n<!-- This is a modified index.hbs! -->");
+
+ write_file(&theme_dir, "index.hbs", &index).unwrap();
+
+ let md = MDBook::load(book_dir).unwrap();
+ md.build().unwrap();
+
+ let built_index = book_dir.join("book").join("index.html");
+ dummy_book::assert_contains_strings(built_index, &["This is a modified index.hbs!"]);
+}
+
+#[test]
+fn no_index_for_print_html() {
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let print_html = temp.path().join("book/print.html");
+ assert_contains_strings(print_html, &[r##"noindex"##]);
+
+ let index_html = temp.path().join("book/index.html");
+ assert_doesnt_contain_strings(index_html, &[r##"noindex"##]);
+}
+
+#[test]
+fn markdown_options() {
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let path = temp.path().join("book/first/markdown.html");
+ assert_contains_strings(
+ &path,
+ &[
+ "<th>foo</th>",
+ "<th>bar</th>",
+ "<td>baz</td>",
+ "<td>bim</td>",
+ ],
+ );
+ assert_contains_strings(
+ &path,
+ &[
+ r##"<sup class="footnote-reference"><a href="#1">1</a></sup>"##,
+ r##"<sup class="footnote-reference"><a href="#word">2</a></sup>"##,
+ r##"<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>"##,
+ r##"<div class="footnote-definition" id="word"><sup class="footnote-definition-label">2</sup>"##,
+ ],
+ );
+ assert_contains_strings(&path, &["<del>strikethrough example</del>"]);
+ assert_contains_strings(
+ &path,
+ &[
+ "<li><input disabled=\"\" type=\"checkbox\" checked=\"\"/>\nApples",
+ "<li><input disabled=\"\" type=\"checkbox\" checked=\"\"/>\nBroccoli",
+ "<li><input disabled=\"\" type=\"checkbox\"/>\nCarrots",
+ ],
+ );
+}
+
+#[test]
+fn redirects_are_emitted_correctly() {
+ let temp = DummyBook::new().build().unwrap();
+ let mut md = MDBook::load(temp.path()).unwrap();
+
+ // override the "outputs.html.redirect" table
+ let redirects: HashMap<PathBuf, String> = vec![
+ (PathBuf::from("/overview.html"), String::from("index.html")),
+ (
+ PathBuf::from("/nexted/page.md"),
+ String::from("https://rust-lang.org/"),
+ ),
+ ]
+ .into_iter()
+ .collect();
+ md.config.set("output.html.redirect", &redirects).unwrap();
+
+ md.build().unwrap();
+
+ for (original, redirect) in &redirects {
+ let mut redirect_file = md.build_dir_for("html");
+ // append everything except the bits that make it absolute
+ // (e.g. "/" or "C:\")
+ redirect_file.extend(remove_absolute_components(original));
+ let contents = fs::read_to_string(&redirect_file).unwrap();
+ assert!(contents.contains(redirect));
+ }
+}
+
+#[test]
+fn edit_url_has_default_src_dir_edit_url() {
+ let temp = DummyBook::new().build().unwrap();
+ let book_toml = r#"
+ [book]
+ title = "implicit"
+
+ [output.html]
+ edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
+ "#;
+
+ write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
+
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let index_html = temp.path().join("book").join("index.html");
+ assert_contains_strings(
+ index_html,
+ &[
+ r#"href="https://github.com/rust-lang/mdBook/edit/master/guide/src/README.md" title="Suggest an edit""#,
+ ],
+ );
+}
+
+#[test]
+fn edit_url_has_configured_src_dir_edit_url() {
+ let temp = DummyBook::new().build().unwrap();
+ let book_toml = r#"
+ [book]
+ title = "implicit"
+ src = "src2"
+
+ [output.html]
+ edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
+ "#;
+
+ write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
+
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let index_html = temp.path().join("book").join("index.html");
+ assert_contains_strings(
+ index_html,
+ &[
+ r#"href="https://github.com/rust-lang/mdBook/edit/master/guide/src2/README.md" title="Suggest an edit""#,
+ ],
+ );
+}
+
+fn remove_absolute_components(path: &Path) -> impl Iterator<Item = Component> + '_ {
+ path.components().skip_while(|c| match c {
+ Component::Prefix(_) | Component::RootDir => true,
+ _ => false,
+ })
+}
+
+/// Checks formatting of summary names with inline elements.
+#[test]
+fn summary_with_markdown_formatting() {
+ let temp = DummyBook::new().build().unwrap();
+ let mut cfg = Config::default();
+ cfg.set("book.src", "summary-formatting").unwrap();
+ let md = MDBook::load_with_config(temp.path(), cfg).unwrap();
+ md.build().unwrap();
+
+ let rendered_path = temp.path().join("book/formatted-summary.html");
+ assert_contains_strings(
+ rendered_path,
+ &[
+ r#"<a href="formatted-summary.html" class="active"><strong aria-hidden="true">1.</strong> Italic code *escape* `escape2`</a>"#,
+ r#"<a href="soft.html"><strong aria-hidden="true">2.</strong> Soft line break</a>"#,
+ r#"<a href="escaped-tag.html"><strong aria-hidden="true">3.</strong> &lt;escaped tag&gt;</a>"#,
+ ],
+ );
+
+ let generated_md = temp.path().join("summary-formatting/formatted-summary.md");
+ assert_eq!(
+ fs::read_to_string(generated_md).unwrap(),
+ "# Italic code *escape* `escape2`\n"
+ );
+ let generated_md = temp.path().join("summary-formatting/soft.md");
+ assert_eq!(
+ fs::read_to_string(generated_md).unwrap(),
+ "# Soft line break\n"
+ );
+ let generated_md = temp.path().join("summary-formatting/escaped-tag.md");
+ assert_eq!(
+ fs::read_to_string(generated_md).unwrap(),
+ "# &lt;escaped tag&gt;\n"
+ );
+}
+
+/// Ensure building fails if `[output.html].theme` points to a non-existent directory
+#[test]
+fn failure_on_missing_theme_directory() {
+ // 1. Using default theme should work
+ let temp = DummyBook::new().build().unwrap();
+ let book_toml = r#"
+ [book]
+ title = "implicit"
+ src = "src"
+ "#;
+
+ write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ let got = md.build();
+ assert!(got.is_ok());
+
+ // 2. Pointing to a normal directory should work
+ let temp = DummyBook::new().build().unwrap();
+ let created = fs::create_dir(temp.path().join("theme-directory"));
+ assert!(created.is_ok());
+ let book_toml = r#"
+ [book]
+ title = "implicit"
+ src = "src"
+
+ [output.html]
+ theme = "./theme-directory"
+ "#;
+
+ write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ let got = md.build();
+ assert!(got.is_ok());
+
+ // 3. Pointing to a non-existent directory should fail
+ let temp = DummyBook::new().build().unwrap();
+ let book_toml = r#"
+ [book]
+ title = "implicit"
+ src = "src"
+
+ [output.html]
+ theme = "./non-existent-directory"
+ "#;
+
+ write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ let got = md.build();
+ assert!(got.is_err());
+}
+
+#[cfg(feature = "search")]
+mod search {
+ use crate::dummy_book::DummyBook;
+ use mdbook::MDBook;
+ use std::fs::{self, File};
+ use std::path::Path;
+
+ fn read_book_index(root: &Path) -> serde_json::Value {
+ let index = root.join("book/searchindex.js");
+ let index = fs::read_to_string(index).unwrap();
+ let index = index.trim_start_matches("Object.assign(window.search, ");
+ let index = index.trim_end_matches(");");
+ serde_json::from_str(index).unwrap()
+ }
+
+ #[test]
+ #[allow(clippy::float_cmp)]
+ fn book_creates_reasonable_search_index() {
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let index = read_book_index(temp.path());
+
+ let doc_urls = index["doc_urls"].as_array().unwrap();
+ let get_doc_ref =
+ |url: &str| -> String { doc_urls.iter().position(|s| s == url).unwrap().to_string() };
+
+ let first_chapter = get_doc_ref("first/index.html#first-chapter");
+ let introduction = get_doc_ref("intro.html#introduction");
+ let some_section = get_doc_ref("first/index.html#some-section");
+ let summary = get_doc_ref("first/includes.html#summary");
+ let no_headers = get_doc_ref("first/no-headers.html");
+ let duplicate_headers_1 = get_doc_ref("first/duplicate-headers.html#header-text-1");
+ let conclusion = get_doc_ref("conclusion.html#conclusion");
+
+ let bodyidx = &index["index"]["index"]["body"]["root"];
+ let textidx = &bodyidx["t"]["e"]["x"]["t"];
+ assert_eq!(textidx["df"], 5);
+ assert_eq!(textidx["docs"][&first_chapter]["tf"], 1.0);
+ assert_eq!(textidx["docs"][&introduction]["tf"], 1.0);
+
+ let docs = &index["index"]["documentStore"]["docs"];
+ assert_eq!(docs[&first_chapter]["body"], "more text.");
+ assert_eq!(docs[&some_section]["body"], "");
+ assert_eq!(
+ docs[&summary]["body"],
+ "Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode No Headers Duplicate Headers Second Chapter Nested Chapter Conclusion"
+ );
+ assert_eq!(
+ docs[&summary]["breadcrumbs"],
+ "First Chapter » Includes » Summary"
+ );
+ assert_eq!(docs[&conclusion]["body"], "I put &lt;HTML&gt; in here!");
+ assert_eq!(
+ docs[&no_headers]["breadcrumbs"],
+ "First Chapter » No Headers"
+ );
+ assert_eq!(
+ docs[&duplicate_headers_1]["breadcrumbs"],
+ "First Chapter » Duplicate Headers » Header Text"
+ );
+ assert_eq!(
+ docs[&no_headers]["body"],
+ "Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex."
+ );
+ }
+
+ // Setting this to `true` may cause issues with `cargo watch`,
+ // since it may not finish writing the fixture before the tests
+ // are run again.
+ const GENERATE_FIXTURE: bool = false;
+
+ fn get_fixture() -> serde_json::Value {
+ if GENERATE_FIXTURE {
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let src = read_book_index(temp.path());
+
+ let dest = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/searchindex_fixture.json");
+ let dest = File::create(&dest).unwrap();
+ serde_json::to_writer_pretty(dest, &src).unwrap();
+
+ src
+ } else {
+ let json = include_str!("searchindex_fixture.json");
+ serde_json::from_str(json).expect("Unable to deserialize the fixture")
+ }
+ }
+
+ // So you've broken the test. If you changed dummy_book, it's probably
+ // safe to regenerate the fixture. If you haven't then make sure that the
+ // search index still works. Run `cargo run -- serve tests/dummy_book`
+ // and try some searches. Are you getting results? Do the teasers look OK?
+ // Are there new errors in the JS console?
+ //
+ // If you're pretty sure you haven't broken anything, change `GENERATE_FIXTURE`
+ // above to `true`, and run `cargo test` to generate a new fixture. Then
+ // **change it back to `false`**. Include the changed `searchindex_fixture.json` in your commit.
+ #[test]
+ fn search_index_hasnt_changed_accidentally() {
+ let temp = DummyBook::new().build().unwrap();
+ let md = MDBook::load(temp.path()).unwrap();
+ md.build().unwrap();
+
+ let book_index = read_book_index(temp.path());
+
+ let fixture_index = get_fixture();
+
+ // Uncomment this if you're okay with pretty-printing 32KB of JSON
+ //assert_eq!(fixture_index, book_index);
+
+ if book_index != fixture_index {
+ panic!("The search index has changed from the fixture");
+ }
+ }
+}
diff --git a/vendor/mdbook/tests/searchindex_fixture.json b/vendor/mdbook/tests/searchindex_fixture.json
new file mode 100644
index 000000000..3d7062d23
--- /dev/null
+++ b/vendor/mdbook/tests/searchindex_fixture.json
@@ -0,0 +1,6991 @@
+{
+ "doc_urls": [
+ "index.html#dummy-book",
+ "intro.html#introduction",
+ "first/index.html#first-chapter",
+ "first/index.html#some-section",
+ "first/nested.html#nested-chapter",
+ "first/nested.html#some-section",
+ "first/nested.html#anchors-include-the-part-of-a-file-between-special-comments",
+ "first/nested.html#rustdoc-include-adds-the-rest-of-the-file-as-hidden",
+ "first/nested.html#rustdoc-include-works-with-anchors-too",
+ "first/includes.html#includes",
+ "first/includes.html#summary",
+ "first/recursive.html",
+ "first/markdown.html#markdown-tests",
+ "first/markdown.html#tables",
+ "first/markdown.html#footnotes",
+ "first/markdown.html#strikethrough",
+ "first/markdown.html#tasklisks",
+ "first/unicode.html#unicode-stress-tests",
+ "first/no-headers.html",
+ "first/duplicate-headers.html#duplicate-headers",
+ "first/duplicate-headers.html#header-text",
+ "first/duplicate-headers.html#header-text-1",
+ "first/duplicate-headers.html#header-text-2",
+ "second.html#second-chapter",
+ "second/nested.html#testing-relative-links-for-the-print-page",
+ "second/nested.html#some-section",
+ "conclusion.html#conclusion"
+ ],
+ "index": {
+ "documentStore": {
+ "docInfo": {
+ "0": {
+ "body": 9,
+ "breadcrumbs": 4,
+ "title": 2
+ },
+ "1": {
+ "body": 3,
+ "breadcrumbs": 2,
+ "title": 1
+ },
+ "10": {
+ "body": 19,
+ "breadcrumbs": 4,
+ "title": 1
+ },
+ "11": {
+ "body": 44,
+ "breadcrumbs": 3,
+ "title": 2
+ },
+ "12": {
+ "body": 3,
+ "breadcrumbs": 5,
+ "title": 2
+ },
+ "13": {
+ "body": 4,
+ "breadcrumbs": 4,
+ "title": 1
+ },
+ "14": {
+ "body": 12,
+ "breadcrumbs": 4,
+ "title": 1
+ },
+ "15": {
+ "body": 2,
+ "breadcrumbs": 4,
+ "title": 1
+ },
+ "16": {
+ "body": 3,
+ "breadcrumbs": 4,
+ "title": 1
+ },
+ "17": {
+ "body": 29,
+ "breadcrumbs": 6,
+ "title": 3
+ },
+ "18": {
+ "body": 6,
+ "breadcrumbs": 3,
+ "title": 2
+ },
+ "19": {
+ "body": 5,
+ "breadcrumbs": 6,
+ "title": 2
+ },
+ "2": {
+ "body": 2,
+ "breadcrumbs": 4,
+ "title": 2
+ },
+ "20": {
+ "body": 0,
+ "breadcrumbs": 6,
+ "title": 2
+ },
+ "21": {
+ "body": 0,
+ "breadcrumbs": 6,
+ "title": 2
+ },
+ "22": {
+ "body": 0,
+ "breadcrumbs": 6,
+ "title": 2
+ },
+ "23": {
+ "body": 20,
+ "breadcrumbs": 4,
+ "title": 2
+ },
+ "24": {
+ "body": 18,
+ "breadcrumbs": 9,
+ "title": 5
+ },
+ "25": {
+ "body": 0,
+ "breadcrumbs": 5,
+ "title": 1
+ },
+ "26": {
+ "body": 3,
+ "breadcrumbs": 2,
+ "title": 1
+ },
+ "3": {
+ "body": 0,
+ "breadcrumbs": 3,
+ "title": 1
+ },
+ "4": {
+ "body": 4,
+ "breadcrumbs": 6,
+ "title": 2
+ },
+ "5": {
+ "body": 1,
+ "breadcrumbs": 5,
+ "title": 1
+ },
+ "6": {
+ "body": 21,
+ "breadcrumbs": 11,
+ "title": 7
+ },
+ "7": {
+ "body": 6,
+ "breadcrumbs": 10,
+ "title": 6
+ },
+ "8": {
+ "body": 6,
+ "breadcrumbs": 8,
+ "title": 4
+ },
+ "9": {
+ "body": 0,
+ "breadcrumbs": 4,
+ "title": 1
+ }
+ },
+ "docs": {
+ "0": {
+ "body": "This file is just here to cause the index preprocessor to run. Does a pretty good job, too.",
+ "breadcrumbs": "Dummy Book » Dummy Book",
+ "id": "0",
+ "title": "Dummy Book"
+ },
+ "1": {
+ "body": "Here's some interesting text...",
+ "breadcrumbs": "Introduction » Introduction",
+ "id": "1",
+ "title": "Introduction"
+ },
+ "10": {
+ "body": "Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode No Headers Duplicate Headers Second Chapter Nested Chapter Conclusion",
+ "breadcrumbs": "First Chapter » Includes » Summary",
+ "id": "10",
+ "title": "Summary"
+ },
+ "11": {
+ "body": "Around the world, around the world Around the world, around the world Around the world, around the world Around the world, around the world Around the world, around the world Around the world, around the world Around the world, around the world Around the world, around the world Around the world, around the world Around the world, around the world Around the world, around the world",
+ "breadcrumbs": "First Chapter » Recursive",
+ "id": "11",
+ "title": "First Chapter"
+ },
+ "12": {
+ "body": "Tests for some markdown output.",
+ "breadcrumbs": "First Chapter » Markdown » Markdown tests",
+ "id": "12",
+ "title": "Markdown tests"
+ },
+ "13": {
+ "body": "foo bar baz bim",
+ "breadcrumbs": "First Chapter » Markdown » Tables",
+ "id": "13",
+ "title": "Tables"
+ },
+ "14": {
+ "body": "Footnote example [1] , or with a word [2] . This is a footnote. A longer footnote. With multiple lines. Third line.",
+ "breadcrumbs": "First Chapter » Markdown » Footnotes",
+ "id": "14",
+ "title": "Footnotes"
+ },
+ "15": {
+ "body": "strikethrough example",
+ "breadcrumbs": "First Chapter » Markdown » Strikethrough",
+ "id": "15",
+ "title": "Strikethrough"
+ },
+ "16": {
+ "body": "Apples Broccoli Carrots",
+ "breadcrumbs": "First Chapter » Markdown » Tasklisks",
+ "id": "16",
+ "title": "Tasklisks"
+ },
+ "17": {
+ "body": "Please be careful editing, this contains carefully crafted characters. Two byte character: spatiëring Combining character: spatiëring Three byte character: 书こんにちは Four byte character: 𐌀‮𐌁‮𐌂‮𐌃‮𐌄‮𐌅‮𐌆‮𐌇‮𐌈‬ Right-to-left: مرحبا Emoticons: 🔊 😍 💜 1️⃣ right-to-left mark: hello באמת!‏ Zalgo: ǫ̛̖̱̗̝͈̋͒͋̏ͥͫ̒̆ͩ̏͌̾͊͐ͪ̾̚",
+ "breadcrumbs": "First Chapter » Unicode » Unicode stress tests",
+ "id": "17",
+ "title": "Unicode stress tests"
+ },
+ "18": {
+ "body": "Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex.",
+ "breadcrumbs": "First Chapter » No Headers",
+ "id": "18",
+ "title": "First Chapter"
+ },
+ "19": {
+ "body": "This page validates behaviour of duplicate headers.",
+ "breadcrumbs": "First Chapter » Duplicate Headers » Duplicate headers",
+ "id": "19",
+ "title": "Duplicate headers"
+ },
+ "2": {
+ "body": "more text.",
+ "breadcrumbs": "First Chapter » First Chapter",
+ "id": "2",
+ "title": "First Chapter"
+ },
+ "20": {
+ "body": "",
+ "breadcrumbs": "First Chapter » Duplicate Headers » Header Text",
+ "id": "20",
+ "title": "Header Text"
+ },
+ "21": {
+ "body": "",
+ "breadcrumbs": "First Chapter » Duplicate Headers » Header Text",
+ "id": "21",
+ "title": "Header Text"
+ },
+ "22": {
+ "body": "",
+ "breadcrumbs": "First Chapter » Duplicate Headers » header-text",
+ "id": "22",
+ "title": "header-text"
+ },
+ "23": {
+ "body": "This makes sure you can insert runnable Rust files. fn main() { println!(\"Hello World!\");\n#\n# // You can even hide lines! :D\n# println!(\"I am hidden! Expand the code snippet to see me\");\n}",
+ "breadcrumbs": "Second Chapter » Second Chapter",
+ "id": "23",
+ "title": "Second Chapter"
+ },
+ "24": {
+ "body": "When we link to the first section , it should work on both the print page and the non-print page. A fragment link should work. Link outside . Some image HTML Link",
+ "breadcrumbs": "Second Chapter » Nested Chapter » Testing relative links for the print page",
+ "id": "24",
+ "title": "Testing relative links for the print page"
+ },
+ "25": {
+ "body": "",
+ "breadcrumbs": "Second Chapter » Nested Chapter » Some section",
+ "id": "25",
+ "title": "Some section"
+ },
+ "26": {
+ "body": "I put &lt;HTML&gt; in here!",
+ "breadcrumbs": "Conclusion » Conclusion",
+ "id": "26",
+ "title": "Conclusion"
+ },
+ "3": {
+ "body": "",
+ "breadcrumbs": "First Chapter » Some Section",
+ "id": "3",
+ "title": "Some Section"
+ },
+ "4": {
+ "body": "This file has some testable code. assert!(true);",
+ "breadcrumbs": "First Chapter » Nested Chapter » Nested Chapter",
+ "id": "4",
+ "title": "Nested Chapter"
+ },
+ "5": {
+ "body": "assert!(true);",
+ "breadcrumbs": "First Chapter » Nested Chapter » Some Section",
+ "id": "5",
+ "title": "Some Section"
+ },
+ "6": {
+ "body": "// The next line will cause a `rendered_output` test to fail if the anchor feature is broken in\n// such a way that the content between anchors isn't included.\n// unique-string-for-anchor-test\nassert!(true);",
+ "breadcrumbs": "First Chapter » Nested Chapter » Anchors include the part of a file between special comments",
+ "id": "6",
+ "title": "Anchors include the part of a file between special comments"
+ },
+ "7": {
+ "body": "# fn some_function() {\n# assert!(true);\n# }\n# fn main() { some_function();\n}",
+ "breadcrumbs": "First Chapter » Nested Chapter » Rustdoc include adds the rest of the file as hidden",
+ "id": "7",
+ "title": "Rustdoc include adds the rest of the file as hidden"
+ },
+ "8": {
+ "body": "# fn some_other_function() {\n# assert!(true);\n# }\n# fn main() { some_other_function();\n}",
+ "breadcrumbs": "First Chapter » Nested Chapter » Rustdoc include works with anchors too",
+ "id": "8",
+ "title": "Rustdoc include works with anchors too"
+ },
+ "9": {
+ "body": "",
+ "breadcrumbs": "First Chapter » Includes » Includes",
+ "id": "9",
+ "title": "Includes"
+ }
+ },
+ "length": 27,
+ "save": true
+ },
+ "fields": [
+ "title",
+ "body",
+ "breadcrumbs"
+ ],
+ "index": {
+ "body": {
+ "root": {
+ "1": {
+ "df": 2,
+ "docs": {
+ "14": {
+ "tf": 1.0
+ },
+ "17": {
+ "tf": 1.0
+ }
+ }
+ },
+ "2": {
+ "df": 1,
+ "docs": {
+ "14": {
+ "tf": 1.0
+ }
+ }
+ },
+ "a": {
+ "d": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "7": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {},
+ "n": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 2,
+ "docs": {
+ "6": {
+ "tf": 2.0
+ },
+ "8": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "p": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "16": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "11": {
+ "tf": 4.69041575982343
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ },
+ "s": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "!": {
+ "(": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 5,
+ "docs": {
+ "4": {
+ "tf": 1.0
+ },
+ "5": {
+ "tf": 1.0
+ },
+ "6": {
+ "tf": 1.0
+ },
+ "7": {
+ "tf": 1.0
+ },
+ "8": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "b": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 1,
+ "docs": {
+ "13": {
+ "tf": 1.0
+ }
+ }
+ },
+ "z": {
+ "df": 1,
+ "docs": {
+ "13": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "v": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 1,
+ "docs": {
+ "19": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "w": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 1,
+ "docs": {
+ "13": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 2,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ },
+ "10": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "c": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 1,
+ "docs": {
+ "16": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "y": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.7320508075688772
+ }
+ }
+ }
+ }
+ }
+ },
+ "c": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "y": {
+ "b": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "a": {
+ "df": 1,
+ "docs": {
+ "18": {
+ "tf": 2.449489742783178
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ },
+ "f": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "16": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 2,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ },
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "h": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 6,
+ "docs": {
+ "10": {
+ "tf": 2.0
+ },
+ "11": {
+ "tf": 1.0
+ },
+ "18": {
+ "tf": 1.0
+ },
+ "2": {
+ "tf": 1.0
+ },
+ "23": {
+ "tf": 1.0
+ },
+ "4": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "a": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 2.23606797749979
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "o": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 2,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ },
+ "4": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "m": {
+ "b": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "n": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 2,
+ "docs": {
+ "10": {
+ "tf": 1.0
+ },
+ "26": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "t": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "f": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "d": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 2,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ },
+ "10": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "p": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "c": {
+ "df": 2,
+ "docs": {
+ "10": {
+ "tf": 1.0
+ },
+ "19": {
+ "tf": 1.4142135623730951
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ },
+ "v": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "x": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 2,
+ "docs": {
+ "14": {
+ "tf": 1.0
+ },
+ "15": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "p": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "f": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "i": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 5,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ },
+ "23": {
+ "tf": 1.0
+ },
+ "4": {
+ "tf": 1.0
+ },
+ "6": {
+ "tf": 1.0
+ },
+ "7": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 5,
+ "docs": {
+ "10": {
+ "tf": 1.0
+ },
+ "11": {
+ "tf": 1.0
+ },
+ "18": {
+ "tf": 1.0
+ },
+ "2": {
+ "tf": 1.0
+ },
+ "24": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "n": {
+ "df": 3,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ },
+ "7": {
+ "tf": 1.4142135623730951
+ },
+ "8": {
+ "tf": 1.4142135623730951
+ }
+ }
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 1,
+ "docs": {
+ "13": {
+ "tf": 1.0
+ }
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "14": {
+ "tf": 2.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "g": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "h": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "a": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 5,
+ "docs": {
+ "10": {
+ "tf": 1.4142135623730951
+ },
+ "19": {
+ "tf": 1.4142135623730951
+ },
+ "20": {
+ "tf": 1.0
+ },
+ "21": {
+ "tf": 1.0
+ },
+ "22": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "'": {
+ "df": 1,
+ "docs": {
+ "1": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 2,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ },
+ "26": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "d": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 2,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ },
+ "7": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "n": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "d": {
+ "df": 5,
+ "docs": {
+ "10": {
+ "tf": 1.0
+ },
+ "6": {
+ "tf": 1.4142135623730951
+ },
+ "7": {
+ "tf": 1.0
+ },
+ "8": {
+ "tf": 1.0
+ },
+ "9": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "d": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "x": {
+ "df": 1,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "1": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 2,
+ "docs": {
+ "1": {
+ "tf": 1.0
+ },
+ "10": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ },
+ "s": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "'": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "j": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "b": {
+ "df": 1,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "l": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "f": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 3,
+ "docs": {
+ "14": {
+ "tf": 1.4142135623730951
+ },
+ "23": {
+ "tf": 1.0
+ },
+ "6": {
+ "tf": 1.0
+ }
+ }
+ },
+ "k": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 2.23606797749979
+ }
+ }
+ }
+ }
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 1,
+ "docs": {
+ "14": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "t": {
+ ";": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "&": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "26": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "m": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 3,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ },
+ "7": {
+ "tf": 1.0
+ },
+ "8": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "k": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "w": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 2,
+ "docs": {
+ "10": {
+ "tf": 1.0
+ },
+ "12": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "2": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "14": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "n": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 2,
+ "docs": {
+ "10": {
+ "tf": 1.4142135623730951
+ },
+ "4": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "x": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "12": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "s": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ }
+ },
+ "p": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 2,
+ "docs": {
+ "19": {
+ "tf": 1.0
+ },
+ "24": {
+ "tf": 1.7320508075688772
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 1,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 1,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.7320508075688772
+ }
+ },
+ "l": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "!": {
+ "(": {
+ "\"": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "26": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 1,
+ "docs": {
+ "10": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ },
+ "n": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "d": {
+ "_": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "7": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ }
+ },
+ "n": {
+ "a": {
+ "b": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "c": {
+ "df": 2,
+ "docs": {
+ "7": {
+ "tf": 1.0
+ },
+ "8": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "s": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "d": {
+ "df": 2,
+ "docs": {
+ "10": {
+ "tf": 1.0
+ },
+ "23": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 4,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ },
+ "25": {
+ "tf": 1.0
+ },
+ "3": {
+ "tf": 1.0
+ },
+ "5": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "n": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "_": {
+ "df": 0,
+ "docs": {},
+ "f": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "7": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "_": {
+ "df": 0,
+ "docs": {},
+ "f": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "8": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "p": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "̈": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "ë": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 1,
+ "docs": {
+ "15": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "n": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 1,
+ "docs": {
+ "10": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "t": {
+ "a": {
+ "b": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "13": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 1,
+ "docs": {
+ "16": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "a": {
+ "b": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "4": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 4,
+ "docs": {
+ "12": {
+ "tf": 1.4142135623730951
+ },
+ "17": {
+ "tf": 1.0
+ },
+ "24": {
+ "tf": 1.0
+ },
+ "6": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ },
+ "x": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 5,
+ "docs": {
+ "1": {
+ "tf": 1.0
+ },
+ "2": {
+ "tf": 1.0
+ },
+ "20": {
+ "tf": 1.0
+ },
+ "21": {
+ "tf": 1.0
+ },
+ "22": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "h": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "14": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "w": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "d": {
+ "df": 2,
+ "docs": {
+ "10": {
+ "tf": 1.0
+ },
+ "17": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "q": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "v": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "19": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "w": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "y": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "14": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 2,
+ "docs": {
+ "24": {
+ "tf": 1.4142135623730951
+ },
+ "8": {
+ "tf": 1.0
+ }
+ }
+ },
+ "l": {
+ "d": {
+ "df": 2,
+ "docs": {
+ "11": {
+ "tf": 4.69041575982343
+ },
+ "23": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ },
+ "z": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "breadcrumbs": {
+ "root": {
+ "1": {
+ "df": 2,
+ "docs": {
+ "14": {
+ "tf": 1.0
+ },
+ "17": {
+ "tf": 1.0
+ }
+ }
+ },
+ "2": {
+ "df": 1,
+ "docs": {
+ "14": {
+ "tf": 1.0
+ }
+ }
+ },
+ "a": {
+ "d": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "7": {
+ "tf": 1.4142135623730951
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {},
+ "n": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 2,
+ "docs": {
+ "6": {
+ "tf": 2.23606797749979
+ },
+ "8": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "p": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "16": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "11": {
+ "tf": 4.69041575982343
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ },
+ "s": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "!": {
+ "(": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 5,
+ "docs": {
+ "4": {
+ "tf": 1.0
+ },
+ "5": {
+ "tf": 1.0
+ },
+ "6": {
+ "tf": 1.0
+ },
+ "7": {
+ "tf": 1.0
+ },
+ "8": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "b": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 1,
+ "docs": {
+ "13": {
+ "tf": 1.0
+ }
+ }
+ },
+ "z": {
+ "df": 1,
+ "docs": {
+ "13": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "v": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 1,
+ "docs": {
+ "19": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "w": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.7320508075688772
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 1,
+ "docs": {
+ "13": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 2,
+ "docs": {
+ "0": {
+ "tf": 1.7320508075688772
+ },
+ "10": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "c": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 1,
+ "docs": {
+ "16": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "y": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.7320508075688772
+ }
+ }
+ }
+ }
+ }
+ },
+ "c": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "y": {
+ "b": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "a": {
+ "df": 1,
+ "docs": {
+ "18": {
+ "tf": 2.449489742783178
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ },
+ "f": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "16": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 2,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ },
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "h": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 24,
+ "docs": {
+ "10": {
+ "tf": 2.23606797749979
+ },
+ "11": {
+ "tf": 1.4142135623730951
+ },
+ "12": {
+ "tf": 1.0
+ },
+ "13": {
+ "tf": 1.0
+ },
+ "14": {
+ "tf": 1.0
+ },
+ "15": {
+ "tf": 1.0
+ },
+ "16": {
+ "tf": 1.0
+ },
+ "17": {
+ "tf": 1.0
+ },
+ "18": {
+ "tf": 1.4142135623730951
+ },
+ "19": {
+ "tf": 1.0
+ },
+ "2": {
+ "tf": 1.7320508075688772
+ },
+ "20": {
+ "tf": 1.0
+ },
+ "21": {
+ "tf": 1.0
+ },
+ "22": {
+ "tf": 1.0
+ },
+ "23": {
+ "tf": 1.7320508075688772
+ },
+ "24": {
+ "tf": 1.4142135623730951
+ },
+ "25": {
+ "tf": 1.4142135623730951
+ },
+ "3": {
+ "tf": 1.0
+ },
+ "4": {
+ "tf": 2.0
+ },
+ "5": {
+ "tf": 1.4142135623730951
+ },
+ "6": {
+ "tf": 1.4142135623730951
+ },
+ "7": {
+ "tf": 1.4142135623730951
+ },
+ "8": {
+ "tf": 1.4142135623730951
+ },
+ "9": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "a": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 2.23606797749979
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "o": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 2,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ },
+ "4": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "m": {
+ "b": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "n": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 2,
+ "docs": {
+ "10": {
+ "tf": 1.0
+ },
+ "26": {
+ "tf": 1.7320508075688772
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "t": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "f": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "d": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 2,
+ "docs": {
+ "0": {
+ "tf": 1.7320508075688772
+ },
+ "10": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "p": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "c": {
+ "df": 5,
+ "docs": {
+ "10": {
+ "tf": 1.0
+ },
+ "19": {
+ "tf": 2.0
+ },
+ "20": {
+ "tf": 1.0
+ },
+ "21": {
+ "tf": 1.0
+ },
+ "22": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ },
+ "v": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "x": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 2,
+ "docs": {
+ "14": {
+ "tf": 1.0
+ },
+ "15": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "p": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "f": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "i": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 5,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ },
+ "23": {
+ "tf": 1.0
+ },
+ "4": {
+ "tf": 1.0
+ },
+ "6": {
+ "tf": 1.4142135623730951
+ },
+ "7": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 22,
+ "docs": {
+ "10": {
+ "tf": 1.4142135623730951
+ },
+ "11": {
+ "tf": 1.4142135623730951
+ },
+ "12": {
+ "tf": 1.0
+ },
+ "13": {
+ "tf": 1.0
+ },
+ "14": {
+ "tf": 1.0
+ },
+ "15": {
+ "tf": 1.0
+ },
+ "16": {
+ "tf": 1.0
+ },
+ "17": {
+ "tf": 1.0
+ },
+ "18": {
+ "tf": 1.4142135623730951
+ },
+ "19": {
+ "tf": 1.0
+ },
+ "2": {
+ "tf": 1.7320508075688772
+ },
+ "20": {
+ "tf": 1.0
+ },
+ "21": {
+ "tf": 1.0
+ },
+ "22": {
+ "tf": 1.0
+ },
+ "24": {
+ "tf": 1.0
+ },
+ "3": {
+ "tf": 1.0
+ },
+ "4": {
+ "tf": 1.0
+ },
+ "5": {
+ "tf": 1.0
+ },
+ "6": {
+ "tf": 1.0
+ },
+ "7": {
+ "tf": 1.0
+ },
+ "8": {
+ "tf": 1.0
+ },
+ "9": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "n": {
+ "df": 3,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ },
+ "7": {
+ "tf": 1.4142135623730951
+ },
+ "8": {
+ "tf": 1.4142135623730951
+ }
+ }
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 1,
+ "docs": {
+ "13": {
+ "tf": 1.0
+ }
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "14": {
+ "tf": 2.23606797749979
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "g": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "h": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "a": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 6,
+ "docs": {
+ "10": {
+ "tf": 1.4142135623730951
+ },
+ "18": {
+ "tf": 1.0
+ },
+ "19": {
+ "tf": 2.0
+ },
+ "20": {
+ "tf": 1.7320508075688772
+ },
+ "21": {
+ "tf": 1.7320508075688772
+ },
+ "22": {
+ "tf": 1.7320508075688772
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "'": {
+ "df": 1,
+ "docs": {
+ "1": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 2,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ },
+ "26": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "d": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 2,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ },
+ "7": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "n": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "d": {
+ "df": 5,
+ "docs": {
+ "10": {
+ "tf": 1.4142135623730951
+ },
+ "6": {
+ "tf": 1.7320508075688772
+ },
+ "7": {
+ "tf": 1.4142135623730951
+ },
+ "8": {
+ "tf": 1.4142135623730951
+ },
+ "9": {
+ "tf": 1.7320508075688772
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "d": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "x": {
+ "df": 1,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "1": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 2,
+ "docs": {
+ "1": {
+ "tf": 1.7320508075688772
+ },
+ "10": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ },
+ "s": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "'": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "j": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "b": {
+ "df": 1,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "l": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "f": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 3,
+ "docs": {
+ "14": {
+ "tf": 1.4142135623730951
+ },
+ "23": {
+ "tf": 1.0
+ },
+ "6": {
+ "tf": 1.0
+ }
+ }
+ },
+ "k": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 2.449489742783178
+ }
+ }
+ }
+ }
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 1,
+ "docs": {
+ "14": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "t": {
+ ";": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "&": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "26": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "m": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 3,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ },
+ "7": {
+ "tf": 1.0
+ },
+ "8": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "k": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "w": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 6,
+ "docs": {
+ "10": {
+ "tf": 1.0
+ },
+ "12": {
+ "tf": 2.0
+ },
+ "13": {
+ "tf": 1.0
+ },
+ "14": {
+ "tf": 1.0
+ },
+ "15": {
+ "tf": 1.0
+ },
+ "16": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "2": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "14": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "n": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 8,
+ "docs": {
+ "10": {
+ "tf": 1.4142135623730951
+ },
+ "24": {
+ "tf": 1.0
+ },
+ "25": {
+ "tf": 1.0
+ },
+ "4": {
+ "tf": 1.7320508075688772
+ },
+ "5": {
+ "tf": 1.0
+ },
+ "6": {
+ "tf": 1.0
+ },
+ "7": {
+ "tf": 1.0
+ },
+ "8": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "x": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "12": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "s": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ }
+ },
+ "p": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 2,
+ "docs": {
+ "19": {
+ "tf": 1.0
+ },
+ "24": {
+ "tf": 2.0
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 1,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 1,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 2.0
+ }
+ },
+ "l": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "!": {
+ "(": {
+ "\"": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "26": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 2,
+ "docs": {
+ "10": {
+ "tf": 1.0
+ },
+ "11": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.4142135623730951
+ }
+ }
+ },
+ "n": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "d": {
+ "_": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "7": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ }
+ },
+ "n": {
+ "a": {
+ "b": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "c": {
+ "df": 2,
+ "docs": {
+ "7": {
+ "tf": 1.4142135623730951
+ },
+ "8": {
+ "tf": 1.4142135623730951
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "s": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "d": {
+ "df": 4,
+ "docs": {
+ "10": {
+ "tf": 1.0
+ },
+ "23": {
+ "tf": 1.7320508075688772
+ },
+ "24": {
+ "tf": 1.0
+ },
+ "25": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 4,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ },
+ "25": {
+ "tf": 1.4142135623730951
+ },
+ "3": {
+ "tf": 1.4142135623730951
+ },
+ "5": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "n": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "_": {
+ "df": 0,
+ "docs": {},
+ "f": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "7": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "_": {
+ "df": 0,
+ "docs": {},
+ "f": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "8": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "p": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "̈": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "ë": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 1,
+ "docs": {
+ "15": {
+ "tf": 1.7320508075688772
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "n": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 1,
+ "docs": {
+ "10": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "t": {
+ "a": {
+ "b": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "13": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 1,
+ "docs": {
+ "16": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "a": {
+ "b": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "4": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 4,
+ "docs": {
+ "12": {
+ "tf": 1.7320508075688772
+ },
+ "17": {
+ "tf": 1.4142135623730951
+ },
+ "24": {
+ "tf": 1.4142135623730951
+ },
+ "6": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ },
+ "x": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 5,
+ "docs": {
+ "1": {
+ "tf": 1.0
+ },
+ "2": {
+ "tf": 1.0
+ },
+ "20": {
+ "tf": 1.4142135623730951
+ },
+ "21": {
+ "tf": 1.4142135623730951
+ },
+ "22": {
+ "tf": 1.4142135623730951
+ }
+ }
+ }
+ }
+ },
+ "h": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "14": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "w": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "d": {
+ "df": 2,
+ "docs": {
+ "10": {
+ "tf": 1.0
+ },
+ "17": {
+ "tf": 1.7320508075688772
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "q": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "v": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "19": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "w": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "y": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "14": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 2,
+ "docs": {
+ "24": {
+ "tf": 1.4142135623730951
+ },
+ "8": {
+ "tf": 1.4142135623730951
+ }
+ }
+ },
+ "l": {
+ "d": {
+ "df": 2,
+ "docs": {
+ "11": {
+ "tf": 4.69041575982343
+ },
+ "23": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ },
+ "z": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "title": {
+ "root": {
+ "a": {
+ "d": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "7": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {},
+ "n": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 2,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ },
+ "8": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "b": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "w": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 1,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "c": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "p": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 5,
+ "docs": {
+ "11": {
+ "tf": 1.0
+ },
+ "18": {
+ "tf": 1.0
+ },
+ "2": {
+ "tf": 1.0
+ },
+ "23": {
+ "tf": 1.0
+ },
+ "4": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "n": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 1,
+ "docs": {
+ "26": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "d": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 1,
+ "docs": {
+ "0": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "p": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "c": {
+ "df": 1,
+ "docs": {
+ "19": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "f": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 2,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ },
+ "7": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 3,
+ "docs": {
+ "11": {
+ "tf": 1.0
+ },
+ "18": {
+ "tf": 1.0
+ },
+ "2": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "o": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "14": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "h": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "a": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 4,
+ "docs": {
+ "19": {
+ "tf": 1.0
+ },
+ "20": {
+ "tf": 1.0
+ },
+ "21": {
+ "tf": 1.0
+ },
+ "22": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "i": {
+ "d": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "7": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "i": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "d": {
+ "df": 4,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ },
+ "7": {
+ "tf": 1.0
+ },
+ "8": {
+ "tf": 1.0
+ },
+ "9": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "1": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ }
+ },
+ "l": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "m": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "w": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 1,
+ "docs": {
+ "12": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "n": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "4": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "p": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "24": {
+ "tf": 1.0
+ }
+ }
+ },
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 1,
+ "docs": {
+ "7": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "d": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "c": {
+ "df": 2,
+ "docs": {
+ "7": {
+ "tf": 1.0
+ },
+ "8": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ },
+ "s": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "23": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 3,
+ "docs": {
+ "25": {
+ "tf": 1.0
+ },
+ "3": {
+ "tf": 1.0
+ },
+ "5": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ },
+ "p": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "6": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "t": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "i": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "u": {
+ "df": 0,
+ "docs": {},
+ "g": {
+ "df": 0,
+ "docs": {},
+ "h": {
+ "df": 1,
+ "docs": {
+ "15": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "df": 0,
+ "docs": {},
+ "m": {
+ "a": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 1,
+ "docs": {
+ "10": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ }
+ },
+ "t": {
+ "a": {
+ "b": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 1,
+ "docs": {
+ "13": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 0,
+ "docs": {},
+ "l": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 1,
+ "docs": {
+ "16": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "df": 0,
+ "docs": {},
+ "e": {
+ "df": 0,
+ "docs": {},
+ "s": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 3,
+ "docs": {
+ "12": {
+ "tf": 1.0
+ },
+ "17": {
+ "tf": 1.0
+ },
+ "24": {
+ "tf": 1.0
+ }
+ }
+ }
+ },
+ "x": {
+ "df": 0,
+ "docs": {},
+ "t": {
+ "df": 3,
+ "docs": {
+ "20": {
+ "tf": 1.0
+ },
+ "21": {
+ "tf": 1.0
+ },
+ "22": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ },
+ "u": {
+ "df": 0,
+ "docs": {},
+ "n": {
+ "df": 0,
+ "docs": {},
+ "i": {
+ "c": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "d": {
+ "df": 1,
+ "docs": {
+ "17": {
+ "tf": 1.0
+ }
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ },
+ "df": 0,
+ "docs": {}
+ }
+ }
+ },
+ "w": {
+ "df": 0,
+ "docs": {},
+ "o": {
+ "df": 0,
+ "docs": {},
+ "r": {
+ "df": 0,
+ "docs": {},
+ "k": {
+ "df": 1,
+ "docs": {
+ "8": {
+ "tf": 1.0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "lang": "English",
+ "pipeline": [
+ "trimmer",
+ "stopWordFilter",
+ "stemmer"
+ ],
+ "ref": "id",
+ "version": "0.9.5"
+ },
+ "results_options": {
+ "limit_results": 30,
+ "teaser_word_count": 30
+ },
+ "search_options": {
+ "bool": "OR",
+ "expand": true,
+ "fields": {
+ "body": {
+ "boost": 1
+ },
+ "breadcrumbs": {
+ "boost": 1
+ },
+ "title": {
+ "boost": 2
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/vendor/mdbook/tests/summary_md_files/example_book.md b/vendor/mdbook/tests/summary_md_files/example_book.md
new file mode 100644
index 000000000..ff3911c72
--- /dev/null
+++ b/vendor/mdbook/tests/summary_md_files/example_book.md
@@ -0,0 +1,20 @@
+# Summary
+
+- [mdBook](README.md)
+- [Command Line Tool](cli/cli-tool.md)
+ - [init](cli/init.md)
+ - [build](cli/build.md)
+ - [watch](cli/watch.md)
+ - [serve](cli/serve.md)
+ - [test](cli/test.md)
+- [Format](format/format.md)
+ - [SUMMARY.md](format/summary.md)
+ - [Configuration](format/config.md)
+ - [Theme](format/theme/theme.md)
+ - [index.hbs](format/theme/index-hbs.md)
+ - [Syntax highlighting](format/theme/syntax-highlighting.md)
+ - [MathJax Support](format/mathjax.md)
+ - [Rust code specific features](format/rust.md)
+- [Rust Library](lib/lib.md)
+-----------
+[Contributors](misc/contributors.md)
diff --git a/vendor/mdbook/tests/summary_md_files/rust_by_example.md b/vendor/mdbook/tests/summary_md_files/rust_by_example.md
new file mode 100644
index 000000000..de7e6f6ee
--- /dev/null
+++ b/vendor/mdbook/tests/summary_md_files/rust_by_example.md
@@ -0,0 +1,191 @@
+# Summary
+
+[Introduction](index.md)
+
+- [Hello World](hello.md)
+ - [Comments](hello/comment.md)
+ - [Formatted print](hello/print.md)
+ - [Debug](hello/print/print_debug.md)
+ - [Display](hello/print/print_display.md)
+ - [Testcase: List](hello/print/print_display/testcase_list.md)
+ - [Formatting](hello/print/fmt.md)
+
+- [Primitives](primitives.md)
+ - [Literals and operators](primitives/literals.md)
+ - [Tuples](primitives/tuples.md)
+ - [Arrays and Slices](primitives/array.md)
+
+- [Custom Types](custom_types.md)
+ - [Structures](custom_types/structs.md)
+ - [Enums](custom_types/enum.md)
+ - [use](custom_types/enum/enum_use.md)
+ - [C-like](custom_types/enum/c_like.md)
+ - [Testcase: linked-list](custom_types/enum/testcase_linked_list.md)
+ - [constants](custom_types/constants.md)
+
+- [Variable Bindings](variable_bindings.md)
+ - [Mutability](variable_bindings/mut.md)
+ - [Scope and Shadowing](variable_bindings/scope.md)
+ - [Declare first](variable_bindings/declare.md)
+
+- [Types](types.md)
+ - [Casting](types/cast.md)
+ - [Literals](types/literals.md)
+ - [Inference](types/inference.md)
+ - [Aliasing](types/alias.md)
+
+- [Conversion](conversion.md)
+ - [From and Into](conversion/from_into.md)
+ - [To and From String](conversion/string.md)
+
+- [Expressions](expression.md)
+
+- [Flow Control](flow_control.md)
+ - [if/else](flow_control/if_else.md)
+ - [loop](flow_control/loop.md)
+ - [Nesting and labels](flow_control/loop/nested.md)
+ - [Returning from loops](flow_control/loop/return.md)
+ - [while](flow_control/while.md)
+ - [for and range](flow_control/for.md)
+ - [match](flow_control/match.md)
+ - [Destructuring](flow_control/match/destructuring.md)
+ - [tuples](flow_control/match/destructuring/destructure_tuple.md)
+ - [enums](flow_control/match/destructuring/destructure_enum.md)
+ - [pointers/ref](flow_control/match/destructuring/destructure_pointers.md)
+ - [structs](flow_control/match/destructuring/destructure_structures.md)
+ - [Guards](flow_control/match/guard.md)
+ - [Binding](flow_control/match/binding.md)
+ - [if let](flow_control/if_let.md)
+ - [while let](flow_control/while_let.md)
+
+- [Functions](fn.md)
+ - [Methods](fn/methods.md)
+ - [Closures](fn/closures.md)
+ - [Capturing](fn/closures/capture.md)
+ - [As input parameters](fn/closures/input_parameters.md)
+ - [Type anonymity](fn/closures/anonymity.md)
+ - [Input functions](fn/closures/input_functions.md)
+ - [As output parameters](fn/closures/output_parameters.md)
+ - [Examples in `std`](fn/closures/closure_examples.md)
+ - [Iterator::any](fn/closures/closure_examples/iter_any.md)
+ - [Iterator::find](fn/closures/closure_examples/iter_find.md)
+ - [Higher Order Functions](fn/hof.md)
+
+- [Modules](mod.md)
+ - [Visibility](mod/visibility.md)
+ - [Struct visibility](mod/struct_visibility.md)
+ - [The `use` declaration](mod/use.md)
+ - [`super` and `self`](mod/super.md)
+ - [File hierarchy](mod/split.md)
+
+- [Crates](crates.md)
+ - [Library](crates/lib.md)
+ - [`extern crate`](crates/link.md)
+
+- [Attributes](attribute.md)
+ - [`dead_code`](attribute/unused.md)
+ - [Crates](attribute/crate.md)
+ - [`cfg`](attribute/cfg.md)
+ - [Custom](attribute/cfg/custom.md)
+
+- [Generics](generics.md)
+ - [Functions](generics/gen_fn.md)
+ - [Implementation](generics/impl.md)
+ - [Traits](generics/gen_trait.md)
+ - [Bounds](generics/bounds.md)
+ - [Testcase: empty bounds](generics/bounds/testcase_empty.md)
+ - [Multiple bounds](generics/multi_bounds.md)
+ - [Where clauses](generics/where.md)
+ - [New Type Idiom](generics/new_types.md)
+ - [Associated items](generics/assoc_items.md)
+ - [The Problem](generics/assoc_items/the_problem.md)
+ - [Associated types](generics/assoc_items/types.md)
+ - [Phantom type parameters](generics/phantom.md)
+ - [Testcase: unit clarification](generics/phantom/testcase_units.md)
+
+- [Scoping rules](scope.md)
+ - [RAII](scope/raii.md)
+ - [Ownership and moves](scope/move.md)
+ - [Mutability](scope/move/mut.md)
+ - [Borrowing](scope/borrow.md)
+ - [Mutability](scope/borrow/mut.md)
+ - [Freezing](scope/borrow/freeze.md)
+ - [Aliasing](scope/borrow/alias.md)
+ - [The ref pattern](scope/borrow/ref.md)
+ - [Lifetimes](scope/lifetime.md)
+ - [Explicit annotation](scope/lifetime/explicit.md)
+ - [Functions](scope/lifetime/fn.md)
+ - [Methods](scope/lifetime/methods.md)
+ - [Structs](scope/lifetime/struct.md)
+ - [Bounds](scope/lifetime/lifetime_bounds.md)
+ - [Coercion](scope/lifetime/lifetime_coercion.md)
+ - [static](scope/lifetime/static_lifetime.md)
+ - [elision](scope/lifetime/elision.md)
+
+- [Traits](trait.md)
+ - [Derive](trait/derive.md)
+ - [Operator Overloading](trait/ops.md)
+ - [Drop](trait/drop.md)
+ - [Iterators](trait/iter.md)
+ - [Clone](trait/clone.md)
+
+- [macro_rules!](macros.md)
+ - [Syntax](macro/syntax.md)
+ - [Designators](macros/designators.md)
+ - [Overload](macros/overload.md)
+ - [Repeat](macros/repeat.md)
+ - [DRY (Don't Repeat Yourself)](macros/dry.md)
+ - [DSL (Domain Specific Languages)](macros/dsl.md)
+ - [Variadics](macros/variadics.md)
+
+- [Error handling](error.md)
+ - [`panic`](error/panic.md)
+ - [`Option` & `unwrap`](error/option_unwrap.md)
+ - [Combinators: `map`](error/option_unwrap/map.md)
+ - [Combinators: `and_then`](error/option_unwrap/and_then.md)
+ - [`Result`](error/result.md)
+ - [`map` for `Result`](error/result/result_map.md)
+ - [aliases for `Result`](error/result/result_alias.md)
+ - [Early returns](error/result/early_returns.md)
+ - [Introducing `?`](error/result/enter_question_mark.md)
+ - [Multiple error types](error/multiple_error_types.md)
+ - [Pulling `Result`s out of `Option`s](error/multiple_error_types/option_result.md)
+ - [Defining an error type](error/multiple_error_types/define_error_type.md)
+ - [`Box`ing errors](error/multiple_error_types/boxing_errors.md)
+ - [Other uses of `?`](error/multiple_error_types/reenter_question_mark.md)
+ - [Wrapping errors](error/multiple_error_types/wrap_error.md)
+ - [Iterating over `Result`s](error/iter_result.md)
+
+- [Std library types](std.md)
+ - [Box, stack and heap](std/box.md)
+ - [Vectors](std/vec.md)
+ - [Strings](std/str.md)
+ - [`Option`](std/option.md)
+ - [`Result`](std/result.md)
+ - [`?`](std/result/question_mark.md)
+ - [`panic!`](std/panic.md)
+ - [HashMap](std/hash.md)
+ - [Alternate/custom key types](std/hash/alt_key_types.md)
+ - [HashSet](std/hash/hashset.md)
+
+- [Std misc](std_misc.md)
+ - [Threads](std_misc/threads.md)
+ - [Testcase: map-reduce](std_misc/threads/testcase_mapreduce.md)
+ - [Channels](std_misc/channels.md)
+ - [Path](std_misc/path.md)
+ - [File I/O](std_misc/file.md)
+ - [`open`](std_misc/file/open.md)
+ - [`create`](std_misc/file/create.md)
+ - [Child processes](std_misc/process.md)
+ - [Pipes](std_misc/process/pipe.md)
+ - [Wait](std_misc/process/wait.md)
+ - [Filesystem Operations](std_misc/fs.md)
+ - [Program arguments](std_misc/arg.md)
+ - [Argument parsing](std_misc/arg/matching.md)
+ - [Foreign Function Interface](std_misc/ffi.md)
+
+- [Meta](meta.md)
+ - [Documentation](meta/doc.md)
+ - [Testing](meta/test.md)
+
+- [Unsafe Operations](unsafe.md)
diff --git a/vendor/mdbook/tests/summary_md_files/rust_ffi_guide.md b/vendor/mdbook/tests/summary_md_files/rust_ffi_guide.md
new file mode 100644
index 000000000..f64fcab1b
--- /dev/null
+++ b/vendor/mdbook/tests/summary_md_files/rust_ffi_guide.md
@@ -0,0 +1,19 @@
+# Summary
+
+- [Overview](./overview.md)
+- [Setting Up](./setting_up.md)
+- [Core Client Library](./client.md)
+- [Constructing a Basic Request](./basic_request.md)
+- [Sending the Request](./send_basic.md)
+- [Generating a Header File](./cbindgen.md)
+- [Better Error Handling](./error_handling.md)
+- [Asynchronous Operations](./async.md)
+- [More Complex Requests](./complex_request.md)
+- [Testing](./testing.md)
+- [Dynamic Loading & Plugins](./dynamic_loading.md)
+
+---
+
+- [Break All The Things!!1!](./fun/index.md)
+ - [Problems](./fun/problems.md)
+ - [Solutions](./fun/solutions.md) \ No newline at end of file
diff --git a/vendor/mdbook/tests/summary_md_files/the_book-2nd_edition.md b/vendor/mdbook/tests/summary_md_files/the_book-2nd_edition.md
new file mode 100644
index 000000000..c7f830674
--- /dev/null
+++ b/vendor/mdbook/tests/summary_md_files/the_book-2nd_edition.md
@@ -0,0 +1,130 @@
+# The Rust Programming Language
+
+## Getting started
+
+- [Introduction](ch01-00-introduction.md)
+ - [Installation](ch01-01-installation.md)
+ - [Hello, World!](ch01-02-hello-world.md)
+
+- [Guessing Game Tutorial](ch02-00-guessing-game-tutorial.md)
+
+- [Common Programming Concepts](ch03-00-common-programming-concepts.md)
+ - [Variables and Mutability](ch03-01-variables-and-mutability.md)
+ - [Data Types](ch03-02-data-types.md)
+ - [How Functions Work](ch03-03-how-functions-work.md)
+ - [Comments](ch03-04-comments.md)
+ - [Control Flow](ch03-05-control-flow.md)
+
+- [Understanding Ownership](ch04-00-understanding-ownership.md)
+ - [What is Ownership?](ch04-01-what-is-ownership.md)
+ - [References & Borrowing](ch04-02-references-and-borrowing.md)
+ - [Slices](ch04-03-slices.md)
+
+- [Using Structs to Structure Related Data](ch05-00-structs.md)
+ - [Defining and Instantiating Structs](ch05-01-defining-structs.md)
+ - [An Example Program Using Structs](ch05-02-example-structs.md)
+ - [Method Syntax](ch05-03-method-syntax.md)
+
+- [Enums and Pattern Matching](ch06-00-enums.md)
+ - [Defining an Enum](ch06-01-defining-an-enum.md)
+ - [The `match` Control Flow Operator](ch06-02-match.md)
+ - [Concise Control Flow with `if let`](ch06-03-if-let.md)
+
+## Basic Rust Literacy
+
+- [Modules](ch07-00-modules.md)
+ - [`mod` and the Filesystem](ch07-01-mod-and-the-filesystem.md)
+ - [Controlling Visibility with `pub`](ch07-02-controlling-visibility-with-pub.md)
+ - [Referring to Names in Different Modules](ch07-03-importing-names-with-use.md)
+
+- [Common Collections](ch08-00-common-collections.md)
+ - [Vectors](ch08-01-vectors.md)
+ - [Strings](ch08-02-strings.md)
+ - [Hash Maps](ch08-03-hash-maps.md)
+
+- [Error Handling](ch09-00-error-handling.md)
+ - [Unrecoverable Errors with `panic!`](ch09-01-unrecoverable-errors-with-panic.md)
+ - [Recoverable Errors with `Result`](ch09-02-recoverable-errors-with-result.md)
+ - [To `panic!` or Not To `panic!`](ch09-03-to-panic-or-not-to-panic.md)
+
+- [Generic Types, Traits, and Lifetimes](ch10-00-generics.md)
+ - [Generic Data Types](ch10-01-syntax.md)
+ - [Traits: Defining Shared Behavior](ch10-02-traits.md)
+ - [Validating References with Lifetimes](ch10-03-lifetime-syntax.md)
+
+- [Testing](ch11-00-testing.md)
+ - [Writing tests](ch11-01-writing-tests.md)
+ - [Running tests](ch11-02-running-tests.md)
+ - [Test Organization](ch11-03-test-organization.md)
+
+- [An I/O Project: Building a Command Line Program](ch12-00-an-io-project.md)
+ - [Accepting Command Line Arguments](ch12-01-accepting-command-line-arguments.md)
+ - [Reading a File](ch12-02-reading-a-file.md)
+ - [Refactoring to Improve Modularity and Error Handling](ch12-03-improving-error-handling-and-modularity.md)
+ - [Developing the Library’s Functionality with Test Driven Development](ch12-04-testing-the-librarys-functionality.md)
+ - [Working with Environment Variables](ch12-05-working-with-environment-variables.md)
+ - [Writing Error Messages to Standard Error Instead of Standard Output](ch12-06-writing-to-stderr-instead-of-stdout.md)
+
+## Thinking in Rust
+
+- [Functional Language Features: Iterators and Closures](ch13-00-functional-features.md)
+ - [Closures: Anonymous Functions that Can Capture Their Environment](ch13-01-closures.md)
+ - [Processing a Series of Items with Iterators](ch13-02-iterators.md)
+ - [Improving Our I/O Project](ch13-03-improving-our-io-project.md)
+ - [Comparing Performance: Loops vs. Iterators](ch13-04-performance.md)
+
+- [More about Cargo and Crates.io](ch14-00-more-about-cargo.md)
+ - [Customizing Builds with Release Profiles](ch14-01-release-profiles.md)
+ - [Publishing a Crate to Crates.io](ch14-02-publishing-to-crates-io.md)
+ - [Cargo Workspaces](ch14-03-cargo-workspaces.md)
+ - [Installing Binaries from Crates.io with `cargo install`](ch14-04-installing-binaries.md)
+ - [Extending Cargo with Custom Commands](ch14-05-extending-cargo.md)
+
+- [Smart Pointers](ch15-00-smart-pointers.md)
+ - [`Box<T>` Points to Data on the Heap and Has a Known Size](ch15-01-box.md)
+ - [The `Deref` Trait Allows Access to the Data Through a Reference](ch15-02-deref.md)
+ - [The `Drop` Trait Runs Code on Cleanup](ch15-03-drop.md)
+ - [`Rc<T>`, the Reference Counted Smart Pointer](ch15-04-rc.md)
+ - [`RefCell<T>` and the Interior Mutability Pattern](ch15-05-interior-mutability.md)
+ - [Creating Reference Cycles and Leaking Memory is Safe](ch15-06-reference-cycles.md)
+
+- [Fearless Concurrency](ch16-00-concurrency.md)
+ - [Threads](ch16-01-threads.md)
+ - [Message Passing](ch16-02-message-passing.md)
+ - [Shared State](ch16-03-shared-state.md)
+ - [Extensible Concurrency: `Sync` and `Send`](ch16-04-extensible-concurrency-sync-and-send.md)
+
+- [Is Rust an Object-Oriented Programming Language?](ch17-00-oop.md)
+ - [What Does Object-Oriented Mean?](ch17-01-what-is-oo.md)
+ - [Trait Objects for Using Values of Different Types](ch17-02-trait-objects.md)
+ - [Object-Oriented Design Pattern Implementations](ch17-03-oo-design-patterns.md)
+
+## Advanced Topics
+
+- [Patterns Match the Structure of Values](ch18-00-patterns.md)
+ - [All the Places Patterns May be Used](ch18-01-all-the-places-for-patterns.md)
+ - [Refutability: Whether a Pattern Might Fail to Match](ch18-02-refutability.md)
+ - [All the Pattern Syntax](ch18-03-pattern-syntax.md)
+
+- [Advanced Features](ch19-00-advanced-features.md)
+ - [Unsafe Rust](ch19-01-unsafe-rust.md)
+ - [Advanced Lifetimes](ch19-02-advanced-lifetimes.md)
+ - [Advanced Traits](ch19-03-advanced-traits.md)
+ - [Advanced Types](ch19-04-advanced-types.md)
+ - [Advanced Functions & Closures](ch19-05-advanced-functions-and-closures.md)
+
+- [Final Project: Building a Multithreaded Web Server](ch20-00-final-project-a-web-server.md)
+ - [A Single Threaded Web Server](ch20-01-single-threaded.md)
+ - [How Slow Requests Affect Throughput](ch20-02-slow-requests.md)
+ - [Designing the Thread Pool Interface](ch20-03-designing-the-interface.md)
+ - [Creating the Thread Pool and Storing Threads](ch20-04-storing-threads.md)
+ - [Sending Requests to Threads Via Channels](ch20-05-sending-requests-via-channels.md)
+ - [Graceful Shutdown and Cleanup](ch20-06-graceful-shutdown-and-cleanup.md)
+
+- [Appendix](appendix-00.md)
+ - [A - Keywords](appendix-01-keywords.md)
+ - [B - Operators and Symbols](appendix-02-operators.md)
+ - [C - Derivable Traits](appendix-03-derivable-traits.md)
+ - [D - Macros](appendix-04-macros.md)
+ - [E - Translations](appendix-05-translation.md)
+ - [F - Newest Features](appendix-06-newest-features.md)
diff --git a/vendor/mdbook/tests/testing.rs b/vendor/mdbook/tests/testing.rs
new file mode 100644
index 000000000..2b2c0fd0d
--- /dev/null
+++ b/vendor/mdbook/tests/testing.rs
@@ -0,0 +1,26 @@
+mod dummy_book;
+
+use crate::dummy_book::DummyBook;
+
+use mdbook::MDBook;
+
+#[test]
+fn mdbook_can_correctly_test_a_passing_book() {
+ let temp = DummyBook::new().with_passing_test(true).build().unwrap();
+ let mut md = MDBook::load(temp.path()).unwrap();
+
+ let result = md.test(vec![]);
+ assert!(
+ result.is_ok(),
+ "Tests failed with {}",
+ result.err().unwrap()
+ );
+}
+
+#[test]
+fn mdbook_detects_book_with_failing_tests() {
+ let temp = DummyBook::new().with_passing_test(false).build().unwrap();
+ let mut md = MDBook::load(temp.path()).unwrap();
+
+ assert!(md.test(vec![]).is_err());
+}
diff --git a/vendor/mdbook/triagebot.toml b/vendor/mdbook/triagebot.toml
new file mode 100644
index 000000000..e7ecb6b3f
--- /dev/null
+++ b/vendor/mdbook/triagebot.toml
@@ -0,0 +1,12 @@
+# This will allow users to self assign, and/or drop assignment
+[assign]
+
+
+[relabel]
+allow-unauthenticated = [
+# For Issue areas
+ "A-*",
+ "E-Help-Wanted",
+ "Bug",
+ "Feature-Request"
+] \ No newline at end of file