mirror of
https://github.com/rust-lang/mdBook.git
synced 2025-12-28 11:24:57 -05:00
Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5eb7d46a99 | ||
|
|
dffcedf031 | ||
|
|
c9b6be8660 | ||
|
|
23af80c506 | ||
|
|
857acb9759 | ||
|
|
2ddcb43899 | ||
|
|
1c0983b811 | ||
|
|
1be69af553 | ||
|
|
c63000f365 | ||
|
|
bbaa0ea1fa | ||
|
|
58bc92d380 | ||
|
|
17d1ed3716 | ||
|
|
8df8ce063d | ||
|
|
c3ff4a5129 | ||
|
|
4d20fa578b | ||
|
|
9e47498458 | ||
|
|
903469a45f | ||
|
|
b8ef89db62 | ||
|
|
c283211a37 | ||
|
|
d5af051d0e | ||
|
|
68f9afe64b | ||
|
|
ffa8284743 | ||
|
|
3e91f9cd5d | ||
|
|
f55028b61a | ||
|
|
0d887505af | ||
|
|
6c20736a55 | ||
|
|
e4a46c9477 | ||
|
|
6ae5c686d9 | ||
|
|
b862080006 | ||
|
|
6b790b83ec | ||
|
|
d8ad68c947 | ||
|
|
6b784be616 | ||
|
|
f5598b2eee | ||
|
|
ff4b8e7a8d | ||
|
|
9c34e602bd | ||
|
|
a306da3ad7 | ||
|
|
9bede85efa | ||
|
|
11b1e86187 | ||
|
|
10d30a2dc0 | ||
|
|
601ebc5499 | ||
|
|
4251d7a838 | ||
|
|
93008cf20b | ||
|
|
3f9f681b9e | ||
|
|
63680d0786 | ||
|
|
656a1825cc | ||
|
|
1a2fa29209 | ||
|
|
6be81214b1 | ||
|
|
d22299d998 | ||
|
|
0af417085f | ||
|
|
9634798eb7 | ||
|
|
2a8af1c21d | ||
|
|
981f8695ff | ||
|
|
48b5e52f62 | ||
|
|
c4fec94c4c | ||
|
|
ab0c338c08 | ||
|
|
8a82f6336a | ||
|
|
1700783594 | ||
|
|
e6629cd75b | ||
|
|
5a077b9ff4 | ||
|
|
8b4e488de1 | ||
|
|
68d8ceec47 | ||
|
|
db337d4a6f | ||
|
|
5e277140be | ||
|
|
14add9c290 | ||
|
|
87877a9dae | ||
|
|
2cf00d0880 | ||
|
|
8c7af3c767 | ||
|
|
6dd785ea6c | ||
|
|
8d131b4310 | ||
|
|
97b38063b1 | ||
|
|
d23734f82e | ||
|
|
2ccfaadd1d | ||
|
|
3d04e5c7ff | ||
|
|
49ef7b6f02 | ||
|
|
da7026190c | ||
|
|
92377013cc | ||
|
|
34b586ab32 | ||
|
|
a79065b0d3 | ||
|
|
b3ab93a4b3 | ||
|
|
49b75810fa | ||
|
|
b85d5eb455 | ||
|
|
27faa54ae8 | ||
|
|
fae0759626 | ||
|
|
cc74ca2e6e | ||
|
|
7cae3a058d | ||
|
|
8fb6ac7987 | ||
|
|
82d32ee761 | ||
|
|
fe9b534ad7 | ||
|
|
56652e8fa6 | ||
|
|
c3a1e41ed7 | ||
|
|
3976c9d8f0 | ||
|
|
96b6f02834 | ||
|
|
b571511737 | ||
|
|
ebdab38a32 | ||
|
|
c06f450e7d | ||
|
|
b87c231fc3 | ||
|
|
8024b08f93 | ||
|
|
8ec0bf6e30 | ||
|
|
a8926d5392 | ||
|
|
00473d8420 | ||
|
|
86d390032b | ||
|
|
b3c0b01350 | ||
|
|
e33192753d | ||
|
|
7932e13512 | ||
|
|
9fd2509c0d | ||
|
|
5dec8508c7 | ||
|
|
4d2dc6f482 | ||
|
|
efb13d7bc1 | ||
|
|
27b1e05c87 | ||
|
|
e5e10c681a | ||
|
|
dcccd3289d | ||
|
|
5637a66459 | ||
|
|
beec17e55d |
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
rust: stable
|
||||
- build: msrv
|
||||
os: ubuntu-latest
|
||||
rust: 1.45.0
|
||||
rust: 1.46.0
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install Rust
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ guide/book
|
||||
|
||||
.vscode
|
||||
tests/dummy_book/book/
|
||||
test_book/book/
|
||||
|
||||
# Ignore Jetbrains specific files.
|
||||
.idea/
|
||||
|
||||
77
CHANGELOG.md
77
CHANGELOG.md
@@ -1,5 +1,82 @@
|
||||
# Changelog
|
||||
|
||||
## 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)
|
||||
|
||||
|
||||
3
CODE_OF_CONDUCT.md
Normal file
3
CODE_OF_CONDUCT.md
Normal file
@@ -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).
|
||||
@@ -6,7 +6,6 @@ If you have come here to learn how to contribute to mdBook, we have some tips fo
|
||||
|
||||
First of all, don't hesitate to ask questions!
|
||||
Use the [issue tracker](https://github.com/rust-lang/mdBook/issues), no question is too simple.
|
||||
If we don't respond in a couple of days, ping us @Michael-F-Bryan, @budziq, @steveklabnik, @frewsxcv it might just be that we forgot. :wink:
|
||||
|
||||
### Issues to work on
|
||||
|
||||
@@ -46,7 +45,7 @@ mdBook builds on stable Rust, if you want to build mdBook from source, here are
|
||||
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`.
|
||||
The resulting binary can be found in `mdBook/target/debug/` under the name `mdbook` or `mdbook.exe`.
|
||||
|
||||
### Code Quality
|
||||
|
||||
@@ -106,3 +105,26 @@ 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. (TODO: It would be nice to have a demo file in the repo to help with this.)
|
||||
|
||||
595
Cargo.lock
generated
595
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
13
Cargo.toml
13
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mdbook"
|
||||
version = "0.4.10"
|
||||
version = "0.4.14"
|
||||
authors = [
|
||||
"Mathieu David <mathieudavid@mathieudavid.org>",
|
||||
"Michael-F-Bryan <michaelfbryan@gmail.com>",
|
||||
@@ -24,8 +24,8 @@ handlebars = "4.0"
|
||||
lazy_static = "1.0"
|
||||
log = "0.4"
|
||||
memchr = "2.0"
|
||||
open = "1.1"
|
||||
pulldown-cmark = "0.7.0"
|
||||
opener = "0.5"
|
||||
pulldown-cmark = "0.8.0"
|
||||
regex = "1.0.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
@@ -33,6 +33,7 @@ serde_json = "1.0"
|
||||
shlex = "1"
|
||||
tempfile = "3.0"
|
||||
toml = "0.5.1"
|
||||
topological-sort = "0.1.0"
|
||||
|
||||
# Watch feature
|
||||
notify = { version = "4.0", optional = true }
|
||||
@@ -40,14 +41,16 @@ gitignore = { version = "1.0", optional = true }
|
||||
|
||||
# Serve feature
|
||||
futures-util = { version = "0.3.4", optional = true }
|
||||
tokio = { version = "0.2.18", features = ["macros"], optional = true }
|
||||
warp = { version = "0.2.2", default-features = false, features = ["websocket"], optional = true }
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"], optional = true }
|
||||
warp = { version = "0.3.1", default-features = false, features = ["websocket"], optional = true }
|
||||
|
||||
# Search feature
|
||||
elasticlunr-rs = { version = "2.3", optional = true, default-features = false }
|
||||
ammonia = { version = "3", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "1"
|
||||
predicates = "2"
|
||||
select = "0.5"
|
||||
semver = "0.11.0"
|
||||
pretty_assertions = "0.6"
|
||||
|
||||
@@ -163,6 +163,7 @@ of a book in order to validate links or run tests. Some existing renderers are:
|
||||
preprocessors.
|
||||
- [`linkcheck`] - a backend which will check that all links are valid
|
||||
- [`epub`] - an experimental EPUB generator
|
||||
- [`man`] - a backend that generates manual pages from the book
|
||||
|
||||
> **Note for Developers:** Feel free to send us a PR if you've developed your
|
||||
> own plugin and want it mentioned here.
|
||||
@@ -234,3 +235,4 @@ All the code in this repository is released under the ***Mozilla Public License
|
||||
[master-docs]: http://rust-lang.github.io/mdBook/
|
||||
[`linkcheck`]: https://crates.io/crates/mdbook-linkcheck
|
||||
[`epub`]: https://crates.io/crates/mdbook-epub
|
||||
[`man`]: https://crates.io/crates/mdbook-man
|
||||
|
||||
@@ -37,7 +37,7 @@ fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
|
||||
let book_version = Version::parse(&ctx.mdbook_version)?;
|
||||
let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?;
|
||||
|
||||
if version_req.matches(&book_version) != true {
|
||||
if !version_req.matches(&book_version) {
|
||||
eprintln!(
|
||||
"Warning: The {} plugin was built against version {} of mdbook, \
|
||||
but we're being called from version {}",
|
||||
@@ -55,7 +55,7 @@ fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
|
||||
|
||||
fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! {
|
||||
let renderer = sub_args.value_of("renderer").expect("Required argument");
|
||||
let supported = pre.supports_renderer(&renderer);
|
||||
let supported = pre.supports_renderer(renderer);
|
||||
|
||||
// Signal whether the renderer is supported by exiting with 1 or 0.
|
||||
if supported {
|
||||
|
||||
@@ -1,25 +1,40 @@
|
||||
# mdBook
|
||||
# Introduction
|
||||
|
||||
**mdBook** is a command line tool and Rust crate to create books using Markdown
|
||||
(as by the [CommonMark](https://commonmark.org/) specification) files. It's very
|
||||
similar to Gitbook but written in [Rust](http://www.rust-lang.org).
|
||||
**mdBook** is a command line tool and Rust crate to create books with Markdown. The output resembles tools like Gitbook,
|
||||
and is ideal for creating product or API documentation, tutorials, course materials or anything that requires a clean,
|
||||
easily navigable and customizable presentation. mdBook is written in [Rust](https://www.rust-lang.org); its performance
|
||||
and simplicity made it ideal for use as a tool to publish directly to hosted websites such
|
||||
as [GitHub Pages](https://pages.github.com) via automation. This guide, in fact, serves as both the mdBook documentation
|
||||
and a fine example of what mdBook produces.
|
||||
|
||||
What you are reading serves as an example of the output of mdBook and at the
|
||||
same time as a high-level documentation.
|
||||
mdBook includes built in support for both preprocessing your Markdown and alternative renderers for producing formats
|
||||
other than HTML. These facilities also enable other functionality such as
|
||||
validation. [Searching](https://crates.io/search?q=mdbook&sort=relevance) Rust's [crates.io](https://crates.io) is a
|
||||
great way to discover more extensions.
|
||||
|
||||
mdBook is free and open source, you can find the source code on
|
||||
[GitHub](https://github.com/rust-lang/mdBook). Issues and feature
|
||||
requests can be posted on the [GitHub issue
|
||||
tracker](https://github.com/rust-lang/mdBook/issues).
|
||||
## API Documentation
|
||||
|
||||
## API docs
|
||||
In addition to the above features, mdBook also has a Rust [API](https://docs.rs/mdbook/*/mdbook/). This allows you to
|
||||
write your own preprocessor or renderer, as well as incorporate mdBook features into other applications.
|
||||
The [For Developers](for_developers) section of this guide contains more information and some examples.
|
||||
|
||||
Alongside this book you can also read the [API
|
||||
docs](https://docs.rs/mdbook/*/mdbook/) generated by Rustdoc if you would like
|
||||
to use mdBook as a crate or write a new renderer and need a more low-level
|
||||
overview.
|
||||
## Markdown
|
||||
|
||||
mdBook's [parser](https://github.com/raphlinus/pulldown-cmark) adheres to the [CommonMark](https://commonmark.org/)
|
||||
specification. You can take a quick [tutorial](https://commonmark.org/help/tutorial/),
|
||||
or [try out](https://spec.commonmark.org/dingus/) CommonMark in real time. For a more in-depth experience, check out the
|
||||
[Markdown Guide](https://www.markdownguide.org).
|
||||
|
||||
## Contributing
|
||||
|
||||
mdBook is free and open source. You can find the source code on
|
||||
[GitHub](https://github.com/rust-lang/mdBook) and issues and feature requests can be posted on
|
||||
the [GitHub issue tracker](https://github.com/rust-lang/mdBook/issues). mdBook relies on the community to fix bugs and
|
||||
add features: if you'd like to contribute, please read
|
||||
the [CONTRIBUTING](https://github.com/rust-lang/mdBook/blob/master/CONTRIBUTING.md) guide and consider opening
|
||||
a [pull request](https://github.com/rust-lang/mdBook/pulls).
|
||||
|
||||
## License
|
||||
|
||||
mdBook, all the source code, is released under the [Mozilla Public License
|
||||
v2.0](https://www.mozilla.org/MPL/2.0/).
|
||||
The mdBook source and documentation are released under
|
||||
the [Mozilla Public License v2.0](https://www.mozilla.org/MPL/2.0/).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Summary
|
||||
|
||||
- [mdBook](README.md)
|
||||
- [Introduction](README.md)
|
||||
- [Command Line Tool](cli/README.md)
|
||||
- [init](cli/init.md)
|
||||
- [build](cli/build.md)
|
||||
|
||||
@@ -52,3 +52,19 @@ directory called `theme` in your source directory so that you can modify it.
|
||||
|
||||
The theme is selectively overwritten, this means that if you don't want to
|
||||
overwrite a specific file, just delete it and the default file will be used.
|
||||
|
||||
#### --title
|
||||
|
||||
Specify a title for the book. If not supplied, an interactive prompt will ask for
|
||||
a title.
|
||||
|
||||
```bash
|
||||
mdbook init --title="my amazing book"
|
||||
```
|
||||
|
||||
#### --ignore
|
||||
|
||||
Create a `.gitignore` file configured to ignore the `book` directory created when [building] a book.
|
||||
If not supplied, an interactive prompt will ask whether it should be created.
|
||||
|
||||
[building]: build.md
|
||||
|
||||
@@ -43,7 +43,17 @@ mdbook test path/to/book
|
||||
The `--library-path` (`-L`) option allows you to add directories to the library
|
||||
search path used by `rustdoc` when it builds and tests the examples. Multiple
|
||||
directories can be specified with multiple options (`-L foo -L bar`) or with a
|
||||
comma-delimited list (`-L foo,bar`).
|
||||
comma-delimited list (`-L foo,bar`). The path should point to the Cargo
|
||||
[build cache](https://doc.rust-lang.org/cargo/guide/build-cache.html) `deps` directory that
|
||||
contains the build output of your project. For example, if your Rust project's book is in a directory
|
||||
named `my-book`, the following command would include the crate's dependencies when running `test`:
|
||||
|
||||
```shell
|
||||
mdbook test my-book -L target/debug/deps/
|
||||
```
|
||||
|
||||
See the `rustdoc` command-line [documentation](https://doc.rust-lang.org/rustdoc/command-line-arguments.html#-l--library-path-where-to-look-for-dependencies)
|
||||
for more information.
|
||||
|
||||
#### --dest-dir
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ before_script:
|
||||
- cargo install-update -a
|
||||
|
||||
script:
|
||||
- mdbook build path/to/mybook && mdbook test path/to/mybook
|
||||
- mdbook build && mdbook test # In case of custom book path: mdbook build path/to/mybook && mdbook test path/to/mybook
|
||||
```
|
||||
|
||||
## Deploying Your Book to GitHub Pages
|
||||
@@ -50,10 +50,10 @@ deploy:
|
||||
provider: pages
|
||||
skip-cleanup: true
|
||||
github-token: $GITHUB_TOKEN
|
||||
local-dir: path/to/mybook/book
|
||||
local-dir: book # In case of custom book path: path/to/mybook/book
|
||||
keep-history: false
|
||||
on:
|
||||
branch: master
|
||||
branch: main
|
||||
```
|
||||
|
||||
That's it!
|
||||
@@ -77,7 +77,7 @@ before_script:
|
||||
- cargo install-update -a
|
||||
|
||||
script:
|
||||
- mdbook build path/to/mybook && mdbook test path/to/mybook
|
||||
- mdbook build && mdbook test # In case of custom book path: mdbook build path/to/mybook && mdbook test path/to/mybook
|
||||
|
||||
deploy:
|
||||
provider: pages
|
||||
@@ -85,10 +85,10 @@ deploy:
|
||||
edge: true
|
||||
cleanup: false
|
||||
github-token: $GITHUB_TOKEN
|
||||
local-dir: path/to/mybook/book
|
||||
local-dir: book # In case of custom book path: path/to/mybook/book
|
||||
keep-history: false
|
||||
on:
|
||||
branch: master
|
||||
branch: main
|
||||
target_branch: gh-pages
|
||||
```
|
||||
|
||||
@@ -140,15 +140,15 @@ pages:
|
||||
- export PATH="$PATH:$CARGO_HOME/bin"
|
||||
- mdbook --version || cargo install mdbook
|
||||
script:
|
||||
- mdbook build -d public
|
||||
only:
|
||||
- master
|
||||
- mdbook build -d public
|
||||
rules:
|
||||
- if: '$CI_COMMIT_REF_NAME == "master"'
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
paths:
|
||||
- public
|
||||
cache:
|
||||
paths:
|
||||
- $CARGO_HOME/bin
|
||||
- $CARGO_HOME/bin
|
||||
```
|
||||
|
||||
After you commit and push this new file, GitLab CI will run and your book will be available!
|
||||
|
||||
@@ -13,6 +13,7 @@ rough example of how this is accomplished in practice.
|
||||
- [mdbook-epub] - an EPUB renderer
|
||||
- [mdbook-test] - a program to run the book's contents through [rust-skeptic] to
|
||||
verify everything compiles and runs correctly (similar to `rustdoc --test`)
|
||||
- [mdbook-man] - generate manual pages from the book
|
||||
|
||||
This page will step you through creating your own alternative backend in the form
|
||||
of a simple word counting program. Although it will be written in Rust, there's
|
||||
@@ -377,6 +378,7 @@ the source code or ask questions.
|
||||
[mdbook-linkcheck]: https://github.com/Michael-F-Bryan/mdbook-linkcheck
|
||||
[mdbook-epub]: https://github.com/Michael-F-Bryan/mdbook-epub
|
||||
[mdbook-test]: https://github.com/Michael-F-Bryan/mdbook-test
|
||||
[mdbook-man]: https://github.com/vv9k/mdbook-man
|
||||
[rust-skeptic]: https://github.com/budziq/rust-skeptic
|
||||
[`RenderContext`]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html
|
||||
[`RenderContext::from_json()`]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html#method.from_json
|
||||
|
||||
@@ -34,8 +34,15 @@ command = "python3 /path/to/foo.py"
|
||||
renderer = ["html", "epub"]
|
||||
```
|
||||
|
||||
In typical unix style, all inputs to the plugin will be written to `stdin` as
|
||||
JSON and `mdbook` will read from `stdout` if it is expecting output.
|
||||
Once the preprocessor has been defined and the build process starts, mdBook executes the command defined in the `preprocessor.foo.command` key twice.
|
||||
The first time it runs the preprocessor to determine if it supports the given renderer.
|
||||
mdBook passes two arguments to the process: the first argument is the string `supports` and the second argument is the renderer name.
|
||||
The preprocessor should exit with a status code 0 if it supports the given renderer, or return a non-zero exit code if it does not.
|
||||
|
||||
If the preprocessor supports the renderer, then mdbook runs it a second time, passing JSON data into stdin.
|
||||
The JSON consists of an array of `[context, book]` where `context` is the serialized object [`PreprocessorContext`] and `book` is a [`Book`] object containing the content of the book.
|
||||
|
||||
The preprocessor should return the JSON format of the [`Book`] object to stdout, with any modifications it wishes to perform.
|
||||
|
||||
The easiest way to get started is by creating your own implementation of the
|
||||
`Preprocessor` trait (e.g. in `lib.rs`) and then creating a shell binary which
|
||||
@@ -106,6 +113,33 @@ fn remove_emphasis(
|
||||
|
||||
For everything else, have a look [at the complete example][example].
|
||||
|
||||
## Implementing a preprocessor with a different language
|
||||
|
||||
The fact that mdBook utilizes stdin and stdout to communicate with the preprocessors makes it easy to implement them in a language other than Rust.
|
||||
The following code shows how to implement a simple preprocessor in Python, which will modify the content of the first chapter.
|
||||
The example below follows the configuration shown above with `preprocessor.foo.command` actually pointing to a Python script.
|
||||
|
||||
```python
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1: # we check if we received any argument
|
||||
if sys.argv[1] == "supports":
|
||||
# then we are good to return an exit status code of 0, since the other argument will just be the renderer's name
|
||||
sys.exit(0)
|
||||
|
||||
# load both the context and the book representations from stdin
|
||||
context, book = json.load(sys.stdin)
|
||||
# and now, we can just modify the content of the first chapter
|
||||
book['sections'][0]['Chapter']['content'] = '# Hello'
|
||||
# we are done with the book's modification, we can just print it to stdout,
|
||||
print(json.dumps(book))
|
||||
```
|
||||
|
||||
|
||||
|
||||
[preprocessor-docs]: https://docs.rs/mdbook/latest/mdbook/preprocess/trait.Preprocessor.html
|
||||
[pc]: https://crates.io/crates/pulldown-cmark
|
||||
[pctc]: https://crates.io/crates/pulldown-cmark-to-cmark
|
||||
@@ -113,3 +147,5 @@ For everything else, have a look [at the complete example][example].
|
||||
[an example no-op preprocessor]: https://github.com/rust-lang/mdBook/blob/master/examples/nop-preprocessor.rs
|
||||
[`CmdPreprocessor::parse_input()`]: https://docs.rs/mdbook/latest/mdbook/preprocess/trait.Preprocessor.html#method.parse_input
|
||||
[`Book::for_each_mut()`]: https://docs.rs/mdbook/latest/mdbook/book/struct.Book.html#method.for_each_mut
|
||||
[`PreprocessorContext`]: https://docs.rs/mdbook/latest/mdbook/preprocess/struct.PreprocessorContext.html
|
||||
[`Book`]: https://docs.rs/mdbook/latest/mdbook/book/struct.Book.html
|
||||
|
||||
@@ -63,8 +63,8 @@ Options for the Rust language, relevant to running tests and playground
|
||||
integration.
|
||||
|
||||
- **edition**: Rust edition to use by default for the code snippets. Default
|
||||
is "2015". Individual code blocks can be controlled with the `edition2015`
|
||||
or `edition2018` annotations, such as:
|
||||
is "2015". Individual code blocks can be controlled with the `edition2015`,
|
||||
`edition2018` or `edition2021` annotations, such as:
|
||||
|
||||
~~~text
|
||||
```rust,edition2015
|
||||
|
||||
@@ -56,3 +56,25 @@ be overridden by adding a `command` field.
|
||||
[preprocessor.random]
|
||||
command = "python random.py"
|
||||
```
|
||||
|
||||
### Require A Certain Order
|
||||
|
||||
The order in which preprocessors are run can be controlled with the `before` and `after` fields.
|
||||
For example, suppose you want your `linenos` preprocessor to process lines that may have been `{{#include}}`d; then you want it to run after the built-in `links` preprocessor, which you can require using either the `before` or `after` field:
|
||||
|
||||
```toml
|
||||
[preprocessor.linenos]
|
||||
after = [ "links" ]
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```toml
|
||||
[preprocessor.links]
|
||||
before = [ "linenos" ]
|
||||
```
|
||||
|
||||
It would also be possible, though redundant, to specify both of the above in the same config file.
|
||||
|
||||
Preprocessors having the same priority specified through `before` and `after` are sorted by name.
|
||||
Any infinite loops will be detected and produce an error.
|
||||
|
||||
@@ -21,8 +21,8 @@ The following configuration options are available:
|
||||
- **mathjax-support:** Adds support for [MathJax](../mathjax.md). Defaults to
|
||||
`false`.
|
||||
- **copy-fonts:** Copies fonts.css and respective font files to the output directory and use them in the default theme. Defaults to `true`.
|
||||
- **google-analytics:** If you use Google Analytics, this option lets you enable
|
||||
it by simply specifying your ID in the configuration file.
|
||||
- **google-analytics:** This field has been deprecated and will be removed in a future release.
|
||||
Use the `theme/head.hbs` file to add the appropriate Google Analytics code instead.
|
||||
- **additional-css:** If you need to slightly change the appearance of your book
|
||||
without overwriting the whole style, you can specify a set of stylesheets that
|
||||
will be loaded after the default ones where you can surgically change the
|
||||
@@ -131,7 +131,6 @@ preferred-dark-theme = "navy"
|
||||
curly-quotes = true
|
||||
mathjax-support = false
|
||||
copy-fonts = true
|
||||
google-analytics = "UA-123456-7"
|
||||
additional-css = ["custom.css", "custom2.css"]
|
||||
additional-js = ["custom.js"]
|
||||
no-section-label = false
|
||||
@@ -188,13 +187,13 @@ Enable it by adding an empty table to your `book.toml` as follows:
|
||||
There are no configuration options for the Markdown renderer at this time;
|
||||
only whether it is enabled or disabled.
|
||||
|
||||
See [the preprocessors documentation](#configuring-preprocessors) for how to
|
||||
See [the preprocessors documentation](preprocessors.md) for how to
|
||||
specify which preprocessors should run before the Markdown renderer.
|
||||
|
||||
### Custom Renderers
|
||||
|
||||
A custom renderer can be enabled by adding a `[output.foo]` table to your
|
||||
`book.toml`. Similar to [preprocessors](#configuring-preprocessors) this will
|
||||
`book.toml`. Similar to [preprocessors](preprocessors.md) this will
|
||||
instruct `mdbook` to pass a representation of the book to `mdbook-foo` for
|
||||
rendering. See the [alternative backends] chapter for more detail.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ Will render as
|
||||
```rust
|
||||
# fn main() {
|
||||
let x = 5;
|
||||
let y = 7;
|
||||
let y = 6;
|
||||
|
||||
println!("{}", x + y);
|
||||
# }
|
||||
|
||||
@@ -15,8 +15,10 @@ shout-out to them!
|
||||
- [projektir](https://github.com/projektir)
|
||||
- [Phaiax](https://github.com/Phaiax)
|
||||
- Matt Ickstadt ([mattico](https://github.com/mattico))
|
||||
- Weihang Lo ([@weihanglo](https://github.com/weihanglo))
|
||||
- Avision Ho ([@avisionh](https://github.com/avisionh))
|
||||
- Vivek Akupatni ([@apatniv](https://github.com/apatniv))
|
||||
- Weihang Lo ([weihanglo](https://github.com/weihanglo))
|
||||
- Avision Ho ([avisionh](https://github.com/avisionh))
|
||||
- Vivek Akupatni ([apatniv](https://github.com/apatniv))
|
||||
- Eric Huss ([ehuss](https://github.com/ehuss))
|
||||
- Josh Rotenberg ([joshrotenberg](https://github.com/joshrotenberg))
|
||||
|
||||
If you feel you're missing from this list, feel free to add yourself in a PR.
|
||||
|
||||
@@ -22,7 +22,7 @@ pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book>
|
||||
.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")?;
|
||||
create_missing(src_dir, &summary).with_context(|| "Unable to create missing chapters")?;
|
||||
}
|
||||
|
||||
load_book_from_disk(&summary, src_dir)
|
||||
@@ -381,7 +381,7 @@ And here is some \
|
||||
|
||||
root.nested_items.push(second.clone().into());
|
||||
root.nested_items.push(SummaryItem::Separator);
|
||||
root.nested_items.push(second.clone().into());
|
||||
root.nested_items.push(second.into());
|
||||
|
||||
(root, temp_dir)
|
||||
}
|
||||
@@ -454,7 +454,7 @@ And here is some \
|
||||
sub_items: vec![
|
||||
BookItem::Chapter(nested.clone()),
|
||||
BookItem::Separator,
|
||||
BookItem::Chapter(nested.clone()),
|
||||
BookItem::Chapter(nested),
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
270
src/book/mod.rs
270
src/book/mod.rs
@@ -20,6 +20,7 @@ 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::{
|
||||
@@ -69,6 +70,20 @@ impl MDBook {
|
||||
|
||||
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);
|
||||
@@ -274,6 +289,9 @@ impl MDBook {
|
||||
RustEdition::E2018 => {
|
||||
cmd.args(&["--edition", "2018"]);
|
||||
}
|
||||
RustEdition::E2021 => {
|
||||
cmd.args(&["--edition", "2021"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,12 +386,7 @@ fn determine_renderers(config: &Config) -> Vec<Box<dyn Renderer>> {
|
||||
renderers
|
||||
}
|
||||
|
||||
fn default_preprocessors() -> Vec<Box<dyn Preprocessor>> {
|
||||
vec![
|
||||
Box::new(LinkPreprocessor::new()),
|
||||
Box::new(IndexPreprocessor::new()),
|
||||
]
|
||||
}
|
||||
const DEFAULT_PREPROCESSORS: &[&'static str] = &["links", "index"];
|
||||
|
||||
fn is_default_preprocessor(pre: &dyn Preprocessor) -> bool {
|
||||
let name = pre.name();
|
||||
@@ -382,36 +395,127 @@ fn is_default_preprocessor(pre: &dyn Preprocessor) -> bool {
|
||||
|
||||
/// Look at the `MDBook` and try to figure out what preprocessors to run.
|
||||
fn determine_preprocessors(config: &Config) -> Result<Vec<Box<dyn Preprocessor>>> {
|
||||
let mut preprocessors = Vec::new();
|
||||
// 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 {
|
||||
preprocessors.extend(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 key in preprocessor_table.keys() {
|
||||
match key.as_ref() {
|
||||
"links" => preprocessors.push(Box::new(LinkPreprocessor::new())),
|
||||
"index" => preprocessors.push(Box::new(IndexPreprocessor::new())),
|
||||
name => preprocessors.push(interpret_custom_preprocessor(
|
||||
name,
|
||||
&preprocessor_table[name],
|
||||
)),
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(preprocessors)
|
||||
// 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 interpret_custom_preprocessor(key: &str, table: &Value) -> Box<CmdPreprocessor> {
|
||||
let command = table
|
||||
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));
|
||||
|
||||
Box::new(CmdPreprocessor::new(key.to_string(), command))
|
||||
.unwrap_or_else(|| format!("mdbook-{}", key))
|
||||
}
|
||||
|
||||
fn interpret_custom_renderer(key: &str, table: &Value) -> Box<CmdRenderer> {
|
||||
@@ -511,8 +615,8 @@ mod tests {
|
||||
|
||||
assert!(got.is_ok());
|
||||
assert_eq!(got.as_ref().unwrap().len(), 2);
|
||||
assert_eq!(got.as_ref().unwrap()[0].name(), "links");
|
||||
assert_eq!(got.as_ref().unwrap()[1].name(), "index");
|
||||
assert_eq!(got.as_ref().unwrap()[0].name(), "index");
|
||||
assert_eq!(got.as_ref().unwrap()[1].name(), "links");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -559,9 +663,123 @@ mod tests {
|
||||
|
||||
// make sure the `preprocessor.random` table exists
|
||||
let random = cfg.get_preprocessor("random").unwrap();
|
||||
let random = interpret_custom_preprocessor("random", &Value::Table(random.clone()));
|
||||
let random = get_custom_preprocessor_cmd("random", &Value::Table(random.clone()));
|
||||
|
||||
assert_eq!(random.cmd(), "python random.py");
|
||||
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()
|
||||
.find(|preprocessor| preprocessor.name() == "random")
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[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()
|
||||
.find(|preprocessor| preprocessor.name() == "links")
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -382,7 +382,7 @@ impl<'a> SummaryParser<'a> {
|
||||
}
|
||||
Some(ev @ Event::Start(Tag::List(..))) => {
|
||||
self.back(ev);
|
||||
let mut bunch_of_items = self.parse_nested_numbered(&root_number)?;
|
||||
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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::get_book_dir;
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use mdbook::config;
|
||||
use mdbook::errors::Result;
|
||||
use mdbook::MDBook;
|
||||
@@ -18,6 +18,21 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||
)
|
||||
.arg_from_usage("--theme 'Copies the default theme into your source folder'")
|
||||
.arg_from_usage("--force 'Skips confirmation prompts'")
|
||||
.arg(
|
||||
Arg::with_name("title")
|
||||
.long("title")
|
||||
.takes_value(true)
|
||||
.help("Sets the book title")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ignore")
|
||||
.long("ignore")
|
||||
.takes_value(true)
|
||||
.possible_values(&["none", "git"])
|
||||
.help("Creates a VCS ignore file (i.e. .gitignore)")
|
||||
.required(false),
|
||||
)
|
||||
}
|
||||
|
||||
// Init command implementation
|
||||
@@ -25,7 +40,6 @@ 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");
|
||||
@@ -45,13 +59,23 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nDo you want a .gitignore to be created? (y/n)");
|
||||
|
||||
if confirm() {
|
||||
builder.create_gitignore(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 = request_book_title();
|
||||
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);
|
||||
|
||||
@@ -467,6 +467,9 @@ pub struct RustConfig {
|
||||
#[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,
|
||||
@@ -855,6 +858,26 @@ mod tests {
|
||||
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)]
|
||||
|
||||
74
src/main.rs
74
src/main.rs
@@ -3,8 +3,9 @@ extern crate clap;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use chrono::Local;
|
||||
use clap::{App, AppSettings, ArgMatches};
|
||||
use clap::{App, AppSettings, Arg, ArgMatches, Shell, SubCommand};
|
||||
use env_logger::Builder;
|
||||
use log::LevelFilter;
|
||||
use mdbook::utils;
|
||||
@@ -20,7 +21,40 @@ const VERSION: &str = concat!("v", crate_version!());
|
||||
fn main() {
|
||||
init_logger();
|
||||
|
||||
// Create a list of valid arguments and sub-commands
|
||||
let app = create_clap_app();
|
||||
|
||||
// Check which subcomamnd the user ran...
|
||||
let res = match app.get_matches().subcommand() {
|
||||
("init", Some(sub_matches)) => cmd::init::execute(sub_matches),
|
||||
("build", Some(sub_matches)) => cmd::build::execute(sub_matches),
|
||||
("clean", Some(sub_matches)) => cmd::clean::execute(sub_matches),
|
||||
#[cfg(feature = "watch")]
|
||||
("watch", Some(sub_matches)) => cmd::watch::execute(sub_matches),
|
||||
#[cfg(feature = "serve")]
|
||||
("serve", Some(sub_matches)) => cmd::serve::execute(sub_matches),
|
||||
("test", Some(sub_matches)) => cmd::test::execute(sub_matches),
|
||||
("completions", Some(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))?;
|
||||
|
||||
create_clap_app().gen_completions_to("mdbook", shell, &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<'a, 'b>() -> App<'a, 'b> {
|
||||
let app = App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.author("Mathieu David <mathieudavid@mathieudavid.org>")
|
||||
@@ -35,31 +69,26 @@ fn main() {
|
||||
.subcommand(cmd::init::make_subcommand())
|
||||
.subcommand(cmd::build::make_subcommand())
|
||||
.subcommand(cmd::test::make_subcommand())
|
||||
.subcommand(cmd::clean::make_subcommand());
|
||||
.subcommand(cmd::clean::make_subcommand())
|
||||
.subcommand(
|
||||
SubCommand::with_name("completions")
|
||||
.about("Generate shell completions for your shell to stdout")
|
||||
.arg(
|
||||
Arg::with_name("shell")
|
||||
.takes_value(true)
|
||||
.possible_values(&Shell::variants())
|
||||
.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());
|
||||
|
||||
// Check which subcomamnd the user ran...
|
||||
let res = match app.get_matches().subcommand() {
|
||||
("init", Some(sub_matches)) => cmd::init::execute(sub_matches),
|
||||
("build", Some(sub_matches)) => cmd::build::execute(sub_matches),
|
||||
("clean", Some(sub_matches)) => cmd::clean::execute(sub_matches),
|
||||
#[cfg(feature = "watch")]
|
||||
("watch", Some(sub_matches)) => cmd::watch::execute(sub_matches),
|
||||
#[cfg(feature = "serve")]
|
||||
("serve", Some(sub_matches)) => cmd::serve::execute(sub_matches),
|
||||
("test", Some(sub_matches)) => cmd::test::execute(sub_matches),
|
||||
(_, _) => unreachable!(),
|
||||
};
|
||||
|
||||
if let Err(e) = res {
|
||||
utils::log_backtrace(&e);
|
||||
|
||||
std::process::exit(101);
|
||||
}
|
||||
app
|
||||
}
|
||||
|
||||
fn init_logger() {
|
||||
@@ -103,7 +132,8 @@ fn get_book_dir(args: &ArgMatches) -> PathBuf {
|
||||
}
|
||||
|
||||
fn open<P: AsRef<OsStr>>(path: P) {
|
||||
if let Err(e) = open::that(path) {
|
||||
info!("Opening web browser");
|
||||
if let Err(e) = opener::open(path) {
|
||||
error!("Error opening web browser: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ impl CmdPreprocessor {
|
||||
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) {
|
||||
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);
|
||||
|
||||
@@ -54,11 +54,8 @@ impl HtmlHandlebars {
|
||||
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),
|
||||
);
|
||||
let fixed_content =
|
||||
utils::render_markdown_with_path(&ch.content, ctx.html_config.curly_quotes, Some(path));
|
||||
if !ctx.is_index {
|
||||
// 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
|
||||
@@ -178,7 +175,7 @@ impl HtmlHandlebars {
|
||||
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())?;
|
||||
utils::fs::write_file(destination, output_file, rendered.as_bytes())?;
|
||||
debug!("Creating 404.html ✓");
|
||||
Ok(())
|
||||
}
|
||||
@@ -223,10 +220,10 @@ impl HtmlHandlebars {
|
||||
}
|
||||
write_file(destination, "css/variables.css", &theme.variables_css)?;
|
||||
if let Some(contents) = &theme.favicon_png {
|
||||
write_file(destination, "favicon.png", &contents)?;
|
||||
write_file(destination, "favicon.png", contents)?;
|
||||
}
|
||||
if let Some(contents) = &theme.favicon_svg {
|
||||
write_file(destination, "favicon.svg", &contents)?;
|
||||
write_file(destination, "favicon.svg", contents)?;
|
||||
}
|
||||
write_file(destination, "highlight.css", &theme.highlight_css)?;
|
||||
write_file(destination, "tomorrow-night.css", &theme.tomorrow_night_css)?;
|
||||
@@ -509,7 +506,7 @@ impl Renderer for HtmlHandlebars {
|
||||
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)?;
|
||||
let mut data = make_data(&ctx.root, book, &ctx.config, &html_config, &theme)?;
|
||||
|
||||
// Print version
|
||||
let mut print_content = String::new();
|
||||
@@ -552,14 +549,14 @@ impl Renderer for HtmlHandlebars {
|
||||
let rendered =
|
||||
self.post_process(rendered, &html_config.playground, ctx.config.rust.edition);
|
||||
|
||||
utils::fs::write_file(&destination, "print.html", rendered.as_bytes())?;
|
||||
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)
|
||||
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)
|
||||
self.copy_additional_css_and_js(&html_config, &ctx.root, destination)
|
||||
.with_context(|| "Unable to copy across additional CSS and JS")?;
|
||||
|
||||
// Render search index
|
||||
@@ -567,7 +564,7 @@ impl Renderer for HtmlHandlebars {
|
||||
{
|
||||
let search = html_config.search.unwrap_or_default();
|
||||
if search.enable {
|
||||
super::search::create_files(&search, &destination, &book)?;
|
||||
super::search::create_files(&search, destination, book)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,7 +572,7 @@ impl Renderer for HtmlHandlebars {
|
||||
.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"])?;
|
||||
utils::fs::copy_files_except_ext(&src_dir, destination, true, Some(&build_dir), &["md"])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -836,13 +833,15 @@ fn add_playground_pre(
|
||||
{
|
||||
let contains_e2015 = classes.contains("edition2015");
|
||||
let contains_e2018 = classes.contains("edition2018");
|
||||
let edition_class = if contains_e2015 || contains_e2018 {
|
||||
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 => "",
|
||||
}
|
||||
};
|
||||
@@ -981,7 +980,7 @@ mod tests {
|
||||
];
|
||||
|
||||
for (src, should_be) in inputs {
|
||||
let got = build_header_links(&src);
|
||||
let got = build_header_links(src);
|
||||
assert_eq!(got, should_be);
|
||||
}
|
||||
}
|
||||
@@ -1064,4 +1063,28 @@ mod tests {
|
||||
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\">\n<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ fn find_chapter(
|
||||
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)? {
|
||||
if let Some(item) = target.find(&base_path, path, &item, &previous)? {
|
||||
return Ok(Some(item));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ impl HelperDef for RenderToc {
|
||||
// Section does not necessarily exist
|
||||
if let Some(section) = item.get("section") {
|
||||
out.write("<strong aria-hidden=\"true\">")?;
|
||||
out.write(§ion)?;
|
||||
out.write(section)?;
|
||||
out.write("</strong> ")?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ pub fn create_files(search_config: &Search, destination: &Path, book: &Book) ->
|
||||
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)?;
|
||||
render_item(&mut index, search_config, &mut doc_urls, item)?;
|
||||
}
|
||||
|
||||
let index = write_to_json(index, &search_config, doc_urls)?;
|
||||
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());
|
||||
@@ -85,7 +85,7 @@ fn render_item(
|
||||
.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).peekable();
|
||||
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);
|
||||
@@ -134,7 +134,7 @@ fn render_item(
|
||||
// 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);
|
||||
html_block.push_str(html);
|
||||
p.next();
|
||||
}
|
||||
|
||||
@@ -165,7 +165,12 @@ fn render_item(
|
||||
}
|
||||
}
|
||||
|
||||
if !heading.is_empty() {
|
||||
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,
|
||||
|
||||
@@ -108,9 +108,12 @@ function playground_text(playground) {
|
||||
|
||||
let text = playground_text(code_block);
|
||||
let classes = code_block.querySelector('code').classList;
|
||||
let has_2018 = classes.contains("edition2018");
|
||||
let edition = has_2018 ? "2018" : "2015";
|
||||
|
||||
let edition = "2015";
|
||||
if(classes.contains("edition2018")) {
|
||||
edition = "2018";
|
||||
} else if(classes.contains("edition2021")) {
|
||||
edition = "2021";
|
||||
}
|
||||
var params = {
|
||||
version: "stable",
|
||||
optimize: "0",
|
||||
@@ -133,7 +136,15 @@ function playground_text(playground) {
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(response => result_block.innerText = response.result)
|
||||
.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);
|
||||
}
|
||||
|
||||
@@ -151,12 +162,13 @@ function playground_text(playground) {
|
||||
if (window.ace) {
|
||||
// language-rust class needs to be removed for editable
|
||||
// blocks or highlightjs will capture events
|
||||
Array
|
||||
.from(document.querySelectorAll('code.editable'))
|
||||
code_nodes
|
||||
.filter(function (node) {return node.classList.contains("editable"); })
|
||||
.forEach(function (block) { block.classList.remove('language-rust'); });
|
||||
|
||||
Array
|
||||
.from(document.querySelectorAll('code:not(.editable)'))
|
||||
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); });
|
||||
@@ -359,7 +371,14 @@ function playground_text(playground) {
|
||||
});
|
||||
|
||||
themePopup.addEventListener('click', function (e) {
|
||||
var theme = e.target.id || e.target.parentElement.id;
|
||||
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);
|
||||
});
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ html {
|
||||
color: var(--fg);
|
||||
background-color: var(--bg);
|
||||
text-size-adjust: none;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -175,3 +176,7 @@ blockquote {
|
||||
margin: 5px 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.result-no-output {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #c5c8c6;;
|
||||
--inline-code-color: #c5c8c6;
|
||||
|
||||
--theme-popup-bg: #141617;
|
||||
--theme-popup-border: #43484d;
|
||||
@@ -147,7 +147,7 @@
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #c5c8c6;;
|
||||
--inline-code-color: #c5c8c6;
|
||||
|
||||
--theme-popup-bg: #161923;
|
||||
--theme-popup-border: #737480;
|
||||
@@ -228,7 +228,7 @@
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #c5c8c6;;
|
||||
--inline-code-color: #c5c8c6;
|
||||
|
||||
--theme-popup-bg: #141617;
|
||||
--theme-popup-border: #43484d;
|
||||
|
||||
@@ -247,7 +247,7 @@ mod tests {
|
||||
}
|
||||
|
||||
if let Err(e) =
|
||||
copy_files_except_ext(&tmp.path(), &tmp.path().join("output"), true, None, &["md"])
|
||||
copy_files_except_ext(tmp.path(), &tmp.path().join("output"), true, None, &["md"])
|
||||
{
|
||||
panic!("Error while executing the function:\n{:?}", e);
|
||||
}
|
||||
|
||||
130
src/utils/mod.rs
130
src/utils/mod.rs
@@ -48,19 +48,11 @@ pub fn id_from_content(content: &str) -> String {
|
||||
let mut content = content.to_string();
|
||||
|
||||
// Skip any tags or html-encoded stuff
|
||||
const REPL_SUB: &[&str] = &[
|
||||
"<em>",
|
||||
"</em>",
|
||||
"<code>",
|
||||
"</code>",
|
||||
"<strong>",
|
||||
"</strong>",
|
||||
"<",
|
||||
">",
|
||||
"&",
|
||||
"'",
|
||||
""",
|
||||
];
|
||||
lazy_static! {
|
||||
static ref HTML: Regex = Regex::new(r"(<.*?>)").unwrap();
|
||||
}
|
||||
content = HTML.replace_all(&content, "").into();
|
||||
const REPL_SUB: &[&str] = &["<", ">", "&", "'", """];
|
||||
for sub in REPL_SUB {
|
||||
content = content.replace(sub, "");
|
||||
}
|
||||
@@ -168,67 +160,40 @@ 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) -> Parser<'_> {
|
||||
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);
|
||||
let mut converter = EventQuoteConverter::new(curly_quotes);
|
||||
let p = new_cmark_parser(text, curly_quotes);
|
||||
let events = p
|
||||
.map(clean_codeblock_headers)
|
||||
.map(|event| adjust_links(event, path))
|
||||
.map(|event| converter.convert(event));
|
||||
.map(|event| adjust_links(event, path));
|
||||
|
||||
html::push_html(&mut s, events);
|
||||
s
|
||||
}
|
||||
|
||||
struct EventQuoteConverter {
|
||||
enabled: bool,
|
||||
convert_text: bool,
|
||||
}
|
||||
|
||||
impl EventQuoteConverter {
|
||||
fn new(enabled: bool) -> Self {
|
||||
EventQuoteConverter {
|
||||
enabled,
|
||||
convert_text: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert<'a>(&mut self, event: Event<'a>) -> Event<'a> {
|
||||
if !self.enabled {
|
||||
return event;
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::Start(Tag::CodeBlock(_)) => {
|
||||
self.convert_text = false;
|
||||
event
|
||||
}
|
||||
Event::End(Tag::CodeBlock(_)) => {
|
||||
self.convert_text = true;
|
||||
event
|
||||
}
|
||||
Event::Text(ref text) if self.convert_text => {
|
||||
Event::Text(CowStr::from(convert_quotes_to_curly(text)))
|
||||
}
|
||||
_ => event,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clean_codeblock_headers(event: Event<'_>) -> Event<'_> {
|
||||
match event {
|
||||
Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(ref info))) => {
|
||||
let info: String = info.chars().filter(|ch| !ch.is_whitespace()).collect();
|
||||
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))))
|
||||
}
|
||||
@@ -236,38 +201,6 @@ fn clean_codeblock_headers(event: Event<'_>) -> Event<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_quotes_to_curly(original_text: &str) -> String {
|
||||
// We'll consider the start to be "whitespace".
|
||||
let mut preceded_by_whitespace = true;
|
||||
|
||||
original_text
|
||||
.chars()
|
||||
.map(|original_char| {
|
||||
let converted_char = match original_char {
|
||||
'\'' => {
|
||||
if preceded_by_whitespace {
|
||||
'‘'
|
||||
} else {
|
||||
'’'
|
||||
}
|
||||
}
|
||||
'"' => {
|
||||
if preceded_by_whitespace {
|
||||
'“'
|
||||
} else {
|
||||
'”'
|
||||
}
|
||||
}
|
||||
_ => original_char,
|
||||
};
|
||||
|
||||
preceded_by_whitespace = original_char.is_whitespace();
|
||||
|
||||
converted_char
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Prints a "backtrace" of some `Error`.
|
||||
pub fn log_backtrace(e: &Error) {
|
||||
error!("Error: {}", e);
|
||||
@@ -372,7 +305,7 @@ more text with spaces
|
||||
```
|
||||
"#;
|
||||
|
||||
let expected = r#"<pre><code class="language-rust,no_run,,,should_panic,,property_3"></code></pre>
|
||||
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);
|
||||
@@ -410,6 +343,10 @@ more text with spaces
|
||||
);
|
||||
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]
|
||||
@@ -443,23 +380,4 @@ more text with spaces
|
||||
assert_eq!(normalize_id(""), "");
|
||||
}
|
||||
}
|
||||
|
||||
mod convert_quotes_to_curly {
|
||||
use super::super::convert_quotes_to_curly;
|
||||
|
||||
#[test]
|
||||
fn it_converts_single_quotes() {
|
||||
assert_eq!(convert_quotes_to_curly("'one', 'two'"), "‘one’, ‘two’");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_converts_double_quotes() {
|
||||
assert_eq!(convert_quotes_to_curly(r#""one", "two""#), "“one”, “two”");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_treats_tab_as_whitespace() {
|
||||
assert_eq!(convert_quotes_to_curly("\t'one'"), "\t‘one’");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
27
test_book/book.toml
Normal file
27
test_book/book.toml
Normal file
@@ -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"
|
||||
12
test_book/src/README.md
Normal file
12
test_book/src/README.md
Normal file
@@ -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.
|
||||
33
test_book/src/SUMMARY.md
Normal file
33
test_book/src/SUMMARY.md
Normal file
@@ -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)
|
||||
17
test_book/src/individual/README.md
Normal file
17
test_book/src/individual/README.md
Normal file
@@ -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
|
||||
30
test_book/src/individual/blockquote.md
Normal file
30
test_book/src/individual/blockquote.md
Normal file
@@ -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
|
||||
31
test_book/src/individual/code.md
Normal file
31
test_book/src/individual/code.md
Normal file
@@ -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
|
||||
```
|
||||
13
test_book/src/individual/emphasis.md
Normal file
13
test_book/src/individual/emphasis.md
Normal file
@@ -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_**.
|
||||
15
test_book/src/individual/heading.md
Normal file
15
test_book/src/individual/heading.md
Normal file
@@ -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
|
||||
27
test_book/src/individual/image.md
Normal file
27
test_book/src/individual/image.md
Normal file
@@ -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
|
||||
|
||||

|
||||
|
||||
## A 32x32 image
|
||||
|
||||

|
||||
|
||||
## A 256x256 image
|
||||
|
||||

|
||||
|
||||
## A 512x512 image
|
||||
|
||||

|
||||
|
||||
## A large image
|
||||
|
||||

|
||||
|
||||
## A SVG image
|
||||
|
||||

|
||||
8
test_book/src/individual/linebreak.md
Normal file
8
test_book/src/individual/linebreak.md
Normal file
@@ -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.
|
||||
15
test_book/src/individual/link_hr.md
Normal file
15
test_book/src/individual/link_hr.md
Normal file
@@ -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
|
||||
35
test_book/src/individual/list.md
Normal file
35
test_book/src/individual/list.md
Normal file
@@ -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
|
||||
- ?!
|
||||
61
test_book/src/individual/mixed.md
Normal file
61
test_book/src/individual/mixed.md
Normal file
@@ -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
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
- ~~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
|
||||
|
||||

|
||||
25
test_book/src/individual/paragraph.md
Normal file
25
test_book/src/individual/paragraph.md
Normal file
@@ -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.
|
||||
5
test_book/src/individual/strikethrough.md
Normal file
5
test_book/src/individual/strikethrough.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Strikethrough
|
||||
|
||||
~~This is Striked~~
|
||||
|
||||
~~This is **strong**, _italic_ , **_both_** and striked~~
|
||||
28
test_book/src/individual/table.md
Normal file
28
test_book/src/individual/table.md
Normal file
@@ -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 |
|
||||
11
test_book/src/individual/task.md
Normal file
11
test_book/src/individual/task.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Tasks
|
||||
|
||||
- [ ] Task 1
|
||||
- [ ] Task 2
|
||||
- [x] Completed Task 1
|
||||
- [x] Completed Task 2
|
||||
|
||||
---
|
||||
|
||||
- [ ] **Important Task**
|
||||
- [x] _Completed Important task_
|
||||
47
test_book/src/languages/README.md
Normal file
47
test_book/src/languages/README.md
Normal file
@@ -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
|
||||
927
test_book/src/languages/highlight.md
Normal file
927
test_book/src/languages/highlight.md
Normal file
@@ -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
|
||||
|
||||
```
|
||||
|
||||
## 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
|
||||
```
|
||||
ansible_variables: "foo {{variable}}"
|
||||
|
||||
array_nested:
|
||||
- a
|
||||
- b: 1
|
||||
c: 2
|
||||
- b
|
||||
- comment
|
||||
```
|
||||
3
test_book/src/prefix.md
Normal file
3
test_book/src/prefix.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Prefix Chapter
|
||||
|
||||
This is to verify the placement and style of prefix chapter in book index.
|
||||
1
test_book/src/rust/README.md
Normal file
1
test_book/src/rust/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# Rust specific code examples
|
||||
27
test_book/src/rust/rust_codeblock.md
Normal file
27
test_book/src/rust/rust_codeblock.md
Normal file
@@ -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!");
|
||||
}
|
||||
```
|
||||
3
test_book/src/suffix.md
Normal file
3
test_book/src/suffix.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Suffix Chapter
|
||||
|
||||
This is to verify the placement and style of suffix chapter in book index.
|
||||
29
tests/cli/build.rs
Normal file
29
tests/cli/build.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use crate::dummy_book::DummyBook;
|
||||
|
||||
use assert_cmd::Command;
|
||||
|
||||
#[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 = Command::cargo_bin("mdbook").unwrap();
|
||||
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());
|
||||
}
|
||||
2
tests/cli/mod.rs
Normal file
2
tests/cli/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod build;
|
||||
mod test;
|
||||
34
tests/cli/test.rs
Normal file
34
tests/cli/test.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use crate::dummy_book::DummyBook;
|
||||
|
||||
use assert_cmd::Command;
|
||||
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 = Command::cargo_bin("mdbook").unwrap();
|
||||
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 = Command::cargo_bin("mdbook").unwrap();
|
||||
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());
|
||||
}
|
||||
2
tests/cli_tests.rs
Normal file
2
tests/cli_tests.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod cli;
|
||||
mod dummy_book;
|
||||
@@ -12,6 +12,7 @@
|
||||
- [Recursive](first/recursive.md)
|
||||
- [Markdown](first/markdown.md)
|
||||
- [Unicode](first/unicode.md)
|
||||
- [No Headers](first/no-headers.md)
|
||||
- [Second Chapter](second.md)
|
||||
- [Nested Chapter](second/nested.md)
|
||||
|
||||
|
||||
3
tests/dummy_book/src/first/no-headers.md
Normal file
3
tests/dummy_book/src/first/no-headers.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Capybara capybara capybara.
|
||||
|
||||
Capybara capybara capybara.
|
||||
@@ -34,6 +34,7 @@ const TOC_SECOND_LEVEL: &[&str] = &[
|
||||
"1.3. Recursive",
|
||||
"1.4. Markdown",
|
||||
"1.5. Unicode",
|
||||
"1.6. No Headers",
|
||||
"2.1. Nested Chapter",
|
||||
];
|
||||
|
||||
@@ -264,7 +265,7 @@ fn root_index_html() -> Result<Document> {
|
||||
fn check_second_toc_level() {
|
||||
let doc = root_index_html().unwrap();
|
||||
let mut should_be = Vec::from(TOC_SECOND_LEVEL);
|
||||
should_be.sort();
|
||||
should_be.sort_unstable();
|
||||
|
||||
let pred = descendants!(
|
||||
Class("chapter"),
|
||||
@@ -288,7 +289,7 @@ fn check_first_toc_level() {
|
||||
let mut should_be = Vec::from(TOC_TOP_LEVEL);
|
||||
|
||||
should_be.extend(TOC_SECOND_LEVEL);
|
||||
should_be.sort();
|
||||
should_be.sort_unstable();
|
||||
|
||||
let pred = descendants!(
|
||||
Class("chapter"),
|
||||
@@ -535,7 +536,7 @@ fn redirects_are_emitted_correctly() {
|
||||
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));
|
||||
redirect_file.extend(remove_absolute_components(original));
|
||||
let contents = fs::read_to_string(&redirect_file).unwrap();
|
||||
assert!(contents.contains(redirect));
|
||||
}
|
||||
@@ -552,7 +553,7 @@ fn edit_url_has_default_src_dir_edit_url() {
|
||||
edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
|
||||
"#;
|
||||
|
||||
write_file(&temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
|
||||
write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
|
||||
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
md.build().unwrap();
|
||||
@@ -560,7 +561,7 @@ fn edit_url_has_default_src_dir_edit_url() {
|
||||
let index_html = temp.path().join("book").join("index.html");
|
||||
assert_contains_strings(
|
||||
index_html,
|
||||
&vec![
|
||||
&[
|
||||
r#"href="https://github.com/rust-lang/mdBook/edit/master/guide/src/README.md" title="Suggest an edit""#,
|
||||
],
|
||||
);
|
||||
@@ -578,7 +579,7 @@ fn edit_url_has_configured_src_dir_edit_url() {
|
||||
edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
|
||||
"#;
|
||||
|
||||
write_file(&temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
|
||||
write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
|
||||
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
md.build().unwrap();
|
||||
@@ -586,7 +587,7 @@ fn edit_url_has_configured_src_dir_edit_url() {
|
||||
let index_html = temp.path().join("book").join("index.html");
|
||||
assert_contains_strings(
|
||||
index_html,
|
||||
&vec![
|
||||
&[
|
||||
r#"href="https://github.com/rust-lang/mdBook/edit/master/guide/src2/README.md" title="Suggest an edit""#,
|
||||
],
|
||||
);
|
||||
@@ -611,7 +612,7 @@ mod search {
|
||||
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()
|
||||
serde_json::from_str(index).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -631,6 +632,7 @@ mod search {
|
||||
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 conclusion = get_doc_ref("conclusion.html#conclusion");
|
||||
|
||||
let bodyidx = &index["index"]["index"]["body"]["root"];
|
||||
@@ -644,13 +646,21 @@ mod search {
|
||||
assert_eq!(docs[&some_section]["body"], "");
|
||||
assert_eq!(
|
||||
docs[&summary]["body"],
|
||||
"Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode Second Chapter Nested Chapter Conclusion"
|
||||
"Dummy Book Introduction First Chapter Nested Chapter Includes Recursive Markdown Unicode No Headers Second Chapter Nested Chapter Conclusion"
|
||||
);
|
||||
assert_eq!(
|
||||
docs[&summary]["breadcrumbs"],
|
||||
"First Chapter » Includes » Summary"
|
||||
);
|
||||
assert_eq!(docs[&conclusion]["body"], "I put <HTML> in here!");
|
||||
assert_eq!(
|
||||
docs[&no_headers]["breadcrumbs"],
|
||||
"First Chapter » No Headers"
|
||||
);
|
||||
assert_eq!(
|
||||
docs[&no_headers]["body"],
|
||||
"Capybara capybara capybara. Capybara capybara capybara."
|
||||
);
|
||||
}
|
||||
|
||||
// Setting this to `true` may cause issues with `cargo watch`,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
12
triagebot.toml
Normal file
12
triagebot.toml
Normal file
@@ -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"
|
||||
]
|
||||
Reference in New Issue
Block a user