feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
+1
@@ -0,0 +1 @@
|
||||
{"files":{".cargo_vcs_info.json":"0d589d218b39648828ab91882cec583cb67ce345c422a25940a9a77538a05223","Cargo.lock":"6139ab7768226ee76ffbe02793ffce2a2ee69ec56b97a954c9f3cdc689dbc4f3","Cargo.toml":"0577f74fec360c2df391f4dc567f6071f67a9670072e01eb42b708052700b10b","Cargo.toml.orig":"cdc8999267590a541b25ad157637e3bc59ee83e82c5e63373bf29629a967c060","LICENSE":"c4f10f55904bdb9f27d3fbf94c354926d6cfe8b982276e556238c258941b243b","README.md":"83c86a8d5c92c4d033667da29bc16288cdf6ba855cb8368f296bd35d49eb163d","build.rs":"87f9e2a9d5c6c939c1f8adba587bc5688dad1506bf61f29c54001cb5ad38195a","examples/broken-link-callbacks.rs":"f98d7092004a338a0f86879b3e30270be28b19d2c1bd250380610f84b84eef4a","examples/event-filter.rs":"2e297d9a6117969cb5f690375122d2306a697bf09aee108ddaecb213f29c8c62","examples/events.rs":"6dcdfda734581a220806aafa58f4aab6de225dfa665f5861e575c05c1f69e9e1","examples/footnote-rewrite.rs":"789772c69ebcc60c879c6a1c71b7d4e9980bc1499467dd734dee566906da92e1","examples/parser-map-event-print.rs":"b35fab7747364e0357f6793c7cab30181d5b6cfd1ee95cd247f688c4fe550e8e","examples/parser-map-tag-print.rs":"95b20abf1fe3ede30d556d86654aa31589aa91cf0137715b127345b7ff06bd6e","examples/string-to-string.rs":"33cfc2ccee93637db64a18135fedb5bbf2950dcb8c72bed50eed067ec3758f57","src/entities.rs":"1602f3c4d22c0b0e3a4a08467a50bf9d0c27aadf2e5661b15a2cda20da0895a9","src/firstpass.rs":"081991be008db39f77085cbd598e28fb2953136efd930471fffa4faf48631714","src/html.rs":"ec0d08e133d8a5898a53210363b54e1b993caae8a0fae0efd84c85cf94c7617b","src/lib.rs":"512b2eea78c939c1f62a9d2912c91861541c15d90fc328385fcb77cf58a3a78b","src/linklabel.rs":"0d94291f80e8eac332b72ce7f0b6f3f666b31cdea43c47277d4d6d4db6da2ab6","src/main.rs":"fa9aa19488b480a27ab130eed289ecb7267ab100250aa176849f85d4cd633c72","src/parse.rs":"790599211fbbcd401a83266b98a51493d3c9c15ebd950a53712e195afb2b5736","src/puncttable.rs":"b591c2b353ba60b88661597f853b4986df87bee6b53045ab94ae562848457be4","src/scanners.rs":"324c3c98634abaaf1fe3c4d53b35a19011d9791a832ebacb44bead84853e189d","src/strings.rs":"1af75a72c60e5cc424fc1e67470d6523a60491d7e11e788ef2ce6f84e41bb9c0","src/tree.rs":"16c0f66855c53cd03521d3adae237afbc27857baa7870e69a3905179e8762581","src/utils.rs":"80ed2dd07ad11bd752df74c30b482d4f49637d3f74f9af5fbf923c616573e3a9","tests/errors.rs":"f755025975b611f4233094f4b8e1f85a4cec7b59c0a69209d2ef70ac065712b9","tests/html.rs":"0c4b19b20d26f2edcd7a5b803a5cc982aa191e50df5005f63942146912a91f00","tests/lib.rs":"eb3e5c199211ec3c0733f2213d83b251cf063cdbe6d19879b53aec0e57c0b8eb","tests/serde.rs":"bc38923455eb8f4dcdf4883d2d8d1331628466d903c9cc78345d09ae00e4d2a0","tests/suite/blockquotes_tags.rs":"a314207dc0c01116f412c407a5d6f75ecd799e854995eb97525f37470e6cefc5","tests/suite/footnotes.rs":"f9200d7e7366349cb498ca6e9a7e2a5169ea3f94efce86f3aa09adcb480136fa","tests/suite/gfm_strikethrough.rs":"119967b89e46e2b439e86081a420e382721d41d39f30e74d0f31a33ea71897fc","tests/suite/gfm_table.rs":"913b6e54e07c7f77cc3d1b5b16993350fe9db64496c41e06596ea9e58aaa3178","tests/suite/gfm_tasklist.rs":"c9688483d63af69cb7979ff0528b691384a2d9a54e8187aa1ea16afb18e7abe1","tests/suite/heading_attrs.rs":"b3b1240aeb9091b1a6572e2f13b7c181bc75e0be6758b23e7d3d2963be3d478a","tests/suite/math.rs":"c634834759e84f454b12cb884605b6c4f1383b39e342908e33e70d70eb3ecfd2","tests/suite/metadata_blocks.rs":"0fc54d468a5cffe35117f7f4595de13babb162518263470c0f85c3aa3074a535","tests/suite/mod.rs":"1d7b3e42f9fb98416724d6ed5bd3b293cf8d8676b7604e8e18fbfc33e87c743c","tests/suite/old_footnotes.rs":"2e798cf7104d450ff34844703cba715995c0090a85e363eb7c33cd4aa5be02c9","tests/suite/regression.rs":"ac603c8a1df7c8dcd344f055a700c9939ef858687d76abce1699a2311698a59a","tests/suite/smart_punct.rs":"f566902ef85a8e96d9632a90d36ac3df29e6ff65c854346aafca621150241662","tests/suite/spec.rs":"407272a852a841b65d1dd90cccef5aa01c2a781af92197633da5df69bc7dbc98","tests/suite/strikethrough.rs":"615c5112e1d0f26733fc274e792dbf5d3c3d1c0aacfd0369ad1d9494b0c9fcca","tests/suite/table.rs":"f531eee76e7f0674ada6a4bdd710cc78f326e0719d3cbedf2b4aaa13133d28e1"},"package":"679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625"}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "c05f017b899e85392af1a25e13bc7b5ca41cb063"
|
||||
},
|
||||
"path_in_vcs": "pulldown-cmark"
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.11.3"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
"getopts",
|
||||
"lazy_static",
|
||||
"memchr",
|
||||
"pulldown-cmark-escape",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark-escape"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.209"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.209"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
Vendored
+118
@@ -0,0 +1,118 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
rust-version = "1.71.1"
|
||||
name = "pulldown-cmark"
|
||||
version = "0.11.3"
|
||||
authors = [
|
||||
"Raph Levien <raph.levien@gmail.com>",
|
||||
"Marcus Klaas de Vries <mail@marcusklaas.nl>",
|
||||
]
|
||||
build = "build.rs"
|
||||
exclude = [
|
||||
"/third_party/**/*",
|
||||
"/tools/**/*",
|
||||
"/specs/**/*",
|
||||
"/azure-pipelines.yml",
|
||||
]
|
||||
description = "A pull parser for CommonMark"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"markdown",
|
||||
"commonmark",
|
||||
]
|
||||
categories = ["text-processing"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/raphlinus/pulldown-cmark"
|
||||
|
||||
[[bin]]
|
||||
name = "pulldown-cmark"
|
||||
doc = false
|
||||
required-features = ["getopts"]
|
||||
|
||||
[[example]]
|
||||
name = "event-filter"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["html"]
|
||||
|
||||
[[example]]
|
||||
name = "footnote-rewrite"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["html"]
|
||||
|
||||
[[example]]
|
||||
name = "parser-map-event-print"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["html"]
|
||||
|
||||
[[example]]
|
||||
name = "parser-map-tag-print"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["html"]
|
||||
|
||||
[[example]]
|
||||
name = "string-to-string"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["html"]
|
||||
|
||||
[[example]]
|
||||
name = "broken-link-callbacks"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["html"]
|
||||
|
||||
[dependencies.bitflags]
|
||||
version = "2"
|
||||
|
||||
[dependencies.getopts]
|
||||
version = "0.2"
|
||||
optional = true
|
||||
|
||||
[dependencies.memchr]
|
||||
version = "2.5"
|
||||
|
||||
[dependencies.pulldown-cmark-escape]
|
||||
version = "0.11"
|
||||
optional = true
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0"
|
||||
features = ["derive"]
|
||||
optional = true
|
||||
|
||||
[dependencies.unicase]
|
||||
version = "2.6"
|
||||
|
||||
[dev-dependencies.bincode]
|
||||
version = "1.3.1"
|
||||
|
||||
[dev-dependencies.lazy_static]
|
||||
version = "1.4"
|
||||
|
||||
[dev-dependencies.regex]
|
||||
version = "1.6"
|
||||
|
||||
[dev-dependencies.serde_json]
|
||||
version = "1.0.61"
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"getopts",
|
||||
"html",
|
||||
]
|
||||
gen-tests = []
|
||||
html = ["pulldown-cmark-escape"]
|
||||
simd = ["pulldown-cmark-escape?/simd"]
|
||||
|
||||
[lints.rust.unexpected_cfgs]
|
||||
level = "warn"
|
||||
priority = 0
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright 2015 Google Inc. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
Vendored
+200
@@ -0,0 +1,200 @@
|
||||
# pulldown-cmark
|
||||
|
||||
[](https://github.com/pulldown-cmark/pulldown-cmark/actions/workflows/rust.yml)
|
||||
[](https://docs.rs/pulldown-cmark)
|
||||
[](https://crates.io/crates/pulldown-cmark)
|
||||
|
||||
[Documentation](https://docs.rs/pulldown-cmark/)
|
||||
|
||||
This library is a pull parser for [CommonMark](http://commonmark.org/), written
|
||||
in [Rust](http://www.rust-lang.org/). It comes with a simple command-line tool,
|
||||
useful for rendering to HTML, and is also designed to be easy to use from as
|
||||
a library.
|
||||
|
||||
It is designed to be:
|
||||
|
||||
* Fast; a bare minimum of allocation and copying
|
||||
* Safe; written in pure Rust with no unsafe blocks (except in the opt-in SIMD feature)
|
||||
* Versatile; in particular source-maps are supported
|
||||
* Correct; the goal is 100% compliance with the [CommonMark spec](http://spec.commonmark.org/)
|
||||
|
||||
Further, it optionally supports parsing footnotes,
|
||||
[Github flavored tables](https://github.github.com/gfm/#tables-extension-),
|
||||
[Github flavored task lists](https://github.github.com/gfm/#task-list-items-extension-) and
|
||||
[strikethrough](https://github.github.com/gfm/#strikethrough-extension-).
|
||||
|
||||
Rustc 1.71.1 or newer is required to build the crate.
|
||||
|
||||
## Example
|
||||
|
||||
Example usage:
|
||||
|
||||
```rust
|
||||
// Create parser with example Markdown text.
|
||||
let markdown_input = "hello world";
|
||||
let parser = pulldown_cmark::Parser::new(markdown_input);
|
||||
|
||||
// Write to a new String buffer.
|
||||
let mut html_output = String::new();
|
||||
pulldown_cmark::html::push_html(&mut html_output, parser);
|
||||
assert_eq!(&html_output, "<p>hello world</p>\n");
|
||||
```
|
||||
|
||||
## Why a pull parser?
|
||||
|
||||
There are many parsers for Markdown and its variants, but to my knowledge none
|
||||
use pull parsing. Pull parsing has become popular for XML, especially for
|
||||
memory-conscious applications, because it uses dramatically less memory than
|
||||
constructing a document tree, but is much easier to use than push parsers. Push
|
||||
parsers are notoriously difficult to use, and also often error-prone because of
|
||||
the need for user to delicately juggle state in a series of callbacks.
|
||||
|
||||
In a clean design, the parsing and rendering stages are neatly separated, but
|
||||
this is often sacrificed in the name of performance and expedience. Many Markdown
|
||||
implementations mix parsing and rendering together, and even designs that try
|
||||
to separate them (such as the popular [hoedown](https://github.com/hoedown/hoedown)),
|
||||
make the assumption that the rendering process can be fully represented as a
|
||||
serialized string.
|
||||
|
||||
Pull parsing is in some sense the most versatile architecture. It's possible to
|
||||
drive a push interface, also with minimal memory, and quite straightforward to
|
||||
construct an AST. Another advantage is that source-map information (the mapping
|
||||
between parsed blocks and offsets within the source text) is readily available;
|
||||
you can call `into_offset_iter()` to create an iterator that yields `(Event, Range)`
|
||||
pairs, where the second element is the event's corresponding range in the source
|
||||
document.
|
||||
|
||||
While manipulating ASTs is the most flexible way to transform documents,
|
||||
operating on iterators is surprisingly easy, and quite efficient. Here, for
|
||||
example, is the code to transform soft line breaks into hard breaks:
|
||||
|
||||
```rust
|
||||
let parser = parser.map(|event| match event {
|
||||
Event::SoftBreak => Event::HardBreak,
|
||||
_ => event
|
||||
});
|
||||
```
|
||||
|
||||
Or expanding an abbreviation in text:
|
||||
|
||||
```rust
|
||||
let parser = parser.map(|event| match event {
|
||||
Event::Text(text) => Event::Text(text.replace("abbr", "abbreviation").into()),
|
||||
_ => event
|
||||
});
|
||||
```
|
||||
|
||||
Another simple example is code to determine the max nesting level:
|
||||
|
||||
```rust
|
||||
let mut max_nesting = 0;
|
||||
let mut level = 0;
|
||||
for event in parser {
|
||||
match event {
|
||||
Event::Start(_) => {
|
||||
level += 1;
|
||||
max_nesting = std::cmp::max(max_nesting, level);
|
||||
}
|
||||
Event::End(_) => level -= 1,
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that consecutive text events can happen due to the manner in which the
|
||||
parser evaluates the source. A utility `TextMergeStream` exists to improve
|
||||
the comfort of iterating the events:
|
||||
|
||||
```rust
|
||||
use pulldown_cmark::{Event, Parser, Options};
|
||||
|
||||
let markdown_input = "Hello world, this is a ~~complicated~~ *very simple* example.";
|
||||
|
||||
let iterator = TextMergeStream::new(Parser::new(markdown_input));
|
||||
|
||||
for event in iterator {
|
||||
match event {
|
||||
Event::Text(text) => println!("{}", text),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are some basic but fully functional examples of the usage of the crate in the
|
||||
`examples` directory of this repository.
|
||||
|
||||
## Using Rust idiomatically
|
||||
|
||||
A lot of the internal scanning code is written at a pretty low level (it
|
||||
pretty much scans byte patterns for the bits of syntax), but the external
|
||||
interface is designed to be idiomatic Rust.
|
||||
|
||||
Pull parsers are at heart an iterator of events (start and end tags, text,
|
||||
and other bits and pieces). The parser data structure implements the
|
||||
Rust Iterator trait directly, and Event is an enum. Thus, you can use the
|
||||
full power and expressivity of Rust's iterator infrastructure, including
|
||||
for loops and `map` (as in the examples above), collecting the events into
|
||||
a vector (for recording, playback, and manipulation), and more.
|
||||
|
||||
Further, the `Text` event (representing text) is a small copy-on-write string.
|
||||
The vast majority of text fragments are just
|
||||
slices of the source document. For these, copy-on-write gives a convenient
|
||||
representation that requires no allocation or copying, but allocated
|
||||
strings are available when they're needed. Thus, when rendering text to
|
||||
HTML, most text is copied just once, from the source document to the
|
||||
HTML buffer.
|
||||
|
||||
When using the pulldown-cmark's own HTML renderer, make sure to write to a buffered
|
||||
target like a `Vec<u8>` or `String`. Since it performs many (very) small writes, writing
|
||||
directly to stdout, files, or sockets is detrimental to performance. Such writers can
|
||||
be wrapped in a [`BufWriter`](https://doc.rust-lang.org/std/io/struct.BufWriter.html).
|
||||
|
||||
## Build options
|
||||
|
||||
By default, the binary is built as well. If you don't want/need it, then build like this:
|
||||
|
||||
```bash
|
||||
> cargo build --no-default-features
|
||||
```
|
||||
|
||||
Or add this package as dependency of your project using `cargo add`:
|
||||
|
||||
```bash
|
||||
> cargo add pulldown-cmark --no-default-features
|
||||
```
|
||||
|
||||
SIMD accelerated scanners are available for the x64 platform from version 0.5 onwards. To
|
||||
enable them, build with simd feature:
|
||||
|
||||
```bash
|
||||
> cargo build --release --features simd
|
||||
```
|
||||
|
||||
Or add this package as dependency of your project with the feature using `cargo add`:
|
||||
|
||||
```bash
|
||||
> cargo add pulldown-cmark --no-default-features --features=simd
|
||||
```
|
||||
|
||||
For a higher release performance you may want this configuration in your profile release:
|
||||
|
||||
```
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
```
|
||||
|
||||
## Authors
|
||||
|
||||
The main author is Raph Levien. The implementation of the new design (v0.3+) was
|
||||
completed by Marcus Klaas de Vries. Since 2023, the development has been driven
|
||||
by Martín Pozo, Michael Howell, Roope Salmi and Martin Geisler.
|
||||
|
||||
## License
|
||||
|
||||
This software is under the MIT license. See details in [license file](./LICENSE).
|
||||
|
||||
## Contributions
|
||||
|
||||
We gladly accept contributions via GitHub pull requests. Please see
|
||||
[CONTRIBUTING.md](CONTRIBUTING.md) for more details.
|
||||
Vendored
+217
@@ -0,0 +1,217 @@
|
||||
fn main() {
|
||||
generate_tests_from_spec()
|
||||
}
|
||||
|
||||
// If the "gen-tests" feature is absent,
|
||||
// this function will be compiled down to nothing
|
||||
#[cfg(not(feature = "gen-tests"))]
|
||||
fn generate_tests_from_spec() {}
|
||||
|
||||
// If the feature is present, generate tests
|
||||
// from any .txt file present in the specs/ directory
|
||||
//
|
||||
// Test cases are present in the files in the
|
||||
// following format:
|
||||
//
|
||||
// ```````````````````````````````` example
|
||||
// markdown
|
||||
// .
|
||||
// expected html output
|
||||
// ````````````````````````````````
|
||||
#[cfg(feature = "gen-tests")]
|
||||
fn generate_tests_from_spec() {
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
// This is a hardcoded path to the CommonMark spec because it is not situated in
|
||||
// the specs/ directory. It's in an array to easily chain it to the other iterator
|
||||
// and make it easy to eventually add other hardcoded paths in the future if needed
|
||||
let hardcoded = [
|
||||
"./third_party/CommonMark/spec.txt",
|
||||
"./third_party/CommonMark/smart_punct.txt",
|
||||
"./third_party/GitHub/gfm_table.txt",
|
||||
"./third_party/GitHub/gfm_strikethrough.txt",
|
||||
"./third_party/GitHub/gfm_tasklist.txt",
|
||||
];
|
||||
let hardcoded_iter = hardcoded.iter().map(PathBuf::from);
|
||||
|
||||
// Create an iterator over the files in the specs/ directory that have a .txt extension
|
||||
let mut spec_files = fs::read_dir("./specs")
|
||||
.expect("Could not find the 'specs' directory")
|
||||
.filter_map(Result::ok)
|
||||
.map(|d| d.path())
|
||||
.filter(|p| p.extension().map(|e| e.to_owned()).is_some())
|
||||
.chain(hardcoded_iter)
|
||||
.collect::<Vec<_>>();
|
||||
// Sort by spec names
|
||||
spec_files.sort_by(|p, q| p.file_stem().cmp(&q.file_stem()));
|
||||
let spec_files = spec_files;
|
||||
|
||||
for file_path in &spec_files {
|
||||
let mut raw_spec = String::new();
|
||||
|
||||
File::open(&file_path)
|
||||
.and_then(|mut f| f.read_to_string(&mut raw_spec))
|
||||
.expect("Could not read the spec file");
|
||||
|
||||
let rs_test_file = PathBuf::from("./tests/suite/")
|
||||
.join(file_path.file_name().expect("Invalid filename"))
|
||||
.with_extension("rs");
|
||||
|
||||
let mut spec_rs =
|
||||
File::create(&rs_test_file).expect(&format!("Could not create {:?}", rs_test_file));
|
||||
|
||||
let spec_name = file_path.file_stem().unwrap().to_str().unwrap();
|
||||
|
||||
let spec = Spec::new(&raw_spec);
|
||||
let mut n_tests = 0;
|
||||
|
||||
spec_rs
|
||||
.write_all(b"// This file is auto-generated by the build script\n")
|
||||
.unwrap();
|
||||
spec_rs
|
||||
.write_all(b"// Please, do not modify it manually\n")
|
||||
.unwrap();
|
||||
spec_rs
|
||||
.write_all(b"\nuse super::test_markdown_html;\n")
|
||||
.unwrap();
|
||||
|
||||
for (i, testcase) in spec.enumerate() {
|
||||
spec_rs
|
||||
.write_fmt(format_args!(
|
||||
r###"
|
||||
#[test]
|
||||
fn {}_test_{i}() {{
|
||||
let original = r##"{original}"##;
|
||||
let expected = r##"{expected}"##;
|
||||
|
||||
test_markdown_html(original, expected, {smart_punct}, {metadata_blocks}, {old_footnotes});
|
||||
}}
|
||||
"###,
|
||||
spec_name,
|
||||
i = i + 1,
|
||||
original = testcase.original,
|
||||
expected = testcase.expected,
|
||||
smart_punct = testcase.smart_punct,
|
||||
metadata_blocks = testcase.metadata_blocks,
|
||||
old_footnotes = testcase.old_footnotes,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
n_tests += 1;
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:warning=Generated {} tests in {:?}",
|
||||
n_tests, rs_test_file
|
||||
);
|
||||
}
|
||||
|
||||
// write mods to suite/mod.rs
|
||||
let suite_mod_file = PathBuf::from("./tests/suite/mod").with_extension("rs");
|
||||
|
||||
let mut mod_rs =
|
||||
File::create(&suite_mod_file).expect(&format!("Could not create {:?}", &suite_mod_file));
|
||||
|
||||
mod_rs
|
||||
.write_all(b"// This file is auto-generated by the build script\n")
|
||||
.unwrap();
|
||||
mod_rs
|
||||
.write_all(b"// Please, do not modify it manually\n")
|
||||
.unwrap();
|
||||
mod_rs
|
||||
.write_all(b"\npub use super::test_markdown_html;\n\n")
|
||||
.unwrap();
|
||||
|
||||
for file_path in &spec_files {
|
||||
let mod_name = file_path.file_stem().unwrap().to_str().unwrap();
|
||||
mod_rs.write_all(b"mod ").unwrap();
|
||||
mod_rs.write_all(mod_name.as_bytes()).unwrap();
|
||||
mod_rs.write_all(b";\n").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "gen-tests")]
|
||||
pub struct Spec<'a> {
|
||||
spec: &'a str,
|
||||
}
|
||||
|
||||
#[cfg(feature = "gen-tests")]
|
||||
impl<'a> Spec<'a> {
|
||||
pub fn new(spec: &'a str) -> Self {
|
||||
Spec { spec }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "gen-tests")]
|
||||
pub struct TestCase {
|
||||
pub original: String,
|
||||
pub expected: String,
|
||||
pub smart_punct: bool,
|
||||
pub metadata_blocks: bool,
|
||||
pub old_footnotes: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "gen-tests")]
|
||||
impl<'a> Iterator for Spec<'a> {
|
||||
type Item = TestCase;
|
||||
|
||||
fn next(&mut self) -> Option<TestCase> {
|
||||
let spec = self.spec;
|
||||
let prefix = "```````````````````````````````` example";
|
||||
|
||||
let (i_start, smart_punct, metadata_blocks, old_footnotes) =
|
||||
self.spec.find(prefix).and_then(|pos| {
|
||||
let smartpunct_suffix = "_smartpunct\n";
|
||||
let metadata_blocks_suffix = "_metadata_blocks\n";
|
||||
let old_footnotes_suffix = "_old_footnotes\n";
|
||||
if spec[(pos + prefix.len())..].starts_with(smartpunct_suffix) {
|
||||
Some((
|
||||
pos + prefix.len() + smartpunct_suffix.len(),
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
))
|
||||
} else if spec[(pos + prefix.len())..].starts_with(metadata_blocks_suffix) {
|
||||
Some((
|
||||
pos + prefix.len() + metadata_blocks_suffix.len(),
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
))
|
||||
} else if spec[(pos + prefix.len())..].starts_with(old_footnotes_suffix) {
|
||||
Some((
|
||||
pos + prefix.len() + old_footnotes_suffix.len(),
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
))
|
||||
} else if spec[(pos + prefix.len())..].starts_with('\n') {
|
||||
Some((pos + prefix.len() + 1, false, false, false))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
|
||||
let i_end = self.spec[i_start..]
|
||||
.find("\n.\n")
|
||||
.map(|pos| (pos + 1) + i_start)?;
|
||||
|
||||
let e_end = self.spec[i_end + 2..]
|
||||
.find("````````````````````````````````\n")
|
||||
.map(|pos| pos + i_end + 2)?;
|
||||
|
||||
self.spec = &self.spec[e_end + 33..];
|
||||
|
||||
let test_case = TestCase {
|
||||
original: spec[i_start..i_end].to_string().replace("→", "\t"),
|
||||
expected: spec[i_end + 2..e_end].to_string().replace("→", "\t"),
|
||||
smart_punct,
|
||||
metadata_blocks,
|
||||
old_footnotes,
|
||||
};
|
||||
|
||||
Some(test_case)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
use pulldown_cmark::{html, BrokenLink, Options, Parser};
|
||||
|
||||
fn main() {
|
||||
let input: &str = "Hello world, check out [my website][].";
|
||||
println!("Parsing the following markdown string:\n{}", input);
|
||||
|
||||
// Setup callback that sets the URL and title when it encounters
|
||||
// a reference to our home page.
|
||||
let callback = |broken_link: BrokenLink| {
|
||||
if broken_link.reference.as_ref() == "my website" {
|
||||
println!(
|
||||
"Replacing the markdown `{}` of type {:?} with a working link",
|
||||
&input[broken_link.span], broken_link.link_type,
|
||||
);
|
||||
Some(("http://example.com".into(), "my example website".into()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// Create a parser with our callback function for broken links.
|
||||
let parser = Parser::new_with_broken_link_callback(input, Options::empty(), Some(callback));
|
||||
|
||||
// Write to String buffer.
|
||||
let mut html_output: String = String::with_capacity(input.len() * 3 / 2);
|
||||
html::push_html(&mut html_output, parser);
|
||||
|
||||
// Check that the output is what we expected.
|
||||
let expected_html: &str =
|
||||
"<p>Hello world, check out <a href=\"http://example.com\" title=\"my example website\">my website</a>.</p>\n";
|
||||
assert_eq!(expected_html, &html_output);
|
||||
|
||||
// Write result to stdout.
|
||||
println!("\nHTML output:\n{}", &html_output);
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
use std::io::Write as _;
|
||||
|
||||
use pulldown_cmark::{html, Event, Options, Parser, Tag, TagEnd};
|
||||
|
||||
fn main() {
|
||||
let markdown_input: &str = "This is Peter on .";
|
||||
println!("Parsing the following markdown string:\n{}", markdown_input);
|
||||
|
||||
// Set up parser. We can treat is as any other iterator. We replace Peter by John
|
||||
// and image by its alt text.
|
||||
let parser = Parser::new_ext(markdown_input, Options::empty())
|
||||
.map(|event| match event {
|
||||
Event::Text(text) => Event::Text(text.replace("Peter", "John").into()),
|
||||
_ => event,
|
||||
})
|
||||
.filter(|event| match event {
|
||||
Event::Start(Tag::Image { .. }) | Event::End(TagEnd::Image) => false,
|
||||
_ => true,
|
||||
});
|
||||
|
||||
// Write to anything implementing the `Write` trait. This could also be a file
|
||||
// or network socket.
|
||||
let stdout = std::io::stdout();
|
||||
let mut handle = stdout.lock();
|
||||
handle.write_all(b"\nHTML output:\n").unwrap();
|
||||
html::write_html_io(&mut handle, parser).unwrap();
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
use std::io::Read;
|
||||
|
||||
use pulldown_cmark::{Event, Parser};
|
||||
|
||||
/// Show all events from the text on stdin.
|
||||
fn main() {
|
||||
let mut text = String::new();
|
||||
std::io::stdin().read_to_string(&mut text).unwrap();
|
||||
|
||||
eprintln!("{text:?} -> [");
|
||||
let mut width = 0;
|
||||
for event in Parser::new(&text) {
|
||||
if let Event::End(_) = event {
|
||||
width -= 2;
|
||||
}
|
||||
eprintln!(" {:width$}{event:?}", "");
|
||||
if let Event::Start(_) = event {
|
||||
width += 2;
|
||||
}
|
||||
}
|
||||
eprintln!("]");
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write as _;
|
||||
use std::io::Write as _;
|
||||
|
||||
use pulldown_cmark::{html, CowStr, Event, Options, Parser, Tag, TagEnd};
|
||||
|
||||
/// This example shows how to do footnotes as bottom-notes, in the style of GitHub.
|
||||
fn main() {
|
||||
let markdown_input: &str = "This is an [^a] footnote [^a].\n\n[^a]: footnote contents";
|
||||
println!("Parsing the following markdown string:\n{}", markdown_input);
|
||||
|
||||
// To generate this style, you have to collect the footnotes at the end, while parsing.
|
||||
// You also need to count usages.
|
||||
let mut footnotes = Vec::new();
|
||||
let mut in_footnote = Vec::new();
|
||||
let mut footnote_numbers = HashMap::new();
|
||||
// ENABLE_FOOTNOTES is used in this example, but ENABLE_OLD_FOOTNOTES would work, too.
|
||||
let parser = Parser::new_ext(markdown_input, Options::ENABLE_FOOTNOTES)
|
||||
.filter_map(|event| {
|
||||
match event {
|
||||
Event::Start(Tag::FootnoteDefinition(_)) => {
|
||||
in_footnote.push(vec![event]);
|
||||
None
|
||||
}
|
||||
Event::End(TagEnd::FootnoteDefinition) => {
|
||||
let mut f = in_footnote.pop().unwrap();
|
||||
f.push(event);
|
||||
footnotes.push(f);
|
||||
None
|
||||
}
|
||||
Event::FootnoteReference(name) => {
|
||||
let n = footnote_numbers.len() + 1;
|
||||
let (n, nr) = footnote_numbers.entry(name.clone()).or_insert((n, 0usize));
|
||||
*nr += 1;
|
||||
let html = Event::Html(format!(r##"<sup class="footnote-reference" id="fr-{name}-{nr}"><a href="#fn-{name}">[{n}]</a></sup>"##).into());
|
||||
if in_footnote.is_empty() {
|
||||
Some(html)
|
||||
} else {
|
||||
in_footnote.last_mut().unwrap().push(html);
|
||||
None
|
||||
}
|
||||
}
|
||||
_ if !in_footnote.is_empty() => {
|
||||
in_footnote.last_mut().unwrap().push(event);
|
||||
None
|
||||
}
|
||||
_ => Some(event),
|
||||
}
|
||||
});
|
||||
|
||||
// Write to anything implementing the `Write` trait. This could also be a file
|
||||
// or network socket.
|
||||
let stdout = std::io::stdout();
|
||||
let mut handle = stdout.lock();
|
||||
handle.write_all(b"\nHTML output:\n").unwrap();
|
||||
html::write_html_io(&mut handle, parser).unwrap();
|
||||
|
||||
// To make the footnotes look right, we need to sort them by their appearance order, not by
|
||||
// the in-tree order of their actual definitions. Unused items are omitted entirely.
|
||||
//
|
||||
// For example, this code:
|
||||
//
|
||||
// test [^1] [^2]
|
||||
// [^2]: second used, first defined
|
||||
// [^1]: test
|
||||
//
|
||||
// Gets rendered like *this* if you copy it into a GitHub comment box:
|
||||
//
|
||||
// <p>test <sup>[1]</sup> <sup>[2]</sup></p>
|
||||
// <hr>
|
||||
// <ol>
|
||||
// <li>test ↩</li>
|
||||
// <li>second used, first defined ↩</li>
|
||||
// </ol>
|
||||
if !footnotes.is_empty() {
|
||||
footnotes.retain(|f| match f.first() {
|
||||
Some(Event::Start(Tag::FootnoteDefinition(name))) => {
|
||||
footnote_numbers.get(name).unwrap_or(&(0, 0)).1 != 0
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
footnotes.sort_by_cached_key(|f| match f.first() {
|
||||
Some(Event::Start(Tag::FootnoteDefinition(name))) => {
|
||||
footnote_numbers.get(name).unwrap_or(&(0, 0)).0
|
||||
}
|
||||
_ => unreachable!(),
|
||||
});
|
||||
handle
|
||||
.write_all(b"<hr><ol class=\"footnotes-list\">\n")
|
||||
.unwrap();
|
||||
html::write_html_io(
|
||||
&mut handle,
|
||||
footnotes.into_iter().flat_map(|fl| {
|
||||
// To write backrefs, the name needs kept until the end of the footnote definition.
|
||||
let mut name = CowStr::from("");
|
||||
// Backrefs are included in the final paragraph of the footnote, if it's normal text.
|
||||
// For example, this DOM can be produced:
|
||||
//
|
||||
// Markdown:
|
||||
//
|
||||
// five [^feet].
|
||||
//
|
||||
// [^feet]:
|
||||
// A foot is defined, in this case, as 0.3048 m.
|
||||
//
|
||||
// Historically, the foot has not been defined this way, corresponding to many
|
||||
// subtly different units depending on the location.
|
||||
//
|
||||
// HTML:
|
||||
//
|
||||
// <p>five <sup class="footnote-reference" id="fr-feet-1"><a href="#fn-feet">[1]</a></sup>.</p>
|
||||
//
|
||||
// <ol class="footnotes-list">
|
||||
// <li id="fn-feet">
|
||||
// <p>A foot is defined, in this case, as 0.3048 m.</p>
|
||||
// <p>Historically, the foot has not been defined this way, corresponding to many
|
||||
// subtly different units depending on the location. <a href="#fr-feet-1">↩</a></p>
|
||||
// </li>
|
||||
// </ol>
|
||||
//
|
||||
// This is mostly a visual hack, so that footnotes use less vertical space.
|
||||
//
|
||||
// If there is no final paragraph, such as a tabular, list, or image footnote, it gets
|
||||
// pushed after the last tag instead.
|
||||
let mut has_written_backrefs = false;
|
||||
let fl_len = fl.len();
|
||||
let footnote_numbers = &footnote_numbers;
|
||||
fl.into_iter().enumerate().map(move |(i, f)| match f {
|
||||
Event::Start(Tag::FootnoteDefinition(current_name)) => {
|
||||
name = current_name;
|
||||
has_written_backrefs = false;
|
||||
Event::Html(format!(r##"<li id="fn-{name}">"##).into())
|
||||
}
|
||||
Event::End(TagEnd::FootnoteDefinition) | Event::End(TagEnd::Paragraph)
|
||||
if !has_written_backrefs && i >= fl_len - 2 =>
|
||||
{
|
||||
let usage_count = footnote_numbers.get(&name).unwrap().1;
|
||||
let mut end = String::with_capacity(
|
||||
name.len() + (r##" <a href="#fr--1">↩</a></li>"##.len() * usage_count),
|
||||
);
|
||||
for usage in 1..=usage_count {
|
||||
if usage == 1 {
|
||||
write!(&mut end, r##" <a href="#fr-{name}-{usage}">↩</a>"##)
|
||||
.unwrap();
|
||||
} else {
|
||||
write!(&mut end, r##" <a href="#fr-{name}-{usage}">↩{usage}</a>"##)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
has_written_backrefs = true;
|
||||
if f == Event::End(TagEnd::FootnoteDefinition) {
|
||||
end.push_str("</li>\n");
|
||||
} else {
|
||||
end.push_str("</p>\n");
|
||||
}
|
||||
Event::Html(end.into())
|
||||
}
|
||||
Event::End(TagEnd::FootnoteDefinition) => Event::Html("</li>\n".into()),
|
||||
Event::FootnoteReference(_) => unreachable!("converted to HTML earlier"),
|
||||
f => f,
|
||||
})
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
handle.write_all(b"</ol>\n").unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
use pulldown_cmark::{html, Event, Parser};
|
||||
|
||||
fn main() {
|
||||
let markdown_input = "# Example Heading\nExample paragraph with **lorem** _ipsum_ text.";
|
||||
println!(
|
||||
"\nParsing the following markdown string:\n{}\n",
|
||||
markdown_input
|
||||
);
|
||||
|
||||
// Set up the parser. We can treat is as any other iterator.
|
||||
// For each event, we print its details, such as the tag or string.
|
||||
// This filter simply returns the same event without any changes;
|
||||
// you can compare the `event-filter` example which alters the output.
|
||||
let parser = Parser::new(markdown_input).map(|event| {
|
||||
match &event {
|
||||
Event::Start(tag) => println!("Start: {:?}", tag),
|
||||
Event::End(tag) => println!("End: {:?}", tag),
|
||||
Event::Html(s) => println!("Html: {:?}", s),
|
||||
Event::InlineHtml(s) => println!("InlineHtml: {:?}", s),
|
||||
Event::Text(s) => println!("Text: {:?}", s),
|
||||
Event::Code(s) => println!("Code: {:?}", s),
|
||||
Event::DisplayMath(s) => println!("DisplayMath: {:?}", s),
|
||||
Event::InlineMath(s) => println!("Math: {:?}", s),
|
||||
Event::FootnoteReference(s) => println!("FootnoteReference: {:?}", s),
|
||||
Event::TaskListMarker(b) => println!("TaskListMarker: {:?}", b),
|
||||
Event::SoftBreak => println!("SoftBreak"),
|
||||
Event::HardBreak => println!("HardBreak"),
|
||||
Event::Rule => println!("Rule"),
|
||||
};
|
||||
event
|
||||
});
|
||||
|
||||
let mut html_output = String::new();
|
||||
html::push_html(&mut html_output, parser);
|
||||
println!("\nHTML output:\n{}\n", &html_output);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
use pulldown_cmark::{Event, Options, Parser, Tag};
|
||||
|
||||
fn main() {
|
||||
let markdown_input = concat!(
|
||||
"# My Heading\n",
|
||||
"\n",
|
||||
"My paragraph.\n",
|
||||
"\n",
|
||||
"* a\n",
|
||||
"* b\n",
|
||||
"* c\n",
|
||||
"\n",
|
||||
"1. d\n",
|
||||
"2. e\n",
|
||||
"3. f\n",
|
||||
"\n",
|
||||
"> my block quote\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"my code block\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"*emphasis*\n",
|
||||
"**strong**\n",
|
||||
"~~strikethrough~~\n",
|
||||
"[My Link](http://example.com)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"| a | b |\n",
|
||||
"| - | - |\n",
|
||||
"| c | d |\n",
|
||||
"\n",
|
||||
"hello[^1]\n",
|
||||
"[^1]: my footnote\n",
|
||||
);
|
||||
println!(
|
||||
"\nParsing the following markdown string:\n{}\n",
|
||||
markdown_input
|
||||
);
|
||||
|
||||
// Set up the parser. We can treat is as any other iterator.
|
||||
// For each event, we print its details, such as the tag or string.
|
||||
// This filter simply returns the same event without any changes;
|
||||
// you can compare the `event-filter` example which alters the output.
|
||||
let parser = Parser::new_ext(markdown_input, Options::all()).map(|event| {
|
||||
match &event {
|
||||
Event::Start(tag) => match tag {
|
||||
Tag::HtmlBlock => println!("HtmlBlock"),
|
||||
Tag::Heading {
|
||||
level,
|
||||
id,
|
||||
classes,
|
||||
attrs,
|
||||
} => println!(
|
||||
"Heading heading_level: {} fragment identifier: {:?} classes: {:?} attrs: {:?}",
|
||||
level, id, classes, attrs
|
||||
),
|
||||
Tag::Paragraph => println!("Paragraph"),
|
||||
Tag::List(ordered_list_first_item_number) => println!(
|
||||
"List ordered_list_first_item_number: {:?}",
|
||||
ordered_list_first_item_number
|
||||
),
|
||||
Tag::Item => println!("Item (this is a list item)"),
|
||||
Tag::Emphasis => println!("Emphasis (this is a span tag)"),
|
||||
Tag::Strong => println!("Strong (this is a span tag)"),
|
||||
Tag::Strikethrough => println!("Strikethrough (this is a span tag)"),
|
||||
Tag::BlockQuote(kind) => println!("BlockQuote ({:?})", kind),
|
||||
Tag::CodeBlock(code_block_kind) => {
|
||||
println!("CodeBlock code_block_kind: {:?}", code_block_kind)
|
||||
}
|
||||
Tag::Link {
|
||||
link_type,
|
||||
dest_url,
|
||||
title,
|
||||
id,
|
||||
} => println!(
|
||||
"Link link_type: {:?} url: {} title: {} id: {}",
|
||||
link_type, dest_url, title, id
|
||||
),
|
||||
Tag::Image {
|
||||
link_type,
|
||||
dest_url,
|
||||
title,
|
||||
id,
|
||||
} => println!(
|
||||
"Image link_type: {:?} url: {} title: {} id: {}",
|
||||
link_type, dest_url, title, id
|
||||
),
|
||||
Tag::Table(column_text_alignment_list) => println!(
|
||||
"Table column_text_alignment_list: {:?}",
|
||||
column_text_alignment_list
|
||||
),
|
||||
Tag::TableHead => println!("TableHead (contains TableRow tags"),
|
||||
Tag::TableRow => println!("TableRow (contains TableCell tags)"),
|
||||
Tag::TableCell => println!("TableCell (contains inline tags)"),
|
||||
Tag::FootnoteDefinition(label) => println!("FootnoteDefinition label: {}", label),
|
||||
Tag::MetadataBlock(kind) => println!("MetadataBlock: {:?}", kind),
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
event
|
||||
});
|
||||
|
||||
let mut html_output = String::new();
|
||||
pulldown_cmark::html::push_html(&mut html_output, parser);
|
||||
println!("\nHTML output:\n{}\n", &html_output);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use pulldown_cmark::{html, Options, Parser};
|
||||
|
||||
fn main() {
|
||||
let markdown_input: &str = "Hello world, this is a ~~complicated~~ *very simple* example.";
|
||||
println!("Parsing the following markdown string:\n{}", markdown_input);
|
||||
|
||||
// Set up options and parser. Strikethroughs are not part of the CommonMark standard
|
||||
// and we therefore must enable it explicitly.
|
||||
let mut options = Options::empty();
|
||||
options.insert(Options::ENABLE_STRIKETHROUGH);
|
||||
let parser = Parser::new_ext(markdown_input, options);
|
||||
|
||||
// Write to String buffer.
|
||||
let mut html_output: String = String::with_capacity(markdown_input.len() * 3 / 2);
|
||||
html::push_html(&mut html_output, parser);
|
||||
|
||||
// Check that the output is what we expected.
|
||||
let expected_html: &str =
|
||||
"<p>Hello world, this is a <del>complicated</del> <em>very simple</em> example.</p>\n";
|
||||
assert_eq!(expected_html, &html_output);
|
||||
|
||||
// Write result to stdout.
|
||||
println!("\nHTML output:\n{}", &html_output);
|
||||
}
|
||||
+2158
File diff suppressed because it is too large.
Load diff
+2711
File diff suppressed because it is too large.
Load diff
Vendored
+593
@@ -0,0 +1,593 @@
|
||||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//! HTML renderer that takes an iterator of events as input.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::strings::CowStr;
|
||||
use crate::Event::*;
|
||||
use crate::{Alignment, BlockQuoteKind, CodeBlockKind, Event, LinkType, Tag, TagEnd};
|
||||
use pulldown_cmark_escape::{
|
||||
escape_href, escape_html, escape_html_body_text, FmtWriter, IoWriter, StrWrite,
|
||||
};
|
||||
|
||||
enum TableState {
|
||||
Head,
|
||||
Body,
|
||||
}
|
||||
|
||||
struct HtmlWriter<'a, I, W> {
|
||||
/// Iterator supplying events.
|
||||
iter: I,
|
||||
|
||||
/// Writer to write to.
|
||||
writer: W,
|
||||
|
||||
/// Whether or not the last write wrote a newline.
|
||||
end_newline: bool,
|
||||
|
||||
/// Whether if inside a metadata block (text should not be written)
|
||||
in_non_writing_block: bool,
|
||||
|
||||
table_state: TableState,
|
||||
table_alignments: Vec<Alignment>,
|
||||
table_cell_index: usize,
|
||||
numbers: HashMap<CowStr<'a>, usize>,
|
||||
}
|
||||
|
||||
impl<'a, I, W> HtmlWriter<'a, I, W>
|
||||
where
|
||||
I: Iterator<Item = Event<'a>>,
|
||||
W: StrWrite,
|
||||
{
|
||||
fn new(iter: I, writer: W) -> Self {
|
||||
Self {
|
||||
iter,
|
||||
writer,
|
||||
end_newline: true,
|
||||
in_non_writing_block: false,
|
||||
table_state: TableState::Head,
|
||||
table_alignments: vec![],
|
||||
table_cell_index: 0,
|
||||
numbers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a new line.
|
||||
#[inline]
|
||||
fn write_newline(&mut self) -> Result<(), W::Error> {
|
||||
self.end_newline = true;
|
||||
self.writer.write_str("\n")
|
||||
}
|
||||
|
||||
/// Writes a buffer, and tracks whether or not a newline was written.
|
||||
#[inline]
|
||||
fn write(&mut self, s: &str) -> Result<(), W::Error> {
|
||||
self.writer.write_str(s)?;
|
||||
|
||||
if !s.is_empty() {
|
||||
self.end_newline = s.ends_with('\n');
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(mut self) -> Result<(), W::Error> {
|
||||
while let Some(event) = self.iter.next() {
|
||||
match event {
|
||||
Start(tag) => {
|
||||
self.start_tag(tag)?;
|
||||
}
|
||||
End(tag) => {
|
||||
self.end_tag(tag)?;
|
||||
}
|
||||
Text(text) => {
|
||||
if !self.in_non_writing_block {
|
||||
escape_html_body_text(&mut self.writer, &text)?;
|
||||
self.end_newline = text.ends_with('\n');
|
||||
}
|
||||
}
|
||||
Code(text) => {
|
||||
self.write("<code>")?;
|
||||
escape_html_body_text(&mut self.writer, &text)?;
|
||||
self.write("</code>")?;
|
||||
}
|
||||
InlineMath(text) => {
|
||||
self.write(r#"<span class="math math-inline">"#)?;
|
||||
escape_html(&mut self.writer, &text)?;
|
||||
self.write("</span>")?;
|
||||
}
|
||||
DisplayMath(text) => {
|
||||
self.write(r#"<span class="math math-display">"#)?;
|
||||
escape_html(&mut self.writer, &text)?;
|
||||
self.write("</span>")?;
|
||||
}
|
||||
Html(html) | InlineHtml(html) => {
|
||||
self.write(&html)?;
|
||||
}
|
||||
SoftBreak => {
|
||||
self.write_newline()?;
|
||||
}
|
||||
HardBreak => {
|
||||
self.write("<br />\n")?;
|
||||
}
|
||||
Rule => {
|
||||
if self.end_newline {
|
||||
self.write("<hr />\n")?;
|
||||
} else {
|
||||
self.write("\n<hr />\n")?;
|
||||
}
|
||||
}
|
||||
FootnoteReference(name) => {
|
||||
let len = self.numbers.len() + 1;
|
||||
self.write("<sup class=\"footnote-reference\"><a href=\"#")?;
|
||||
escape_html(&mut self.writer, &name)?;
|
||||
self.write("\">")?;
|
||||
let number = *self.numbers.entry(name).or_insert(len);
|
||||
write!(&mut self.writer, "{}", number)?;
|
||||
self.write("</a></sup>")?;
|
||||
}
|
||||
TaskListMarker(true) => {
|
||||
self.write("<input disabled=\"\" type=\"checkbox\" checked=\"\"/>\n")?;
|
||||
}
|
||||
TaskListMarker(false) => {
|
||||
self.write("<input disabled=\"\" type=\"checkbox\"/>\n")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes the start of an HTML tag.
|
||||
fn start_tag(&mut self, tag: Tag<'a>) -> Result<(), W::Error> {
|
||||
match tag {
|
||||
Tag::HtmlBlock => Ok(()),
|
||||
Tag::Paragraph => {
|
||||
if self.end_newline {
|
||||
self.write("<p>")
|
||||
} else {
|
||||
self.write("\n<p>")
|
||||
}
|
||||
}
|
||||
Tag::Heading {
|
||||
level,
|
||||
id,
|
||||
classes,
|
||||
attrs,
|
||||
} => {
|
||||
if self.end_newline {
|
||||
self.write("<")?;
|
||||
} else {
|
||||
self.write("\n<")?;
|
||||
}
|
||||
write!(&mut self.writer, "{}", level)?;
|
||||
if let Some(id) = id {
|
||||
self.write(" id=\"")?;
|
||||
escape_html(&mut self.writer, &id)?;
|
||||
self.write("\"")?;
|
||||
}
|
||||
let mut classes = classes.iter();
|
||||
if let Some(class) = classes.next() {
|
||||
self.write(" class=\"")?;
|
||||
escape_html(&mut self.writer, class)?;
|
||||
for class in classes {
|
||||
self.write(" ")?;
|
||||
escape_html(&mut self.writer, class)?;
|
||||
}
|
||||
self.write("\"")?;
|
||||
}
|
||||
for (attr, value) in attrs {
|
||||
self.write(" ")?;
|
||||
escape_html(&mut self.writer, &attr)?;
|
||||
if let Some(val) = value {
|
||||
self.write("=\"")?;
|
||||
escape_html(&mut self.writer, &val)?;
|
||||
self.write("\"")?;
|
||||
} else {
|
||||
self.write("=\"\"")?;
|
||||
}
|
||||
}
|
||||
self.write(">")
|
||||
}
|
||||
Tag::Table(alignments) => {
|
||||
self.table_alignments = alignments;
|
||||
self.write("<table>")
|
||||
}
|
||||
Tag::TableHead => {
|
||||
self.table_state = TableState::Head;
|
||||
self.table_cell_index = 0;
|
||||
self.write("<thead><tr>")
|
||||
}
|
||||
Tag::TableRow => {
|
||||
self.table_cell_index = 0;
|
||||
self.write("<tr>")
|
||||
}
|
||||
Tag::TableCell => {
|
||||
match self.table_state {
|
||||
TableState::Head => {
|
||||
self.write("<th")?;
|
||||
}
|
||||
TableState::Body => {
|
||||
self.write("<td")?;
|
||||
}
|
||||
}
|
||||
match self.table_alignments.get(self.table_cell_index) {
|
||||
Some(&Alignment::Left) => self.write(" style=\"text-align: left\">"),
|
||||
Some(&Alignment::Center) => self.write(" style=\"text-align: center\">"),
|
||||
Some(&Alignment::Right) => self.write(" style=\"text-align: right\">"),
|
||||
_ => self.write(">"),
|
||||
}
|
||||
}
|
||||
Tag::BlockQuote(kind) => {
|
||||
let class_str = match kind {
|
||||
None => "",
|
||||
Some(kind) => match kind {
|
||||
BlockQuoteKind::Note => " class=\"markdown-alert-note\"",
|
||||
BlockQuoteKind::Tip => " class=\"markdown-alert-tip\"",
|
||||
BlockQuoteKind::Important => " class=\"markdown-alert-important\"",
|
||||
BlockQuoteKind::Warning => " class=\"markdown-alert-warning\"",
|
||||
BlockQuoteKind::Caution => " class=\"markdown-alert-caution\"",
|
||||
},
|
||||
};
|
||||
if self.end_newline {
|
||||
self.write(&format!("<blockquote{}>\n", class_str))
|
||||
} else {
|
||||
self.write(&format!("\n<blockquote{}>\n", class_str))
|
||||
}
|
||||
}
|
||||
Tag::CodeBlock(info) => {
|
||||
if !self.end_newline {
|
||||
self.write_newline()?;
|
||||
}
|
||||
match info {
|
||||
CodeBlockKind::Fenced(info) => {
|
||||
let lang = info.split(' ').next().unwrap();
|
||||
if lang.is_empty() {
|
||||
self.write("<pre><code>")
|
||||
} else {
|
||||
self.write("<pre><code class=\"language-")?;
|
||||
escape_html(&mut self.writer, lang)?;
|
||||
self.write("\">")
|
||||
}
|
||||
}
|
||||
CodeBlockKind::Indented => self.write("<pre><code>"),
|
||||
}
|
||||
}
|
||||
Tag::List(Some(1)) => {
|
||||
if self.end_newline {
|
||||
self.write("<ol>\n")
|
||||
} else {
|
||||
self.write("\n<ol>\n")
|
||||
}
|
||||
}
|
||||
Tag::List(Some(start)) => {
|
||||
if self.end_newline {
|
||||
self.write("<ol start=\"")?;
|
||||
} else {
|
||||
self.write("\n<ol start=\"")?;
|
||||
}
|
||||
write!(&mut self.writer, "{}", start)?;
|
||||
self.write("\">\n")
|
||||
}
|
||||
Tag::List(None) => {
|
||||
if self.end_newline {
|
||||
self.write("<ul>\n")
|
||||
} else {
|
||||
self.write("\n<ul>\n")
|
||||
}
|
||||
}
|
||||
Tag::Item => {
|
||||
if self.end_newline {
|
||||
self.write("<li>")
|
||||
} else {
|
||||
self.write("\n<li>")
|
||||
}
|
||||
}
|
||||
Tag::Emphasis => self.write("<em>"),
|
||||
Tag::Strong => self.write("<strong>"),
|
||||
Tag::Strikethrough => self.write("<del>"),
|
||||
Tag::Link {
|
||||
link_type: LinkType::Email,
|
||||
dest_url,
|
||||
title,
|
||||
id: _,
|
||||
} => {
|
||||
self.write("<a href=\"mailto:")?;
|
||||
escape_href(&mut self.writer, &dest_url)?;
|
||||
if !title.is_empty() {
|
||||
self.write("\" title=\"")?;
|
||||
escape_html(&mut self.writer, &title)?;
|
||||
}
|
||||
self.write("\">")
|
||||
}
|
||||
Tag::Link {
|
||||
link_type: _,
|
||||
dest_url,
|
||||
title,
|
||||
id: _,
|
||||
} => {
|
||||
self.write("<a href=\"")?;
|
||||
escape_href(&mut self.writer, &dest_url)?;
|
||||
if !title.is_empty() {
|
||||
self.write("\" title=\"")?;
|
||||
escape_html(&mut self.writer, &title)?;
|
||||
}
|
||||
self.write("\">")
|
||||
}
|
||||
Tag::Image {
|
||||
link_type: _,
|
||||
dest_url,
|
||||
title,
|
||||
id: _,
|
||||
} => {
|
||||
self.write("<img src=\"")?;
|
||||
escape_href(&mut self.writer, &dest_url)?;
|
||||
self.write("\" alt=\"")?;
|
||||
self.raw_text()?;
|
||||
if !title.is_empty() {
|
||||
self.write("\" title=\"")?;
|
||||
escape_html(&mut self.writer, &title)?;
|
||||
}
|
||||
self.write("\" />")
|
||||
}
|
||||
Tag::FootnoteDefinition(name) => {
|
||||
if self.end_newline {
|
||||
self.write("<div class=\"footnote-definition\" id=\"")?;
|
||||
} else {
|
||||
self.write("\n<div class=\"footnote-definition\" id=\"")?;
|
||||
}
|
||||
escape_html(&mut self.writer, &name)?;
|
||||
self.write("\"><sup class=\"footnote-definition-label\">")?;
|
||||
let len = self.numbers.len() + 1;
|
||||
let number = *self.numbers.entry(name).or_insert(len);
|
||||
write!(&mut self.writer, "{}", number)?;
|
||||
self.write("</sup>")
|
||||
}
|
||||
Tag::MetadataBlock(_) => {
|
||||
self.in_non_writing_block = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn end_tag(&mut self, tag: TagEnd) -> Result<(), W::Error> {
|
||||
match tag {
|
||||
TagEnd::HtmlBlock => {}
|
||||
TagEnd::Paragraph => {
|
||||
self.write("</p>\n")?;
|
||||
}
|
||||
TagEnd::Heading(level) => {
|
||||
self.write("</")?;
|
||||
write!(&mut self.writer, "{}", level)?;
|
||||
self.write(">\n")?;
|
||||
}
|
||||
TagEnd::Table => {
|
||||
self.write("</tbody></table>\n")?;
|
||||
}
|
||||
TagEnd::TableHead => {
|
||||
self.write("</tr></thead><tbody>\n")?;
|
||||
self.table_state = TableState::Body;
|
||||
}
|
||||
TagEnd::TableRow => {
|
||||
self.write("</tr>\n")?;
|
||||
}
|
||||
TagEnd::TableCell => {
|
||||
match self.table_state {
|
||||
TableState::Head => {
|
||||
self.write("</th>")?;
|
||||
}
|
||||
TableState::Body => {
|
||||
self.write("</td>")?;
|
||||
}
|
||||
}
|
||||
self.table_cell_index += 1;
|
||||
}
|
||||
TagEnd::BlockQuote => {
|
||||
self.write("</blockquote>\n")?;
|
||||
}
|
||||
TagEnd::CodeBlock => {
|
||||
self.write("</code></pre>\n")?;
|
||||
}
|
||||
TagEnd::List(true) => {
|
||||
self.write("</ol>\n")?;
|
||||
}
|
||||
TagEnd::List(false) => {
|
||||
self.write("</ul>\n")?;
|
||||
}
|
||||
TagEnd::Item => {
|
||||
self.write("</li>\n")?;
|
||||
}
|
||||
TagEnd::Emphasis => {
|
||||
self.write("</em>")?;
|
||||
}
|
||||
TagEnd::Strong => {
|
||||
self.write("</strong>")?;
|
||||
}
|
||||
TagEnd::Strikethrough => {
|
||||
self.write("</del>")?;
|
||||
}
|
||||
TagEnd::Link => {
|
||||
self.write("</a>")?;
|
||||
}
|
||||
TagEnd::Image => (), // shouldn't happen, handled in start
|
||||
TagEnd::FootnoteDefinition => {
|
||||
self.write("</div>\n")?;
|
||||
}
|
||||
TagEnd::MetadataBlock(_) => {
|
||||
self.in_non_writing_block = false;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// run raw text, consuming end tag
|
||||
fn raw_text(&mut self) -> Result<(), W::Error> {
|
||||
let mut nest = 0;
|
||||
while let Some(event) = self.iter.next() {
|
||||
match event {
|
||||
Start(_) => nest += 1,
|
||||
End(_) => {
|
||||
if nest == 0 {
|
||||
break;
|
||||
}
|
||||
nest -= 1;
|
||||
}
|
||||
Html(_) => {}
|
||||
InlineHtml(text) | Code(text) | Text(text) => {
|
||||
// Don't use escape_html_body_text here.
|
||||
// The output of this function is used in the `alt` attribute.
|
||||
escape_html(&mut self.writer, &text)?;
|
||||
self.end_newline = text.ends_with('\n');
|
||||
}
|
||||
InlineMath(text) => {
|
||||
self.write("$")?;
|
||||
escape_html(&mut self.writer, &text)?;
|
||||
self.write("$")?;
|
||||
}
|
||||
DisplayMath(text) => {
|
||||
self.write("$$")?;
|
||||
escape_html(&mut self.writer, &text)?;
|
||||
self.write("$$")?;
|
||||
}
|
||||
SoftBreak | HardBreak | Rule => {
|
||||
self.write(" ")?;
|
||||
}
|
||||
FootnoteReference(name) => {
|
||||
let len = self.numbers.len() + 1;
|
||||
let number = *self.numbers.entry(name).or_insert(len);
|
||||
write!(&mut self.writer, "[{}]", number)?;
|
||||
}
|
||||
TaskListMarker(true) => self.write("[x]")?,
|
||||
TaskListMarker(false) => self.write("[ ]")?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and
|
||||
/// push it to a `String`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use pulldown_cmark::{html, Parser};
|
||||
///
|
||||
/// let markdown_str = r#"
|
||||
/// hello
|
||||
/// =====
|
||||
///
|
||||
/// * alpha
|
||||
/// * beta
|
||||
/// "#;
|
||||
/// let parser = Parser::new(markdown_str);
|
||||
///
|
||||
/// let mut html_buf = String::new();
|
||||
/// html::push_html(&mut html_buf, parser);
|
||||
///
|
||||
/// assert_eq!(html_buf, r#"<h1>hello</h1>
|
||||
/// <ul>
|
||||
/// <li>alpha</li>
|
||||
/// <li>beta</li>
|
||||
/// </ul>
|
||||
/// "#);
|
||||
/// ```
|
||||
pub fn push_html<'a, I>(s: &mut String, iter: I)
|
||||
where
|
||||
I: Iterator<Item = Event<'a>>,
|
||||
{
|
||||
write_html_fmt(s, iter).unwrap()
|
||||
}
|
||||
|
||||
/// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and
|
||||
/// write it out to an I/O stream.
|
||||
///
|
||||
/// **Note**: using this function with an unbuffered writer like a file or socket
|
||||
/// will result in poor performance. Wrap these in a
|
||||
/// [`BufWriter`](https://doc.rust-lang.org/std/io/struct.BufWriter.html) to
|
||||
/// prevent unnecessary slowdowns.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use pulldown_cmark::{html, Parser};
|
||||
/// use std::io::Cursor;
|
||||
///
|
||||
/// let markdown_str = r#"
|
||||
/// hello
|
||||
/// =====
|
||||
///
|
||||
/// * alpha
|
||||
/// * beta
|
||||
/// "#;
|
||||
/// let mut bytes = Vec::new();
|
||||
/// let parser = Parser::new(markdown_str);
|
||||
///
|
||||
/// html::write_html_io(Cursor::new(&mut bytes), parser);
|
||||
///
|
||||
/// assert_eq!(&String::from_utf8_lossy(&bytes)[..], r#"<h1>hello</h1>
|
||||
/// <ul>
|
||||
/// <li>alpha</li>
|
||||
/// <li>beta</li>
|
||||
/// </ul>
|
||||
/// "#);
|
||||
/// ```
|
||||
pub fn write_html_io<'a, I, W>(writer: W, iter: I) -> std::io::Result<()>
|
||||
where
|
||||
I: Iterator<Item = Event<'a>>,
|
||||
W: std::io::Write,
|
||||
{
|
||||
HtmlWriter::new(iter, IoWriter(writer)).run()
|
||||
}
|
||||
|
||||
/// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and
|
||||
/// write it into Unicode-accepting buffer or stream.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use pulldown_cmark::{html, Parser};
|
||||
///
|
||||
/// let markdown_str = r#"
|
||||
/// hello
|
||||
/// =====
|
||||
///
|
||||
/// * alpha
|
||||
/// * beta
|
||||
/// "#;
|
||||
/// let mut buf = String::new();
|
||||
/// let parser = Parser::new(markdown_str);
|
||||
///
|
||||
/// html::write_html_fmt(&mut buf, parser);
|
||||
///
|
||||
/// assert_eq!(buf, r#"<h1>hello</h1>
|
||||
/// <ul>
|
||||
/// <li>alpha</li>
|
||||
/// <li>beta</li>
|
||||
/// </ul>
|
||||
/// "#);
|
||||
/// ```
|
||||
pub fn write_html_fmt<'a, I, W>(writer: W, iter: I) -> std::fmt::Result
|
||||
where
|
||||
I: Iterator<Item = Event<'a>>,
|
||||
W: std::fmt::Write,
|
||||
{
|
||||
HtmlWriter::new(iter, FmtWriter(writer)).run()
|
||||
}
|
||||
Vendored
+501
@@ -0,0 +1,501 @@
|
||||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//! Pull parser for [CommonMark](https://commonmark.org). This crate provides a [Parser](struct.Parser.html) struct
|
||||
//! which is an iterator over [Event](enum.Event.html)s. This iterator can be used
|
||||
//! directly, or to output HTML using the [HTML module](html/index.html).
|
||||
//!
|
||||
//! By default, only CommonMark features are enabled. To use extensions like tables,
|
||||
//! footnotes or task lists, enable them by setting the corresponding flags in the
|
||||
//! [Options](struct.Options.html) struct.
|
||||
//!
|
||||
//! # Example
|
||||
//! ```rust
|
||||
//! use pulldown_cmark::{Parser, Options};
|
||||
//!
|
||||
//! let markdown_input = "Hello world, this is a ~~complicated~~ *very simple* example.";
|
||||
//!
|
||||
//! // Set up options and parser. Strikethroughs are not part of the CommonMark standard
|
||||
//! // and we therefore must enable it explicitly.
|
||||
//! let mut options = Options::empty();
|
||||
//! options.insert(Options::ENABLE_STRIKETHROUGH);
|
||||
//! let parser = Parser::new_ext(markdown_input, options);
|
||||
//!
|
||||
//! # #[cfg(feature = "html")] {
|
||||
//! // Write to String buffer.
|
||||
//! let mut html_output = String::new();
|
||||
//! pulldown_cmark::html::push_html(&mut html_output, parser);
|
||||
//!
|
||||
//! // Check that the output is what we expected.
|
||||
//! let expected_html = "<p>Hello world, this is a <del>complicated</del> <em>very simple</em> example.</p>\n";
|
||||
//! assert_eq!(expected_html, &html_output);
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! Note that consecutive text events can happen due to the manner in which the
|
||||
//! parser evaluates the source. A utility `TextMergeStream` exists to improve
|
||||
//! the comfort of iterating the events:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use pulldown_cmark::{Event, Parser, TextMergeStream};
|
||||
//!
|
||||
//! let markdown_input = "Hello world, this is a ~~complicated~~ *very simple* example.";
|
||||
//!
|
||||
//! let iterator = TextMergeStream::new(Parser::new(markdown_input));
|
||||
//!
|
||||
//! for event in iterator {
|
||||
//! match event {
|
||||
//! Event::Text(text) => println!("{}", text),
|
||||
//! _ => {}
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
|
||||
// When compiled for the rustc compiler itself we want to make sure that this is
|
||||
// an unstable crate.
|
||||
#![cfg_attr(rustbuild, feature(staged_api, rustc_private))]
|
||||
#![cfg_attr(rustbuild, unstable(feature = "rustc_private", issue = "27812"))]
|
||||
// Forbid unsafe code unless the SIMD feature is enabled.
|
||||
#![cfg_attr(not(feature = "simd"), forbid(unsafe_code))]
|
||||
#![warn(missing_debug_implementations)]
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "html")]
|
||||
pub mod html;
|
||||
|
||||
pub mod utils;
|
||||
|
||||
mod entities;
|
||||
mod firstpass;
|
||||
mod linklabel;
|
||||
mod parse;
|
||||
mod puncttable;
|
||||
mod scanners;
|
||||
mod strings;
|
||||
mod tree;
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
pub use crate::parse::{
|
||||
BrokenLink, BrokenLinkCallback, DefaultBrokenLinkCallback, OffsetIter, Parser, RefDefs,
|
||||
};
|
||||
pub use crate::strings::{CowStr, InlineStr};
|
||||
pub use crate::utils::*;
|
||||
|
||||
/// Codeblock kind.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum CodeBlockKind<'a> {
|
||||
Indented,
|
||||
/// The value contained in the tag describes the language of the code, which may be empty.
|
||||
#[cfg_attr(feature = "serde", serde(borrow))]
|
||||
Fenced(CowStr<'a>),
|
||||
}
|
||||
|
||||
impl<'a> CodeBlockKind<'a> {
|
||||
pub fn is_indented(&self) -> bool {
|
||||
matches!(*self, CodeBlockKind::Indented)
|
||||
}
|
||||
|
||||
pub fn is_fenced(&self) -> bool {
|
||||
matches!(*self, CodeBlockKind::Fenced(_))
|
||||
}
|
||||
}
|
||||
|
||||
/// BlockQuote kind (Note, Tip, Important, Warning, Caution).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum BlockQuoteKind {
|
||||
Note,
|
||||
Tip,
|
||||
Important,
|
||||
Warning,
|
||||
Caution,
|
||||
}
|
||||
|
||||
/// Metadata block kind.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum MetadataBlockKind {
|
||||
YamlStyle,
|
||||
PlusesStyle,
|
||||
}
|
||||
|
||||
/// Tags for elements that can contain other elements.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Tag<'a> {
|
||||
/// A paragraph of text and other inline elements.
|
||||
Paragraph,
|
||||
|
||||
/// A heading, with optional identifier, classes and custom attributes.
|
||||
/// The identifier is prefixed with `#` and the last one in the attributes
|
||||
/// list is chosen, classes are prefixed with `.` and custom attributes
|
||||
/// have no prefix and can optionally have a value (`myattr` or `myattr=myvalue`).
|
||||
Heading {
|
||||
level: HeadingLevel,
|
||||
id: Option<CowStr<'a>>,
|
||||
classes: Vec<CowStr<'a>>,
|
||||
/// The first item of the tuple is the attr and second one the value.
|
||||
attrs: Vec<(CowStr<'a>, Option<CowStr<'a>>)>,
|
||||
},
|
||||
|
||||
BlockQuote(Option<BlockQuoteKind>),
|
||||
/// A code block.
|
||||
CodeBlock(CodeBlockKind<'a>),
|
||||
|
||||
/// A HTML block.
|
||||
HtmlBlock,
|
||||
|
||||
/// A list. If the list is ordered the field indicates the number of the first item.
|
||||
/// Contains only list items.
|
||||
List(Option<u64>), // TODO: add delim and tight for ast (not needed for html)
|
||||
/// A list item.
|
||||
Item,
|
||||
/// A footnote definition. The value contained is the footnote's label by which it can
|
||||
/// be referred to.
|
||||
#[cfg_attr(feature = "serde", serde(borrow))]
|
||||
FootnoteDefinition(CowStr<'a>),
|
||||
|
||||
/// A table. Contains a vector describing the text-alignment for each of its columns.
|
||||
Table(Vec<Alignment>),
|
||||
/// A table header. Contains only `TableCell`s. Note that the table body starts immediately
|
||||
/// after the closure of the `TableHead` tag. There is no `TableBody` tag.
|
||||
TableHead,
|
||||
/// A table row. Is used both for header rows as body rows. Contains only `TableCell`s.
|
||||
TableRow,
|
||||
TableCell,
|
||||
|
||||
// span-level tags
|
||||
Emphasis,
|
||||
Strong,
|
||||
Strikethrough,
|
||||
|
||||
/// A link.
|
||||
Link {
|
||||
link_type: LinkType,
|
||||
dest_url: CowStr<'a>,
|
||||
title: CowStr<'a>,
|
||||
/// Identifier of reference links, e.g. `world` in the link `[hello][world]`.
|
||||
id: CowStr<'a>,
|
||||
},
|
||||
|
||||
/// An image. The first field is the link type, the second the destination URL and the third is a title,
|
||||
/// the fourth is the link identifier.
|
||||
Image {
|
||||
link_type: LinkType,
|
||||
dest_url: CowStr<'a>,
|
||||
title: CowStr<'a>,
|
||||
/// Identifier of reference links, e.g. `world` in the link `[hello][world]`.
|
||||
id: CowStr<'a>,
|
||||
},
|
||||
|
||||
/// A metadata block.
|
||||
MetadataBlock(MetadataBlockKind),
|
||||
}
|
||||
|
||||
impl<'a> Tag<'a> {
|
||||
pub fn to_end(&self) -> TagEnd {
|
||||
match self {
|
||||
Tag::Paragraph => TagEnd::Paragraph,
|
||||
Tag::Heading { level, .. } => TagEnd::Heading(*level),
|
||||
Tag::BlockQuote(_) => TagEnd::BlockQuote,
|
||||
Tag::CodeBlock(_) => TagEnd::CodeBlock,
|
||||
Tag::HtmlBlock => TagEnd::HtmlBlock,
|
||||
Tag::List(number) => TagEnd::List(number.is_some()),
|
||||
Tag::Item => TagEnd::Item,
|
||||
Tag::FootnoteDefinition(_) => TagEnd::FootnoteDefinition,
|
||||
Tag::Table(_) => TagEnd::Table,
|
||||
Tag::TableHead => TagEnd::TableHead,
|
||||
Tag::TableRow => TagEnd::TableRow,
|
||||
Tag::TableCell => TagEnd::TableCell,
|
||||
Tag::Emphasis => TagEnd::Emphasis,
|
||||
Tag::Strong => TagEnd::Strong,
|
||||
Tag::Strikethrough => TagEnd::Strikethrough,
|
||||
Tag::Link { .. } => TagEnd::Link,
|
||||
Tag::Image { .. } => TagEnd::Image,
|
||||
Tag::MetadataBlock(kind) => TagEnd::MetadataBlock(*kind),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The end of a `Tag`.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum TagEnd {
|
||||
Paragraph,
|
||||
Heading(HeadingLevel),
|
||||
|
||||
BlockQuote,
|
||||
CodeBlock,
|
||||
|
||||
HtmlBlock,
|
||||
|
||||
/// A list, `true` for ordered lists.
|
||||
List(bool),
|
||||
Item,
|
||||
FootnoteDefinition,
|
||||
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
|
||||
Emphasis,
|
||||
Strong,
|
||||
Strikethrough,
|
||||
|
||||
Link,
|
||||
Image,
|
||||
|
||||
MetadataBlock(MetadataBlockKind),
|
||||
}
|
||||
|
||||
/// Make sure `TagEnd` is no more than two bytes in size.
|
||||
/// This is why it's used instead of just using `Tag`.
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
const _STATIC_ASSERT_TAG_END_SIZE: [(); 2] = [(); std::mem::size_of::<TagEnd>()];
|
||||
|
||||
impl<'a> From<Tag<'a>> for TagEnd {
|
||||
fn from(value: Tag) -> Self {
|
||||
value.to_end()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum HeadingLevel {
|
||||
H1 = 1,
|
||||
H2,
|
||||
H3,
|
||||
H4,
|
||||
H5,
|
||||
H6,
|
||||
}
|
||||
|
||||
impl Display for HeadingLevel {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::H1 => write!(f, "h1"),
|
||||
Self::H2 => write!(f, "h2"),
|
||||
Self::H3 => write!(f, "h3"),
|
||||
Self::H4 => write!(f, "h4"),
|
||||
Self::H5 => write!(f, "h5"),
|
||||
Self::H6 => write!(f, "h6"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returned when trying to convert a `usize` into a `Heading` but it fails
|
||||
/// because the usize isn't a valid heading level
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
||||
pub struct InvalidHeadingLevel(usize);
|
||||
|
||||
impl TryFrom<usize> for HeadingLevel {
|
||||
type Error = InvalidHeadingLevel;
|
||||
|
||||
fn try_from(value: usize) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
1 => Ok(Self::H1),
|
||||
2 => Ok(Self::H2),
|
||||
3 => Ok(Self::H3),
|
||||
4 => Ok(Self::H4),
|
||||
5 => Ok(Self::H5),
|
||||
6 => Ok(Self::H6),
|
||||
_ => Err(InvalidHeadingLevel(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type specifier for inline links. See [the Tag::Link](enum.Tag.html#variant.Link) for more information.
|
||||
#[derive(Clone, Debug, PartialEq, Copy)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum LinkType {
|
||||
/// Inline link like `[foo](bar)`
|
||||
Inline,
|
||||
/// Reference link like `[foo][bar]`
|
||||
Reference,
|
||||
/// Reference without destination in the document, but resolved by the broken_link_callback
|
||||
ReferenceUnknown,
|
||||
/// Collapsed link like `[foo][]`
|
||||
Collapsed,
|
||||
/// Collapsed link without destination in the document, but resolved by the broken_link_callback
|
||||
CollapsedUnknown,
|
||||
/// Shortcut link like `[foo]`
|
||||
Shortcut,
|
||||
/// Shortcut without destination in the document, but resolved by the broken_link_callback
|
||||
ShortcutUnknown,
|
||||
/// Autolink like `<http://foo.bar/baz>`
|
||||
Autolink,
|
||||
/// Email address in autolink like `<john@example.org>`
|
||||
Email,
|
||||
}
|
||||
|
||||
impl LinkType {
|
||||
/// Map the link type to an equivalent _Unknown link type.
|
||||
fn to_unknown(self) -> Self {
|
||||
match self {
|
||||
LinkType::Reference => LinkType::ReferenceUnknown,
|
||||
LinkType::Collapsed => LinkType::CollapsedUnknown,
|
||||
LinkType::Shortcut => LinkType::ShortcutUnknown,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Markdown events that are generated in a preorder traversal of the document
|
||||
/// tree, with additional `End` events whenever all of an inner node's children
|
||||
/// have been visited.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Event<'a> {
|
||||
/// Start of a tagged element. Events that are yielded after this event
|
||||
/// and before its corresponding `End` event are inside this element.
|
||||
/// Start and end events are guaranteed to be balanced.
|
||||
#[cfg_attr(feature = "serde", serde(borrow))]
|
||||
Start(Tag<'a>),
|
||||
/// End of a tagged element.
|
||||
End(TagEnd),
|
||||
/// A text node.
|
||||
#[cfg_attr(feature = "serde", serde(borrow))]
|
||||
Text(CowStr<'a>),
|
||||
/// An inline code node.
|
||||
#[cfg_attr(feature = "serde", serde(borrow))]
|
||||
Code(CowStr<'a>),
|
||||
/// An inline math environment node.
|
||||
#[cfg_attr(feature = "serde", serde(borrow))]
|
||||
InlineMath(CowStr<'a>),
|
||||
/// A display math environment node.
|
||||
#[cfg_attr(feature = "serde", serde(borrow))]
|
||||
DisplayMath(CowStr<'a>),
|
||||
/// An HTML node.
|
||||
#[cfg_attr(feature = "serde", serde(borrow))]
|
||||
Html(CowStr<'a>),
|
||||
/// An inline HTML node.
|
||||
#[cfg_attr(feature = "serde", serde(borrow))]
|
||||
InlineHtml(CowStr<'a>),
|
||||
/// A reference to a footnote with given label, which may or may not be defined
|
||||
/// by an event with a `Tag::FootnoteDefinition` tag. Definitions and references to them may
|
||||
/// occur in any order.
|
||||
#[cfg_attr(feature = "serde", serde(borrow))]
|
||||
FootnoteReference(CowStr<'a>),
|
||||
/// A soft line break.
|
||||
SoftBreak,
|
||||
/// A hard line break.
|
||||
HardBreak,
|
||||
/// A horizontal ruler.
|
||||
Rule,
|
||||
/// A task list marker, rendered as a checkbox in HTML. Contains a true when it is checked.
|
||||
TaskListMarker(bool),
|
||||
}
|
||||
|
||||
/// Table column text alignment.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
|
||||
pub enum Alignment {
|
||||
/// Default text alignment.
|
||||
None,
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Option struct containing flags for enabling extra features
|
||||
/// that are not part of the CommonMark spec.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Options: u32 {
|
||||
const ENABLE_TABLES = 1 << 1;
|
||||
/// GitHub-compatible footnote syntax.
|
||||
///
|
||||
/// Footnotes are referenced with the syntax `[^IDENT]`,
|
||||
/// and defined with an identifier followed by a colon at top level.
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// ```markdown
|
||||
/// Footnote referenced [^1].
|
||||
///
|
||||
/// [^1]: footnote defined
|
||||
/// ```
|
||||
///
|
||||
/// Footnote referenced [^1].
|
||||
///
|
||||
/// [^1]: footnote defined
|
||||
const ENABLE_FOOTNOTES = 1 << 2;
|
||||
const ENABLE_STRIKETHROUGH = 1 << 3;
|
||||
const ENABLE_TASKLISTS = 1 << 4;
|
||||
const ENABLE_SMART_PUNCTUATION = 1 << 5;
|
||||
/// Extension to allow headings to have ID and classes.
|
||||
///
|
||||
/// `# text { #id .class1 .class2 myattr other_attr=myvalue }`
|
||||
/// is interpreted as a level 1 heading
|
||||
/// with the content `text`, ID `id`, classes `class1` and `class2` and
|
||||
/// custom attributes `myattr` (without value) and
|
||||
/// `other_attr` with value `myvalue`.
|
||||
/// Note that ID, classes, and custom attributes should be space-separated.
|
||||
const ENABLE_HEADING_ATTRIBUTES = 1 << 6;
|
||||
/// Metadata blocks in YAML style, i.e.:
|
||||
/// - starting with a `---` line
|
||||
/// - ending with a `---` or `...` line
|
||||
const ENABLE_YAML_STYLE_METADATA_BLOCKS = 1 << 7;
|
||||
/// Metadata blocks delimited by:
|
||||
/// - `+++` line at start
|
||||
/// - `+++` line at end
|
||||
const ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS = 1 << 8;
|
||||
/// Older footnote syntax. This flag implies `ENABLE_FOOTNOTES`, changing it to use an
|
||||
/// older syntax instead of the new, default, GitHub-compatible syntax.
|
||||
///
|
||||
/// New syntax is different from the old syntax regarding
|
||||
/// indentation, nesting, and footnote references with no definition:
|
||||
///
|
||||
/// ```markdown
|
||||
/// [^1]: In new syntax, this is two footnote definitions.
|
||||
/// [^2]: In old syntax, this is a single footnote definition with two lines.
|
||||
///
|
||||
/// [^3]:
|
||||
///
|
||||
/// In new syntax, this is a footnote with two paragraphs.
|
||||
///
|
||||
/// In old syntax, this is a footnote followed by a code block.
|
||||
///
|
||||
/// In new syntax, this undefined footnote definition renders as
|
||||
/// literal text [^4]. In old syntax, it creates a dangling link.
|
||||
/// ```
|
||||
const ENABLE_OLD_FOOTNOTES = (1 << 9) | (1 << 2);
|
||||
/// With this feature enabled, two events `Event::InlineMath` and `Event::DisplayMath`
|
||||
/// are emitted that conventionally contain TeX formulas.
|
||||
const ENABLE_MATH = 1 << 10;
|
||||
/// Misc GitHub Flavored Markdown features not supported in CommonMark.
|
||||
/// The following features are currently behind this tag:
|
||||
/// - Blockquote tags ([!NOTE], [!TIP], [!IMPORTANT], [!WARNING], [!CAUTION]).
|
||||
const ENABLE_GFM = 1 << 11;
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub(crate) fn has_gfm_footnotes(&self) -> bool {
|
||||
self.contains(Options::ENABLE_FOOTNOTES) && !self.contains(Options::ENABLE_OLD_FOOTNOTES)
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//! Link label parsing and matching.
|
||||
|
||||
use unicase::UniCase;
|
||||
|
||||
use crate::scanners::{is_ascii_whitespace, scan_eol, is_ascii_punctuation};
|
||||
use crate::strings::CowStr;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ReferenceLabel<'a> {
|
||||
Link(CowStr<'a>),
|
||||
Footnote(CowStr<'a>),
|
||||
}
|
||||
|
||||
pub(crate) type LinkLabel<'a> = UniCase<CowStr<'a>>;
|
||||
|
||||
pub(crate) type FootnoteLabel<'a> = UniCase<CowStr<'a>>;
|
||||
|
||||
/// Assumes the opening bracket has already been scanned.
|
||||
/// The line break handler determines what happens when a linebreak
|
||||
/// is found. It is passed the bytes following the line break and
|
||||
/// either returns `Some(k)`, where `k` is the number of bytes to skip,
|
||||
/// or `None` to abort parsing the label.
|
||||
/// Returns the number of bytes read (including closing bracket) and label on success.
|
||||
pub(crate) fn scan_link_label_rest<'t>(
|
||||
text: &'t str,
|
||||
linebreak_handler: &dyn Fn(&[u8]) -> Option<usize>,
|
||||
is_in_table: bool,
|
||||
) -> Option<(usize, CowStr<'t>)> {
|
||||
let bytes = text.as_bytes();
|
||||
let mut ix = 0;
|
||||
let mut only_white_space = true;
|
||||
let mut codepoints = 0;
|
||||
// no worries, doesn't allocate until we push things onto it
|
||||
let mut label = String::new();
|
||||
let mut mark = 0;
|
||||
|
||||
loop {
|
||||
if codepoints >= 1000 {
|
||||
return None;
|
||||
}
|
||||
match *bytes.get(ix)? {
|
||||
b'[' => return None,
|
||||
b']' => break,
|
||||
// Backslash escapes in link references are normally untouched, but
|
||||
// tables are an exception, because they're parsed as-if the tables
|
||||
// were parsed in a discrete pass, changing `\|` to `|`, and then
|
||||
// passing the changed string to the inline parser.
|
||||
b'|' if is_in_table && ix != 0 && bytes.get(ix - 1) == Some(&b'\\') => {
|
||||
// only way to reach this spot is to have `\\|` (even number of `\` before `|`)
|
||||
label.push_str(&text[mark..ix - 1]);
|
||||
label.push('|');
|
||||
ix += 1;
|
||||
only_white_space = false;
|
||||
mark = ix;
|
||||
}
|
||||
b'\\' if is_in_table && bytes.get(ix + 1) == Some(&b'|') => {
|
||||
// only way to reach this spot is to have `\|` (odd number of `\` before `|`)
|
||||
label.push_str(&text[mark..ix]);
|
||||
label.push('|');
|
||||
ix += 2;
|
||||
codepoints += 1;
|
||||
only_white_space = false;
|
||||
mark = ix;
|
||||
}
|
||||
b'\\' if is_ascii_punctuation(*bytes.get(ix + 1)?) => {
|
||||
ix += 2;
|
||||
codepoints += 2;
|
||||
only_white_space = false;
|
||||
}
|
||||
b if is_ascii_whitespace(b) => {
|
||||
// normalize labels by collapsing whitespaces, including linebreaks
|
||||
let mut whitespaces = 0;
|
||||
let mut linebreaks = 0;
|
||||
let whitespace_start = ix;
|
||||
|
||||
while ix < bytes.len() && is_ascii_whitespace(bytes[ix]) {
|
||||
if let Some(eol_bytes) = scan_eol(&bytes[ix..]) {
|
||||
linebreaks += 1;
|
||||
if linebreaks > 1 {
|
||||
return None;
|
||||
}
|
||||
ix += eol_bytes;
|
||||
ix += linebreak_handler(&bytes[ix..])?;
|
||||
whitespaces += 2; // indicate that we need to replace
|
||||
} else {
|
||||
whitespaces += if bytes[ix] == b' ' { 1 } else { 2 };
|
||||
ix += 1;
|
||||
}
|
||||
}
|
||||
if whitespaces > 1 {
|
||||
label.push_str(&text[mark..whitespace_start]);
|
||||
label.push(' ');
|
||||
mark = ix;
|
||||
codepoints += ix - whitespace_start;
|
||||
} else {
|
||||
codepoints += 1;
|
||||
}
|
||||
}
|
||||
b => {
|
||||
only_white_space = false;
|
||||
ix += 1;
|
||||
if b & 0b1000_0000 != 0 {
|
||||
codepoints += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if only_white_space {
|
||||
None
|
||||
} else {
|
||||
let cow = if mark == 0 {
|
||||
let asciiws = &[' ', '\r', '\n', '\t'][..];
|
||||
text[..ix].trim_matches(asciiws).into()
|
||||
} else {
|
||||
label.push_str(&text[mark..ix]);
|
||||
while matches!(label.as_bytes().last(), Some(&b' ' | &b'\r' | &b'\n' | &b'\t')) {
|
||||
label.pop();
|
||||
}
|
||||
while matches!(label.as_bytes().first(), Some(&b' ' | &b'\r' | &b'\n' | &b'\t')) {
|
||||
label.remove(0);
|
||||
}
|
||||
label.into()
|
||||
};
|
||||
Some((ix + 1, cow))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::scan_link_label_rest;
|
||||
|
||||
#[test]
|
||||
fn whitespace_normalization() {
|
||||
let input = "«\t\tBlurry Eyes\t\t»][blurry_eyes]";
|
||||
let expected_output = "« Blurry Eyes »"; // regular spaces!
|
||||
|
||||
let (_bytes, normalized_label) = scan_link_label_rest(input, &|_| None, false).unwrap();
|
||||
assert_eq!(expected_output, normalized_label.as_ref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_carriage_linefeed_ok() {
|
||||
let input = "hello\r\nworld\r\n]";
|
||||
assert!(scan_link_label_rest(input, &|_| Some(0), false).is_some());
|
||||
}
|
||||
}
|
||||
Vendored
+195
@@ -0,0 +1,195 @@
|
||||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//! Command line tool to exercise pulldown-cmark.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use pulldown_cmark::{html, BrokenLink, Options, Parser};
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn dry_run(text: &str, opts: Options, broken_links: &mut Vec<BrokenLink<'static>>) {
|
||||
let p = Parser::new_with_broken_link_callback(
|
||||
text,
|
||||
opts,
|
||||
Some(|link: BrokenLink<'_>| {
|
||||
broken_links.push(link.into_static());
|
||||
None
|
||||
}),
|
||||
);
|
||||
let count = p.count();
|
||||
println!("{} events", count);
|
||||
}
|
||||
|
||||
fn print_events(text: &str, opts: Options, broken_links: &mut Vec<BrokenLink<'static>>) {
|
||||
let parser = Parser::new_with_broken_link_callback(
|
||||
text,
|
||||
opts,
|
||||
Some(|link: BrokenLink<'_>| {
|
||||
broken_links.push(link.into_static());
|
||||
None
|
||||
}),
|
||||
)
|
||||
.into_offset_iter();
|
||||
for (event, range) in parser {
|
||||
println!("{:?}: {:?}", range, event);
|
||||
}
|
||||
println!("EOF");
|
||||
}
|
||||
|
||||
fn brief(program: &str) -> String {
|
||||
format!(
|
||||
"Usage: {} [options]\n\n{}",
|
||||
program, "Reads markdown from file or standard input and emits HTML.",
|
||||
)
|
||||
}
|
||||
|
||||
pub fn main() -> std::io::Result<()> {
|
||||
let args: Vec<_> = env::args().collect();
|
||||
let mut opts = getopts::Options::new();
|
||||
opts.optflag("h", "help", "this help message");
|
||||
opts.optflag("d", "dry-run", "dry run, produce no output");
|
||||
opts.optflag("e", "events", "print event sequence instead of rendering");
|
||||
opts.optflag("T", "enable-tables", "enable GitHub-style tables");
|
||||
opts.optflag("m", "enable-math", "enable LaTeX-style math");
|
||||
opts.optflag("F", "enable-footnotes", "enable GitHub-style footnotes");
|
||||
opts.optflag("", "enable-old-footnotes", "enable Hoedown-style footnotes");
|
||||
opts.optflag(
|
||||
"S",
|
||||
"enable-strikethrough",
|
||||
"enable GitHub-style strikethrough",
|
||||
);
|
||||
opts.optflag("L", "enable-tasklists", "enable GitHub-style task lists");
|
||||
opts.optflag("P", "enable-smart-punctuation", "enable smart punctuation");
|
||||
opts.optflag(
|
||||
"H",
|
||||
"enable-heading-attributes",
|
||||
"enable heading attributes",
|
||||
);
|
||||
opts.optflag("M", "enable-metadata-blocks", "enable metadata blocks");
|
||||
opts.optflag(
|
||||
"R",
|
||||
"reject-broken-links",
|
||||
"fail if input file has broken links",
|
||||
);
|
||||
opts.optflag("G", "enable-gfm", "enable misc GFM features");
|
||||
|
||||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => m,
|
||||
Err(f) => {
|
||||
eprintln!("{}\n{}", f, opts.usage(&brief(&args[0])));
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
if matches.opt_present("help") {
|
||||
println!("{}", opts.usage(&brief(&args[0])));
|
||||
return Ok(());
|
||||
}
|
||||
let mut opts = Options::empty();
|
||||
if matches.opt_present("enable-tables") {
|
||||
opts.insert(Options::ENABLE_TABLES);
|
||||
}
|
||||
if matches.opt_present("enable-math") {
|
||||
opts.insert(Options::ENABLE_MATH);
|
||||
}
|
||||
if matches.opt_present("enable-footnotes") {
|
||||
opts.insert(Options::ENABLE_FOOTNOTES);
|
||||
}
|
||||
if matches.opt_present("enable-old-footnotes") {
|
||||
opts.insert(Options::ENABLE_OLD_FOOTNOTES);
|
||||
}
|
||||
if matches.opt_present("enable-strikethrough") {
|
||||
opts.insert(Options::ENABLE_STRIKETHROUGH);
|
||||
}
|
||||
if matches.opt_present("enable-tasklists") {
|
||||
opts.insert(Options::ENABLE_TASKLISTS);
|
||||
}
|
||||
if matches.opt_present("enable-smart-punctuation") {
|
||||
opts.insert(Options::ENABLE_SMART_PUNCTUATION);
|
||||
}
|
||||
if matches.opt_present("enable-heading-attributes") {
|
||||
opts.insert(Options::ENABLE_HEADING_ATTRIBUTES);
|
||||
}
|
||||
if matches.opt_present("enable-metadata-blocks") {
|
||||
opts.insert(Options::ENABLE_YAML_STYLE_METADATA_BLOCKS);
|
||||
opts.insert(Options::ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS);
|
||||
}
|
||||
if matches.opt_present("enable-gfm") {
|
||||
opts.insert(Options::ENABLE_GFM);
|
||||
}
|
||||
|
||||
let mut input = String::new();
|
||||
let mut broken_links = vec![];
|
||||
if !&matches.free.is_empty() {
|
||||
for filename in &matches.free {
|
||||
let real_path = PathBuf::from(filename);
|
||||
let mut f = File::open(&real_path).expect("file not found");
|
||||
f.read_to_string(&mut input)
|
||||
.expect("something went wrong reading the file");
|
||||
if matches.opt_present("events") {
|
||||
print_events(&input, opts, &mut broken_links);
|
||||
} else if matches.opt_present("dry-run") {
|
||||
dry_run(&input, opts, &mut broken_links);
|
||||
} else {
|
||||
pulldown_cmark(&input, opts, &mut broken_links);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let _ = io::stdin().lock().read_to_string(&mut input);
|
||||
if matches.opt_present("events") {
|
||||
print_events(&input, opts, &mut broken_links);
|
||||
} else if matches.opt_present("dry-run") {
|
||||
dry_run(&input, opts, &mut broken_links);
|
||||
} else {
|
||||
pulldown_cmark(&input, opts, &mut broken_links);
|
||||
}
|
||||
}
|
||||
|
||||
if matches.opt_present("reject-broken-links") && !broken_links.is_empty() {
|
||||
eprintln!("Error: {} broken links:", broken_links.len());
|
||||
for link in broken_links {
|
||||
let start = link.span.start;
|
||||
let end = link.span.end;
|
||||
let reference = link.reference;
|
||||
eprintln!("[{start}-{end}]: {reference}");
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pulldown_cmark(input: &str, opts: Options, broken_links: &mut Vec<BrokenLink<'static>>) {
|
||||
let mut p = Parser::new_with_broken_link_callback(
|
||||
input,
|
||||
opts,
|
||||
Some(|link: BrokenLink<'_>| {
|
||||
broken_links.push(link.into_static());
|
||||
None
|
||||
}),
|
||||
);
|
||||
let stdio = io::stdout();
|
||||
let buffer = std::io::BufWriter::with_capacity(1024 * 1024, stdio.lock());
|
||||
let _ = html::write_html_io(buffer, &mut p);
|
||||
}
|
||||
Vendored
+2605
File diff suppressed because it is too large.
Load diff
+1541
File diff suppressed because it is too large.
Load diff
+1522
File diff suppressed because it is too large.
Load diff
Vendored
+389
@@ -0,0 +1,389 @@
|
||||
use std::borrow::{Borrow, Cow};
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::Deref;
|
||||
use std::str::from_utf8;
|
||||
|
||||
const MAX_INLINE_STR_LEN: usize = 3 * std::mem::size_of::<isize>() - 2;
|
||||
|
||||
/// Returned when trying to convert a `&str` into a `InlineStr`
|
||||
/// but it fails because it doesn't fit.
|
||||
#[derive(Debug)]
|
||||
pub struct StringTooLongError;
|
||||
|
||||
/// An inline string that can contain almost three words
|
||||
/// of utf-8 text.
|
||||
#[derive(Debug, Clone, Copy, Eq)]
|
||||
pub struct InlineStr {
|
||||
inner: [u8; MAX_INLINE_STR_LEN],
|
||||
len: u8,
|
||||
}
|
||||
|
||||
impl AsRef<str> for InlineStr {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for InlineStr {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.deref().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<char> for InlineStr {
|
||||
fn from(c: char) -> Self {
|
||||
let mut inner = [0u8; MAX_INLINE_STR_LEN];
|
||||
c.encode_utf8(&mut inner);
|
||||
let len = c.len_utf8() as u8;
|
||||
Self { inner, len }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialEq<InlineStr> for InlineStr {
|
||||
fn eq(&self, other: &InlineStr) -> bool {
|
||||
self.deref() == other.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for InlineStr {
|
||||
type Error = StringTooLongError;
|
||||
|
||||
fn try_from(s: &str) -> Result<InlineStr, StringTooLongError> {
|
||||
let len = s.len();
|
||||
if len <= MAX_INLINE_STR_LEN {
|
||||
let mut inner = [0u8; MAX_INLINE_STR_LEN];
|
||||
inner[..len].copy_from_slice(s.as_bytes());
|
||||
let len = len as u8;
|
||||
Ok(Self { inner, len })
|
||||
} else {
|
||||
Err(StringTooLongError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for InlineStr {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &str {
|
||||
let len = self.len as usize;
|
||||
from_utf8(&self.inner[..len]).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for InlineStr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
/// A copy-on-write string that can be owned, borrowed
|
||||
/// or inlined.
|
||||
///
|
||||
/// It is three words long.
|
||||
#[derive(Debug, Eq)]
|
||||
pub enum CowStr<'a> {
|
||||
/// An owned, immutable string.
|
||||
Boxed(Box<str>),
|
||||
/// A borrowed string.
|
||||
Borrowed(&'a str),
|
||||
/// A short inline string.
|
||||
Inlined(InlineStr),
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
mod serde_impl {
|
||||
use super::CowStr;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt;
|
||||
|
||||
impl<'a> Serialize for CowStr<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
struct CowStrVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for CowStrVisitor {
|
||||
type Value = CowStr<'de>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a string")
|
||||
}
|
||||
|
||||
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(CowStr::Borrowed(v))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
match v.try_into() {
|
||||
Ok(it) => Ok(CowStr::Inlined(it)),
|
||||
Err(_) => Ok(CowStr::Boxed(String::from(v).into_boxed_str())),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(CowStr::Boxed(v.into_boxed_str()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'de: 'a> Deserialize<'de> for CowStr<'a> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(CowStrVisitor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsRef<str> for CowStr<'a> {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Hash for CowStr<'a> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.deref().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::clone::Clone for CowStr<'a> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
CowStr::Boxed(s) => match InlineStr::try_from(&**s) {
|
||||
Ok(inline) => CowStr::Inlined(inline),
|
||||
Err(..) => CowStr::Boxed(s.clone()),
|
||||
},
|
||||
CowStr::Borrowed(s) => CowStr::Borrowed(s),
|
||||
CowStr::Inlined(s) => CowStr::Inlined(*s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::cmp::PartialEq<CowStr<'a>> for CowStr<'a> {
|
||||
fn eq(&self, other: &CowStr<'_>) -> bool {
|
||||
self.deref() == other.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for CowStr<'a> {
|
||||
fn from(s: &'a str) -> Self {
|
||||
CowStr::Borrowed(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<String> for CowStr<'a> {
|
||||
fn from(s: String) -> Self {
|
||||
CowStr::Boxed(s.into_boxed_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<char> for CowStr<'a> {
|
||||
fn from(c: char) -> Self {
|
||||
CowStr::Inlined(c.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Cow<'a, str>> for CowStr<'a> {
|
||||
fn from(s: Cow<'a, str>) -> Self {
|
||||
match s {
|
||||
Cow::Borrowed(s) => CowStr::Borrowed(s),
|
||||
Cow::Owned(s) => CowStr::Boxed(s.into_boxed_str()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<CowStr<'a>> for Cow<'a, str> {
|
||||
fn from(s: CowStr<'a>) -> Self {
|
||||
match s {
|
||||
CowStr::Boxed(s) => Cow::Owned(s.to_string()),
|
||||
CowStr::Inlined(s) => Cow::Owned(s.to_string()),
|
||||
CowStr::Borrowed(s) => Cow::Borrowed(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Cow<'a, char>> for CowStr<'a> {
|
||||
fn from(s: Cow<'a, char>) -> Self {
|
||||
CowStr::Inlined(InlineStr::from(*s))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for CowStr<'a> {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &str {
|
||||
match self {
|
||||
CowStr::Boxed(ref b) => b,
|
||||
CowStr::Borrowed(b) => b,
|
||||
CowStr::Inlined(ref s) => s.deref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Borrow<str> for CowStr<'a> {
|
||||
fn borrow(&self) -> &str {
|
||||
self.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> CowStr<'a> {
|
||||
pub fn into_string(self) -> String {
|
||||
match self {
|
||||
CowStr::Boxed(b) => b.into(),
|
||||
CowStr::Borrowed(b) => b.to_owned(),
|
||||
CowStr::Inlined(s) => s.deref().to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for CowStr<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_special_string {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn inlinestr_ascii() {
|
||||
let s: InlineStr = 'a'.into();
|
||||
assert_eq!("a", s.deref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inlinestr_unicode() {
|
||||
let s: InlineStr = '🍔'.into();
|
||||
assert_eq!("🍔", s.deref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cowstr_size() {
|
||||
let size = std::mem::size_of::<CowStr>();
|
||||
let word_size = std::mem::size_of::<isize>();
|
||||
assert_eq!(3 * word_size, size);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cowstr_char_to_string() {
|
||||
let c = '藏';
|
||||
let smort: CowStr = c.into();
|
||||
let owned: String = smort.to_string();
|
||||
let expected = "藏".to_owned();
|
||||
assert_eq!(expected, owned);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_inline_str_len_atleast_four() {
|
||||
// we need 4 bytes to store a char
|
||||
assert!(MAX_INLINE_STR_LEN >= 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
fn inlinestr_fits_twentytwo() {
|
||||
let s = "0123456789abcdefghijkl";
|
||||
let stack_str = InlineStr::try_from(s).unwrap();
|
||||
assert_eq!(stack_str.deref(), s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
fn inlinestr_not_fits_twentythree() {
|
||||
let s = "0123456789abcdefghijklm";
|
||||
let _stack_str = InlineStr::try_from(s).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
fn small_boxed_str_clones_to_stack() {
|
||||
let s = "0123456789abcde".to_owned();
|
||||
let smort: CowStr = s.into();
|
||||
let smort_clone = smort.clone();
|
||||
|
||||
if let CowStr::Inlined(..) = smort_clone {
|
||||
} else {
|
||||
panic!("Expected a Inlined variant!");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cow_to_cow_str() {
|
||||
let s = "some text";
|
||||
let cow = Cow::Borrowed(s);
|
||||
let actual = CowStr::from(cow);
|
||||
let expected = CowStr::Borrowed(s);
|
||||
assert_eq!(actual, expected);
|
||||
assert!(variant_eq(&actual, &expected));
|
||||
|
||||
let s = "some text".to_string();
|
||||
let cow: Cow<str> = Cow::Owned(s.clone());
|
||||
let actual = CowStr::from(cow);
|
||||
let expected = CowStr::Boxed(s.into_boxed_str());
|
||||
assert_eq!(actual, expected);
|
||||
assert!(variant_eq(&actual, &expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cow_str_to_cow() {
|
||||
let s = "some text";
|
||||
let cow_str = CowStr::Borrowed(s);
|
||||
let actual = Cow::from(cow_str);
|
||||
let expected = Cow::Borrowed(s);
|
||||
assert_eq!(actual, expected);
|
||||
assert!(variant_eq(&actual, &expected));
|
||||
|
||||
let s = "s";
|
||||
let inline_str: InlineStr = InlineStr::try_from(s).unwrap();
|
||||
let cow_str = CowStr::Inlined(inline_str);
|
||||
let actual = Cow::from(cow_str);
|
||||
let expected: Cow<str> = Cow::Owned(s.to_string());
|
||||
assert_eq!(actual, expected);
|
||||
assert!(variant_eq(&actual, &expected));
|
||||
|
||||
let s = "s";
|
||||
let cow_str = CowStr::Boxed(s.to_string().into_boxed_str());
|
||||
let actual = Cow::from(cow_str);
|
||||
let expected: Cow<str> = Cow::Owned(s.to_string());
|
||||
assert_eq!(actual, expected);
|
||||
assert!(variant_eq(&actual, &expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cow_char_to_cow_str() {
|
||||
let c = 'c';
|
||||
let cow: Cow<char> = Cow::Owned(c);
|
||||
let actual = CowStr::from(cow);
|
||||
let expected = CowStr::Inlined(InlineStr::from(c));
|
||||
assert_eq!(actual, expected);
|
||||
assert!(variant_eq(&actual, &expected));
|
||||
|
||||
let c = 'c';
|
||||
let cow: Cow<char> = Cow::Borrowed(&c);
|
||||
let actual = CowStr::from(cow);
|
||||
let expected = CowStr::Inlined(InlineStr::from(c));
|
||||
assert_eq!(actual, expected);
|
||||
assert!(variant_eq(&actual, &expected));
|
||||
}
|
||||
|
||||
fn variant_eq<T>(a: &T, b: &T) -> bool {
|
||||
std::mem::discriminant(a) == std::mem::discriminant(b)
|
||||
}
|
||||
}
|
||||
Vendored
+285
@@ -0,0 +1,285 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
//! A Vec-based container for a tree structure.
|
||||
|
||||
use std::num::NonZeroUsize;
|
||||
use std::ops::{Add, Sub};
|
||||
|
||||
use crate::parse::{Item, ItemBody};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone, PartialOrd)]
|
||||
pub(crate) struct TreeIndex(NonZeroUsize);
|
||||
|
||||
impl TreeIndex {
|
||||
fn new(i: usize) -> Self {
|
||||
TreeIndex(NonZeroUsize::new(i).unwrap())
|
||||
}
|
||||
|
||||
pub fn get(self) -> usize {
|
||||
self.0.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for TreeIndex {
|
||||
type Output = TreeIndex;
|
||||
|
||||
fn add(self, rhs: usize) -> Self {
|
||||
let inner = self.0.get() + rhs;
|
||||
TreeIndex::new(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<usize> for TreeIndex {
|
||||
type Output = TreeIndex;
|
||||
|
||||
fn sub(self, rhs: usize) -> Self {
|
||||
let inner = self.0.get().checked_sub(rhs).unwrap();
|
||||
TreeIndex::new(inner)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct Node<T> {
|
||||
pub child: Option<TreeIndex>,
|
||||
pub next: Option<TreeIndex>,
|
||||
pub item: T,
|
||||
}
|
||||
|
||||
/// A tree abstraction, intended for fast building as a preorder traversal.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Tree<T> {
|
||||
nodes: Vec<Node<T>>,
|
||||
spine: Vec<TreeIndex>, // indices of nodes on path to current node
|
||||
cur: Option<TreeIndex>,
|
||||
}
|
||||
|
||||
impl<T: Default> Tree<T> {
|
||||
// Indices start at one, so we place a dummy value at index zero.
|
||||
// The alternative would be subtracting one from every TreeIndex
|
||||
// every time we convert it to usize to index our nodes.
|
||||
pub(crate) fn with_capacity(cap: usize) -> Tree<T> {
|
||||
let mut nodes = Vec::with_capacity(cap);
|
||||
nodes.push(Node {
|
||||
child: None,
|
||||
next: None,
|
||||
item: <T as Default>::default(),
|
||||
});
|
||||
Tree {
|
||||
nodes,
|
||||
spine: Vec::new(),
|
||||
cur: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the index of the element currently in focus.
|
||||
pub(crate) fn cur(&self) -> Option<TreeIndex> {
|
||||
self.cur
|
||||
}
|
||||
|
||||
/// Append one item to the current position in the tree.
|
||||
pub(crate) fn append(&mut self, item: T) -> TreeIndex {
|
||||
let ix = self.create_node(item);
|
||||
let this = Some(ix);
|
||||
|
||||
if let Some(ix) = self.cur {
|
||||
self[ix].next = this;
|
||||
} else if let Some(&parent) = self.spine.last() {
|
||||
self[parent].child = this;
|
||||
}
|
||||
self.cur = this;
|
||||
ix
|
||||
}
|
||||
|
||||
/// Create an isolated node.
|
||||
pub(crate) fn create_node(&mut self, item: T) -> TreeIndex {
|
||||
let this = self.nodes.len();
|
||||
self.nodes.push(Node {
|
||||
child: None,
|
||||
next: None,
|
||||
item,
|
||||
});
|
||||
TreeIndex::new(this)
|
||||
}
|
||||
|
||||
/// Push down one level, so that new items become children of the current node.
|
||||
/// The new focus index is returned.
|
||||
pub(crate) fn push(&mut self) -> TreeIndex {
|
||||
let cur_ix = self.cur.unwrap();
|
||||
self.spine.push(cur_ix);
|
||||
self.cur = self[cur_ix].child;
|
||||
cur_ix
|
||||
}
|
||||
|
||||
/// Pop back up a level.
|
||||
pub(crate) fn pop(&mut self) -> Option<TreeIndex> {
|
||||
let ix = Some(self.spine.pop()?);
|
||||
self.cur = ix;
|
||||
ix
|
||||
}
|
||||
|
||||
/// Remove the last node, as `pop` but removing it.
|
||||
pub(crate) fn remove_node(&mut self) -> Option<TreeIndex> {
|
||||
let ix = self.spine.pop()?;
|
||||
self.cur = Some(ix);
|
||||
self.nodes.pop()?;
|
||||
self[ix].child = None;
|
||||
Some(ix)
|
||||
}
|
||||
|
||||
/// Look at the parent node.
|
||||
pub(crate) fn peek_up(&self) -> Option<TreeIndex> {
|
||||
self.spine.last().copied()
|
||||
}
|
||||
|
||||
/// Look at grandparent node.
|
||||
pub(crate) fn peek_grandparent(&self) -> Option<TreeIndex> {
|
||||
if self.spine.len() >= 2 {
|
||||
Some(self.spine[self.spine.len() - 2])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true when there are no nodes other than the root node
|
||||
/// in the tree, false otherwise.
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.nodes.len() <= 1
|
||||
}
|
||||
|
||||
/// Returns the length of the spine.
|
||||
pub(crate) fn spine_len(&self) -> usize {
|
||||
self.spine.len()
|
||||
}
|
||||
|
||||
/// Resets the focus to the first node added to the tree, if it exists.
|
||||
pub(crate) fn reset(&mut self) {
|
||||
self.cur = if self.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(TreeIndex::new(1))
|
||||
};
|
||||
self.spine.clear();
|
||||
}
|
||||
|
||||
/// Walks the spine from a root node up to, but not including, the current node.
|
||||
pub(crate) fn walk_spine(&self) -> impl std::iter::DoubleEndedIterator<Item = &TreeIndex> {
|
||||
self.spine.iter()
|
||||
}
|
||||
|
||||
/// Moves focus to the next sibling of the given node.
|
||||
pub(crate) fn next_sibling(&mut self, cur_ix: TreeIndex) -> Option<TreeIndex> {
|
||||
self.cur = self[cur_ix].next;
|
||||
self.cur
|
||||
}
|
||||
}
|
||||
|
||||
impl Tree<Item> {
|
||||
/// Truncates the preceding siblings to the given end position,
|
||||
/// and returns the new current node.
|
||||
pub(crate) fn truncate_siblings(&mut self, end_byte_ix: usize) {
|
||||
let parent_ix = self.peek_up().unwrap();
|
||||
let mut next_child_ix = self[parent_ix].child;
|
||||
let mut prev_child_ix = None;
|
||||
|
||||
// drop or truncate children based on its range
|
||||
while let Some(child_ix) = next_child_ix {
|
||||
let child_end = self[child_ix].item.end;
|
||||
if child_end < end_byte_ix {
|
||||
// preserve this node, and go to the next
|
||||
prev_child_ix = Some(child_ix);
|
||||
next_child_ix = self[child_ix].next;
|
||||
continue;
|
||||
} else if child_end == end_byte_ix {
|
||||
// this will be the last node
|
||||
self[child_ix].next = None;
|
||||
// focus to the new last child (this node)
|
||||
self.cur = Some(child_ix);
|
||||
} else if self[child_ix].item.start == end_byte_ix {
|
||||
// check whether the previous character is a backslash
|
||||
let is_previous_char_backslash_escape = match self[child_ix].item.body {
|
||||
ItemBody::Text { backslash_escaped } => backslash_escaped,
|
||||
_ => false,
|
||||
};
|
||||
if is_previous_char_backslash_escape {
|
||||
// rescue the backslash as a plain text content
|
||||
let last_byte_ix = end_byte_ix - 1;
|
||||
self[child_ix].item.start = last_byte_ix;
|
||||
self[child_ix].item.end = end_byte_ix;
|
||||
self.cur = Some(child_ix);
|
||||
} else if let Some(prev_child_ix) = prev_child_ix {
|
||||
// the node will become empty. drop the node
|
||||
// a preceding sibling exists
|
||||
self[prev_child_ix].next = None;
|
||||
self.cur = Some(prev_child_ix);
|
||||
} else {
|
||||
// no preceding siblings. remove the node from the parent
|
||||
self[parent_ix].child = None;
|
||||
self.cur = None;
|
||||
}
|
||||
} else {
|
||||
debug_assert!(self[child_ix].item.start < end_byte_ix);
|
||||
debug_assert!(end_byte_ix < child_end);
|
||||
// truncate the node
|
||||
self[child_ix].item.end = end_byte_ix;
|
||||
self[child_ix].next = None;
|
||||
// focus to the new last child
|
||||
self.cur = Some(child_ix);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for Tree<T>
|
||||
where
|
||||
T: std::fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn debug_tree<T>(
|
||||
tree: &Tree<T>,
|
||||
cur: TreeIndex,
|
||||
indent: usize,
|
||||
f: &mut std::fmt::Formatter<'_>,
|
||||
) -> std::fmt::Result
|
||||
where
|
||||
T: std::fmt::Debug,
|
||||
{
|
||||
for _ in 0..indent {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
writeln!(f, "{:?}", &tree[cur].item)?;
|
||||
if let Some(child_ix) = tree[cur].child {
|
||||
debug_tree(tree, child_ix, indent + 1, f)?;
|
||||
}
|
||||
if let Some(next_ix) = tree[cur].next {
|
||||
debug_tree(tree, next_ix, indent, f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
if self.nodes.len() > 1 {
|
||||
let cur = TreeIndex(NonZeroUsize::new(1).unwrap());
|
||||
debug_tree(self, cur, 0, f)
|
||||
} else {
|
||||
write!(f, "Empty tree")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Index<TreeIndex> for Tree<T> {
|
||||
type Output = Node<T>;
|
||||
|
||||
fn index(&self, ix: TreeIndex) -> &Self::Output {
|
||||
self.nodes.index(ix.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::IndexMut<TreeIndex> for Tree<T> {
|
||||
fn index_mut(&mut self, ix: TreeIndex) -> &mut Node<T> {
|
||||
self.nodes.index_mut(ix.get())
|
||||
}
|
||||
}
|
||||
Vendored
+150
@@ -0,0 +1,150 @@
|
||||
//! Miscellaneous utilities to increase comfort.
|
||||
//! Special thanks to:
|
||||
//!
|
||||
//! - <https://github.com/BenjaminRi/Redwood-Wiki/blob/master/src/markdown_utils.rs>.
|
||||
//! Its author authorized the use of this GPL code in this project in
|
||||
//! <https://github.com/raphlinus/pulldown-cmark/issues/507>.
|
||||
//!
|
||||
//! - <https://gist.github.com/rambip/a507c312ed61c99c24b2a54f98325721>.
|
||||
//! Its author proposed the solution in
|
||||
//! <https://github.com/raphlinus/pulldown-cmark/issues/708>.
|
||||
|
||||
use crate::{
|
||||
BrokenLinkCallback, CowStr, DefaultBrokenLinkCallback, Event, OffsetIter, Options, Parser,
|
||||
};
|
||||
use std::{iter::Peekable, ops::Range};
|
||||
|
||||
/// Merge consecutive `Event::Text` events into only one.
|
||||
#[derive(Debug)]
|
||||
pub struct TextMergeStream<'a, I> {
|
||||
iter: I,
|
||||
last_event: Option<Event<'a>>,
|
||||
}
|
||||
|
||||
impl<'a, I> TextMergeStream<'a, I>
|
||||
where
|
||||
I: Iterator<Item = Event<'a>>,
|
||||
{
|
||||
pub fn new(iter: I) -> Self {
|
||||
Self {
|
||||
iter,
|
||||
last_event: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I> Iterator for TextMergeStream<'a, I>
|
||||
where
|
||||
I: Iterator<Item = Event<'a>>,
|
||||
{
|
||||
type Item = Event<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match (self.last_event.take(), self.iter.next()) {
|
||||
(Some(Event::Text(last_text)), Some(Event::Text(next_text))) => {
|
||||
// We need to start merging consecutive text events together into one
|
||||
let mut string_buf: String = last_text.into_string();
|
||||
string_buf.push_str(&next_text);
|
||||
loop {
|
||||
// Avoid recursion to avoid stack overflow and to optimize concatenation
|
||||
match self.iter.next() {
|
||||
Some(Event::Text(next_text)) => {
|
||||
string_buf.push_str(&next_text);
|
||||
}
|
||||
next_event => {
|
||||
self.last_event = next_event;
|
||||
if string_buf.is_empty() {
|
||||
// Discard text event(s) altogether if there is no text
|
||||
break self.next();
|
||||
} else {
|
||||
break Some(Event::Text(CowStr::Boxed(
|
||||
string_buf.into_boxed_str(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(None, Some(next_event)) => {
|
||||
// This only happens once during the first iteration and if there are items
|
||||
self.last_event = Some(next_event);
|
||||
self.next()
|
||||
}
|
||||
(None, None) => {
|
||||
// This happens when the iterator is depleted
|
||||
None
|
||||
}
|
||||
(last_event, next_event) => {
|
||||
// The ordinary case, emit one event after the other without modification
|
||||
self.last_event = next_event;
|
||||
last_event
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge consecutive `Event::Text` events into only one with offsets.
|
||||
#[derive(Debug)]
|
||||
pub struct TextMergeWithOffset<'input, F = DefaultBrokenLinkCallback>
|
||||
where
|
||||
F: BrokenLinkCallback<'input>,
|
||||
{
|
||||
source: &'input str,
|
||||
parser: Peekable<OffsetIter<'input, F>>,
|
||||
}
|
||||
|
||||
impl<'input, F> TextMergeWithOffset<'input, F>
|
||||
where
|
||||
F: BrokenLinkCallback<'input>,
|
||||
{
|
||||
pub fn new_ext(source: &'input str, options: Options) -> Self {
|
||||
Self {
|
||||
source,
|
||||
parser: Parser::new_with_broken_link_callback(source, options, None)
|
||||
.into_offset_iter()
|
||||
.peekable(),
|
||||
}
|
||||
}
|
||||
pub fn new_ext_with_broken_link_callback(
|
||||
source: &'input str,
|
||||
options: Options,
|
||||
callback: Option<F>,
|
||||
) -> Self {
|
||||
Self {
|
||||
source,
|
||||
parser: Parser::new_with_broken_link_callback(source, options, callback)
|
||||
.into_offset_iter()
|
||||
.peekable(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'input, F> Iterator for TextMergeWithOffset<'input, F>
|
||||
where
|
||||
F: BrokenLinkCallback<'input>,
|
||||
{
|
||||
type Item = (Event<'input>, Range<usize>);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let is_empty_text = |x: Option<&(Event<'input>, Range<usize>)>| match x {
|
||||
Some(e) => matches!(&e.0, Event::Text(t) if t.is_empty()),
|
||||
None => false,
|
||||
};
|
||||
|
||||
while is_empty_text(self.parser.peek()) {
|
||||
self.parser.next();
|
||||
}
|
||||
|
||||
match self.parser.peek()? {
|
||||
(Event::Text(_), range) => {
|
||||
let start = range.start;
|
||||
let mut end = range.end;
|
||||
while let Some((Event::Text(_), _)) = self.parser.peek() {
|
||||
end = self.parser.next().unwrap().1.end;
|
||||
}
|
||||
|
||||
Some((Event::Text(self.source[start..end].into()), start..end))
|
||||
}
|
||||
_ => self.parser.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
use pulldown_cmark::{Options, Parser};
|
||||
|
||||
fn parse(md: &str) {
|
||||
let parser = Parser::new(md);
|
||||
|
||||
for _ in parser {}
|
||||
}
|
||||
|
||||
fn parse_all_options(md: &str) {
|
||||
let parser = Parser::new_ext(md, Options::all());
|
||||
|
||||
for _ in parser {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lists_inside_code_spans() {
|
||||
parse(
|
||||
r"- `
|
||||
x
|
||||
**
|
||||
*
|
||||
`",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzer_input_1() {
|
||||
parse(">\n >>><N\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzer_input_2() {
|
||||
parse(" \u{b}\\\r- ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzer_input_3() {
|
||||
parse_all_options("\n # #\r\u{1c} ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzer_input_4() {
|
||||
parse_all_options("\u{0}{\tϐ}\n-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzer_input_5() {
|
||||
parse_all_options(" \u{c}{}\n-\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzer_input_6() {
|
||||
parse("*\t[][\n\t<p]>\n\t[]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzer_input_7() {
|
||||
parse_all_options("[][{]}\n-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzer_input_8() {
|
||||
parse_all_options("a\n \u{c}{}\n-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzer_input_9() {
|
||||
parse_all_options("a\n \u{c}{}\\\n-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzer_input_10() {
|
||||
parse_all_options("[[ \t\n \u{c}\u{c}\u{c}\u{c}\u{c} {}\n-\r\u{e}\u{0}\u{0}{# }\n\u{b}\u{b}\u{b}\u{b}\u{b}\u{b}\u{b}\u{b}\u{b}\u{b}\u{0}\u{0}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzer_input_11() {
|
||||
parse_all_options(
|
||||
"[[\u{c}\u{c} \t\n \u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c}\u{c} {}\n-\r\u{e}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzer_input_12() {
|
||||
parse_all_options("\u{c}-\n\u{c}\n-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzer_input_13() {
|
||||
// Does not fail with Options::all():
|
||||
Parser::new_ext(
|
||||
".\r> ^](\r\u{c}\r\0\0\r.\r[^\0\0\\\0\0\0^^^^^]",
|
||||
Options::ENABLE_FOOTNOTES,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_code_block() {
|
||||
parse(
|
||||
r##"```
|
||||
* ```
|
||||
"##,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unterminated_link() {
|
||||
parse("[](\\");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unterminated_autolink() {
|
||||
parse("<a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_infinite_loop() {
|
||||
parse("[<!W\n\\\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_html_tag() {
|
||||
parse("<script\u{feff}");
|
||||
}
|
||||
|
||||
// all of test_bad_slice_* were found in https://github.com/raphlinus/pulldown-cmark/issues/521
|
||||
#[test]
|
||||
fn test_bad_slice_a() {
|
||||
parse("><a\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bad_slice_b() {
|
||||
parse("><a a\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bad_slice_unicode() {
|
||||
parse("><a a=\n毿>")
|
||||
}
|
||||
Vendored
+351
@@ -0,0 +1,351 @@
|
||||
// Tests for HTML spec.
|
||||
#![cfg(feature = "html")]
|
||||
|
||||
use pulldown_cmark::{html, BrokenLink, Options, Parser};
|
||||
|
||||
#[test]
|
||||
fn html_test_1() {
|
||||
let original = r##"Little header
|
||||
|
||||
<script type="text/js">
|
||||
function some_func() {
|
||||
console.log("teeeest");
|
||||
}
|
||||
|
||||
|
||||
function another_func() {
|
||||
console.log("fooooo");
|
||||
}
|
||||
</script>"##;
|
||||
let expected = r##"<p>Little header</p>
|
||||
<script type="text/js">
|
||||
function some_func() {
|
||||
console.log("teeeest");
|
||||
}
|
||||
|
||||
|
||||
function another_func() {
|
||||
console.log("fooooo");
|
||||
}
|
||||
</script>"##;
|
||||
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn html_test_2() {
|
||||
let original = r##"Little header
|
||||
|
||||
<script
|
||||
type="text/js">
|
||||
function some_func() {
|
||||
console.log("teeeest");
|
||||
}
|
||||
|
||||
|
||||
function another_func() {
|
||||
console.log("fooooo");
|
||||
}
|
||||
</script>"##;
|
||||
let expected = r##"<p>Little header</p>
|
||||
<script
|
||||
type="text/js">
|
||||
function some_func() {
|
||||
console.log("teeeest");
|
||||
}
|
||||
|
||||
|
||||
function another_func() {
|
||||
console.log("fooooo");
|
||||
}
|
||||
</script>"##;
|
||||
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn html_test_3() {
|
||||
let original = r##"Little header
|
||||
|
||||
<?
|
||||
<div></div>
|
||||
<p>Useless</p>
|
||||
?>"##;
|
||||
let expected = r##"<p>Little header</p>
|
||||
<?
|
||||
<div></div>
|
||||
<p>Useless</p>
|
||||
?>"##;
|
||||
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn html_test_4() {
|
||||
let original = r##"Little header
|
||||
|
||||
<!--
|
||||
<div></div>
|
||||
<p>Useless</p>
|
||||
-->"##;
|
||||
let expected = r##"<p>Little header</p>
|
||||
<!--
|
||||
<div></div>
|
||||
<p>Useless</p>
|
||||
-->"##;
|
||||
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn html_test_5() {
|
||||
let original = r##"Little header
|
||||
|
||||
<![CDATA[
|
||||
<div></div>
|
||||
<p>Useless</p>
|
||||
]]>"##;
|
||||
let expected = r##"<p>Little header</p>
|
||||
<![CDATA[
|
||||
<div></div>
|
||||
<p>Useless</p>
|
||||
]]>"##;
|
||||
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn html_test_6() {
|
||||
let original = r##"Little header
|
||||
|
||||
<!X
|
||||
Some things are here...
|
||||
>"##;
|
||||
let expected = r##"<p>Little header</p>
|
||||
<!X
|
||||
Some things are here...
|
||||
>"##;
|
||||
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn html_test_7() {
|
||||
let original = r##"Little header
|
||||
-----------
|
||||
|
||||
<script>
|
||||
function some_func() {
|
||||
console.log("teeeest");
|
||||
}
|
||||
|
||||
|
||||
function another_func() {
|
||||
console.log("fooooo");
|
||||
}
|
||||
</script>"##;
|
||||
let expected = r##"<h2>Little header</h2>
|
||||
<script>
|
||||
function some_func() {
|
||||
console.log("teeeest");
|
||||
}
|
||||
|
||||
|
||||
function another_func() {
|
||||
console.log("fooooo");
|
||||
}
|
||||
</script>"##;
|
||||
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn html_test_8() {
|
||||
let original = "A | B\n---|---\nfoo | bar";
|
||||
let expected = r##"<table><thead><tr><th>A</th><th>B</th></tr></thead><tbody>
|
||||
<tr><td>foo</td><td>bar</td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
let mut s = String::new();
|
||||
let mut opts = Options::empty();
|
||||
opts.insert(Options::ENABLE_TABLES);
|
||||
html::push_html(&mut s, Parser::new_ext(original, opts));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn html_test_9() {
|
||||
let original = "---";
|
||||
let expected = "<hr />\n";
|
||||
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn html_test_10() {
|
||||
let original = "* * *";
|
||||
let expected = "<hr />\n";
|
||||
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn html_test_11() {
|
||||
let original = "hi ~~no~~";
|
||||
let expected = "<p>hi ~~no~~</p>\n";
|
||||
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn html_test_broken_callback() {
|
||||
let original = r##"[foo],
|
||||
[bar],
|
||||
[baz],
|
||||
|
||||
[baz]: https://example.org
|
||||
"##;
|
||||
|
||||
let expected = r##"<p><a href="https://replaced.example.org" title="some title">foo</a>,
|
||||
[bar],
|
||||
<a href="https://example.org">baz</a>,</p>
|
||||
"##;
|
||||
|
||||
use pulldown_cmark::{html, Options, Parser};
|
||||
|
||||
let mut s = String::new();
|
||||
|
||||
let mut callback = |broken_link: BrokenLink| {
|
||||
if &*broken_link.reference == "foo" || &*broken_link.reference == "baz" {
|
||||
Some(("https://replaced.example.org".into(), "some title".into()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let p = Parser::new_with_broken_link_callback(original, Options::empty(), Some(&mut callback));
|
||||
html::push_html(&mut s, p);
|
||||
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn newline_in_code() {
|
||||
let originals = ["`\n `x", "` \n`x"];
|
||||
let expected = "<p><code> </code>x</p>\n";
|
||||
|
||||
for original in originals {
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn newline_start_end_of_code() {
|
||||
let original = "`\nx\n`x";
|
||||
let expected = "<p><code>x</code>x</p>\n";
|
||||
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
// https://github.com/raphlinus/pulldown-cmark/issues/715
|
||||
|
||||
#[test]
|
||||
fn trim_space_and_tab_at_end_of_paragraph() {
|
||||
let original = "one\ntwo \t";
|
||||
let expected = "<p>one\ntwo</p>\n";
|
||||
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn newline_within_code() {
|
||||
let originals = ["`\nx \ny\n`x", "`x \ny`x", "`x\n y`x"];
|
||||
let expected = "<p><code>x y</code>x</p>\n";
|
||||
|
||||
for original in originals {
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trim_space_tab_nl_at_end_of_paragraph() {
|
||||
let original = "one\ntwo \t\n";
|
||||
let expected = "<p>one\ntwo</p>\n";
|
||||
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trim_space_nl_at_end_of_paragraph() {
|
||||
let original = "one\ntwo \n";
|
||||
let expected = "<p>one\ntwo</p>\n";
|
||||
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trim_space_before_soft_break() {
|
||||
let original = "one \ntwo";
|
||||
let expected = "<p>one\ntwo</p>\n";
|
||||
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(original));
|
||||
assert_eq!(expected, s);
|
||||
}
|
||||
|
||||
// Can't easily use regression.txt due to newline normalization.
|
||||
#[test]
|
||||
fn issue_819() {
|
||||
let original = [
|
||||
"# \\", "# \\\n", "# \\\n\n", "# \\\r\n", "# \\\r\n\r\n", "# \\\n\r\n", "# \\\r\n\n"
|
||||
];
|
||||
let expected = "<h1>\\</h1>";
|
||||
|
||||
for orig in original {
|
||||
let mut s = String::new();
|
||||
html::push_html(&mut s, Parser::new(orig));
|
||||
// Trailing newline doesn't matter. Just the actual HTML.
|
||||
assert_eq!(expected, s.trim_end_matches('\n'));
|
||||
}
|
||||
for orig in original {
|
||||
let mut s = String::new();
|
||||
let mut opts = Options::empty();
|
||||
opts.insert(Options::ENABLE_HEADING_ATTRIBUTES);
|
||||
html::push_html(&mut s, Parser::new_ext(orig, opts));
|
||||
// Trailing newline doesn't matter. Just the actual HTML.
|
||||
assert_eq!(expected, s.trim_end_matches('\n'));
|
||||
}
|
||||
}
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
#![cfg(feature = "html")]
|
||||
|
||||
use pulldown_cmark::{Options, Parser};
|
||||
|
||||
mod suite;
|
||||
|
||||
#[inline(never)]
|
||||
pub fn test_markdown_html(
|
||||
input: &str,
|
||||
output: &str,
|
||||
smart_punct: bool,
|
||||
metadata_blocks: bool,
|
||||
old_footnotes: bool,
|
||||
) {
|
||||
let mut s = String::new();
|
||||
|
||||
let mut opts = Options::empty();
|
||||
opts.insert(Options::ENABLE_MATH);
|
||||
opts.insert(Options::ENABLE_TABLES);
|
||||
opts.insert(Options::ENABLE_STRIKETHROUGH);
|
||||
opts.insert(Options::ENABLE_TASKLISTS);
|
||||
opts.insert(Options::ENABLE_GFM);
|
||||
if old_footnotes {
|
||||
opts.insert(Options::ENABLE_OLD_FOOTNOTES);
|
||||
} else {
|
||||
opts.insert(Options::ENABLE_FOOTNOTES);
|
||||
}
|
||||
if metadata_blocks {
|
||||
opts.insert(Options::ENABLE_YAML_STYLE_METADATA_BLOCKS);
|
||||
opts.insert(Options::ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS);
|
||||
}
|
||||
if smart_punct {
|
||||
opts.insert(Options::ENABLE_SMART_PUNCTUATION);
|
||||
}
|
||||
opts.insert(Options::ENABLE_HEADING_ATTRIBUTES);
|
||||
|
||||
let p = Parser::new_ext(input, opts);
|
||||
pulldown_cmark::html::push_html(&mut s, p);
|
||||
|
||||
// normalizing the HTML using html5ever may hide actual errors
|
||||
// assert_eq!(html_standardize(output), html_standardize(&s));
|
||||
assert_eq!(html_standardize(output), html_standardize(&s));
|
||||
}
|
||||
|
||||
fn html_standardize(s: &str) -> String {
|
||||
s.replace("<br>", "<br />")
|
||||
.replace("<br/>", "<br />")
|
||||
.replace("<hr>", "<hr />")
|
||||
.replace("<hr/>", "<hr />")
|
||||
// permit extra or missing line breaks only between tags
|
||||
.replace(">\n<", "><")
|
||||
}
|
||||
Vendored
+86
@@ -0,0 +1,86 @@
|
||||
#[cfg(feature = "serde")]
|
||||
mod tests {
|
||||
use std::convert::TryInto;
|
||||
|
||||
use pulldown_cmark::CowStr;
|
||||
|
||||
#[test]
|
||||
fn escaped() {
|
||||
let must_be_escaped = "\"";
|
||||
let wire = serde_json::to_string(must_be_escaped).unwrap();
|
||||
let de = serde_json::from_str::<CowStr>(&wire).unwrap();
|
||||
assert_eq!(&*de, must_be_escaped);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cow_str_to_str_round_trip_bincode() {
|
||||
for i in &[
|
||||
CowStr::Borrowed("dssa"),
|
||||
CowStr::Borrowed(""),
|
||||
CowStr::Boxed("hello".to_owned().into_boxed_str()),
|
||||
CowStr::Boxed("".to_owned().into_boxed_str()),
|
||||
CowStr::Inlined("inline".try_into().unwrap()),
|
||||
CowStr::Inlined("".try_into().unwrap()),
|
||||
] {
|
||||
let encoded = bincode::serialize(i).unwrap();
|
||||
let decoded1: CowStr = bincode::deserialize(&encoded).unwrap();
|
||||
let decoded2: String = bincode::deserialize(&encoded).unwrap();
|
||||
let decoded3: &str = bincode::deserialize(&encoded).unwrap();
|
||||
|
||||
assert_eq!(&decoded1, i);
|
||||
assert_eq!(decoded2, i.as_ref());
|
||||
assert_eq!(decoded3, i.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cow_str_to_str_round_trip_json() {
|
||||
for i in &[
|
||||
CowStr::Borrowed("dssa"),
|
||||
CowStr::Borrowed(""),
|
||||
CowStr::Boxed("hello".to_owned().into_boxed_str()),
|
||||
CowStr::Boxed("".to_owned().into_boxed_str()),
|
||||
CowStr::Inlined("inline".try_into().unwrap()),
|
||||
CowStr::Inlined("".try_into().unwrap()),
|
||||
] {
|
||||
let encoded = serde_json::to_string(i).unwrap();
|
||||
let decoded1: CowStr = serde_json::from_str(&encoded).unwrap();
|
||||
let decoded2: String = serde_json::from_str(&encoded).unwrap();
|
||||
let decoded3: &str = serde_json::from_str(&encoded).unwrap();
|
||||
|
||||
assert_eq!(&decoded1, i);
|
||||
assert_eq!(decoded2, i.as_ref());
|
||||
assert_eq!(decoded3, i.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_to_cow_str_json() {
|
||||
let str = "a borrowed str";
|
||||
let string = "a owned str".to_owned();
|
||||
|
||||
let encoded_str = serde_json::to_string(&str).unwrap();
|
||||
let encoded_string = serde_json::to_string(&string).unwrap();
|
||||
|
||||
let decoded_str: CowStr = serde_json::from_str(&encoded_str).unwrap();
|
||||
let decoded_string: CowStr = serde_json::from_str(&encoded_string).unwrap();
|
||||
|
||||
assert_eq!(decoded_str.as_ref(), str);
|
||||
assert_eq!(decoded_string.as_ref(), string);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_to_cow_str_bincode() {
|
||||
let str = "a borrowed str";
|
||||
let string = "a owned str".to_owned();
|
||||
|
||||
let encoded_str = bincode::serialize(&str).unwrap();
|
||||
let encoded_string = bincode::serialize(&string).unwrap();
|
||||
|
||||
let decoded_str: CowStr = bincode::deserialize(&encoded_str).unwrap();
|
||||
let decoded_string: CowStr = bincode::deserialize(&encoded_string).unwrap();
|
||||
|
||||
assert_eq!(decoded_str.as_ref(), str);
|
||||
assert_eq!(decoded_string.as_ref(), string);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
// This file is auto-generated by the build script
|
||||
// Please, do not modify it manually
|
||||
|
||||
use super::test_markdown_html;
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_1() {
|
||||
let original = r##"> This is a normal blockquote without tag.
|
||||
"##;
|
||||
let expected = r##"<blockquote><p>This is a normal blockquote without tag.</p></blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_2() {
|
||||
let original = r##"> [!NOTE]
|
||||
> Note blockquote
|
||||
"##;
|
||||
let expected = r##"<blockquote class="markdown-alert-note"><p>Note blockquote</p></blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_3() {
|
||||
let original = r##"> [!TIP]
|
||||
> Tip blockquote
|
||||
"##;
|
||||
let expected = r##"<blockquote class="markdown-alert-tip"><p>Tip blockquote</p></blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_4() {
|
||||
let original = r##"> [!IMPORTANT]
|
||||
> Important blockquote
|
||||
"##;
|
||||
let expected = r##"<blockquote class="markdown-alert-important"><p>Important blockquote</p></blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_5() {
|
||||
let original = r##"> [!WARNING]
|
||||
> Warning blockquote
|
||||
"##;
|
||||
let expected = r##"<blockquote class="markdown-alert-warning"><p>Warning blockquote</p></blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_6() {
|
||||
let original = r##"> [!CAUTION]
|
||||
> Caution blockquote
|
||||
"##;
|
||||
let expected = r##"<blockquote class="markdown-alert-caution"><p>Caution blockquote</p></blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_7() {
|
||||
let original = r##"> [!CAUTION]
|
||||
"##;
|
||||
let expected = r##"<blockquote class="markdown-alert-caution"></blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_8() {
|
||||
let original = r##"> [!CAUTION]
|
||||
> Line 1.
|
||||
> Line 2.
|
||||
"##;
|
||||
let expected = r##"<blockquote class="markdown-alert-caution"><p>Line 1.
|
||||
Line 2.</p></blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_9() {
|
||||
let original = r##"> [!CAUTION]
|
||||
> Line 1.
|
||||
> [!CAUTION]
|
||||
> Line 2.
|
||||
"##;
|
||||
let expected = r##"<blockquote class="markdown-alert-caution"><p>Line 1.
|
||||
[!CAUTION]
|
||||
Line 2.</p></blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_10() {
|
||||
let original = r##"> [!CAUTION]
|
||||
> Line 1.
|
||||
> > [!TIP]
|
||||
> Line 2.
|
||||
"##;
|
||||
let expected = r##"<blockquote class="markdown-alert-caution"><p>Line 1.</p><blockquote class="markdown-alert-tip"><p>Line 2.</p></blockquote></blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_11() {
|
||||
let original = r##"> [!CAUTION]
|
||||
> Line 1.
|
||||
|
||||
|
||||
> [!TIP]
|
||||
> Line 2.
|
||||
"##;
|
||||
let expected = r##"<blockquote class="markdown-alert-caution"><p>Line 1.</p></blockquote><blockquote class="markdown-alert-tip"><p>Line 2.</p></blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_12() {
|
||||
let original = r##"> > [!CAUTION]
|
||||
> > Line 1.
|
||||
> Line 2.
|
||||
"##;
|
||||
let expected = r##"<blockquote><blockquote class="markdown-alert-caution"><p>Line 1.
|
||||
Line 2.</p></blockquote></blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_13() {
|
||||
let original = r##"> [!CAUTION]
|
||||
> Line 1.
|
||||
> > [!NOTE]
|
||||
> > Line 2.
|
||||
"##;
|
||||
let expected = r##"<blockquote class="markdown-alert-caution"><p>Line 1.</p>
|
||||
<blockquote class="markdown-alert-note"><p>Line 2.</p></blockquote>
|
||||
</blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_14() {
|
||||
let original = r##"> [!caution]
|
||||
> Line 1.
|
||||
> > [!note]
|
||||
> > Line 2.
|
||||
"##;
|
||||
let expected = r##"<blockquote class="markdown-alert-caution"><p>Line 1.</p>
|
||||
<blockquote class="markdown-alert-note"><p>Line 2.</p></blockquote>
|
||||
</blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_15() {
|
||||
let original = r##" * loose lists
|
||||
|
||||
> [!NOTE]
|
||||
> sink ships
|
||||
"##;
|
||||
let expected = r##"<ul><li><p>loose lists</p>
|
||||
<blockquote class="markdown-alert-note"><p>sink ships</p></blockquote>
|
||||
</li></ul>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_16() {
|
||||
let original = r##"> [!NOTE]
|
||||
sink ships
|
||||
|
||||
> [!NOTE]
|
||||
> sink ships
|
||||
|
||||
* loose lists
|
||||
|
||||
> [!NOTE]
|
||||
sink ships
|
||||
|
||||
> [!NOTE]
|
||||
> sink ships
|
||||
"##;
|
||||
let expected = r##"<blockquote class="markdown-alert-note"><p>sink ships</p></blockquote>
|
||||
<blockquote class="markdown-alert-note"><p>sink ships</p></blockquote>
|
||||
<ul><li><p>loose lists</p>
|
||||
<blockquote class="markdown-alert-note"><p>sink ships</p></blockquote>
|
||||
<blockquote class="markdown-alert-note"><p>sink ships</p></blockquote>
|
||||
</li></ul>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_17() {
|
||||
let original = r##" * loose lists
|
||||
|
||||
> [!NOTE]
|
||||
- sink ships
|
||||
|
||||
> [!NOTE]
|
||||
> - sink ships
|
||||
"##;
|
||||
let expected = r##"<ul><li><p>loose lists</p>
|
||||
<blockquote class="markdown-alert-note"></blockquote>
|
||||
<ul><li>sink ships</li></ul>
|
||||
<blockquote class="markdown-alert-note">
|
||||
<ul><li>sink ships</li></ul></blockquote>
|
||||
</li></ul>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockquotes_tags_test_18() {
|
||||
let original = r##"> [!Hello]
|
||||
> This should be a normal block quote.
|
||||
"##;
|
||||
let expected = r##"<blockquote>
|
||||
<p>[!Hello]
|
||||
This should be a normal block quote.</p>
|
||||
</blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
+686
@@ -0,0 +1,686 @@
|
||||
// This file is auto-generated by the build script
|
||||
// Please, do not modify it manually
|
||||
|
||||
use super::test_markdown_html;
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_1() {
|
||||
let original = r##"Lorem ipsum.[^a] [^missing]
|
||||
|
||||
[^a]: Cool.
|
||||
"##;
|
||||
let expected = r##"<p>Lorem ipsum.<sup class="footnote-reference"><a href="#a">1</a></sup> [^missing]</p>
|
||||
<div class="footnote-definition" id="a"><sup class="footnote-definition-label">1</sup>
|
||||
<p>Cool.</p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_2() {
|
||||
let original = r##"> This is the song that never ends.\
|
||||
> Yes it goes on and on my friends.[^lambchops]
|
||||
>
|
||||
> [^lambchops]: <https://www.youtube.com/watch?v=0U2zJOryHKQ>
|
||||
"##;
|
||||
let expected = r##"<blockquote>
|
||||
<p>This is the song that never ends.<br />
|
||||
Yes it goes on and on my friends.<sup class="footnote-reference"><a href="#lambchops">1</a></sup></p>
|
||||
<div class="footnote-definition" id="lambchops"><sup class="footnote-definition-label">1</sup>
|
||||
<p><a href="https://www.youtube.com/watch?v=0U2zJOryHKQ">https://www.youtube.com/watch?v=0U2zJOryHKQ</a></p>
|
||||
</div>
|
||||
</blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_3() {
|
||||
let original = r##"Songs that simply loop are a popular way to annoy people. [^examples]
|
||||
|
||||
[^examples]:
|
||||
* [The song that never ends](https://www.youtube.com/watch?v=0U2zJOryHKQ)
|
||||
* [I know a song that gets on everybody's nerves](https://www.youtube.com/watch?v=TehWI09qxls)
|
||||
* [Ninety-nine bottles of beer on the wall](https://www.youtube.com/watch?v=qVjCag8XoHQ)
|
||||
"##;
|
||||
let expected = r##"<p>Songs that simply loop are a popular way to annoy people. <sup class="footnote-reference"><a href="#examples">1</a></sup></p>
|
||||
<div class="footnote-definition" id="examples"><sup class="footnote-definition-label">1</sup></div>
|
||||
<ul>
|
||||
<li><a href="https://www.youtube.com/watch?v=0U2zJOryHKQ">The song that never ends</a></li>
|
||||
<li><a href="https://www.youtube.com/watch?v=TehWI09qxls">I know a song that gets on everybody's nerves</a></li>
|
||||
<li><a href="https://www.youtube.com/watch?v=qVjCag8XoHQ">Ninety-nine bottles of beer on the wall</a></li>
|
||||
</ul>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_4() {
|
||||
let original = r##"Songs that simply loop are a popular way to annoy people. [^examples]
|
||||
|
||||
[^examples]:
|
||||
* [The song that never ends](https://www.youtube.com/watch?v=0U2zJOryHKQ)
|
||||
* [I know a song that gets on everybody's nerves](https://www.youtube.com/watch?v=TehWI09qxls)
|
||||
* [Ninety-nine bottles of beer on the wall](https://www.youtube.com/watch?v=qVjCag8XoHQ)
|
||||
"##;
|
||||
let expected = r##"<p>Songs that simply loop are a popular way to annoy people. <sup class="footnote-reference"><a href="#examples">1</a></sup></p>
|
||||
<div class="footnote-definition" id="examples"><sup class="footnote-definition-label">1</sup>
|
||||
<ul>
|
||||
<li><a href="https://www.youtube.com/watch?v=0U2zJOryHKQ">The song that never ends</a></li>
|
||||
<li><a href="https://www.youtube.com/watch?v=TehWI09qxls">I know a song that gets on everybody's nerves</a></li>
|
||||
<li><a href="https://www.youtube.com/watch?v=qVjCag8XoHQ">Ninety-nine bottles of beer on the wall</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_5() {
|
||||
let original = r##"[^not-code] [^code] [^quote] [^not-quote] [^indented-quote]
|
||||
|
||||
[^not-code]: not code
|
||||
|
||||
[^code]:
|
||||
code
|
||||
|
||||
[^quote]: > quote
|
||||
|
||||
[^not-quote]:
|
||||
> external quote
|
||||
|
||||
[^indented-quote]:
|
||||
> indented quote
|
||||
"##;
|
||||
let expected = r##"<p><sup class="footnote-reference"><a href="#not-code">1</a></sup> <sup class="footnote-reference"><a href="#code">2</a></sup> <sup class="footnote-reference"><a href="#quote">3</a></sup> <sup class="footnote-reference"><a href="#not-quote">4</a></sup> <sup class="footnote-reference"><a href="#indented-quote">5</a></sup></p>
|
||||
<div class="footnote-definition" id="not-code"><sup class="footnote-definition-label">1</sup>
|
||||
<p>not code</p>
|
||||
</div>
|
||||
<div class="footnote-definition" id="code"><sup class="footnote-definition-label">2</sup>
|
||||
<pre><code>code
|
||||
</code></pre>
|
||||
</div>
|
||||
<div class="footnote-definition" id="quote"><sup class="footnote-definition-label">3</sup>
|
||||
<blockquote><p>quote</p></blockquote>
|
||||
</div>
|
||||
<div class="footnote-definition" id="not-quote"><sup class="footnote-definition-label">4</sup></div>
|
||||
<blockquote><p>external quote</p></blockquote><div class="footnote-definition" id="indented-quote"><sup class="footnote-definition-label">5</sup>
|
||||
<blockquote><p>indented quote</p></blockquote>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_6() {
|
||||
let original = r##"[^ab] [^cd]
|
||||
|
||||
[^ab]: a
|
||||
b
|
||||
|
||||
[^cd]: c\
|
||||
d
|
||||
"##;
|
||||
let expected = r##"<p><sup class="footnote-reference"><a href="#ab">1</a></sup> <sup class="footnote-reference"><a href="#cd">2</a></sup></p>
|
||||
<div class="footnote-definition" id="ab"><sup class="footnote-definition-label">1</sup>
|
||||
<p>a
|
||||
b</p>
|
||||
</div>
|
||||
<div class="footnote-definition" id="cd"><sup class="footnote-definition-label">2</sup>
|
||||
<p>c<br>
|
||||
d</p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_7() {
|
||||
let original = r##"[^lorem]: If heaven ever wishes to grant me a boon, it will be a total effacing of the results of a mere chance which fixed my eye on a certain stray piece of shelf-paper. It was nothing on which I would naturally have stumbled in the course of my daily round, for it was an old number of an Australian journal, the Sydney Bulletin for April 18, 1925. It had escaped even the cutting bureau which had at the time of its issuance been avidly collecting material for my uncle's research.
|
||||
|
||||
I had largely given over my inquiries into what Professor Angell called the "Cthulhu Cult", and was visiting a learned friend in Paterson, New Jersey; the curator of a local museum and a mineralogist of note. Examining one day the reserve specimens roughly set on the storage shelves in a rear room of the museum, my eye was caught by an odd picture in one of the old papers spread beneath the stones. It was the Sydney Bulletin I have mentioned, for my friend had wide affiliations in all conceivable foreign parts; and the picture was a half-tone cut of a hideous stone image almost identical with that which Legrasse had found in the swamp.
|
||||
|
||||
[^ipsum]: If heaven ever wishes to grant me a boon, it will be a total effacing of the results of a mere chance which fixed my eye on a certain stray piece of shelf-paper. It was nothing on which I would naturally have stumbled in the course of my daily round, for it was an old number of an Australian journal, the Sydney Bulletin for April 18, 1925. It had escaped even the cutting bureau which had at the time of its issuance been avidly collecting material for my uncle's research.
|
||||
|
||||
I had largely given over my inquiries into what Professor Angell called the "Cthulhu Cult", and was visiting a learned friend in Paterson, New Jersey; the curator of a local museum and a mineralogist of note. Examining one day the reserve specimens roughly set on the storage shelves in a rear room of the museum, my eye was caught by an odd picture in one of the old papers spread beneath the stones. It was the Sydney Bulletin I have mentioned, for my friend had wide affiliations in all conceivable foreign parts; and the picture was a half-tone cut of a hideous stone image almost identical with that which Legrasse had found in the swamp.
|
||||
"##;
|
||||
let expected = r##"<div class="footnote-definition" id="lorem"><sup class="footnote-definition-label">1</sup>
|
||||
<p>If heaven ever wishes to grant me a boon, it will be a total effacing of the results of a mere chance which fixed my eye on a certain stray piece of shelf-paper. It was nothing on which I would naturally have stumbled in the course of my daily round, for it was an old number of an Australian journal, the Sydney Bulletin for April 18, 1925. It had escaped even the cutting bureau which had at the time of its issuance been avidly collecting material for my uncle's research.</p>
|
||||
</div>
|
||||
<p>I had largely given over my inquiries into what Professor Angell called the "Cthulhu Cult", and was visiting a learned friend in Paterson, New Jersey; the curator of a local museum and a mineralogist of note. Examining one day the reserve specimens roughly set on the storage shelves in a rear room of the museum, my eye was caught by an odd picture in one of the old papers spread beneath the stones. It was the Sydney Bulletin I have mentioned, for my friend had wide affiliations in all conceivable foreign parts; and the picture was a half-tone cut of a hideous stone image almost identical with that which Legrasse had found in the swamp.</p>
|
||||
<div class="footnote-definition" id="ipsum"><sup class="footnote-definition-label">2</sup>
|
||||
<p>If heaven ever wishes to grant me a boon, it will be a total effacing of the results of a mere chance which fixed my eye on a certain stray piece of shelf-paper. It was nothing on which I would naturally have stumbled in the course of my daily round, for it was an old number of an Australian journal, the Sydney Bulletin for April 18, 1925. It had escaped even the cutting bureau which had at the time of its issuance been avidly collecting material for my uncle's research.</p>
|
||||
<p>I had largely given over my inquiries into what Professor Angell called the "Cthulhu Cult", and was visiting a learned friend in Paterson, New Jersey; the curator of a local museum and a mineralogist of note. Examining one day the reserve specimens roughly set on the storage shelves in a rear room of the museum, my eye was caught by an odd picture in one of the old papers spread beneath the stones. It was the Sydney Bulletin I have mentioned, for my friend had wide affiliations in all conceivable foreign parts; and the picture was a half-tone cut of a hideous stone image almost identical with that which Legrasse had found in the swamp.</p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_8() {
|
||||
let original = r##"[^ipsum]: How much wood would a woodchuck chuck.
|
||||
|
||||
If a woodchuck could chuck wood.
|
||||
|
||||
|
||||
# Forms of entertainment that aren't childish
|
||||
"##;
|
||||
let expected = r##"<div class="footnote-definition" id="ipsum"><sup class="footnote-definition-label">1</sup>
|
||||
<p>How much wood would a woodchuck chuck.</p>
|
||||
</div>
|
||||
<p>If a woodchuck could chuck wood.</p>
|
||||
<h1>Forms of entertainment that aren't childish</h1>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_9() {
|
||||
let original = r##"Footnotes [^one] [^many].
|
||||
|
||||
[^one]:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
first paragraph inside footnote
|
||||
|
||||
[^many]: first paragraph inside footnote
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
second paragraph still inside footnote
|
||||
"##;
|
||||
let expected = r##"<p>Footnotes <sup class="footnote-reference"><a href="#one">1</a></sup> <sup class="footnote-reference"><a href="#many">2</a></sup>.</p>
|
||||
<div class="footnote-definition" id="one"><sup class="footnote-definition-label">1</sup>
|
||||
<p>first paragraph inside footnote</p>
|
||||
</div>
|
||||
<div class="footnote-definition" id="many"><sup class="footnote-definition-label">2</sup>
|
||||
<p>first paragraph inside footnote</p>
|
||||
<p>second paragraph still inside footnote</p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_10() {
|
||||
let original = r##"> He's also really stupid. [^why]
|
||||
>
|
||||
> [^why]: Because your mamma!
|
||||
|
||||
As such, we can guarantee that the non-childish forms of entertainment are probably more entertaining to adults, since, having had a whole childhood doing the childish ones, the non-childish ones are merely the ones that haven't gotten boring yet.
|
||||
"##;
|
||||
let expected = r##"<blockquote>
|
||||
<p>He's also really stupid. <sup class="footnote-reference"><a href="#why">1</a></sup></p>
|
||||
<div class="footnote-definition" id="why"><sup class="footnote-definition-label">1</sup>
|
||||
<p>Because your mamma!</p>
|
||||
</div>
|
||||
</blockquote>
|
||||
<p>As such, we can guarantee that the non-childish forms of entertainment are probably more entertaining to adults, since, having had a whole childhood doing the childish ones, the non-childish ones are merely the ones that haven't gotten boring yet.</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_11() {
|
||||
let original = r##"Nested footnotes are considered poor style. [^a] [^xkcd] [^indent1] [^indent2]
|
||||
|
||||
[^a]: This does not mean that footnotes cannot reference each other. [^b]
|
||||
|
||||
[^b]: This means that a footnote definition cannot be directly inside another footnote definition.
|
||||
> This means that a footnote cannot be directly inside another footnote's body. [^e]
|
||||
>
|
||||
> [^e]: They can, however, be inside anything else.
|
||||
|
||||
[^xkcd]: [The other kind of nested footnote is, however, considered poor style.](https://xkcd.com/1208/)
|
||||
|
||||
[^indent1]: indent1
|
||||
|
||||
[^indent2]: indent2
|
||||
"##;
|
||||
let expected = r##"<p>Nested footnotes are considered poor style. <sup class="footnote-reference"><a href="#a">1</a></sup> <sup class="footnote-reference"><a href="#xkcd">2</a></sup> <sup class="footnote-reference"><a href="#indent1">3</a></sup> <sup class="footnote-reference"><a href="#indent2">4</a></sup></p>
|
||||
<div class="footnote-definition" id="a"><sup class="footnote-definition-label">1</sup>
|
||||
<p>This does not mean that footnotes cannot reference each other. <sup class="footnote-reference"><a href="#b">5</a></sup></p>
|
||||
</div>
|
||||
<div class="footnote-definition" id="b"><sup class="footnote-definition-label">5</sup>
|
||||
<p>This means that a footnote definition cannot be directly inside another footnote definition.</p>
|
||||
</div>
|
||||
<blockquote>
|
||||
<p>This means that a footnote cannot be directly inside another footnote's body. <sup class="footnote-reference"><a href="#e">6</a></sup></p>
|
||||
<div class="footnote-definition" id="e"><sup class="footnote-definition-label">6</sup>
|
||||
<p>They can, however, be inside anything else.</p>
|
||||
</div>
|
||||
</blockquote>
|
||||
<div class="footnote-definition" id="xkcd"><sup class="footnote-definition-label">2</sup>
|
||||
<p><a href="https://xkcd.com/1208/">The other kind of nested footnote is, however, considered poor style.</a></p>
|
||||
</div>
|
||||
<div class="footnote-definition" id="indent1"><sup class="footnote-definition-label">3</sup>
|
||||
<p>indent1</p>
|
||||
</div>
|
||||
<div class="footnote-definition" id="indent2"><sup class="footnote-definition-label">4</sup>
|
||||
<p>indent2</p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_12() {
|
||||
let original = r##"[^foo] [^bar]
|
||||
|
||||
[^foo]: [^bar]: 1
|
||||
"##;
|
||||
let expected = r##"<p><sup class="footnote-reference"><a href="#foo">1</a></sup> <sup class="footnote-reference"><a href="#bar">2</a></sup></p>
|
||||
<div class="footnote-definition" id="foo"><sup class="footnote-definition-label">1</sup></div>
|
||||
<div class="footnote-definition" id="bar"><sup class="footnote-definition-label">2</sup>
|
||||
<p>1</p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_13() {
|
||||
let original = r##"[^Doh] Ray Me Fa So La Te Do! [^1]
|
||||
|
||||
[^Doh]: I know. Wrong Doe. And it won't render right.
|
||||
[^1]: Common for people practicing music.
|
||||
"##;
|
||||
let expected = r##"<p><sup class="footnote-reference"><a href="#Doh">1</a></sup> Ray Me Fa So La Te Do! <sup class="footnote-reference"><a href="#1">2</a></sup></p>
|
||||
<div class="footnote-definition" id="Doh"><sup class="footnote-definition-label">1</sup>
|
||||
<p>I know. Wrong Doe. And it won't render right.</p>
|
||||
</div>
|
||||
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">2</sup>
|
||||
<p>Common for people practicing music.</p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_14() {
|
||||
let original = r##"Lorem ipsum.[^a]
|
||||
|
||||
An unordered list before the footnotes:
|
||||
* Ipsum
|
||||
* Lorem
|
||||
|
||||
[^a]: Cool.
|
||||
"##;
|
||||
let expected = r##"<p>Lorem ipsum.<sup class="footnote-reference"><a href="#a">1</a></sup></p>
|
||||
<p>An unordered list before the footnotes:</p>
|
||||
<ul>
|
||||
<li>Ipsum</li>
|
||||
<li>Lorem</li>
|
||||
</ul>
|
||||
<div class="footnote-definition" id="a"><sup class="footnote-definition-label">1</sup>
|
||||
<p>Cool.</p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_15() {
|
||||
let original = r##"Songs that simply loop are a popular way to annoy people. [^examples]
|
||||
|
||||
[^examples]: * [The song that never ends](https://www.youtube.com/watch?v=0U2zJOryHKQ)
|
||||
* [I know a song that gets on everybody's nerves](https://www.youtube.com/watch?v=TehWI09qxls)
|
||||
* [Ninety-nine bottles of beer on the wall](https://www.youtube.com/watch?v=qVjCag8XoHQ)
|
||||
|
||||
|
||||
Songs that simply loop are a popular way to annoy people. [^examples2]
|
||||
|
||||
[^examples2]: * [The song that never ends](https://www.youtube.com/watch?v=0U2zJOryHKQ) 2
|
||||
* [I know a song that gets on everybody's nerves](https://www.youtube.com/watch?v=TehWI09qxls) 2
|
||||
- [Ninety-nine bottles of beer on the wall](https://www.youtube.com/watch?v=qVjCag8XoHQ) 2
|
||||
|
||||
|
||||
Songs that simply loop are a popular way to annoy people. [^examples3]
|
||||
|
||||
[^examples3]: * [The song that never ends](https://www.youtube.com/watch?v=0U2zJOryHKQ) 3
|
||||
|
||||
* [I know a song that gets on everybody's nerves](https://www.youtube.com/watch?v=TehWI09qxls) 3
|
||||
|
||||
* [Ninety-nine bottles of beer on the wall](https://www.youtube.com/watch?v=qVjCag8XoHQ) 3
|
||||
"##;
|
||||
let expected = r##"<p>Songs that simply loop are a popular way to annoy people. <sup class="footnote-reference"><a href="#examples">1</a></sup></p>
|
||||
<div class="footnote-definition" id="examples"><sup class="footnote-definition-label">1</sup>
|
||||
<ul>
|
||||
<li><a href="https://www.youtube.com/watch?v=0U2zJOryHKQ">The song that never ends</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul>
|
||||
<li><a href="https://www.youtube.com/watch?v=TehWI09qxls">I know a song that gets on everybody's nerves</a></li>
|
||||
<li><a href="https://www.youtube.com/watch?v=qVjCag8XoHQ">Ninety-nine bottles of beer on the wall</a></li>
|
||||
</ul>
|
||||
<p>Songs that simply loop are a popular way to annoy people. <sup class="footnote-reference"><a href="#examples2">2</a></sup></p>
|
||||
<div class="footnote-definition" id="examples2"><sup class="footnote-definition-label">2</sup>
|
||||
<ul>
|
||||
<li><a href="https://www.youtube.com/watch?v=0U2zJOryHKQ">The song that never ends</a> 2</li>
|
||||
<li><a href="https://www.youtube.com/watch?v=TehWI09qxls">I know a song that gets on everybody's nerves</a> 2</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="https://www.youtube.com/watch?v=qVjCag8XoHQ">Ninety-nine bottles of beer on the wall</a> 2</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>Songs that simply loop are a popular way to annoy people. <sup class="footnote-reference"><a href="#examples3">3</a></sup></p>
|
||||
<div class="footnote-definition" id="examples3"><sup class="footnote-definition-label">3</sup>
|
||||
<ul>
|
||||
<li><p><a href="https://www.youtube.com/watch?v=0U2zJOryHKQ">The song that never ends</a> 3</p>
|
||||
</li><li><p><a href="https://www.youtube.com/watch?v=TehWI09qxls">I know a song that gets on everybody's nerves</a> 3</p>
|
||||
</li><li><p><a href="https://www.youtube.com/watch?v=qVjCag8XoHQ">Ninety-nine bottles of beer on the wall</a> 3</p></li>
|
||||
</ul>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_16() {
|
||||
let original = r##"My [cmark-gfm][^c].
|
||||
|
||||
My [cmark-gfm][cmark-gfm][^c].
|
||||
|
||||
My [cmark-gfm][][^c].
|
||||
|
||||
My [cmark-gfm] [^c].
|
||||
|
||||
My [cmark-gfm[^c]].
|
||||
|
||||
[cmark-gfm]: https://github.com/github/cmark-gfm/blob/1e230827a584ebc9938c3eadc5059c55ef3c9abf/test/extensions.txt#L702
|
||||
|
||||
[^c]: cmark-gfm is under the MIT license, so incorporating parts of its
|
||||
test suite into pulldown-cmark should be fine.
|
||||
|
||||
|
||||
My [otherlink[^c]].
|
||||
|
||||
[otherlink[^c]]: https://github.com/github/cmark-gfm/blob/1e230827a584ebc9938c3eadc5059c55ef3c9abf/test/extensions.txt#L702
|
||||
"##;
|
||||
let expected = r##"<p>My <a href="https://github.com/github/cmark-gfm/blob/1e230827a584ebc9938c3eadc5059c55ef3c9abf/test/extensions.txt#L702">cmark-gfm</a><sup class="footnote-reference"><a href="#c">1</a></sup>.</p>
|
||||
<p>My <a href="https://github.com/github/cmark-gfm/blob/1e230827a584ebc9938c3eadc5059c55ef3c9abf/test/extensions.txt#L702">cmark-gfm</a><sup class="footnote-reference"><a href="#c">1</a></sup>.</p>
|
||||
<p>My <a href="https://github.com/github/cmark-gfm/blob/1e230827a584ebc9938c3eadc5059c55ef3c9abf/test/extensions.txt#L702">cmark-gfm</a><sup class="footnote-reference"><a href="#c">1</a></sup>.</p>
|
||||
<p>My <a href="https://github.com/github/cmark-gfm/blob/1e230827a584ebc9938c3eadc5059c55ef3c9abf/test/extensions.txt#L702">cmark-gfm</a> <sup class="footnote-reference"><a href="#c">1</a></sup>.</p>
|
||||
<p>My [cmark-gfm<sup class="footnote-reference"><a href="#c">1</a></sup>].</p>
|
||||
<div class="footnote-definition" id="c"><sup class="footnote-definition-label">1</sup>
|
||||
<p>cmark-gfm is under the MIT license, so incorporating parts of its
|
||||
test suite into pulldown-cmark should be fine.</p>
|
||||
</div>
|
||||
<p>My [otherlink<sup class="footnote-reference"><a href="#c">1</a></sup>].</p>
|
||||
<p>[otherlink<sup class="footnote-reference"><a href="#c">1</a></sup>]: https://github.com/github/cmark-gfm/blob/1e230827a584ebc9938c3eadc5059c55ef3c9abf/test/extensions.txt#L702</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_17() {
|
||||
let original = r##"[^1]: footnote definition text
|
||||
|
||||
<!-- -->
|
||||
|
||||
// indented code block
|
||||
fn main() {
|
||||
println!("hello world!");
|
||||
}
|
||||
"##;
|
||||
let expected = r##"<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
|
||||
<p>footnote definition text</p>
|
||||
</div>
|
||||
<!-- -->
|
||||
<pre><code>// indented code block
|
||||
fn main() {
|
||||
println!("hello world!");
|
||||
}
|
||||
</code></pre>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_18() {
|
||||
let original = r##"[^1]: footnote definition text
|
||||
[^1]\: this is a reference, rather than a definition
|
||||
"##;
|
||||
let expected = r##"<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
|
||||
<p>footnote definition text
|
||||
<sup class="footnote-reference"><a href="#1">1</a></sup>: this is a reference, rather than a definition</p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_19() {
|
||||
let original = r##"[^1]:
|
||||
|
||||
| column1 | column2 |
|
||||
|---------|---------|
|
||||
| row1a | row1b |
|
||||
| row2a | row2b |
|
||||
"##;
|
||||
let expected = r##"<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup><table>
|
||||
<thead>
|
||||
<tr><th>column1</th><th>column2</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>row1a</td><td>row1b</td></tr>
|
||||
<tr><td>row2a</td><td>row2b</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_20() {
|
||||
let original = r##"* First
|
||||
[^1]: test
|
||||
* Second [^1] test
|
||||
|
||||
|
||||
> first
|
||||
> [^2]: test
|
||||
> Second [^2] test
|
||||
|
||||
|
||||
First | Second
|
||||
-----------|----------
|
||||
first | second
|
||||
[^3]: test | test [^3]
|
||||
|
||||
|
||||
| First | Second |
|
||||
|------------|-----------|
|
||||
| first | second |
|
||||
| [^4]: test | test [^4] |
|
||||
|
||||
> [^5]: * test [^5]
|
||||
"##;
|
||||
let expected = r##"<ul>
|
||||
<li>First
|
||||
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
|
||||
<p>test</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>Second <sup class="footnote-reference"><a href="#1">1</a></sup> test</li>
|
||||
</ul>
|
||||
<blockquote>
|
||||
<p>first</p>
|
||||
<div class="footnote-definition" id="2"><sup class="footnote-definition-label">2</sup>
|
||||
<p>test
|
||||
Second <sup class="footnote-reference"><a href="#2">2</a></sup> test</p>
|
||||
</div>
|
||||
</blockquote>
|
||||
<table><thead><tr><th>First</th><th>Second</th></tr></thead><tbody>
|
||||
<tr><td>first</td><td>second</td></tr>
|
||||
</tbody></table>
|
||||
<div class="footnote-definition" id="3"><sup class="footnote-definition-label">3</sup>
|
||||
<p>test | test <sup class="footnote-reference"><a href="#3">3</a></sup></p>
|
||||
</div>
|
||||
<table><thead><tr><th>First</th><th>Second</th></tr></thead><tbody>
|
||||
<tr><td>first</td><td>second</td></tr>
|
||||
<tr><td>[^4]: test</td><td>test [^4]</td></tr>
|
||||
</tbody></table>
|
||||
<blockquote>
|
||||
<div class="footnote-definition" id="5"><sup class="footnote-definition-label">4</sup>
|
||||
<ul>
|
||||
<li>test <sup class="footnote-reference"><a href="#5">4</a></sup></li>
|
||||
</ul>
|
||||
</div>
|
||||
</blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_21() {
|
||||
let original = r##"Test [^] link
|
||||
|
||||
[^]: https://rust-lang.org
|
||||
"##;
|
||||
let expected = r##"<p>Test <a href="https://rust-lang.org">^</a> link</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_22() {
|
||||
let original = r##"[^foo\
|
||||
bar]: not a footnote definition
|
||||
|
||||
[baz\
|
||||
quux]: https://rust-lang.org
|
||||
|
||||
[first
|
||||
second]: https://rust-lang.org
|
||||
|
||||
[^third
|
||||
fourth]: not a footnote definition
|
||||
|
||||
[baz\
|
||||
quux]
|
||||
[^foo\
|
||||
bar]
|
||||
[first
|
||||
second]
|
||||
[^third
|
||||
fourth]
|
||||
"##;
|
||||
let expected = r##"<p>[^foo<br>
|
||||
bar]: not a footnote definition</p>
|
||||
<p>[^third
|
||||
fourth]: not a footnote definition</p>
|
||||
<p><a href="https://rust-lang.org">baz<br>
|
||||
quux</a>
|
||||
[^foo<br>
|
||||
bar]
|
||||
<a href="https://rust-lang.org">first
|
||||
second</a>
|
||||
[^third
|
||||
fourth]</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_23() {
|
||||
let original = r##"[^foo
|
||||
]: https://rust-lang.org
|
||||
|
||||
[^foo
|
||||
]
|
||||
"##;
|
||||
let expected = r##"<p><a href="https://rust-lang.org">^foo
|
||||
</a></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_24() {
|
||||
let original = r##"footnote [^baz]
|
||||
footnote [^quux]
|
||||
|
||||
[^quux]: x
|
||||
|
||||
[^baz]: x
|
||||
"##;
|
||||
let expected = r##"<p>footnote <sup class="footnote-reference"><a href="#baz">1</a></sup>
|
||||
footnote [^quux]</p>
|
||||
<pre><code>[^quux]: x
|
||||
</code></pre>
|
||||
<div class="footnote-definition" id="baz"><sup class="footnote-definition-label">1</sup>
|
||||
<p>x</p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_25() {
|
||||
let original = r##"Lorem ipsum.[^a][^b]
|
||||
|
||||
[^a]: Foo
|
||||
[^b]: Bar
|
||||
"##;
|
||||
let expected = r##"<p>Lorem ipsum.<sup class="footnote-reference"><a href="#a">1</a></sup><sup class="footnote-reference"><a href="#b">2</a></sup></p>
|
||||
<div class="footnote-definition" id="a"><sup class="footnote-definition-label">1</sup>
|
||||
<p>Foo</p>
|
||||
</div>
|
||||
<div class="footnote-definition" id="b"><sup class="footnote-definition-label">2</sup>
|
||||
<p>Bar</p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footnotes_test_26() {
|
||||
let original = r##"Lorem ipsum.[^a] [^b]
|
||||
|
||||
[^a]: Foo
|
||||
[^b]: Bar
|
||||
"##;
|
||||
let expected = r##"<p>Lorem ipsum.<sup class="footnote-reference"><a href="#a">1</a></sup> <sup class="footnote-reference"><a href="#b">2</a></sup></p>
|
||||
<div class="footnote-definition" id="a"><sup class="footnote-definition-label">1</sup>
|
||||
<p>Foo</p>
|
||||
</div>
|
||||
<div class="footnote-definition" id="b"><sup class="footnote-definition-label">2</sup>
|
||||
<p>Bar</p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// This file is auto-generated by the build script
|
||||
// Please, do not modify it manually
|
||||
|
||||
use super::test_markdown_html;
|
||||
|
||||
#[test]
|
||||
fn gfm_strikethrough_test_1() {
|
||||
let original = r##"~~Hi~~ Hello, ~there~ world!
|
||||
"##;
|
||||
let expected = r##"<p><del>Hi</del> Hello, <del>there</del> world!</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gfm_strikethrough_test_2() {
|
||||
let original = r##"This ~~has a
|
||||
|
||||
new paragraph~~.
|
||||
"##;
|
||||
let expected = r##"<p>This ~~has a</p>
|
||||
<p>new paragraph~~.</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gfm_strikethrough_test_3() {
|
||||
let original = r##"This will ~~~not~~~ strike.
|
||||
"##;
|
||||
let expected = r##"<p>This will ~~~not~~~ strike.</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
// This file is auto-generated by the build script
|
||||
// Please, do not modify it manually
|
||||
|
||||
use super::test_markdown_html;
|
||||
|
||||
#[test]
|
||||
fn gfm_table_test_1() {
|
||||
let original = r##"| foo | bar |
|
||||
| --- | --- |
|
||||
| baz | bim |
|
||||
"##;
|
||||
let expected = r##"<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>foo</th>
|
||||
<th>bar</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>baz</td>
|
||||
<td>bim</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gfm_table_test_2() {
|
||||
let original = r##"| abc | defghi |
|
||||
:-: | -----------:
|
||||
bar | baz
|
||||
"##;
|
||||
let expected = r##"<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align: center">abc</th>
|
||||
<th style="text-align: right">defghi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="text-align: center">bar</td>
|
||||
<td style="text-align: right">baz</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gfm_table_test_3() {
|
||||
let original = r##"| f\|oo |
|
||||
| ------ |
|
||||
| b `\|` az |
|
||||
| b **\|** im |
|
||||
"##;
|
||||
let expected = r##"<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>f|oo</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>b <code>|</code> az</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>b <strong>|</strong> im</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gfm_table_test_4() {
|
||||
let original = r##"| abc | def |
|
||||
| --- | --- |
|
||||
| bar | baz |
|
||||
> bar
|
||||
"##;
|
||||
let expected = r##"<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>abc</th>
|
||||
<th>def</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>bar</td>
|
||||
<td>baz</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<blockquote>
|
||||
<p>bar</p>
|
||||
</blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gfm_table_test_5() {
|
||||
let original = r##"| abc | def |
|
||||
| --- | --- |
|
||||
| bar | baz |
|
||||
bar
|
||||
|
||||
bar
|
||||
"##;
|
||||
let expected = r##"<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>abc</th>
|
||||
<th>def</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>bar</td>
|
||||
<td>baz</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>bar</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>bar</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gfm_table_test_6() {
|
||||
let original = r##"| abc | def |
|
||||
| --- |
|
||||
| bar |
|
||||
"##;
|
||||
let expected = r##"<p>| abc | def |
|
||||
| --- |
|
||||
| bar |</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gfm_table_test_7() {
|
||||
let original = r##"| abc | def |
|
||||
| --- | --- |
|
||||
| bar |
|
||||
| bar | baz | boo |
|
||||
"##;
|
||||
let expected = r##"<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>abc</th>
|
||||
<th>def</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>bar</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>bar</td>
|
||||
<td>baz</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gfm_table_test_8() {
|
||||
let original = r##"| abc | def |
|
||||
| --- | --- |
|
||||
"##;
|
||||
let expected = r##"<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>abc</th>
|
||||
<th>def</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gfm_table_test_9() {
|
||||
let original = r##"Hello World
|
||||
| abc | def |
|
||||
| --- | --- |
|
||||
| bar | baz |
|
||||
"##;
|
||||
let expected = r##"<p>Hello World</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>abc</th>
|
||||
<th>def</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>bar</td>
|
||||
<td>baz</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// This file is auto-generated by the build script
|
||||
// Please, do not modify it manually
|
||||
|
||||
use super::test_markdown_html;
|
||||
|
||||
#[test]
|
||||
fn gfm_tasklist_test_1() {
|
||||
let original = r##"- [ ] foo
|
||||
- [x] bar
|
||||
"##;
|
||||
let expected = r##"<ul>
|
||||
<li><input disabled="" type="checkbox"/>
|
||||
foo</li>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
bar</li>
|
||||
</ul>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gfm_tasklist_test_2() {
|
||||
let original = r##"- [x] foo
|
||||
- [ ] bar
|
||||
- [x] baz
|
||||
- [ ] bim
|
||||
"##;
|
||||
let expected = r##"<ul>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
foo
|
||||
<ul>
|
||||
<li><input disabled="" type="checkbox"/>
|
||||
bar</li>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
baz</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><input disabled="" type="checkbox"/>
|
||||
bim</li>
|
||||
</ul>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
+596
@@ -0,0 +1,596 @@
|
||||
// This file is auto-generated by the build script
|
||||
// Please, do not modify it manually
|
||||
|
||||
use super::test_markdown_html;
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_1() {
|
||||
let original = r##"with the ID {#myh1}
|
||||
===================
|
||||
with a class {.myclass}
|
||||
------------
|
||||
with a custom attribute {myattr=myvalue}
|
||||
========================================
|
||||
multiple! {.myclass1 myattr #myh3 otherattr=value .myclass2}
|
||||
--
|
||||
"##;
|
||||
let expected = r##"<h1 id="myh1">with the ID</h1>
|
||||
<h2 class="myclass">with a class</h2>
|
||||
<h1 myattr="myvalue">with a custom attribute</h1>
|
||||
<h2 id="myh3" class="myclass1 myclass2" myattr="" otherattr="value">multiple!</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_2() {
|
||||
let original = r##"# with the ID {#myh1}
|
||||
## with a class {.myclass}
|
||||
#### with a custom attribute {myattr=myvalue}
|
||||
### multiple! {.myclass1 myattr #myh3 otherattr=value .myclass2}
|
||||
"##;
|
||||
let expected = r##"<h1 id="myh1">with the ID</h1>
|
||||
<h2 class="myclass">with a class</h2>
|
||||
<h4 myattr="myvalue">with a custom attribute</h4>
|
||||
<h3 id="myh3" class="myclass1 myclass2" myattr="" otherattr="value">multiple!</h3>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_3() {
|
||||
let original = r##"# H1 # {#id1}
|
||||
## H2 ## with ## multiple ## hashes ## {#id2}
|
||||
### with trailing hash # ### {#id3}
|
||||
|
||||
#### non-attribute-block {#id4} ####
|
||||
"##;
|
||||
let expected = r##"<h1 id="id1">H1</h1>
|
||||
<h2 id="id2">H2 ## with ## multiple ## hashes</h2>
|
||||
<h3 id="id3">with trailing hash #</h3>
|
||||
<h4>non-attribute-block {#id4}</h4>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_4() {
|
||||
let original = r##"# spaces {#myid1}
|
||||
## tabs {#myid2}
|
||||
"##;
|
||||
let expected = r##"<h1 id="myid1">spaces</h1>
|
||||
<h2 id="myid2">tabs</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_5() {
|
||||
let original = r##"# H1 \
|
||||
nextline
|
||||
"##;
|
||||
let expected = r##"<h1>H1 \</h1>
|
||||
<p>nextline</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_6() {
|
||||
let original = r##"# H1 \
|
||||
{#myid}
|
||||
|
||||
## H2 \
|
||||
nextline {.class}
|
||||
|
||||
### H3 [link
|
||||
](https://example.com/) {#myid3}
|
||||
"##;
|
||||
let expected = r##"<h1>H1 \</h1>
|
||||
<p>{#myid}</p>
|
||||
<h2>H2 \</h2>
|
||||
<p>nextline {.class}</p>
|
||||
<h3>H3 [link</h3>
|
||||
<p>](https://example.com/) {#myid3}</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_7() {
|
||||
let original = r##"H1
|
||||
cont
|
||||
{#myid}
|
||||
==
|
||||
"##;
|
||||
let expected = r##"<h1 id="myid">H1
|
||||
cont
|
||||
</h1>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_8() {
|
||||
let original = r##"H1
|
||||
{
|
||||
.class1
|
||||
.class2
|
||||
}
|
||||
==
|
||||
"##;
|
||||
let expected = r##"<h1>H1
|
||||
{
|
||||
.class1
|
||||
.class2
|
||||
}</h1>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_9() {
|
||||
let original = r##"# without space, not recommended{#id1}
|
||||
## recommended style with spaces {#id2}
|
||||
"##;
|
||||
let expected = r##"<h1 id="id1">without space, not recommended</h1>
|
||||
<h2 id="id2">recommended style with spaces</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_10() {
|
||||
let original = r##"# H1 { #id1 }
|
||||
## H2 {.myclass #id2 }
|
||||
### H3 { .myclass}
|
||||
"##;
|
||||
let expected = r##"<h1 id="id1">H1</h1>
|
||||
<h2 id="id2" class="myclass">H2</h2>
|
||||
<h3 class="myclass">H3</h3>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_11() {
|
||||
let original = r##"# H1 {#id1.class1.class2 .class3}
|
||||
## H2 {.class1#id2.class2}
|
||||
"##;
|
||||
let expected = r##"<h1 id="id1.class1.class2" class="class3">H1</h1>
|
||||
<h2 class="class1#id2.class2">H2</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_12() {
|
||||
let original = r##"# H1 { #id1
|
||||
## H2 {#id2
|
||||
"##;
|
||||
let expected = r##"<h1>H1 { #id1</h1>
|
||||
<h2>H2 {#id2</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_13() {
|
||||
let original = r##"# H1 #id1 }
|
||||
## H2 #id2}
|
||||
"##;
|
||||
let expected = r##"<h1>H1 #id1 }</h1>
|
||||
<h2>H2 #id2}</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_14() {
|
||||
let original = r##"# H1 { #id1 } foo
|
||||
## H2 {#id2} <!-- hello -->
|
||||
"##;
|
||||
let expected = r##"<h1>H1 { #id1 } foo</h1>
|
||||
<h2>H2 {#id2} <!-- hello --></h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_15() {
|
||||
let original = r##"# *H1* { #id1 }
|
||||
## **H2** {#id2}
|
||||
### _H3_ {#id3}
|
||||
#### ~~H4~~ {#id4}
|
||||
##### [text](uri) {#id5}
|
||||
"##;
|
||||
let expected = r##"<h1 id="id1"><em>H1</em></h1>
|
||||
<h2 id="id2"><strong>H2</strong></h2>
|
||||
<h3 id="id3"><em>H3</em></h3>
|
||||
<h4 id="id4"><del>H4</del></h4>
|
||||
<h5 id="id5"><a href="uri">text</a></h5>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_16() {
|
||||
let original = r##"# H1 {#first #second #last}
|
||||
"##;
|
||||
let expected = r##"<h1 id="last">H1</h1>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_17() {
|
||||
let original = r##"# H1 {.z .a .zz}
|
||||
"##;
|
||||
let expected = r##"<h1 class="z a zz">H1</h1>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_18() {
|
||||
let original = r##"# H1 {.a .a .a}
|
||||
"##;
|
||||
let expected = r##"<h1 class="a a a">H1</h1>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_19() {
|
||||
let original = r##"# H1 {.myclass #myid}
|
||||
## H2 {.z #m .a}
|
||||
"##;
|
||||
let expected = r##"<h1 id="myid" class="myclass">H1</h1>
|
||||
<h2 id="m" class="z a">H2</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_20() {
|
||||
let original = r##"# H1 {foo}
|
||||
## H2 {#myid unknown this#is.ignored attr=value .myclass}
|
||||
"##;
|
||||
let expected = r##"<h1 foo="">H1</h1>
|
||||
<h2 id="myid" class="myclass" unknown="" this#is.ignored="" attr="value">H2</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_21() {
|
||||
let original = r##"# Header # {myattr=value other_attr}
|
||||
"##;
|
||||
let expected = r##"<h1 myattr="value" other_attr="">Header</h1>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_22() {
|
||||
let original = r##"#### Header {#id myattr= .class1 other_attr=false}
|
||||
"##;
|
||||
let expected = r##"<h4 id="id" class="class1" myattr="" other_attr="false">Header</h4>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_23() {
|
||||
let original = r##"# H1 {.foo{unknown}
|
||||
## H2 {.foo{.bar}
|
||||
"##;
|
||||
let expected = r##"<h1 unknown="">H1 {.foo</h1>
|
||||
<h2 class="bar">H2 {.foo</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_24() {
|
||||
let original = r##"# H1 {.foo}bar}
|
||||
"##;
|
||||
let expected = r##"<h1>H1 {.foo}bar}</h1>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_25() {
|
||||
let original = r##"# H1 {<i>foo</i>}
|
||||
"##;
|
||||
let expected = r##"<h1>H1 {<i>foo</i>}</h1>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_26() {
|
||||
let original = r##"# H1 {.foo\}
|
||||
"##;
|
||||
let expected = r##"<h1>H1 {.foo}</h1>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_27() {
|
||||
let original = r##"H1 {.foo
|
||||
.bar}
|
||||
==
|
||||
"##;
|
||||
let expected = r##"<h1>H1 {.foo
|
||||
.bar}</h1>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_28() {
|
||||
let original = r##"H1 {} {}
|
||||
=====
|
||||
|
||||
## H2 {} {}
|
||||
"##;
|
||||
let expected = r##"<h1>H1 {}</h1>
|
||||
<h2>H2 {}</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_29() {
|
||||
let original = r##"## H2 {} ##
|
||||
"##;
|
||||
let expected = r##"<h2>H2 {}</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_30() {
|
||||
let original = r##"# H1 {\}
|
||||
## this is also ok \{\}
|
||||
|
||||
newline can be used for setext heading {
|
||||
}
|
||||
--
|
||||
"##;
|
||||
let expected = r##"<h1>H1 {}</h1>
|
||||
<h2>this is also ok {}</h2>
|
||||
<h2>newline can be used for setext heading {
|
||||
}</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_31() {
|
||||
let original = r##"# H1 \{.foo}
|
||||
## H2 \\{.bar}
|
||||
### stray backslash at the end is preserved \
|
||||
"##;
|
||||
let expected = r##"<h1 class="foo">H1 \</h1>
|
||||
<h2 class="bar">H2 \</h2>
|
||||
<h3>stray backslash at the end is preserved \</h3>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_32() {
|
||||
let original = r##"H1 \{.foo}
|
||||
==
|
||||
H2 \\{.bar}
|
||||
--
|
||||
|
||||
stray backslash at the end is preserved \
|
||||
--
|
||||
"##;
|
||||
let expected = r##"<h1 class="foo">H1 \</h1>
|
||||
<h2 class="bar">H2 \</h2>
|
||||
<h2>stray backslash at the end is preserved \</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_33() {
|
||||
let original = r##"# H1 {#`code`}
|
||||
## H2 {#foo__bar__baz}
|
||||
### H3 {#foo**bar**baz}
|
||||
"##;
|
||||
let expected = r##"<h1 id="`code`">H1</h1>
|
||||
<h2 id="foo__bar__baz">H2</h2>
|
||||
<h3 id="foo**bar**baz">H3</h3>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_34() {
|
||||
let original = r##"H1 {#`code`}
|
||||
==
|
||||
|
||||
H2-1 {#foo__bar__baz}
|
||||
----
|
||||
|
||||
H2-2 {#foo**bar**baz}
|
||||
--
|
||||
"##;
|
||||
let expected = r##"<h1 id="`code`">H1</h1>
|
||||
<h2 id="foo__bar__baz">H2-1</h2>
|
||||
<h2 id="foo**bar**baz">H2-2</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_35() {
|
||||
let original = r##"# H1 {.foo#bar}
|
||||
## H2 {#foo.bar}
|
||||
### H3 {.a"b'c&d}
|
||||
"##;
|
||||
let expected = r##"<h1 class="foo#bar">H1</h1>
|
||||
<h2 id="foo.bar">H2</h2>
|
||||
<h3 class="a"b'c&d">H3</h3>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_36() {
|
||||
let original = r##"# H1 {#}
|
||||
## H2 {.}
|
||||
"##;
|
||||
let expected = r##"<h1>H1</h1>
|
||||
<h2>H2</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_37() {
|
||||
let original = r##"# H1 {#foo #}
|
||||
# H1 {.foo . . .bar}
|
||||
"##;
|
||||
let expected = r##"<h1 id="foo">H1</h1>
|
||||
<h1 class="foo bar">H1</h1>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_38() {
|
||||
let original = r##"# {}
|
||||
## {}
|
||||
### {\}
|
||||
#### {} {}
|
||||
|
||||
#{}
|
||||
"##;
|
||||
let expected = r##"<h1></h1>
|
||||
<h2></h2>
|
||||
<h3>{}</h3>
|
||||
<h4>{}</h4>
|
||||
<p>#{}</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_39() {
|
||||
let original = r##"{}
|
||||
==
|
||||
|
||||
\{}
|
||||
--
|
||||
|
||||
\
|
||||
--
|
||||
|
||||
{\}
|
||||
==
|
||||
|
||||
{}{}
|
||||
--
|
||||
"##;
|
||||
let expected = r##"<h1></h1>
|
||||
<h2>\</h2>
|
||||
<h2>\</h2>
|
||||
<h1>{}</h1>
|
||||
<h2>{}</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_40() {
|
||||
let original = r##"# horizontal tab
|
||||
# horizontal tab {#ht}
|
||||
## form feed
|
||||
## form feed{#ff}
|
||||
### vertical tab
|
||||
### vertical tab{#vt}
|
||||
"##;
|
||||
let expected = r##"<h1>horizontal tab </h1>
|
||||
<h1 id="ht">horizontal tab </h1>
|
||||
<h2>form feed</h2>
|
||||
<h2 id="ff">form feed</h2>
|
||||
<h3>vertical tab</h3>
|
||||
<h3 id="vt">vertical tab</h3>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_41() {
|
||||
let original = r##"# horizontal tab (U+000A) {#ht .myclass}
|
||||
## form feed (U+000C) {#ff.myclass}
|
||||
|
||||
# vertical tab (U+000B) {#vt.myclass}
|
||||
"##;
|
||||
let expected = r##"<h1 id="ht" class="myclass">horizontal tab (U+000A)</h1>
|
||||
<h2 id="ff" class="myclass">form feed (U+000C)</h2>
|
||||
<h1 id="vt.myclass">vertical tab (U+000B)</h1>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heading_attrs_test_42() {
|
||||
let original = r##"# EN SPACE (U+2002) {#en-space .myclass}
|
||||
## IDEOGRAPHIC SPACE (U+3000) {#ideographic-space .myclass}
|
||||
"##;
|
||||
let expected = r##"<h1 id="en-space .myclass">EN SPACE (U+2002)</h1>
|
||||
<h2 id="ideographic-space .myclass">IDEOGRAPHIC SPACE (U+3000)</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
+919
@@ -0,0 +1,919 @@
|
||||
// This file is auto-generated by the build script
|
||||
// Please, do not modify it manually
|
||||
|
||||
use super::test_markdown_html;
|
||||
|
||||
#[test]
|
||||
fn math_test_1() {
|
||||
let original = r##"This sentence uses `$` delimiters to show math inline: $\sqrt{3x-1}+(1+x)^2$
|
||||
$\sum_{k=1}^n a_k b_k$: Mathematical expression at head of line
|
||||
|
||||
`\` may follow just after the first `$`: $\{1, 2, 3\}$
|
||||
"##;
|
||||
let expected = r##"<p>This sentence uses <code>$</code> delimiters to show math inline: <span class="math math-inline">\sqrt{3x-1}+(1+x)^2</span>
|
||||
<span class="math math-inline">\sum_{k=1}^n a_k b_k</span>: Mathematical expression at head of line</p>
|
||||
<p><code>\</code> may follow just after the first <code>$</code>: <span class="math math-inline">\{1, 2, 3\}</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_2() {
|
||||
let original = r##"**The Cauchy-Schwarz Inequality**
|
||||
|
||||
$$\left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)$$
|
||||
"##;
|
||||
let expected = r##"<p><strong>The Cauchy-Schwarz Inequality</strong></p>
|
||||
<p><span class="math math-display">\left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_3() {
|
||||
let original = r##"Oops empty $$ expression.
|
||||
|
||||
$$$$
|
||||
"##;
|
||||
let expected = r##"<p>Oops empty $$ expression.</p>
|
||||
<p><span class="math math-display"></span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_4() {
|
||||
let original = r##"$x$$$$$$$y$$
|
||||
|
||||
$x$$$$$$y$$
|
||||
|
||||
$$x$$$$$$y$$
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-inline">x</span><span class="math math-display"></span><span class="math math-display">y</span></p>
|
||||
<p><span class="math math-inline">x</span><span class="math math-display"></span><span class="math math-inline">y</span>$</p>
|
||||
<p><span class="math math-display">x</span><span class="math math-display"></span>y$$</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_5() {
|
||||
let original = r##"$a<b>c</b>$
|
||||
|
||||
$${a*b*c} _c_ d$$
|
||||
|
||||
$not `code`$
|
||||
|
||||
$$
|
||||
|
||||
$<https://not.a.link/>$
|
||||
|
||||
$α$
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-inline">a<b>c</b></span></p>
|
||||
<p><span class="math math-display">{a*b*c} _c_ d</span></p>
|
||||
<p><span class="math math-inline">not `code`</span></p>
|
||||
<p><span class="math math-inline"></span></p>
|
||||
<p><span class="math math-inline"><https://not.a.link/></span></p>
|
||||
<p><span class="math math-inline">&alpha;</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_6() {
|
||||
let original = r##"Hello $world.
|
||||
|
||||
Dollar at end of line$
|
||||
"##;
|
||||
let expected = r##"<p>Hello $world.</p>
|
||||
<p>Dollar at end of line$</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_7() {
|
||||
let original = r##"$5x + 2 =
|
||||
17$
|
||||
|
||||
$$\left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right)
|
||||
\left( \sum_{k=1}^n b_k^2 \right)$$
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-inline">5x + 2 =
|
||||
17</span></p>
|
||||
<p><span class="math math-display">\left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right)
|
||||
\left( \sum_{k=1}^n b_k^2 \right)</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_8() {
|
||||
let original = r##"$not a\
|
||||
hard break
|
||||
either$
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-inline">not a\
|
||||
hard break
|
||||
either</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_9() {
|
||||
let original = r##"$\$$
|
||||
|
||||
$$y = \$ x$$
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-inline">\$</span></p>
|
||||
<p><span class="math math-display">y = \$ x</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_10() {
|
||||
let original = r##"$x $ x$
|
||||
|
||||
$$ $ $$
|
||||
"##;
|
||||
let expected = r##"<p>$x $ x$</p>
|
||||
<p>$$ $ $$</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_11() {
|
||||
let original = r##"alpha$$beta$gamma$$delta
|
||||
"##;
|
||||
let expected = r##"<p>alpha$<span class="math math-inline">beta</span>gamma$$delta</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_12() {
|
||||
let original = r##"these are not math texts: $ y=x$, $y=x $, $
|
||||
y=x$ and $y=x
|
||||
$
|
||||
|
||||
>The start of a line counts as whitespace $2 +
|
||||
>$
|
||||
|
||||
While displays can start with whitespace, {${
|
||||
they should not allow inlines to do that $$2 +
|
||||
$*$
|
||||
"##;
|
||||
let expected = r##"<p>these are not math texts: $ y=x$, $y=x $, $
|
||||
y=x$ and $y=x
|
||||
$</p>
|
||||
<blockquote>
|
||||
<p>The start of a line counts as whitespace $2 +
|
||||
$</p>
|
||||
</blockquote>
|
||||
<p>While displays can start with whitespace, {${
|
||||
they should not allow inlines to do that $$2 +
|
||||
<span class="math math-inline">*</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_13() {
|
||||
let original = r##"these are math texts: foo$y=x$bar and $y=x$bar and foo$y=x$ bar
|
||||
"##;
|
||||
let expected = r##"<p>these are math texts: foo<span class="math math-inline">y=x</span>bar and <span class="math math-inline">y=x</span>bar and foo<span class="math math-inline">y=x</span> bar</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_14() {
|
||||
let original = r##"math texts: $x=y$! and $x=y$? and $x=y$: and $x=y$. and $x=y$"
|
||||
|
||||
also math texts: !$x=y$! and ?$x=y$? and :$x=y$: and .$x=y$. and "$x=y$"
|
||||
|
||||
braces: ($x=y$) [$x=y$] {$x=y$}
|
||||
"##;
|
||||
let expected = r##"<p>math texts: <span class="math math-inline">x=y</span>! and <span class="math math-inline">x=y</span>? and <span class="math math-inline">x=y</span>: and <span class="math math-inline">x=y</span>. and <span class="math math-inline">x=y</span>"</p>
|
||||
<p>also math texts: !<span class="math math-inline">x=y</span>! and ?<span class="math math-inline">x=y</span>? and :<span class="math math-inline">x=y</span>: and .<span class="math math-inline">x=y</span>. and "<span class="math math-inline">x=y</span>"</p>
|
||||
<p>braces: (<span class="math math-inline">x=y</span>) [<span class="math math-inline">x=y</span>] {<span class="math math-inline">x=y</span>}</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_15() {
|
||||
let original = r##"$x=y$
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-inline">x=y</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_16() {
|
||||
let original = r##"$a$$b$
|
||||
|
||||
$a$$$b$$
|
||||
|
||||
$$a$$$b$
|
||||
|
||||
$$a$$$$b$$
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-inline">a</span><span class="math math-inline">b</span></p>
|
||||
<p><span class="math math-inline">a</span><span class="math math-display">b</span></p>
|
||||
<p><span class="math math-display">a</span><span class="math math-inline">b</span></p>
|
||||
<p><span class="math math-display">a</span><span class="math math-display">b</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_17() {
|
||||
let original = r##"$Inline `first$ then` code
|
||||
|
||||
`Code $first` then$ inline
|
||||
|
||||
$$ Display `first $$ then` code
|
||||
|
||||
`Code $$ first` then $$ display
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-inline">Inline `first</span> then` code</p>
|
||||
<p><code>Code $first</code> then$ inline</p>
|
||||
<p><span class="math math-display"> Display `first </span> then` code</p>
|
||||
<p><code>Code $$ first</code> then $$ display</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_18() {
|
||||
let original = r##"$x + y - z$
|
||||
|
||||
$x + y
|
||||
- z$
|
||||
|
||||
$$ x + y
|
||||
> z $$
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-inline">x + y - z</span></p>
|
||||
<p>$x + y</p>
|
||||
<ul>
|
||||
<li>z$</li>
|
||||
</ul>
|
||||
<p>$$ x + y</p>
|
||||
<blockquote>
|
||||
<p>z $$</p>
|
||||
</blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_19() {
|
||||
let original = r##"$not
|
||||
|
||||
math$
|
||||
|
||||
$$
|
||||
not
|
||||
|
||||
math
|
||||
$$
|
||||
"##;
|
||||
let expected = r##"<p>$not</p>
|
||||
<p>math$</p>
|
||||
<p>$$
|
||||
not</p>
|
||||
<p>math
|
||||
$$</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_20() {
|
||||
let original = r##"- $not
|
||||
- *
|
||||
math$
|
||||
"##;
|
||||
let expected = r##"<ul>
|
||||
<li>$not
|
||||
<ul>
|
||||
<li>
|
||||
<ul>
|
||||
<li>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
math$</li>
|
||||
</ul>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_21() {
|
||||
let original = r##"This is display math:
|
||||
$$
|
||||
\text{Hello $x^2$}
|
||||
$$
|
||||
And this is inline math:
|
||||
$\text{Hello $x$ there!}$
|
||||
"##;
|
||||
let expected = r##"<p>This is display math:
|
||||
<span class="math math-display">
|
||||
\text{Hello $x^2$}
|
||||
</span>
|
||||
And this is inline math:
|
||||
<span class="math math-inline">\text{Hello $x$ there!}</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_22() {
|
||||
let original = r##"This is not valid math: $}{$
|
||||
|
||||
Neither is this: { $}{$ }
|
||||
|
||||
This is: $\}\{$
|
||||
|
||||
This is: $\}$
|
||||
|
||||
Math environment contains 2+2: $}$2+2$
|
||||
|
||||
Math environment contains y: $x {$ $ } $y$
|
||||
"##;
|
||||
let expected = r##"<p>This is not valid math: $}{$</p>
|
||||
<p>Neither is this: { $}{$ }</p>
|
||||
<p>This is: <span class="math math-inline">\}\{</span></p>
|
||||
<p>This is: <span class="math math-inline">\}</span></p>
|
||||
<p>Math environment contains 2+2: $}<span class="math math-inline">2+2</span></p>
|
||||
<p>Math environment contains y: $x {$ $ } <span class="math math-inline">y</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_23() {
|
||||
let original = r##"This is not display math. It is inline math:
|
||||
|
||||
$$\text{first $$ second}$
|
||||
|
||||
$$$\text{first $$ second}$
|
||||
|
||||
This is display math:
|
||||
|
||||
$$\text{first $$ second}$$
|
||||
|
||||
$$$\text{first $$ second}$$
|
||||
|
||||
This is also display math, but (counterintuitively) it's allowed to be empty
|
||||
and expected to be as short as possible:
|
||||
|
||||
$$$$\text{first $$ second}$$
|
||||
"##;
|
||||
let expected = r##"<p>This is not display math. It is inline math:</p>
|
||||
<p>$<span class="math math-inline">\text{first $$ second}</span></p>
|
||||
<p>$$<span class="math math-inline">\text{first $$ second}</span></p>
|
||||
<p>This is display math:</p>
|
||||
<p><span class="math math-display">\text{first $$ second}</span></p>
|
||||
<p>$<span class="math math-display">\text{first $$ second}</span></p>
|
||||
<p>This is also display math, but (counterintuitively) it's allowed to be empty
|
||||
and expected to be as short as possible:</p>
|
||||
<p><span class="math math-display"></span>\text{first $$ second}$$</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_24() {
|
||||
let original = r##"$\text{\$}$
|
||||
|
||||
$$x$x$$
|
||||
|
||||
${$^$$
|
||||
|
||||
$}$$$$
|
||||
|
||||
$}$] $$
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-inline">\text{\$}</span></p>
|
||||
<p>$<span class="math math-inline">x</span>x$$</p>
|
||||
<p>${<span class="math math-inline">^</span>$</p>
|
||||
<p>$}<span class="math math-display"></span></p>
|
||||
<p>$}$] $$</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_25() {
|
||||
let original = r##"$x$ $`y`$
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-inline">x</span> <span class="math math-inline">`y`</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_26() {
|
||||
let original = r##"- $a$
|
||||
|
||||
```math
|
||||
a
|
||||
```
|
||||
|
||||
$$
|
||||
a
|
||||
$$
|
||||
|
||||
- ```math
|
||||
b
|
||||
```
|
||||
|
||||
$$
|
||||
b
|
||||
$$
|
||||
"##;
|
||||
let expected = r##"<ul>
|
||||
<li>
|
||||
<p><span class="math math-inline">a</span></p>
|
||||
<pre><code class="language-math">a
|
||||
</code></pre>
|
||||
<p><span class="math math-display">
|
||||
a
|
||||
</span></p>
|
||||
</li>
|
||||
<li>
|
||||
<pre><code class="language-math">b
|
||||
</code></pre>
|
||||
<p><span class="math math-display">
|
||||
b
|
||||
</span></p>
|
||||
</li>
|
||||
</ul>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_27() {
|
||||
let original = r##"- 
|
||||
- $x$
|
||||
"##;
|
||||
let expected = r##"<ul>
|
||||
<li><img src="https://nodejs.org/static/images/logo.svg" alt="node logo" /></li>
|
||||
<li><span class="math math-inline">x</span></li>
|
||||
</ul>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_28() {
|
||||
let original = r##"<details>
|
||||
|
||||
$A = 5$
|
||||
|
||||
$$
|
||||
A = 5
|
||||
$$
|
||||
|
||||
</details>
|
||||
"##;
|
||||
let expected = r##"<details>
|
||||
<p><span class="math math-inline">A = 5</span></p>
|
||||
<p><span class="math math-display">
|
||||
A = 5
|
||||
</span></p>
|
||||
</details>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_29() {
|
||||
let original = r##"$a<b$
|
||||
|
||||
$$a<b$$
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-inline">a<b</span></p>
|
||||
<p><span class="math math-display">a<b</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_30() {
|
||||
let original = r##"[^a]
|
||||
|
||||
[^a]: Lorem $a$
|
||||
"##;
|
||||
let expected = r##"<p><sup class="footnote-reference"><a href="#a">1</a></sup></p>
|
||||
<div class="footnote-definition" id="a"><sup class="footnote-definition-label">1</sup>
|
||||
<p>Lorem <span class="math math-inline">a</span></p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_31() {
|
||||
let original = r##"[$a$](x)
|
||||
"##;
|
||||
let expected = r##"<p>
|
||||
<a href="x"><span class="math math-inline">a</span></a>
|
||||
</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_32() {
|
||||
let original = r##"a$x$
|
||||
|
||||
-$x$
|
||||
|
||||
1$x$
|
||||
"##;
|
||||
let expected = r##"<p>a<span class="math math-inline">x</span></p>
|
||||
<p>-<span class="math math-inline">x</span></p>
|
||||
<p>1<span class="math math-inline">x</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_33() {
|
||||
let original = r##"_$a$ equals $b$_
|
||||
|
||||
_$a$ equals $b$_
|
||||
|
||||
**$a$ equals $b$**
|
||||
"##;
|
||||
let expected = r##"<p><em><span class="math math-inline">a</span> equals <span class="math math-inline">b</span></em></p>
|
||||
<p><em><span class="math math-inline">a</span> equals <span class="math math-inline">b</span></em></p>
|
||||
<p><strong><span class="math math-inline">a</span> equals <span class="math math-inline">b</span></strong></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_34() {
|
||||
let original = r##"$$
|
||||
a
|
||||
$$
|
||||
|
||||
- $$
|
||||
\text{$b$}
|
||||
$$
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-display">
|
||||
a
|
||||
</span>
|
||||
</p><ul>
|
||||
<li><span class="math math-display">
|
||||
\text{$b$}
|
||||
</span></li>
|
||||
</ul>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_35() {
|
||||
let original = r##"$\{a\,b\}$
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-inline">\{a\,b\}</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_36() {
|
||||
let original = r##"$a <b > c$
|
||||
|
||||
$[(a+b)c](d+e)$
|
||||
|
||||
${a}_b c_{d}$
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-inline">a <b > c</span></p>
|
||||
<p><span class="math math-inline">[(a+b)c](d+e)</span></p>
|
||||
<p><span class="math math-inline">{a}_b c_{d}</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_37() {
|
||||
let original = r##"When $a \ne 0$, there are two solutions to $(ax^2 + bx + c = 0)$ and they are
|
||||
$$ x = {-b \pm \sqrt{b^2-4ac} \over 2a} $$
|
||||
"##;
|
||||
let expected = r##"<p>When <span class="math math-inline">a \ne 0</span>, there are two solutions to <span class="math math-inline">(ax^2 + bx + c = 0)</span> and they are
|
||||
<span class="math math-display"> x = {-b \pm \sqrt{b^2-4ac} \over 2a} </span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_38() {
|
||||
let original = r##"$x = \$$
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-inline">x = \$</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_39() {
|
||||
let original = r##"_Equation $\Omega(69)$ in italic text_
|
||||
"##;
|
||||
let expected = r##"<p><em>Equation <span class="math math-inline">\Omega(69)</span> in italic text</em></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_40() {
|
||||
let original = r##"$\pi$
|
||||
'$\pi$
|
||||
"$\pi$
|
||||
($\pi$
|
||||
[$\pi$
|
||||
{$\pi$
|
||||
/$\pi$
|
||||
"##;
|
||||
let expected = r##"<p>
|
||||
<span class="math math-inline">\pi</span>
|
||||
'<span class="math math-inline">\pi</span>
|
||||
"<span class="math math-inline">\pi</span>
|
||||
(<span class="math math-inline">\pi</span>
|
||||
[<span class="math math-inline">\pi</span>
|
||||
{<span class="math math-inline">\pi</span>
|
||||
/<span class="math math-inline">\pi</span>
|
||||
</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_41() {
|
||||
let original = r##"| first $|$ second |
|
||||
|--------|---------|
|
||||
| a ${ | }$ b |
|
||||
"##;
|
||||
let expected = r##"<table><thead>
|
||||
<tr><th>first $</th><th>$ second</th></tr>
|
||||
</thead><tbody>
|
||||
<tr><td>a ${</td><td>}$ b</td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_42() {
|
||||
let original = r##"| first $\|$ second |
|
||||
|-------------------|
|
||||
| a ${ \| }$ b |
|
||||
"##;
|
||||
let expected = r##"<table><thead>
|
||||
<tr><th>first <span class="math math-inline">|</span> second</th></tr>
|
||||
</thead><tbody>
|
||||
<tr><td>a <span class="math math-inline">{ | }</span> b</td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_43() {
|
||||
let original = r##"| Description | Test case |
|
||||
|-------------|-----------|
|
||||
| Single | $\$ |
|
||||
| Double | $\\$ |
|
||||
| Basic test | $\|$ |
|
||||
| Basic test 2| $\|\|\$ |
|
||||
| Basic test 3| $x\|y\|z\$|
|
||||
| Not pipe | $\.$ |
|
||||
| Combo | $\.\|$ |
|
||||
| Combo 2 | $\.\|\$ |
|
||||
| Extra | $\\\.$ |
|
||||
| Wait, what? | $\\|$ |
|
||||
| Wait, what? | $\\\|$ |
|
||||
| Wait, what? | $\\\\|$ |
|
||||
| Wait, what? | $\\\\\|$ |
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>Description</th><th>Test case</th></tr></thead><tbody>
|
||||
<tr><td>Single</td><td>$$</td></tr>
|
||||
<tr><td>Double</td><td><span class="math math-inline">\\</span></td></tr>
|
||||
<tr><td>Basic test</td><td><span class="math math-inline">|</span></td></tr>
|
||||
<tr><td>Basic test 2</td><td>$||$</td></tr>
|
||||
<tr><td>Basic test 3</td><td>$x|y|z$</td></tr>
|
||||
<tr><td>Not pipe</td><td><span class="math math-inline">\.</span></td></tr>
|
||||
<tr><td>Combo</td><td><span class="math math-inline">\.|</span></td></tr>
|
||||
<tr><td>Combo 2</td><td>$.|$</td></tr>
|
||||
<tr><td>Extra</td><td><span class="math math-inline">\\\.</span></td></tr>
|
||||
<tr><td>Wait, what?</td><td><span class="math math-inline">\|</span></td></tr>
|
||||
<tr><td>Wait, what?</td><td><span class="math math-inline">\\|</span></td></tr>
|
||||
<tr><td>Wait, what?</td><td><span class="math math-inline">\\\|</span></td></tr>
|
||||
<tr><td>Wait, what?</td><td><span class="math math-inline">\\\\|</span></td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_44() {
|
||||
let original = r##"This is not an inline math environment: $}{$
|
||||
But, because it's nested too deeply, this is parsed as an inline math environment:
|
||||
{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
|
||||
improperly $}{$ nested
|
||||
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
||||
But this still isn't, because the braces are still counted: $}{$
|
||||
"##;
|
||||
let expected = r##"<p>This is not an inline math environment: $}{$
|
||||
But, because it's nested too deeply, this is parsed as an inline math environment:
|
||||
{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
|
||||
improperly <span class="math math-inline">}{</span> nested
|
||||
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
||||
But this still isn't, because the braces are still counted: $}{$</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_45() {
|
||||
let original = r##"This is also deeply nested, but, unlike the first example,
|
||||
they don't have an equal number of close braces and open braces,
|
||||
so aren't detected as math.
|
||||
{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
|
||||
improperly $}$ nested ${$ example
|
||||
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
||||
This, however, is detected ${}$
|
||||
|
||||
${{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
|
||||
another improperly nested example
|
||||
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}$
|
||||
"##;
|
||||
let expected = r##"<p>This is also deeply nested, but, unlike the first example,
|
||||
they don't have an equal number of close braces and open braces,
|
||||
so aren't detected as math.
|
||||
{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
|
||||
improperly $}$ nested ${$ example
|
||||
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
||||
This, however, is detected <span class="math math-inline">{}</span></p>
|
||||
<p><span class="math math-inline">{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
|
||||
another improperly nested example
|
||||
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}</span></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_46() {
|
||||
let original = r##"${}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 20 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 40 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 60 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 80 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 100 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 120 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 140 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 160 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 180 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 200 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 220 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 240 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{$ 255 brace pairs and one unclosed brace
|
||||
"##;
|
||||
let expected = r##"<p><span class="math math-inline">{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 20 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 40 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 60 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 80 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 100 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 120 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 140 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 160 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 180 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 200 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 220 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{} 240 brace pairs
|
||||
{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{</span> 255 brace pairs and one unclosed brace</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_test_47() {
|
||||
let original = r##"${{{{{{{{{{{{{{{{{{{{ 20 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 40 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 60 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 80 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 100 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 110 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 120 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 140 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 160 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 180 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 200 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 220 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 240 open braces
|
||||
{{{{{{{{{{{{{{{ 255 open braces
|
||||
}}}}}}}}}}}}}}}}}}}} 20 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 40 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 60 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 80 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 100 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 120 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 140 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 160 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 180 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 200 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 220 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 240 close braces
|
||||
}}}}}}}}}}}}}}}{$ 255 close braces and one open brace
|
||||
"##;
|
||||
let expected = r##"<p>${{{{{{{{{{{{{{{{{{{{ 20 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 40 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 60 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 80 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 100 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 110 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 120 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 140 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 160 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 180 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 200 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 220 open braces
|
||||
{{{{{{{{{{{{{{{{{{{{ 240 open braces
|
||||
{{{{{{{{{{{{{{{ 255 open braces
|
||||
}}}}}}}}}}}}}}}}}}}} 20 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 40 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 60 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 80 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 100 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 120 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 140 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 160 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 180 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 200 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 220 close braces
|
||||
}}}}}}}}}}}}}}}}}}}} 240 close braces
|
||||
}}}}}}}}}}}}}}}{$ 255 close braces and one open brace</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
// This file is auto-generated by the build script
|
||||
// Please, do not modify it manually
|
||||
|
||||
use super::test_markdown_html;
|
||||
|
||||
#[test]
|
||||
fn metadata_blocks_test_1() {
|
||||
let original = r##"---
|
||||
title: example
|
||||
another_field: 0
|
||||
---
|
||||
"##;
|
||||
let expected = r##""##;
|
||||
|
||||
test_markdown_html(original, expected, false, true, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_blocks_test_2() {
|
||||
let original = r##"---
|
||||
title: example
|
||||
another_field: 0
|
||||
"##;
|
||||
let expected = r##"<hr>
|
||||
<p>title: example
|
||||
another_field: 0</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, true, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_blocks_test_3() {
|
||||
let original = r##"---
|
||||
---
|
||||
"##;
|
||||
let expected = r##"<hr>
|
||||
<hr>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, true, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_blocks_test_4() {
|
||||
let original = r##"---
|
||||
|
||||
title: example
|
||||
another_field: 0
|
||||
---
|
||||
"##;
|
||||
let expected = r##"<hr>
|
||||
<h2>title: example
|
||||
another_field: 0</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, true, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_blocks_test_5() {
|
||||
let original = r##"My paragraph here.
|
||||
---
|
||||
title: example
|
||||
another_field: 0
|
||||
---
|
||||
"##;
|
||||
let expected = r##"<h2>My paragraph here.</h2>
|
||||
<h2>title: example
|
||||
another_field: 0</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, true, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_blocks_test_6() {
|
||||
let original = r##"My paragraph here.
|
||||
|
||||
---
|
||||
title: example
|
||||
another_field: 0
|
||||
---
|
||||
"##;
|
||||
let expected = r##"<p>My paragraph here.</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, true, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_blocks_test_7() {
|
||||
let original = r##"---
|
||||
title: example
|
||||
another_field: 0
|
||||
---
|
||||
|
||||
--- -
|
||||
title: example
|
||||
another_field: 0
|
||||
---
|
||||
"##;
|
||||
let expected = r##"<hr>
|
||||
<h2>title: example
|
||||
another_field: 0</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, true, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_blocks_test_8() {
|
||||
let original = r##"---
|
||||
title: example
|
||||
another_field: 0
|
||||
---
|
||||
|
||||
---
|
||||
title: example
|
||||
another_field: 0
|
||||
---a
|
||||
"##;
|
||||
let expected = r##"<hr>
|
||||
<p>title: example
|
||||
another_field: 0
|
||||
---a</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, true, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_blocks_test_9() {
|
||||
let original = r##"---
|
||||
title: example
|
||||
another_field: 0
|
||||
...
|
||||
"##;
|
||||
let expected = r##""##;
|
||||
|
||||
test_markdown_html(original, expected, false, true, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_blocks_test_10() {
|
||||
let original = r##"+++
|
||||
title: example
|
||||
another_field: 0
|
||||
+++
|
||||
"##;
|
||||
let expected = r##""##;
|
||||
|
||||
test_markdown_html(original, expected, false, true, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_blocks_test_11() {
|
||||
let original = r##" ---
|
||||
Things
|
||||
---
|
||||
"##;
|
||||
let expected = r##"<pre><code>---
|
||||
Things
|
||||
---
|
||||
</code></pre>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, true, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_blocks_test_12() {
|
||||
let original = r##"---
|
||||
- Item 1
|
||||
- Item 2
|
||||
---
|
||||
"##;
|
||||
let expected = r##""##;
|
||||
|
||||
test_markdown_html(original, expected, false, true, false);
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// This file is auto-generated by the build script
|
||||
// Please, do not modify it manually
|
||||
|
||||
pub use super::test_markdown_html;
|
||||
|
||||
mod blockquotes_tags;
|
||||
mod footnotes;
|
||||
mod gfm_strikethrough;
|
||||
mod gfm_table;
|
||||
mod gfm_tasklist;
|
||||
mod heading_attrs;
|
||||
mod math;
|
||||
mod metadata_blocks;
|
||||
mod old_footnotes;
|
||||
mod regression;
|
||||
mod smart_punct;
|
||||
mod spec;
|
||||
mod strikethrough;
|
||||
mod table;
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
// This file is auto-generated by the build script
|
||||
// Please, do not modify it manually
|
||||
|
||||
use super::test_markdown_html;
|
||||
|
||||
#[test]
|
||||
fn old_footnotes_test_1() {
|
||||
let original = r##"Lorem ipsum.[^a]
|
||||
|
||||
[^a]: Cool.
|
||||
"##;
|
||||
let expected = r##"<p>Lorem ipsum.<sup class="footnote-reference"><a href="#a">1</a></sup></p>
|
||||
<div class="footnote-definition" id="a"><sup class="footnote-definition-label">1</sup>
|
||||
<p>Cool.</p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_footnotes_test_2() {
|
||||
let original = r##"> This is the song that never ends.\
|
||||
> Yes it goes on and on my friends.[^lambchops]
|
||||
>
|
||||
> [^lambchops]: <https://www.youtube.com/watch?v=0U2zJOryHKQ>
|
||||
"##;
|
||||
let expected = r##"<blockquote>
|
||||
<p>This is the song that never ends.<br />
|
||||
Yes it goes on and on my friends.<sup class="footnote-reference"><a href="#lambchops">1</a></sup></p>
|
||||
<div class="footnote-definition" id="lambchops"><sup class="footnote-definition-label">1</sup>
|
||||
<p><a href="https://www.youtube.com/watch?v=0U2zJOryHKQ">https://www.youtube.com/watch?v=0U2zJOryHKQ</a></p>
|
||||
</div>
|
||||
</blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_footnotes_test_3() {
|
||||
let original = r##"Songs that simply loop are a popular way to annoy people. [^examples]
|
||||
|
||||
[^examples]:
|
||||
* [The song that never ends](https://www.youtube.com/watch?v=0U2zJOryHKQ)
|
||||
* [I know a song that gets on everybody's nerves](https://www.youtube.com/watch?v=TehWI09qxls)
|
||||
* [Ninety-nine bottles of beer on the wall](https://www.youtube.com/watch?v=qVjCag8XoHQ)
|
||||
"##;
|
||||
let expected = r##"<p>Songs that simply loop are a popular way to annoy people. <sup class="footnote-reference"><a href="#examples">1</a></sup></p>
|
||||
<div class="footnote-definition" id="examples"><sup class="footnote-definition-label">1</sup>
|
||||
<ul>
|
||||
<li><a href="https://www.youtube.com/watch?v=0U2zJOryHKQ">The song that never ends</a></li>
|
||||
<li><a href="https://www.youtube.com/watch?v=TehWI09qxls">I know a song that gets on everybody's nerves</a></li>
|
||||
<li><a href="https://www.youtube.com/watch?v=qVjCag8XoHQ">Ninety-nine bottles of beer on the wall</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_footnotes_test_4() {
|
||||
let original = r##"[^lorem]: If heaven ever wishes to grant me a boon, it will be a total effacing of the results of a mere chance which fixed my eye on a certain stray piece of shelf-paper. It was nothing on which I would naturally have stumbled in the course of my daily round, for it was an old number of an Australian journal, the Sydney Bulletin for April 18, 1925. It had escaped even the cutting bureau which had at the time of its issuance been avidly collecting material for my uncle's research.
|
||||
|
||||
I had largely given over my inquiries into what Professor Angell called the "Cthulhu Cult", and was visiting a learned friend in Paterson, New Jersey; the curator of a local museum and a mineralogist of note. Examining one day the reserve specimens roughly set on the storage shelves in a rear room of the museum, my eye was caught by an odd picture in one of the old papers spread beneath the stones. It was the Sydney Bulletin I have mentioned, for my friend had wide affiliations in all conceivable foreign parts; and the picture was a half-tone cut of a hideous stone image almost identical with that which Legrasse had found in the swamp.
|
||||
"##;
|
||||
let expected = r##"<div class="footnote-definition" id="lorem"><sup class="footnote-definition-label">1</sup>
|
||||
<p>If heaven ever wishes to grant me a boon, it will be a total effacing of the results of a mere chance which fixed my eye on a certain stray piece of shelf-paper. It was nothing on which I would naturally have stumbled in the course of my daily round, for it was an old number of an Australian journal, the Sydney Bulletin for April 18, 1925. It had escaped even the cutting bureau which had at the time of its issuance been avidly collecting material for my uncle's research.</p>
|
||||
</div>
|
||||
<p>I had largely given over my inquiries into what Professor Angell called the "Cthulhu Cult", and was visiting a learned friend in Paterson, New Jersey; the curator of a local museum and a mineralogist of note. Examining one day the reserve specimens roughly set on the storage shelves in a rear room of the museum, my eye was caught by an odd picture in one of the old papers spread beneath the stones. It was the Sydney Bulletin I have mentioned, for my friend had wide affiliations in all conceivable foreign parts; and the picture was a half-tone cut of a hideous stone image almost identical with that which Legrasse had found in the swamp.</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_footnotes_test_5() {
|
||||
let original = r##"[^ipsum]: How much wood would a woodchuck chuck.
|
||||
|
||||
If a woodchuck could chuck wood.
|
||||
|
||||
|
||||
# Forms of entertainment that aren't childish
|
||||
"##;
|
||||
let expected = r##"<div class="footnote-definition" id="ipsum"><sup class="footnote-definition-label">1</sup>
|
||||
<p>How much wood would a woodchuck chuck.</p>
|
||||
</div>
|
||||
<p>If a woodchuck could chuck wood.</p>
|
||||
<h1>Forms of entertainment that aren't childish</h1>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_footnotes_test_6() {
|
||||
let original = r##"> He's also really stupid. [^why]
|
||||
>
|
||||
> [^why]: Because your mamma!
|
||||
|
||||
As such, we can guarantee that the non-childish forms of entertainment are probably more entertaining to adults, since, having had a whole childhood doing the childish ones, the non-childish ones are merely the ones that haven't gotten boring yet.
|
||||
"##;
|
||||
let expected = r##"<blockquote>
|
||||
<p>He's also really stupid. <sup class="footnote-reference"><a href="#why">1</a></sup></p>
|
||||
<div class="footnote-definition" id="why"><sup class="footnote-definition-label">1</sup>
|
||||
<p>Because your mamma!</p>
|
||||
</div>
|
||||
</blockquote>
|
||||
<p>As such, we can guarantee that the non-childish forms of entertainment are probably more entertaining to adults, since, having had a whole childhood doing the childish ones, the non-childish ones are merely the ones that haven't gotten boring yet.</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_footnotes_test_7() {
|
||||
let original = r##"Nested footnotes are considered poor style. [^a] [^xkcd]
|
||||
|
||||
[^a]: This does not mean that footnotes cannot reference each other. [^b]
|
||||
|
||||
[^b]: This means that a footnote definition cannot be directly inside another footnote definition.
|
||||
> This means that a footnote cannot be directly inside another footnote's body. [^e]
|
||||
>
|
||||
> [^e]: They can, however, be inside anything else.
|
||||
|
||||
[^xkcd]: [The other kind of nested footnote is, however, considered poor style.](https://xkcd.com/1208/)
|
||||
"##;
|
||||
let expected = r##"<p>Nested footnotes are considered poor style. <sup class="footnote-reference"><a href="#a">1</a></sup> <sup class="footnote-reference"><a href="#xkcd">2</a></sup></p>
|
||||
<div class="footnote-definition" id="a"><sup class="footnote-definition-label">1</sup>
|
||||
<p>This does not mean that footnotes cannot reference each other. <sup class="footnote-reference"><a href="#b">3</a></sup></p>
|
||||
</div>
|
||||
<div class="footnote-definition" id="b"><sup class="footnote-definition-label">3</sup>
|
||||
<p>This means that a footnote definition cannot be directly inside another footnote definition.</p>
|
||||
<blockquote>
|
||||
<p>This means that a footnote cannot be directly inside another footnote's body. <sup class="footnote-reference"><a href="#e">4</a></sup></p>
|
||||
<div class="footnote-definition" id="e"><sup class="footnote-definition-label">4</sup>
|
||||
<p>They can, however, be inside anything else.</p>
|
||||
</div>
|
||||
</blockquote>
|
||||
</div>
|
||||
<div class="footnote-definition" id="xkcd"><sup class="footnote-definition-label">2</sup>
|
||||
<p><a href="https://xkcd.com/1208/">The other kind of nested footnote is, however, considered poor style.</a></p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_footnotes_test_8() {
|
||||
let original = r##"[^Doh] Ray Me Fa So La Te Do! [^1]
|
||||
|
||||
[^Doh]: I know. Wrong Doe. And it will render right.
|
||||
[^1]: Common for people practicing music.
|
||||
"##;
|
||||
let expected = r##"<p><sup class="footnote-reference"><a href="#Doh">1</a></sup> Ray Me Fa So La Te Do! <sup class="footnote-reference"><a href="#1">2</a></sup></p>
|
||||
<div class="footnote-definition" id="Doh"><sup class="footnote-definition-label">1</sup><p>I know. Wrong Doe. And it will render right.</p></div>
|
||||
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">2</sup><p>Common for people practicing music.</p></div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_footnotes_test_9() {
|
||||
let original = r##"[Reference to footnotes A[^1], B[^2] and C[^3].
|
||||
|
||||
[^1]: Footnote A.
|
||||
[^2]: Footnote B.
|
||||
[^3]: Footnote C.
|
||||
"##;
|
||||
let expected = r##"<p>[Reference to footnotes A<sup class="footnote-reference"><a href="#1">1</a></sup>, B<sup class="footnote-reference"><a href="#2">2</a></sup> and C<sup class="footnote-reference"><a href="#3">3</a></sup>.</p><div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup><p>Footnote A.</p></div><div class="footnote-definition" id="2"><sup class="footnote-definition-label">2</sup><p>Footnote B.</p></div><div class="footnote-definition" id="3"><sup class="footnote-definition-label">3</sup><p>Footnote C.</p></div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_footnotes_test_10() {
|
||||
let original = r##"Lorem ipsum.[^a][^b]
|
||||
|
||||
[^a]: Foo
|
||||
[^b]: Bar
|
||||
"##;
|
||||
let expected = r##"<p>Lorem ipsum.<sup class="footnote-reference"><a href="#a">1</a></sup><sup class="footnote-reference"><a href="#b">2</a></sup></p>
|
||||
<div class="footnote-definition" id="a"><sup class="footnote-definition-label">1</sup>
|
||||
<p>Foo</p>
|
||||
</div>
|
||||
<div class="footnote-definition" id="b"><sup class="footnote-definition-label">2</sup>
|
||||
<p>Bar</p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_footnotes_test_11() {
|
||||
let original = r##"Lorem ipsum.[^a] [^b]
|
||||
|
||||
[^a]: Foo
|
||||
[^b]: Bar
|
||||
"##;
|
||||
let expected = r##"<p>Lorem ipsum.<sup class="footnote-reference"><a href="#a">1</a></sup> <sup class="footnote-reference"><a href="#b">2</a></sup></p>
|
||||
<div class="footnote-definition" id="a"><sup class="footnote-definition-label">1</sup>
|
||||
<p>Foo</p>
|
||||
</div>
|
||||
<div class="footnote-definition" id="b"><sup class="footnote-definition-label">2</sup>
|
||||
<p>Bar</p>
|
||||
</div>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
+3155
File diff suppressed because it is too large.
Load diff
+201
@@ -0,0 +1,201 @@
|
||||
// This file is auto-generated by the build script
|
||||
// Please, do not modify it manually
|
||||
|
||||
use super::test_markdown_html;
|
||||
|
||||
#[test]
|
||||
fn smart_punct_test_1() {
|
||||
let original = r##""Hello," said the spider.
|
||||
"'Shelob' is my name."
|
||||
"##;
|
||||
let expected = r##"<p>“Hello,” said the spider.
|
||||
“‘Shelob’ is my name.”</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, true, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smart_punct_test_2() {
|
||||
let original = r##"'A', 'B', and 'C' are letters.
|
||||
"##;
|
||||
let expected = r##"<p>‘A’, ‘B’, and ‘C’ are letters.</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, true, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smart_punct_test_3() {
|
||||
let original = r##"'Oak,' 'elm,' and 'beech' are names of trees.
|
||||
So is 'pine.'
|
||||
"##;
|
||||
let expected = r##"<p>‘Oak,’ ‘elm,’ and ‘beech’ are names of trees.
|
||||
So is ‘pine.’</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, true, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smart_punct_test_4() {
|
||||
let original = r##"'He said, "I want to go."'
|
||||
"##;
|
||||
let expected = r##"<p>‘He said, “I want to go.”’</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, true, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smart_punct_test_5() {
|
||||
let original = r##"Were you alive in the 70's?
|
||||
"##;
|
||||
let expected = r##"<p>Were you alive in the 70’s?</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, true, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smart_punct_test_6() {
|
||||
let original = r##"Here is some quoted '`code`' and a "[quoted link](url)".
|
||||
"##;
|
||||
let expected = r##"<p>Here is some quoted ‘<code>code</code>’ and a “<a href="url">quoted link</a>”.</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, true, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smart_punct_test_7() {
|
||||
let original = r##"'tis the season to be 'jolly'
|
||||
"##;
|
||||
let expected = r##"<p>’tis the season to be ‘jolly’</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, true, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smart_punct_test_8() {
|
||||
let original = r##"'We'll use Jane's boat and John's truck,' Jenna said.
|
||||
"##;
|
||||
let expected = r##"<p>‘We’ll use Jane’s boat and John’s truck,’ Jenna said.</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, true, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smart_punct_test_9() {
|
||||
let original = r##""A paragraph with no closing quote.
|
||||
|
||||
"Second paragraph by same speaker, in fiction."
|
||||
"##;
|
||||
let expected = r##"<p>“A paragraph with no closing quote.</p>
|
||||
<p>“Second paragraph by same speaker, in fiction.”</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, true, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smart_punct_test_10() {
|
||||
let original = r##"[a]'s b'
|
||||
"##;
|
||||
let expected = r##"<p>[a]’s b’</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, true, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smart_punct_test_11() {
|
||||
let original = r##"\"This is not smart.\"
|
||||
This isn\'t either.
|
||||
5\'8\"
|
||||
"##;
|
||||
let expected = r##"<p>"This is not smart."
|
||||
This isn't either.
|
||||
5'8"</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, true, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smart_punct_test_12() {
|
||||
let original = r##"Some dashes: em---em
|
||||
en--en
|
||||
em --- em
|
||||
en -- en
|
||||
2--3
|
||||
"##;
|
||||
let expected = r##"<p>Some dashes: em—em
|
||||
en–en
|
||||
em — em
|
||||
en – en
|
||||
2–3</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, true, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smart_punct_test_13() {
|
||||
let original = r##"one-
|
||||
two--
|
||||
three---
|
||||
four----
|
||||
five-----
|
||||
six------
|
||||
seven-------
|
||||
eight--------
|
||||
nine---------
|
||||
thirteen-------------.
|
||||
"##;
|
||||
let expected = r##"<p>one-
|
||||
two–
|
||||
three—
|
||||
four––
|
||||
five—–
|
||||
six——
|
||||
seven—––
|
||||
eight––––
|
||||
nine———
|
||||
thirteen———––.</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, true, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smart_punct_test_14() {
|
||||
let original = r##"Escaped hyphens: \-- \-\-\-.
|
||||
"##;
|
||||
let expected = r##"<p>Escaped hyphens: -- ---.</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, true, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smart_punct_test_15() {
|
||||
let original = r##"Ellipses...and...and....
|
||||
"##;
|
||||
let expected = r##"<p>Ellipses…and…and….</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, true, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smart_punct_test_16() {
|
||||
let original = r##"No ellipses\.\.\.
|
||||
"##;
|
||||
let expected = r##"<p>No ellipses...</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, true, false, false);
|
||||
}
|
||||
+8496
File diff suppressed because it is too large.
Load diff
+164
@@ -0,0 +1,164 @@
|
||||
// This file is auto-generated by the build script
|
||||
// Please, do not modify it manually
|
||||
|
||||
use super::test_markdown_html;
|
||||
|
||||
#[test]
|
||||
fn strikethrough_test_1() {
|
||||
let original = r##"~~This is *stricken out*~~
|
||||
"##;
|
||||
let expected = r##"<p><del>This is <em>stricken out</em></del></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strikethrough_test_2() {
|
||||
let original = r##"~~This is \~\~stricken~~
|
||||
"##;
|
||||
let expected = r##"<p><del>This is ~~stricken</del></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strikethrough_test_3() {
|
||||
let original = r##"This~~is~~stricken
|
||||
"##;
|
||||
let expected = r##"<p>This<del>is</del>stricken</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strikethrough_test_4() {
|
||||
let original = r##"~~This~~is~~stricken~~
|
||||
"##;
|
||||
let expected = r##"<p><del>This</del>is<del>stricken</del></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strikethrough_test_5() {
|
||||
let original = r##"Here I strike out an exclamation point~~!~~.
|
||||
"##;
|
||||
let expected = r##"<p>Here I strike out an exclamation point<del>!</del>.</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strikethrough_test_6() {
|
||||
let original = r##"~This is stricken out~
|
||||
"##;
|
||||
let expected = r##"<p><del>This is stricken out</del></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strikethrough_test_7() {
|
||||
let original = r##"~This is \~stricken~
|
||||
"##;
|
||||
let expected = r##"<p><del>This is ~stricken</del></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strikethrough_test_8() {
|
||||
let original = r##"This~is~nothing
|
||||
"##;
|
||||
let expected = r##"<p>This~is~nothing</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strikethrough_test_9() {
|
||||
let original = r##"~This~is~nothing~
|
||||
"##;
|
||||
let expected = r##"<p><del>This~is~nothing</del></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strikethrough_test_10() {
|
||||
let original = r##"Here I fail to strike out an exclamation point~!~.
|
||||
"##;
|
||||
let expected = r##"<p>Here I fail to strike out an exclamation point~!~.</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strikethrough_test_11() {
|
||||
let original = r##"Here I fail to strike out a tilde ~~~.
|
||||
"##;
|
||||
let expected = r##"<p>Here I fail to strike out a tilde ~~~.</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strikethrough_test_12() {
|
||||
let original = r##"Here I fail to match up ~~tildes~.
|
||||
"##;
|
||||
let expected = r##"<p>Here I fail to match up ~~tildes~.</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strikethrough_test_13() {
|
||||
let original = r##"Here I fail to match up ~tildes~~.
|
||||
"##;
|
||||
let expected = r##"<p>Here I fail to match up ~tildes~~.</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strikethrough_test_14() {
|
||||
let original = r##"~~This ~is stricken.~~
|
||||
"##;
|
||||
let expected = r##"<p><del>This ~is stricken.</del></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strikethrough_test_15() {
|
||||
let original = r##"~This ~~is stricken.~
|
||||
"##;
|
||||
let expected = r##"<p><del>This ~~is stricken.</del></p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strikethrough_test_16() {
|
||||
let original = r##"~This ~~is stricken~ but this is not~~
|
||||
"##;
|
||||
let expected = r##"<p><del>This ~~is stricken</del> but this is not~~</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
+632
@@ -0,0 +1,632 @@
|
||||
// This file is auto-generated by the build script
|
||||
// Please, do not modify it manually
|
||||
|
||||
use super::test_markdown_html;
|
||||
|
||||
#[test]
|
||||
fn table_test_1() {
|
||||
let original = r##"Test header
|
||||
-----------
|
||||
"##;
|
||||
let expected = r##"<h2>Test header</h2>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_2() {
|
||||
let original = r##"Test|Table
|
||||
----|-----
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>Test</th><th>Table</th></tr></thead><tbody></tbody>
|
||||
</table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_3() {
|
||||
let original = r##"> Test | Table
|
||||
> ------|------
|
||||
> Row 1 | Every
|
||||
> Row 2 | Day
|
||||
>
|
||||
> Paragraph
|
||||
"##;
|
||||
let expected = r##"<blockquote>
|
||||
<table><thead><tr><th>Test</th><th>Table</th></tr></thead><tbody>
|
||||
<tr><td>Row 1</td><td>Every</td></tr>
|
||||
<tr><td>Row 2</td><td>Day</td></tr>
|
||||
</tbody></table>
|
||||
<p>Paragraph</p>
|
||||
</blockquote>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_4() {
|
||||
let original = r##" 1. First entry
|
||||
2. Second entry
|
||||
|
||||
Col 1|Col 2
|
||||
-|-
|
||||
Row 1|Part 2
|
||||
Row 2|Part 2
|
||||
"##;
|
||||
let expected = r##"<ol>
|
||||
<li>
|
||||
<p>First entry</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Second entry</p>
|
||||
<table><thead><tr><th>Col 1</th><th>Col 2</th></tr></thead><tbody>
|
||||
<tr><td>Row 1</td><td>Part 2</td></tr>
|
||||
<tr><td>Row 2</td><td>Part 2</td></tr>
|
||||
</tbody></table>
|
||||
</li>
|
||||
</ol>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_5() {
|
||||
let original = r##"|Col 1|Col 2|
|
||||
|-----|-----|
|
||||
|R1C1 |R1C2 |
|
||||
|R2C1 |R2C2 |
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>Col 1</th><th>Col 2</th></tr></thead><tbody>
|
||||
<tr><td>R1C1</td><td>R1C2</td></tr>
|
||||
<tr><td>R2C1</td><td>R2C2</td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_6() {
|
||||
let original = r##"| Col 1 | Col 2 |
|
||||
|-------|-------|
|
||||
| | |
|
||||
| | |
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>Col 1</th><th>Col 2</th></tr></thead><tbody>
|
||||
<tr><td></td><td></td></tr>
|
||||
<tr><td></td><td></td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_7() {
|
||||
let original = r##"| Col 1 | Col 2 |
|
||||
|-------|-------|
|
||||
| x | |
|
||||
| | x |
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>Col 1</th><th>Col 2</th></tr></thead><tbody>
|
||||
<tr><td>x</td><td></td></tr>
|
||||
<tr><td></td><td>x</td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_8() {
|
||||
let original = r##"|Col 1|Col 2|
|
||||
|-----|-----|
|
||||
|✓ |✓ |
|
||||
|✓ |✓ |
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>Col 1</th><th>Col 2</th></tr></thead><tbody>
|
||||
<tr><td>✓</td><td>✓</td></tr>
|
||||
<tr><td>✓</td><td>✓</td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_9() {
|
||||
let original = r##"| Target | std |rustc|cargo| notes |
|
||||
|-------------------------------|-----|-----|-----|----------------------------|
|
||||
| `x86_64-unknown-linux-musl` | ✓ | | | 64-bit Linux with MUSL |
|
||||
| `arm-linux-androideabi` | ✓ | | | ARM Android |
|
||||
| `arm-unknown-linux-gnueabi` | ✓ | ✓ | | ARM Linux (2.6.18+) |
|
||||
| `arm-unknown-linux-gnueabihf` | ✓ | ✓ | | ARM Linux (2.6.18+) |
|
||||
| `aarch64-unknown-linux-gnu` | ✓ | | | ARM64 Linux (2.6.18+) |
|
||||
| `mips-unknown-linux-gnu` | ✓ | | | MIPS Linux (2.6.18+) |
|
||||
| `mipsel-unknown-linux-gnu` | ✓ | | | MIPS (LE) Linux (2.6.18+) |
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>Target</th><th>std</th><th>rustc</th><th>cargo</th><th>notes</th></tr></thead><tbody>
|
||||
<tr><td><code>x86_64-unknown-linux-musl</code></td><td>✓</td><td></td><td></td><td>64-bit Linux with MUSL</td></tr>
|
||||
<tr><td><code>arm-linux-androideabi</code></td><td>✓</td><td></td><td></td><td>ARM Android</td></tr>
|
||||
<tr><td><code>arm-unknown-linux-gnueabi</code></td><td>✓</td><td>✓</td><td></td><td>ARM Linux (2.6.18+)</td></tr>
|
||||
<tr><td><code>arm-unknown-linux-gnueabihf</code></td><td>✓</td><td>✓</td><td></td><td>ARM Linux (2.6.18+)</td></tr>
|
||||
<tr><td><code>aarch64-unknown-linux-gnu</code></td><td>✓</td><td></td><td></td><td>ARM64 Linux (2.6.18+)</td></tr>
|
||||
<tr><td><code>mips-unknown-linux-gnu</code></td><td>✓</td><td></td><td></td><td>MIPS Linux (2.6.18+)</td></tr>
|
||||
<tr><td><code>mipsel-unknown-linux-gnu</code></td><td>✓</td><td></td><td></td><td>MIPS (LE) Linux (2.6.18+)</td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_10() {
|
||||
let original = r##"|-|-|
|
||||
|ぃ|い|
|
||||
"##;
|
||||
let expected = r##"<p>|-|-|
|
||||
|ぃ|い|</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_11() {
|
||||
let original = r##"|ぁ|ぃ|
|
||||
|-|-|
|
||||
|ぃ|ぃ|
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>ぁ</th><th>ぃ</th></tr></thead><tbody>
|
||||
<tr><td>ぃ</td><td>ぃ</td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_12() {
|
||||
let original = r##"|Колонка 1|Колонка 2|
|
||||
|---------|---------|
|
||||
|Ячейка 1 |Ячейка 2 |
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>Колонка 1</th><th>Колонка 2</th></tr></thead><tbody>
|
||||
<tr><td>Ячейка 1</td><td>Ячейка 2</td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_13() {
|
||||
let original = r##"table a
|
||||
| a | b |
|
||||
| --- | --- |
|
||||
| c | d |
|
||||
|
||||
|
||||
table b
|
||||
| a | b |
|
||||
| --- | --- |
|
||||
| c | d |
|
||||
|
||||
|
||||
table c
|
||||
a | b
|
||||
--- | ---
|
||||
c | d
|
||||
|
||||
|
||||
table d
|
||||
a | b
|
||||
--|--
|
||||
c | d
|
||||
|
||||
|
||||
table e
|
||||
a | b
|
||||
--|--
|
||||
c | d
|
||||
|
||||
table f
|
||||
| a | b |
|
||||
| --- | --- |
|
||||
| c | d |
|
||||
|
||||
|
||||
table g
|
||||
a | b
|
||||
--- | ---
|
||||
c | d
|
||||
|
||||
table h
|
||||
a
|
||||
|-|
|
||||
b
|
||||
|
||||
table i
|
||||
| a
|
||||
|-
|
||||
b
|
||||
|
||||
table j
|
||||
| a
|
||||
-
|
||||
b
|
||||
"##;
|
||||
let expected = r##"<p>table a</p>
|
||||
<table><thead><tr><th>a</th><th>b</th></tr></thead><tbody>
|
||||
<tr><td>c</td><td>d</td></tr>
|
||||
</tbody></table>
|
||||
<p>table b
|
||||
| a | b |
|
||||
| --- | --- |
|
||||
| c | d |</p>
|
||||
<p>table c
|
||||
a | b
|
||||
--- | ---
|
||||
c | d</p>
|
||||
<p>table d
|
||||
a | b
|
||||
--|--
|
||||
c | d</p>
|
||||
<p>table e
|
||||
a | b
|
||||
--|--
|
||||
c | d</p>
|
||||
<p>table f</p>
|
||||
<table><thead><tr><th>a</th><th>b</th></tr></thead><tbody>
|
||||
<tr><td>c</td><td>d</td></tr>
|
||||
</tbody></table>
|
||||
<p>table g
|
||||
a | b
|
||||
--- | ---
|
||||
c | d</p>
|
||||
<p>table h
|
||||
a
|
||||
|-|
|
||||
b</p>
|
||||
<p>table i</p>
|
||||
<table><thead><tr><th>a</th></tr></thead><tbody>
|
||||
<tr><td>b</td></tr>
|
||||
</tbody></table>
|
||||
<h2>table j
|
||||
| a</h2>
|
||||
<p>b</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_14() {
|
||||
let original = r##"a | b
|
||||
- | -
|
||||
1 | 2
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>a</th><th>b</th></tr></thead><tbody>
|
||||
<tr><td>1</td><td>2</td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_15() {
|
||||
let original = r##"a | b\
|
||||
- | -
|
||||
1 | 2
|
||||
"##;
|
||||
let expected = r##"<p>a | b\</p>
|
||||
<ul>
|
||||
<li>| -
|
||||
1 | 2</li>
|
||||
</ul>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_16() {
|
||||
let original = r##"a\
|
||||
| b | c |
|
||||
|---|---|
|
||||
| d | e |
|
||||
"##;
|
||||
let expected = r##"<p>a\</p>
|
||||
<table><thead><tr><th>b</th><th>c</th></tr></thead><tbody>
|
||||
<tr><td>d</td><td>e</td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_17() {
|
||||
let original = r##"| Description | Test case |
|
||||
|-------------|-----------|
|
||||
| Single | `\` |
|
||||
| Double | `\\` |
|
||||
| Basic test | `\|` |
|
||||
| Basic test 2| `\|\|\` |
|
||||
| Basic test 3| `x\|y\|z\`|
|
||||
| Not pipe | `\.` |
|
||||
| Combo | `\.\|\` |
|
||||
| Extra | `\\\.` |
|
||||
| Wait, what? | `\\|` |
|
||||
| Wait, what? | `\\\|` |
|
||||
| Wait, what? | `\\\\|` |
|
||||
| Wait, what? | `\\\\\|` |
|
||||
| Wait, what? | \|
|
||||
| Wait, what? | \\|
|
||||
| Wait, what? | \\\|
|
||||
| Wait, what?x| \|x
|
||||
| Wait, what?x| \\|x
|
||||
| Wait, what?x| \\\|x
|
||||
| Direct trail| \.|x
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>Description</th><th>Test case</th></tr></thead><tbody>
|
||||
<tr><td>Single</td><td><code>\</code></td></tr>
|
||||
<tr><td>Double</td><td><code>\\</code></td></tr>
|
||||
<tr><td>Basic test</td><td><code>|</code></td></tr>
|
||||
<tr><td>Basic test 2</td><td><code>||\</code></td></tr>
|
||||
<tr><td>Basic test 3</td><td><code>x|y|z\</code></td></tr>
|
||||
<tr><td>Not pipe</td><td><code>\.</code></td></tr>
|
||||
<tr><td>Combo</td><td><code>\.|\</code></td></tr>
|
||||
<tr><td>Extra</td><td><code>\\\.</code></td></tr>
|
||||
<tr><td>Wait, what?</td><td><code>\|</code></td></tr>
|
||||
<tr><td>Wait, what?</td><td><code>\\|</code></td></tr>
|
||||
<tr><td>Wait, what?</td><td><code>\\\|</code></td></tr>
|
||||
<tr><td>Wait, what?</td><td><code>\\\\|</code></td></tr>
|
||||
<tr><td>Wait, what?</td><td>|</td></tr>
|
||||
<tr><td>Wait, what?</td><td>|</td></tr>
|
||||
<tr><td>Wait, what?</td><td>\|</td></tr>
|
||||
<tr><td>Wait, what?x</td><td>|x</td></tr>
|
||||
<tr><td>Wait, what?x</td><td>|x</td></tr>
|
||||
<tr><td>Wait, what?x</td><td>\|x</td></tr>
|
||||
<tr><td>Direct trail</td><td>.</td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_18() {
|
||||
let original = r##"| Single | `\|` |
|
||||
|--|--|
|
||||
| Single | `\|` |
|
||||
|
||||
|
||||
| Double | `\\|` |
|
||||
|--|--|
|
||||
| Double | `\\|` |
|
||||
|
||||
|
||||
| Double Twice | `\\|\\|` |
|
||||
|--|--|
|
||||
| Double Twice | `\\|\\|` |
|
||||
|
||||
|
||||
| Triple | `\\\|` |
|
||||
|--|--|
|
||||
| Triple | `\\\|` |
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>Single</th><th><code>|</code></th></tr></thead><tbody>
|
||||
<tr><td>Single</td><td><code>|</code></td></tr>
|
||||
</tbody></table>
|
||||
<table><thead><tr><th>Double</th><th><code>\|</code></th></tr></thead><tbody>
|
||||
<tr><td>Double</td><td><code>\|</code></td></tr>
|
||||
</tbody></table>
|
||||
<table><thead><tr><th>Double Twice</th><th><code>\|\|</code></th></tr></thead><tbody>
|
||||
<tr><td>Double Twice</td><td><code>\|\|</code></td></tr>
|
||||
</tbody></table>
|
||||
<table><thead><tr><th>Triple</th><th><code>\\|</code></th></tr></thead><tbody>
|
||||
<tr><td>Triple</td><td><code>\\|</code></td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_19() {
|
||||
let original = r##"| Table | Header |
|
||||
|-------|--------|
|
||||
| Table | Body |
|
||||
|
|
||||
| Not | Enough |
|
||||
|
||||
|
||||
| Table | Header |
|
||||
|-------|--------|
|
||||
| Table | Body |
|
||||
|
|
||||
| Not | Enough |
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>Table</th><th>Header</th></tr></thead><tbody>
|
||||
<tr><td>Table</td><td>Body</td></tr>
|
||||
</tbody></table>
|
||||
<p>|
|
||||
| Not | Enough |</p>
|
||||
<table><thead><tr><th>Table</th><th>Header</th></tr></thead><tbody>
|
||||
<tr><td>Table</td><td>Body</td></tr>
|
||||
</tbody></table>
|
||||
<p>|
|
||||
| Not | Enough |</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_20() {
|
||||
let original = r##"| Table | Header |
|
||||
|-------|--------|
|
||||
|
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>Table</th><th>Header</th></tr></thead><tbody>
|
||||
</tbody></table>
|
||||
<p>|</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_21() {
|
||||
let original = r##"|
|
||||
|-------|--------|
|
||||
| Table | Body |
|
||||
"##;
|
||||
let expected = r##"<p>|
|
||||
|-------|--------|
|
||||
| Table | Body |</p>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_22() {
|
||||
let original = r##"| Single | [test](first\|second) |
|
||||
|--|--|
|
||||
|
||||
| Double | [test](first\\|second) |
|
||||
|--|--|
|
||||
|
||||
| Triple | [test](first\\\|second) |
|
||||
|--|--|
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>Single</th><th><a href="first%7Csecond">test</a></th></tr></thead><tbody>
|
||||
</tbody></table>
|
||||
<table><thead><tr><th>Double</th><th><a href="first%7Csecond">test</a></th></tr></thead><tbody>
|
||||
</tbody></table>
|
||||
<table><thead><tr><th>Triple</th><th><a href="first%5C%7Csecond">test</a></th></tr></thead><tbody>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_23() {
|
||||
let original = r##"| Single | [first\|second] |
|
||||
|--|--|
|
||||
|
||||
| Double | [first\\|second] |
|
||||
|--|--|
|
||||
|
||||
| Triple | [first\\\|second] |
|
||||
|--|--|
|
||||
|
||||
[first\|second]: https://rust-lang.org
|
||||
|
||||
[first\\|second]: https://docs.rs
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>Single</th><th>[first|second]</th></tr></thead><tbody>
|
||||
</tbody></table>
|
||||
<table><thead><tr><th>Double</th><th><a href="https://rust-lang.org">first|second</a></th></tr></thead><tbody>
|
||||
</tbody></table>
|
||||
<table><thead><tr><th>Triple</th><th><a href="https://docs.rs">first\|second</a></th></tr></thead><tbody>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_24() {
|
||||
let original = r##"Q: Knock knock.
|
||||
A: Who's there.
|
||||
Q: Interrupting cow.
|
||||
A: Interrupting —?
|
||||
| `Moo\\|ooo` |
|
||||
|-------------|
|
||||
| `ooo\\|ooo` |
|
||||
"##;
|
||||
let expected = r##"<p>Q: Knock knock.
|
||||
A: Who's there.
|
||||
Q: Interrupting cow.
|
||||
A: Interrupting —?</p>
|
||||
<table><thead><tr><th><code>Moo\|ooo</code></th></tr></thead><tbody>
|
||||
<tr><td><code>ooo\|ooo</code></td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_25() {
|
||||
let original = r##"|  |
|
||||
|-------------|
|
||||
|  |
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th><img src="image.png" alt="Moo|Moo" /></th></tr></thead><tbody>
|
||||
<tr><td><img src="image.png" alt="Moo\|Moo" /></td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_26() {
|
||||
let original = r##"| [Moo](https://example.org "Example\\|Link") |
|
||||
|---------------------------------------------|
|
||||
| [Moo](https://example.org "Example\\\|Link") |
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th><a href="https://example.org" title="Example|Link">Moo</a></th></tr></thead><tbody>
|
||||
<tr><td><a href="https://example.org" title="Example\|Link">Moo</a></td></tr>
|
||||
</tbody></table>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_27() {
|
||||
let original = r##"moo | moo
|
||||
----|----
|
||||
moo | moo
|
||||
*
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>moo</th><th>moo</th></tr></thead><tbody>
|
||||
<tr><td>moo</td><td>moo</td></tr>
|
||||
</tbody></table>
|
||||
<ul>
|
||||
<li></li>
|
||||
</ul>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_test_28() {
|
||||
let original = r##"moo | moo
|
||||
----|----
|
||||
moo | moo
|
||||
2.
|
||||
"##;
|
||||
let expected = r##"<table><thead><tr><th>moo</th><th>moo</th></tr></thead><tbody>
|
||||
<tr><td>moo</td><td>moo</td></tr>
|
||||
</tbody></table>
|
||||
<ol start="2">
|
||||
<li></li>
|
||||
</ol>
|
||||
"##;
|
||||
|
||||
test_markdown_html(original, expected, false, false, false);
|
||||
}
|
||||
Reference in new issue
Block a user