diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml new file mode 100644 index 0000000..0bcab8c --- /dev/null +++ b/.github/workflows/pipeline.yaml @@ -0,0 +1,40 @@ +name: Build +on: + pull_request: + push: + branches: [master] + +jobs: + + nightly-clippy: + name: Nightly clippy (wasm32) + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + target: wasm32-unknown-unknown + components: clippy + override: true + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: -p vertigo-cmark --all-features --target wasm32-unknown-unknown -- -Dwarnings + name: Vertigo-cmark Clippy Output + + # Uncomment after vertigo 0.8.4 + # nightly-tests: + # name: Nightly tests + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v2 + # - uses: actions-rs/toolchain@v1 + # with: + # toolchain: nightly + # override: true + # - uses: actions-rs/cargo@v1 + # with: + # command: test + # args: --all-features diff --git a/.gitignore b/.gitignore index 6839061..8e62d94 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ /target -Cargo.lock Cargo.toml.overrides diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..871c92e --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,22 @@ + + + +## 0.1.0 - 2025-10-03 + +### Added + +* Regular, bod, italic, strike-through text +* Headings +* Paragraphs +* Tables +* Blockquotes +* Codeblocks +* Code highlighting (with `syntect` feature) +* Lists (numbers, bullets) +* Rules +* Task list markers +* Footnotes +* Soft/hard breaks +* Links +* Images +* Html diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e75344d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,923 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "css-color" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42aaeae719fd78ce501d77c6cdf01f7e96f26bcd5617a4903a1c2b97e388543a" + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.106", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "deranged" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive-where" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fancy-regex" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getopts" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "html5tokenizer" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f0913f97f19bf45cdef63d2cb153c388ef57267984d7a15a4ef678e0da29afe" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "libc" +version = "0.2.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "pest" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc58706f770acb1dbd0973e6530a3cff4746fb721207feb3a8a6064cd0b6c663" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d4f36811dfe07f7b8573462465d5cb8965fffc2e71ae377a33aecf14c2c9a2f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "pest_meta" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42919b05089acbd0a5dcd5405fb304d17d1053847b81163d09c4ad18ce8e8420" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "pkg-version" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e848f61ee4b2010345e65757e427a077213af1cee5d3e6a02e4a151dabca377" +dependencies = [ + "pkg-version-impl", + "proc-macro-hack", +] + +[[package]] +name = "pkg-version-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1564bf5d476bf4a5eac420b88c500454c000dca79cef0a2e4304a1fe34361a3b" +dependencies = [ + "proc-macro-hack", +] + +[[package]] +name = "plist" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +dependencies = [ + "base64", + "indexmap", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "version_check", + "yansi", +] + +[[package]] +name = "pulldown-cmark" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" +dependencies = [ + "bitflags", + "getopts", + "memchr", + "pulldown-cmark-escape", + "unicase", +] + +[[package]] +name = "pulldown-cmark-escape" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" + +[[package]] +name = "quick-xml" +version = "0.38.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex-automata" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[package]] +name = "rstml" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61cf4616de7499fc5164570d40ca4e1b24d231c6833a88bff0fe00725080fd56" +dependencies = [ + "derive-where", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.106", + "syn_derive", + "thiserror", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb066a04799e45f5d582e8fc6ec8e6d6896040d00898eb4e6a835196815b219" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "syntect" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" +dependencies = [ + "bincode", + "fancy-regex", + "flate2", + "fnv", + "once_cell", + "plist", + "regex-syntax", + "serde", + "serde_derive", + "serde_json", + "thiserror", + "walkdir", + "yaml-rust", +] + +[[package]] +name = "tailwind-ast" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac41127bfd988693b7beaa0a49de45bd452ebeb3bafec2387a4de25bdca6420e" +dependencies = [ + "nom", +] + +[[package]] +name = "tailwind-css" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f319cee4a7ae6c915292d55d6d7ef10266512dec123283921055c6cd7562f1a5" +dependencies = [ + "css-color", + "itertools 0.10.5", + "log", + "nom", + "tailwind-ast", + "tailwind-error", + "xxhash-rust", +] + +[[package]] +name = "tailwind-error" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6223cefeaba484fd356eec1f7e31ea3eb18e0348a0fc6222f877e0d2a76b76b6" +dependencies = [ + "css-color", + "nom", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vertigo" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c91668f09013330be8f4c679e485615e72d943edef9d3c4cd037318945fbcb" +dependencies = [ + "log", + "vertigo-macro", +] + +[[package]] +name = "vertigo-cmark" +version = "0.1.0" +dependencies = [ + "html5tokenizer", + "pulldown-cmark", + "syntect", + "vertigo", +] + +[[package]] +name = "vertigo-macro" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dafcc4d6a43aa373ad8d09e3790860eee6e2d608013249b02aed30d4a7d8e012" +dependencies = [ + "base64", + "crc", + "darling", + "itertools 0.14.0", + "pest", + "pest_derive", + "pkg-version", + "proc-macro-error", + "proc-macro2", + "quote", + "rstml", + "syn 2.0.106", + "tailwind-css", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-sys" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +dependencies = [ + "windows-link", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/Cargo.toml b/Cargo.toml index fb34346..886f169 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,16 @@ [package] name = "vertigo-cmark" -version = "0.1.0-alpha.3" -authors = ["Michal Pokrywka "] -edition = "2018" -license = "MIT OR Apache-2.0" +version = "0.1.0" +authors = ["MichaƂ Pokrywka "] description = "Allows to render CommonMark inside Vertigo tree" +readme = "README.md" +keywords = ["wasm", "web", "isomorphic", "reactive", "markdown", "commonmark"] +homepage = "https://vertigo.znoj.pl" +repository = "https://github.com/vertigo-web/vertigo-cmark" +documentation = "https://docs.rs/vertigo-cmark/" +categories = ["asynchronous", "gui", "wasm", "web-programming"] +license = "MIT OR Apache-2.0" +edition = "2024" [features] default = ["html"] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..f646235 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021-NOW Vertigo Team + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..9ff678b --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2021-2025 Vertigo Team + +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. diff --git a/README.md b/README.md index a970f9c..167f264 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,24 @@ Converts CommonMark string into rendered vertigo DomElement. -```toml -vertigo-cmark = { git = "https://github.com/vertigo-web/vertigo-cmark" } -``` +[![crates.io](https://img.shields.io/crates/v/vertigo-cmark)](https://crates.io/crates/vertigo-cmark) +[![Documentation](https://docs.rs/vertigo-cmark/badge.svg)](https://docs.rs/vertigo-cmark) +![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/vertigo-cmark.svg) +[![Dependency Status](https://deps.rs/crate/vertigo-cmark/0.1.0/status.svg)](https://deps.rs/crate/vertigo-cmark/0.1.0) +[![CI](https://github.com/vertigo-web/vertigo-cmark/actions/workflows/pipeline.yaml/badge.svg)](https://github.com/vertigo-web/vertigo-cmark/actions/workflows/pipeline.yaml) +[![downloads](https://img.shields.io/crates/d/vertigo-cmark.svg)](https://crates.io/crates/vertigo-cmark) + +See [Changelog](https://github.com/vertigo-web/vertigo-cmark/blob/master/CHANGES.md) for recent features. ## Example +Dependencies: + +```toml +vertigo = "0.8" +vertigo-cmark = "0.1" +``` + ```rust use vertigo::{start_app, DomElement, dom}; @@ -65,4 +77,4 @@ pub fn start_application() { - [x] Soft/hard breaks - [x] Links - [x] Images -- [x] Html +- [x] Html (with `html` feature) diff --git a/src/generate.rs b/src/generate.rs deleted file mode 100644 index 0493fcd..0000000 --- a/src/generate.rs +++ /dev/null @@ -1,536 +0,0 @@ -// Based on https://github.com/pulldown-cmark/pulldown-cmark/blob/master/pulldown-cmark/src/html.rs - -use pulldown_cmark::{ - Alignment, BlockQuoteKind, CodeBlockKind, CowStr, Event, Event::*, HeadingLevel, LinkType, Tag, - TagEnd, -}; -use std::{ - collections::{HashMap, VecDeque}, - rc::Rc, -}; -use vertigo::{log, Css, DomElement, DomNode, DomText}; - -#[cfg(feature = "syntect")] -use crate::highlighting::highlight; -use crate::styling::CMarkStyle; - -enum TableState { - Head, - Body, -} - -struct VertigoWriter<'a, I> { - /// Iterator supplying events. - iter: I, - - /// Whether if inside a metadata block (text should not be written) - in_non_writing_block: bool, - - table_state: TableState, - table_alignments: Vec, - table_cell_index: usize, - numbers: HashMap, usize>, - - // Stack of nested nodes - soc: VecDeque, - - styling: Rc, - - #[cfg(feature = "syntect")] - in_code_block: Option>, -} - -impl<'a, I> VertigoWriter<'a, I> -where - I: Iterator>, -{ - fn new(iter: I, styling: CMarkStyle) -> Self { - Self { - iter, - in_non_writing_block: false, - table_state: TableState::Head, - table_alignments: vec![], - table_cell_index: 0, - numbers: HashMap::new(), - soc: VecDeque::new(), - styling: Rc::new(styling), - #[cfg(feature = "syntect")] - in_code_block: None, - } - } - - fn run(mut self) -> DomNode { - self.push_element_styled(DomElement::new("div"), &self.styling.clone().container); - 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 { - #[cfg(feature = "syntect")] - if let Some(ref info) = self.in_code_block { - for el in highlight(info, &text) { - self.add_child(el) - } - } else { - self.add_child(DomText::new(text)); - } - #[cfg(not(feature = "syntect"))] - self.add_child(DomText::new(text)); - } - } - Code(text) => { - let element = DomElement::new("code").child(DomText::new(text)); - self.add_child(element); - } - InlineMath(text) => { - let element = DomElement::new("span") - .attr("class", "math math-inline") - .child(DomText::new(text)); - self.add_child(element); - } - DisplayMath(text) => { - let element = DomElement::new("span") - .attr("class", "math math-display") - .child(DomText::new(text)); - self.add_child(element); - } - Html(html) | InlineHtml(html) => { - #[cfg(feature = "html")] - self.html(&html); - #[cfg(not(feature = "html"))] - let _ = html; - } - SoftBreak => { - // Add space to not glue sibling texts in render - self.add_child(DomText::new(" ")); - } - HardBreak => { - self.add_child_name("br"); - } - Rule => { - self.add_child_name("hr"); - } - FootnoteReference(name) => { - let len = self.numbers.len() + 1; - let link = DomElement::new("a").attr("href", ["#", name.as_ref()].concat()); - let number = *self.numbers.entry(name).or_insert(len); - link.add_child_text(number.to_string()); - let mut element = DomElement::new("sup") - .attr("class", "footnote-reference") - .child(link); - if !self.styling.sup.groups.is_empty() { - element = element.css(&self.styling.sup); - } - self.add_child(element); - } - TaskListMarker(true) => { - self.add_child( - DomElement::new("input") - .attr("disabled", "") - .attr("type", "checkbox") - .attr("checked", "checked"), - ); - } - TaskListMarker(false) => { - self.add_child( - DomElement::new("input") - .attr("disabled", "") - .attr("type", "checkbox"), - ); - } - } - } - self.pop_node().unwrap_or_else(|| { - log::error!("Popping nesting did not produce root node!"); - DomElement::new("div").into() - }) - } - - /// Pushes dom element on stack - fn start_tag(&mut self, tag: Tag<'a>) { - let styling = self.styling.clone(); - match &tag { - Tag::HtmlBlock => {} - Tag::Paragraph => { - self.push_elname("p", &styling.p); - } - Tag::Heading { - level, - id, - classes, - attrs: _, // Vertigo doesn't support dynamic attributes keys - } => { - let (el_name, css) = match level { - HeadingLevel::H1 => ("h1", &styling.h1), - HeadingLevel::H2 => ("h2", &styling.h2), - HeadingLevel::H3 => ("h3", &styling.h3), - HeadingLevel::H4 => ("h4", &styling.h4), - HeadingLevel::H5 => ("h5", &styling.h5), - HeadingLevel::H6 => ("h6", &styling.h6), - }; - let element = DomElement::new(el_name); - if let Some(id) = id { - element.add_attr("id", id); - } - if !classes.is_empty() { - let value = classes.join(" "); - element.add_attr("class", value); - } - self.push_element_styled(element, css); - } - Tag::Table(alignments) => { - self.table_alignments = alignments.clone(); - self.push_element_styled(DomElement::new("table"), &styling.table) - } - Tag::TableHead => { - self.table_state = TableState::Head; - self.table_cell_index = 0; - self.push_elname("thead", &styling.thead); - self.push_elname("tr", &styling.tr); - } - Tag::TableRow => { - self.table_cell_index = 0; - self.push_elname("tr", &styling.tr); - } - Tag::TableCell => { - let (el_name, style) = match self.table_state { - TableState::Head => ("th", &styling.th), - TableState::Body => ("td", &styling.td), - }; - let element = DomElement::new(el_name); - match self.table_alignments.get(self.table_cell_index) { - Some(&Alignment::Left) => element.add_attr("style", "text-align: left"), - Some(&Alignment::Center) => element.add_attr("style", "text-align: center"), - Some(&Alignment::Right) => element.add_attr("style", "text-align: right"), - _ => (), - } - self.push_element_styled(element, style); - } - #[cfg(feature = "syntect")] - Tag::CodeBlock(info) => { - if let CodeBlockKind::Fenced(info) = info { - self.push_element_styled(DomElement::new("pre"), &styling.codeblock); - // TODO: info - self.in_code_block = Some(info.clone()); - } - } - #[cfg(not(feature = "syntect"))] - Tag::CodeBlock(info) => { - self.push_element_styled(DomElement::new("pre"), &styling.codeblock); - let element = DomElement::new("code"); - match info { - CodeBlockKind::Fenced(info) => { - let lang = info.split(' ').next().unwrap_or_default(); - if !lang.is_empty() { - element.add_attr("class", format!("language-{lang}")); - } - } - CodeBlockKind::Indented => {} - }; - self.push_node(element); - } - Tag::BlockQuote(kind) => { - let element = DomElement::new("blockquote"); - - if let Some(kind) = kind { - let kind_value = match kind { - BlockQuoteKind::Note => "markdown-alert-note", - BlockQuoteKind::Tip => "markdown-alert-tip", - BlockQuoteKind::Important => "markdown-alert-important", - BlockQuoteKind::Warning => "markdown-alert-warning", - BlockQuoteKind::Caution => "markdown-alert-caution", - }; - element.add_attr("class", kind_value); - }; - self.push_element_styled(element, &styling.blockquote); - } - Tag::List(Some(1)) => self.push_elname("ol", &styling.ol), - Tag::List(Some(start)) => { - self.push_element_styled(DomElement::new("ol").attr("start", start), &styling.ol); - } - Tag::List(None) => self.push_elname("ul", &styling.ul), - Tag::Item => self.push_elname("li", &styling.li), - Tag::DefinitionList => self.push_elname("dl", &styling.dl), - Tag::DefinitionListTitle => self.push_elname("dt", &styling.dt), - Tag::DefinitionListDefinition => self.push_elname("dd", &styling.dd), - Tag::Subscript => self.push_elname("sub", &styling.sub), - Tag::Superscript => self.push_elname("sup", &styling.sup), - Tag::Emphasis => self.push_elname("em", &styling.em), - Tag::Strong => self.push_elname("strong", &styling.strong), - Tag::Strikethrough => self.push_elname("del", &styling.del), - Tag::Link { - link_type, - dest_url, - title, - id: _, - } => { - let prefix = match link_type { - LinkType::Email => "mailto:", - _ => "", - }; - let element = DomElement::new("a").attr("href", [prefix, dest_url].concat()); - if !title.is_empty() { - element.add_attr("title", title); - } - self.push_element_styled(element, &styling.a); - } - Tag::Image { - link_type: _, - dest_url, - title, - id: _, - } => { - let mut element = DomElement::new("img") - .attr("src", dest_url) - .attr("alt", self.raw_text()); - - if !styling.img.groups.is_empty() { - element = element.css(&styling.img); - } - if !title.is_empty() { - element.add_attr("title", title); - } - self.add_child(element); - } - Tag::FootnoteDefinition(name) => { - let len = self.numbers.len() + 1; - let number = *self.numbers.entry(name.clone()).or_insert(len); - let mut sup_element = DomElement::new("sup") - .attr("class", "footnote-definition-label") - .child(DomText::new(number.to_string())); - if !styling.sup.groups.is_empty() { - sup_element = sup_element.css(&styling.sub); - } - self.push_node( - DomElement::new("div") - .attr("class", "footnote-definition") - .attr("id", name) - .child(sup_element), - ); - } - Tag::MetadataBlock(_) => { - self.in_non_writing_block = true; - } - } - } - - fn end_tag(&mut self, tag: TagEnd) { - match tag { - TagEnd::HtmlBlock => {} - TagEnd::Table => { - // - self.pop_node(); - self.pop_node(); - } - TagEnd::TableHead => { - // - self.pop_node(); - self.pop_node(); - self.push_elname("tbody", &self.styling.clone().tbody); - self.table_state = TableState::Body; - } - TagEnd::TableCell => { - self.pop_node(); - self.table_cell_index += 1; - } - TagEnd::CodeBlock => { - // or - self.pop_node(); - #[cfg(feature = "syntect")] - { - self.in_code_block = None; - } - #[cfg(not(feature = "syntect"))] - { - self.pop_node(); - } - } - TagEnd::TableRow - | TagEnd::Paragraph - | TagEnd::Heading(_) - | TagEnd::BlockQuote(_) - | TagEnd::List(_) - | TagEnd::Item - | TagEnd::DefinitionList - | TagEnd::DefinitionListTitle - | TagEnd::DefinitionListDefinition - | TagEnd::Subscript - | TagEnd::Superscript - | TagEnd::Emphasis - | TagEnd::Strong - | TagEnd::Strikethrough - | TagEnd::Link - | TagEnd::FootnoteDefinition => { - self.pop_node(); - } - TagEnd::Image => {} // shouldn't happen, handled in start - TagEnd::MetadataBlock(_) => { - self.in_non_writing_block = false; - } - } - } - - // run raw text, consuming end tag - fn raw_text(&mut self) -> String { - let mut nest = 0; - let mut writer = String::new(); - for event in self.iter.by_ref() { - match event { - Start(_) => nest += 1, - End(_) => { - if nest == 0 { - break; - } - nest -= 1; - } - Html(_) => {} - InlineHtml(text) | Code(text) | Text(text) => { - writer.push_str(&text); - } - InlineMath(text) => { - writer.push('$'); - writer.push_str(&text); - writer.push('$'); - } - DisplayMath(text) => { - writer.push_str("$$"); - writer.push_str(&text); - writer.push_str("$$"); - } - SoftBreak | HardBreak | Rule => { - writer.push(' '); - } - FootnoteReference(name) => { - let len = self.numbers.len() + 1; - let number = *self.numbers.entry(name).or_insert(len); - writer.push_str(&format!("[{}]", number)); - } - TaskListMarker(true) => { - writer.push_str("[x]"); - } - TaskListMarker(false) => { - writer.push_str("[ ]"); - } - } - } - writer - } - - #[cfg(feature = "html")] - fn html(&mut self, input: &str) { - use html5tokenizer::{NaiveParser, Token}; - - let mut chars: Vec = vec![]; - - let consume_chars = |myself: &mut Self, chars: &mut Vec| { - if !chars.is_empty() { - let inner_text: String = chars.drain(..).collect(); - if let Some(node) = myself.soc.front_mut() { - match node { - DomNode::Node { node } => { - node.add_child_text(inner_text); - } - _ => { - log::error!("Ignored inner text `{}` (invalid parent)", inner_text); - } - } - } else { - log::error!("Ignored inner text `{}` (no parent)", inner_text); - } - } - }; - - for token in NaiveParser::new(&input.to_string()).flatten() { - match token { - Token::StartTag(tag) => { - consume_chars(self, &mut chars); - let v_el = DomElement::new(tag.name); - for attr in tag.attributes { - v_el.add_attr(attr.name, attr.value); - } - self.push_node(v_el); - - if tag.self_closing { - self.pop_node(); - } - } - Token::EndTag(_tag) => { - consume_chars(self, &mut chars); - self.pop_node(); - } - Token::Char(x) => { - chars.push(x); - } - Token::EndOfFile | Token::Comment(_) | Token::Doctype(_) => {} - } - } - } - - fn push_node(&mut self, node: impl Into) { - self.soc.push_front(node.into()); - } - - fn push_element_styled(&mut self, element: DomElement, css: &Css) { - let mut element = element; - if !css.groups.is_empty() { - element = element.css(css) - } - self.soc.push_front(element.into()); - } - - fn push_elname(&mut self, name: impl Into, css: &Css) { - let name = name.into(); - let mut element = DomElement::new(name); - if !css.groups.is_empty() { - element = element.css(css) - } - self.push_node(element); - } - - fn pop_node(&mut self) -> Option { - if let Some(child) = self.soc.pop_front() { - match self.soc.front_mut() { - Some(parent) => { - match parent { - DomNode::Node { node } => node.add_child(child), - _ => { - unreachable!("Can't push children to non-element node"); - } - } - return None; - } - None => return Some(child), - } - } - None - } - - fn add_child(&mut self, child: impl Into) { - if let Some(parent) = self.soc.front_mut() { - match parent { - DomNode::Node { node } => node.add_child(child), - _ => log::error!("Can't push child to non-element node (2)"), - } - } else { - log::error!("Can't add child without parent node") - } - } - - fn add_child_name(&mut self, child_name: impl Into) { - self.add_child(DomElement::new(child_name.into())); - } -} - -/// Iterate over an iterator of pulldown's events, generate DomNode for each `Event`, -/// structure it into DOM tree and return the root node. -pub fn generate_tree<'a, I>(iter: I, styling: CMarkStyle) -> DomNode -where - I: Iterator>, -{ - VertigoWriter::new(iter, styling).run() -} diff --git a/src/generate/end_tag.rs b/src/generate/end_tag.rs new file mode 100644 index 0000000..2ba3ff0 --- /dev/null +++ b/src/generate/end_tag.rs @@ -0,0 +1,66 @@ +// Based on https://github.com/pulldown-cmark/pulldown-cmark/blob/master/pulldown-cmark/src/html.rs + +use pulldown_cmark::{Event, TagEnd}; + +use super::writer::{TableState, VertigoWriter}; + +impl<'a, I> VertigoWriter<'a, I> +where + I: Iterator>, +{ + pub(super) fn end_tag(&mut self, tag: TagEnd) { + match tag { + TagEnd::HtmlBlock => {} + TagEnd::Table => { + // + self.pop_node(); + self.pop_node(); + } + TagEnd::TableHead => { + // + self.pop_node(); + self.pop_node(); + self.push_elname("tbody", &self.styling.clone().tbody); + self.table_state = TableState::Body; + } + TagEnd::TableCell => { + self.pop_node(); + self.table_cell_index += 1; + } + TagEnd::CodeBlock => { + // or + self.pop_node(); + #[cfg(feature = "syntect")] + { + self.in_code_block = None; + } + #[cfg(not(feature = "syntect"))] + { + self.pop_node(); + } + } + TagEnd::TableRow + | TagEnd::Paragraph + | TagEnd::Heading(_) + | TagEnd::BlockQuote(_) + | TagEnd::List(_) + | TagEnd::Item + | TagEnd::DefinitionList + | TagEnd::DefinitionListTitle + | TagEnd::DefinitionListDefinition + | TagEnd::Subscript + | TagEnd::Superscript + | TagEnd::Emphasis + | TagEnd::Strong + | TagEnd::Strikethrough + | TagEnd::Link + | TagEnd::FootnoteDefinition => { + self.pop_node(); + } + TagEnd::Image => {} // shouldn't happen, handled in start + TagEnd::MetadataBlock(_) => { + self.in_non_writing_block = false; + } + } + } +} diff --git a/src/generate/html.rs b/src/generate/html.rs new file mode 100644 index 0000000..95a5aeb --- /dev/null +++ b/src/generate/html.rs @@ -0,0 +1,60 @@ +// Based on https://github.com/pulldown-cmark/pulldown-cmark/blob/master/pulldown-cmark/src/html.rs + +use pulldown_cmark::Event; +use vertigo::{DomElement, DomNode, log}; + +use super::VertigoWriter; + +impl<'a, I> VertigoWriter<'a, I> +where + I: Iterator>, +{ + pub(super) fn html(&mut self, input: &str) { + use html5tokenizer::{NaiveParser, Token}; + + let mut chars: Vec = vec![]; + + let consume_chars = |myself: &mut Self, chars: &mut Vec| { + if !chars.is_empty() { + let inner_text: String = chars.drain(..).collect(); + if let Some(node) = myself.soc.front_mut() { + match node { + DomNode::Node { node } => { + node.add_child_text(inner_text); + } + _ => { + log::error!("Ignored inner text `{}` (invalid parent)", inner_text); + } + } + } else { + log::error!("Ignored inner text `{}` (no parent)", inner_text); + } + } + }; + + for token in NaiveParser::new(&input.to_string()).flatten() { + match token { + Token::StartTag(tag) => { + consume_chars(self, &mut chars); + let v_el = DomElement::new(tag.name); + for attr in tag.attributes { + v_el.add_attr(attr.name, attr.value); + } + self.push_node(v_el); + + if tag.self_closing { + self.pop_node(); + } + } + Token::EndTag(_tag) => { + consume_chars(self, &mut chars); + self.pop_node(); + } + Token::Char(x) => { + chars.push(x); + } + Token::EndOfFile | Token::Comment(_) | Token::Doctype(_) => {} + } + } + } +} diff --git a/src/generate/mod.rs b/src/generate/mod.rs new file mode 100644 index 0000000..27c50b4 --- /dev/null +++ b/src/generate/mod.rs @@ -0,0 +1,26 @@ +// Based on https://github.com/pulldown-cmark/pulldown-cmark/blob/master/pulldown-cmark/src/html.rs + +use pulldown_cmark::Event; +use vertigo::DomNode; + +use crate::styling::CMarkStyle; + +#[cfg(feature = "html")] +mod html; + +mod end_tag; +mod raw_text; +mod run; +mod start_tag; + +mod writer; +use writer::VertigoWriter; + +/// Iterate over an iterator of pulldown's events, generate DomNode for each `Event`, +/// structure it into DOM tree and return the root node. +pub fn generate_tree<'a, I>(iter: I, styling: CMarkStyle) -> DomNode +where + I: Iterator>, +{ + VertigoWriter::new(iter, styling).run() +} diff --git a/src/generate/raw_text.rs b/src/generate/raw_text.rs new file mode 100644 index 0000000..36de060 --- /dev/null +++ b/src/generate/raw_text.rs @@ -0,0 +1,56 @@ +// Based on https://github.com/pulldown-cmark/pulldown-cmark/blob/master/pulldown-cmark/src/html.rs + +use pulldown_cmark::{Event, Event::*}; + +use super::VertigoWriter; + +impl<'a, I> VertigoWriter<'a, I> +where + I: Iterator>, +{ + // run raw text, consuming end tag + pub(super) fn raw_text(&mut self) -> String { + let mut nest = 0; + let mut writer = String::new(); + for event in self.iter.by_ref() { + match event { + Start(_) => nest += 1, + End(_) => { + if nest == 0 { + break; + } + nest -= 1; + } + Html(_) => {} + InlineHtml(text) | Code(text) | Text(text) => { + writer.push_str(&text); + } + InlineMath(text) => { + writer.push('$'); + writer.push_str(&text); + writer.push('$'); + } + DisplayMath(text) => { + writer.push_str("$$"); + writer.push_str(&text); + writer.push_str("$$"); + } + SoftBreak | HardBreak | Rule => { + writer.push(' '); + } + FootnoteReference(name) => { + let len = self.numbers.len() + 1; + let number = *self.numbers.entry(name).or_insert(len); + writer.push_str(&format!("[{}]", number)); + } + TaskListMarker(true) => { + writer.push_str("[x]"); + } + TaskListMarker(false) => { + writer.push_str("[ ]"); + } + } + } + writer + } +} diff --git a/src/generate/run.rs b/src/generate/run.rs new file mode 100644 index 0000000..4c9abc7 --- /dev/null +++ b/src/generate/run.rs @@ -0,0 +1,106 @@ +// Based on https://github.com/pulldown-cmark/pulldown-cmark/blob/master/pulldown-cmark/src/html.rs + +use pulldown_cmark::{Event, Event::*}; +use vertigo::{DomElement, DomNode, DomText, log}; + +#[cfg(feature = "syntect")] +use crate::highlighting::highlight; + +use super::VertigoWriter; + +impl<'a, I> VertigoWriter<'a, I> +where + I: Iterator>, +{ + pub(super) fn run(mut self) -> DomNode { + self.push_element_styled(DomElement::new("div"), &self.styling.clone().container); + 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 { + #[cfg(feature = "syntect")] + if let Some(ref info) = self.in_code_block { + for el in highlight(info, &text) { + self.add_child(el) + } + } else { + self.add_child(DomText::new(text)); + } + #[cfg(not(feature = "syntect"))] + self.add_child(DomText::new(text)); + } + } + Code(text) => { + let element = DomElement::new("code").child(DomText::new(text)); + self.add_child(element); + } + InlineMath(text) => { + let element = DomElement::new("span") + .attr("class", "math math-inline") + .child(DomText::new(text)); + self.add_child(element); + } + DisplayMath(text) => { + let element = DomElement::new("span") + .attr("class", "math math-display") + .child(DomText::new(text)); + self.add_child(element); + } + Html(html) | InlineHtml(html) => { + #[cfg(feature = "html")] + self.html(&html); + #[cfg(not(feature = "html"))] + let _ = html; + } + SoftBreak => { + // Add space to not glue sibling texts in render + self.add_child(DomText::new(" ")); + } + HardBreak => { + self.add_child_name("br"); + } + Rule => { + self.add_child_name("hr"); + } + FootnoteReference(name) => { + let len = self.numbers.len() + 1; + let link = DomElement::new("a").attr("href", ["#", name.as_ref()].concat()); + let number = *self.numbers.entry(name).or_insert(len); + link.add_child_text(number.to_string()); + let mut element = DomElement::new("sup") + .attr("class", "footnote-reference") + .child(link); + if !self.styling.sup.groups.is_empty() { + element = element.css(&self.styling.sup); + } + self.add_child(element); + } + TaskListMarker(true) => { + self.add_child( + DomElement::new("input") + .attr("disabled", "") + .attr("type", "checkbox") + .attr("checked", "checked"), + ); + } + TaskListMarker(false) => { + self.add_child( + DomElement::new("input") + .attr("disabled", "") + .attr("type", "checkbox"), + ); + } + } + } + self.pop_node().unwrap_or_else(|| { + log::error!("Popping nesting did not produce root node!"); + DomElement::new("div").into() + }) + } +} diff --git a/src/generate/start_tag.rs b/src/generate/start_tag.rs new file mode 100644 index 0000000..73a8ccc --- /dev/null +++ b/src/generate/start_tag.rs @@ -0,0 +1,181 @@ +// Based on https://github.com/pulldown-cmark/pulldown-cmark/blob/master/pulldown-cmark/src/html.rs + +use pulldown_cmark::{ + Alignment, BlockQuoteKind, CodeBlockKind, Event, HeadingLevel, LinkType, Tag, +}; +use vertigo::{DomElement, DomText}; + +use super::writer::{TableState, VertigoWriter}; + +impl<'a, I> VertigoWriter<'a, I> +where + I: Iterator>, +{ + /// Pushes dom element on stack + pub(super) fn start_tag(&mut self, tag: Tag<'a>) { + let styling = self.styling.clone(); + match &tag { + Tag::HtmlBlock => {} + Tag::Paragraph => { + self.push_elname("p", &styling.p); + } + Tag::Heading { + level, + id, + classes, + attrs: _, // Vertigo doesn't support dynamic attributes keys + } => { + let (el_name, css) = match level { + HeadingLevel::H1 => ("h1", &styling.h1), + HeadingLevel::H2 => ("h2", &styling.h2), + HeadingLevel::H3 => ("h3", &styling.h3), + HeadingLevel::H4 => ("h4", &styling.h4), + HeadingLevel::H5 => ("h5", &styling.h5), + HeadingLevel::H6 => ("h6", &styling.h6), + }; + let element = DomElement::new(el_name); + if let Some(id) = id { + element.add_attr("id", id); + } + if !classes.is_empty() { + let value = classes.join(" "); + element.add_attr("class", value); + } + self.push_element_styled(element, css); + } + Tag::Table(alignments) => { + self.table_alignments = alignments.clone(); + self.push_element_styled(DomElement::new("table"), &styling.table) + } + Tag::TableHead => { + self.table_state = TableState::Head; + self.table_cell_index = 0; + self.push_elname("thead", &styling.thead); + self.push_elname("tr", &styling.tr); + } + Tag::TableRow => { + self.table_cell_index = 0; + self.push_elname("tr", &styling.tr); + } + Tag::TableCell => { + let (el_name, style) = match self.table_state { + TableState::Head => ("th", &styling.th), + TableState::Body => ("td", &styling.td), + }; + let element = DomElement::new(el_name); + match self.table_alignments.get(self.table_cell_index) { + Some(&Alignment::Left) => element.add_attr("style", "text-align: left"), + Some(&Alignment::Center) => element.add_attr("style", "text-align: center"), + Some(&Alignment::Right) => element.add_attr("style", "text-align: right"), + _ => (), + } + self.push_element_styled(element, style); + } + #[cfg(feature = "syntect")] + Tag::CodeBlock(info) => { + if let CodeBlockKind::Fenced(info) = info { + self.push_element_styled(DomElement::new("pre"), &styling.codeblock); + // TODO: info + self.in_code_block = Some(info.clone()); + } + } + #[cfg(not(feature = "syntect"))] + Tag::CodeBlock(info) => { + self.push_element_styled(DomElement::new("pre"), &styling.codeblock); + let element = DomElement::new("code"); + match info { + CodeBlockKind::Fenced(info) => { + let lang = info.split(' ').next().unwrap_or_default(); + if !lang.is_empty() { + element.add_attr("class", format!("language-{lang}")); + } + } + CodeBlockKind::Indented => {} + }; + self.push_node(element); + } + Tag::BlockQuote(kind) => { + let element = DomElement::new("blockquote"); + + if let Some(kind) = kind { + let kind_value = match kind { + BlockQuoteKind::Note => "markdown-alert-note", + BlockQuoteKind::Tip => "markdown-alert-tip", + BlockQuoteKind::Important => "markdown-alert-important", + BlockQuoteKind::Warning => "markdown-alert-warning", + BlockQuoteKind::Caution => "markdown-alert-caution", + }; + element.add_attr("class", kind_value); + }; + self.push_element_styled(element, &styling.blockquote); + } + Tag::List(Some(1)) => self.push_elname("ol", &styling.ol), + Tag::List(Some(start)) => { + self.push_element_styled(DomElement::new("ol").attr("start", start), &styling.ol); + } + Tag::List(None) => self.push_elname("ul", &styling.ul), + Tag::Item => self.push_elname("li", &styling.li), + Tag::DefinitionList => self.push_elname("dl", &styling.dl), + Tag::DefinitionListTitle => self.push_elname("dt", &styling.dt), + Tag::DefinitionListDefinition => self.push_elname("dd", &styling.dd), + Tag::Subscript => self.push_elname("sub", &styling.sub), + Tag::Superscript => self.push_elname("sup", &styling.sup), + Tag::Emphasis => self.push_elname("em", &styling.em), + Tag::Strong => self.push_elname("strong", &styling.strong), + Tag::Strikethrough => self.push_elname("del", &styling.del), + Tag::Link { + link_type, + dest_url, + title, + id: _, + } => { + let prefix = match link_type { + LinkType::Email => "mailto:", + _ => "", + }; + let element = DomElement::new("a").attr("href", [prefix, dest_url].concat()); + if !title.is_empty() { + element.add_attr("title", title); + } + self.push_element_styled(element, &styling.a); + } + Tag::Image { + link_type: _, + dest_url, + title, + id: _, + } => { + let mut element = DomElement::new("img") + .attr("src", dest_url) + .attr("alt", self.raw_text()); + + if !styling.img.groups.is_empty() { + element = element.css(&styling.img); + } + if !title.is_empty() { + element.add_attr("title", title); + } + self.add_child(element); + } + Tag::FootnoteDefinition(name) => { + let len = self.numbers.len() + 1; + let number = *self.numbers.entry(name.clone()).or_insert(len); + let mut sup_element = DomElement::new("sup") + .attr("class", "footnote-definition-label") + .child(DomText::new(number.to_string())); + if !styling.sup.groups.is_empty() { + sup_element = sup_element.css(&styling.sub); + } + self.push_node( + DomElement::new("div") + .attr("class", "footnote-definition") + .attr("id", name) + .child(sup_element), + ); + } + Tag::MetadataBlock(_) => { + self.in_non_writing_block = true; + } + } + } +} diff --git a/src/generate/writer.rs b/src/generate/writer.rs new file mode 100644 index 0000000..2018049 --- /dev/null +++ b/src/generate/writer.rs @@ -0,0 +1,110 @@ +// Based on https://github.com/pulldown-cmark/pulldown-cmark/blob/master/pulldown-cmark/src/html.rs + +use pulldown_cmark::{Alignment, CowStr, Event}; +use std::{ + collections::{HashMap, VecDeque}, + rc::Rc, +}; +use vertigo::{Css, DomElement, DomNode, log}; + +use crate::styling::CMarkStyle; + +pub(super) enum TableState { + Head, + Body, +} + +pub(super) struct VertigoWriter<'a, I> { + /// Iterator supplying events. + pub(super) iter: I, + + /// Whether if inside a metadata block (text should not be written) + pub(super) in_non_writing_block: bool, + + pub(super) table_state: TableState, + pub(super) table_alignments: Vec, + pub(super) table_cell_index: usize, + pub(super) numbers: HashMap, usize>, + + // Stack of nested nodes + pub(super) soc: VecDeque, + + pub(super) styling: Rc, + + #[cfg(feature = "syntect")] + pub(super) in_code_block: Option>, +} + +impl<'a, I> VertigoWriter<'a, I> +where + I: Iterator>, +{ + pub fn new(iter: I, styling: CMarkStyle) -> Self { + Self { + iter, + in_non_writing_block: false, + table_state: TableState::Head, + table_alignments: vec![], + table_cell_index: 0, + numbers: HashMap::new(), + soc: VecDeque::new(), + styling: Rc::new(styling), + #[cfg(feature = "syntect")] + in_code_block: None, + } + } + + pub(super) fn push_node(&mut self, node: impl Into) { + self.soc.push_front(node.into()); + } + + pub(super) fn push_element_styled(&mut self, element: DomElement, css: &Css) { + let mut element = element; + if !css.groups.is_empty() { + element = element.css(css) + } + self.soc.push_front(element.into()); + } + + pub(super) fn push_elname(&mut self, name: impl Into, css: &Css) { + let name = name.into(); + let mut element = DomElement::new(name); + if !css.groups.is_empty() { + element = element.css(css) + } + self.push_node(element); + } + + pub(super) fn pop_node(&mut self) -> Option { + if let Some(child) = self.soc.pop_front() { + match self.soc.front_mut() { + Some(parent) => { + match parent { + DomNode::Node { node } => node.add_child(child), + _ => { + unreachable!("Can't push children to non-element node"); + } + } + return None; + } + None => return Some(child), + } + } + None + } + + pub(super) fn add_child(&mut self, child: impl Into) { + if let Some(parent) = self.soc.front_mut() { + match parent { + DomNode::Node { node } => node.add_child(child), + _ => log::error!("Can't push child to non-element node (2)"), + } + } else { + log::error!("Can't add child without parent node") + } + } + + pub(super) fn add_child_name(&mut self, child_name: impl Into) { + self.add_child(DomElement::new(child_name.into())); + } +} diff --git a/src/tests/code.rs b/src/tests/code.rs index f8043bf..dccd6ef 100644 --- a/src/tests/code.rs +++ b/src/tests/code.rs @@ -1,6 +1,6 @@ use vertigo::{ dom, - inspect::{log_start, DomDebugFragment}, + inspect::{DomDebugFragment, log_start}, }; use crate::to_vertigo; diff --git a/src/tests/code_highlighting.rs b/src/tests/code_highlighting.rs index 3181acb..f4953e0 100644 --- a/src/tests/code_highlighting.rs +++ b/src/tests/code_highlighting.rs @@ -1,6 +1,6 @@ use vertigo::{ dom, - inspect::{log_start, DomDebugFragment}, + inspect::{DomDebugFragment, log_start}, }; use crate::to_vertigo; diff --git a/src/tests/html.rs b/src/tests/html.rs index 6824bf1..5402b13 100644 --- a/src/tests/html.rs +++ b/src/tests/html.rs @@ -1,6 +1,6 @@ use vertigo::{ dom, - inspect::{log_start, DomDebugFragment}, + inspect::{DomDebugFragment, log_start}, }; use crate::to_vertigo; diff --git a/src/tests/lists.rs b/src/tests/lists.rs index 4f1f16c..024eee4 100644 --- a/src/tests/lists.rs +++ b/src/tests/lists.rs @@ -1,9 +1,9 @@ use vertigo::{ dom, - inspect::{log_start, DomDebugFragment}, + inspect::{DomDebugFragment, log_start}, }; -use crate::{to_vertigo, to_vertigo_opts, Options}; +use crate::{Options, to_vertigo, to_vertigo_opts}; #[test] fn lists() { diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 3137dbd..6a42852 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,6 +1,6 @@ use vertigo::{ dom, - inspect::{log_start, DomDebugFragment}, + inspect::{DomDebugFragment, log_start}, }; use crate::{to_vertigo, to_vertigo_opts}; @@ -17,143 +17,7 @@ mod html; mod lists; mod styling; mod table; - -#[test] -fn text() { - log_start(); - let _el1 = to_vertigo("foo bar"); - let el1_str = DomDebugFragment::from_log().to_pseudo_html(); - - log_start(); - let _el2 = dom! {

"foo bar"

}; - let el2_str = DomDebugFragment::from_log().to_pseudo_html(); - - assert_eq!(el1_str, el2_str); -} - -#[test] -fn heading_rule() { - log_start(); - let _el1 = to_vertigo( - r#" -# Heading 1 - -foo - ---- - -bar -"#, - ); - let el1_str = DomDebugFragment::from_log().to_pseudo_html(); - - log_start(); - let _el2 = dom! { -
-

"Heading 1"

-

"foo"

-
-

"bar"

-
- }; - let el2_str = DomDebugFragment::from_log().to_pseudo_html(); - - assert_eq!(el1_str, el2_str); -} - -#[test] -fn text_bold_inline() { - log_start(); - let _el1 = to_vertigo("foo __spam__ bar"); - let el1_str = DomDebugFragment::from_log().to_pseudo_html(); - - log_start(); - let _el2 = dom! {

"foo spam bar"

}; - let el2_str = DomDebugFragment::from_log().to_pseudo_html(); - - assert_eq!(el1_str, el2_str); -} - -#[test] -fn text_italics_inline() { - log_start(); - let _el1 = to_vertigo("foo *spam* bar"); - let el1_str = DomDebugFragment::from_log().to_pseudo_html(); - - log_start(); - let _el2 = dom! {

"foo spam bar"

}; - let el2_str = DomDebugFragment::from_log().to_pseudo_html(); - - assert_eq!(el1_str, el2_str); -} - -#[test] -fn paragraph_line_join() { - log_start(); - let _el1 = to_vertigo( - r#"Some text. -Another line for the same paragraph. -And another"#, - ); - let el1_str = DomDebugFragment::from_log().to_pseudo_html(); - - log_start(); - let _el2 = dom! { -
-

"Some text. Another line for the same paragraph. And another"

-
- }; - let el2_str = DomDebugFragment::from_log().to_pseudo_html(); - - assert_eq!(el1_str, el2_str); -} - -#[test] -fn two_paragraphs() { - log_start(); - let _el1 = to_vertigo( - r#"Some text. - -Line for second paragraph. -Another line of second paragraph."#, - ); - let el1_str = DomDebugFragment::from_log().to_pseudo_html(); - - log_start(); - let _el2 = dom! { -
-

"Some text."

-

"Line for second paragraph. Another line of second paragraph."

-
- }; - let el2_str = DomDebugFragment::from_log().to_pseudo_html(); - - assert_eq!(el1_str, el2_str); -} - -#[test] -fn two_paragraphs_2() { - log_start(); - let _el1 = to_vertigo( - r#"Some text. -Some more text. - -Line for second paragraph. -Another line of second paragraph."#, - ); - let el1_str = DomDebugFragment::from_log().to_pseudo_html(); - - log_start(); - let _el2 = dom! { -
-

"Some text. Some more text."

-

"Line for second paragraph. Another line of second paragraph."

-
- }; - let el2_str = DomDebugFragment::from_log().to_pseudo_html(); - - assert_eq!(el1_str, el2_str); -} +mod typesetting; #[test] fn brackets() { @@ -246,42 +110,6 @@ This is sentence with footnote[^1]. I hope it works[^2]. assert_eq!(el1_str, el2_str); } -#[test] -fn all_headings() { - log_start(); - let _el1 = to_vertigo( - r#" -# Heading 1 - -## Heading 2 - -### Heading 3 - -#### Heading 4 - -##### Heading 5 - -###### Heading 6 -"#, - ); - let el1_str = DomDebugFragment::from_log().to_pseudo_html(); - - log_start(); - let _el2 = dom! { -
-

"Heading 1"

-

"Heading 2"

-

"Heading 3"

-

"Heading 4"

-
"Heading 5"
-
"Heading 6"
-
- }; - let el2_str = DomDebugFragment::from_log().to_pseudo_html(); - - assert_eq!(el1_str, el2_str); -} - #[test] fn blockquote() { let opts = super::Options::ENABLE_GFM; diff --git a/src/tests/styling.rs b/src/tests/styling.rs index 238e1d8..e00cec9 100644 --- a/src/tests/styling.rs +++ b/src/tests/styling.rs @@ -1,10 +1,9 @@ use vertigo::{ - dom, - inspect::{log_start, DomDebugFragment}, - Css, + Css, dom, + inspect::{DomDebugFragment, log_start}, }; -use crate::{to_vertigo_opts_styled, to_vertigo_styled, Options}; +use crate::{Options, to_vertigo_opts_styled, to_vertigo_styled}; static TEST_STYLE: &str = "color: green"; diff --git a/src/tests/table.rs b/src/tests/table.rs index bd45451..f839389 100644 --- a/src/tests/table.rs +++ b/src/tests/table.rs @@ -1,6 +1,6 @@ use vertigo::{ dom, - inspect::{log_start, DomDebugFragment}, + inspect::{DomDebugFragment, log_start}, }; use crate::to_vertigo; diff --git a/src/tests/typesetting.rs b/src/tests/typesetting.rs new file mode 100644 index 0000000..24d8ddd --- /dev/null +++ b/src/tests/typesetting.rs @@ -0,0 +1,179 @@ +use vertigo::{ + dom, + inspect::{DomDebugFragment, log_start}, +}; + +use crate::to_vertigo; + +#[test] +fn text() { + log_start(); + let _el1 = to_vertigo("foo bar"); + let el1_str = DomDebugFragment::from_log().to_pseudo_html(); + + log_start(); + let _el2 = dom! {

"foo bar"

}; + let el2_str = DomDebugFragment::from_log().to_pseudo_html(); + + assert_eq!(el1_str, el2_str); +} + +#[test] +fn heading_rule() { + log_start(); + let _el1 = to_vertigo( + r#" +# Heading 1 + +foo + +--- + +bar +"#, + ); + let el1_str = DomDebugFragment::from_log().to_pseudo_html(); + + log_start(); + let _el2 = dom! { +
+

"Heading 1"

+

"foo"

+
+

"bar"

+
+ }; + let el2_str = DomDebugFragment::from_log().to_pseudo_html(); + + assert_eq!(el1_str, el2_str); +} + +#[test] +fn text_bold_inline() { + log_start(); + let _el1 = to_vertigo("foo __spam__ bar"); + let el1_str = DomDebugFragment::from_log().to_pseudo_html(); + + log_start(); + let _el2 = dom! {

"foo spam bar"

}; + let el2_str = DomDebugFragment::from_log().to_pseudo_html(); + + assert_eq!(el1_str, el2_str); +} + +#[test] +fn text_italics_inline() { + log_start(); + let _el1 = to_vertigo("foo *spam* bar"); + let el1_str = DomDebugFragment::from_log().to_pseudo_html(); + + log_start(); + let _el2 = dom! {

"foo spam bar"

}; + let el2_str = DomDebugFragment::from_log().to_pseudo_html(); + + assert_eq!(el1_str, el2_str); +} + +#[test] +fn paragraph_line_join() { + log_start(); + let _el1 = to_vertigo( + r#"Some text. +Another line for the same paragraph. +And another"#, + ); + let el1_str = DomDebugFragment::from_log().to_pseudo_html(); + + log_start(); + let _el2 = dom! { +
+

"Some text. Another line for the same paragraph. And another"

+
+ }; + let el2_str = DomDebugFragment::from_log().to_pseudo_html(); + + assert_eq!(el1_str, el2_str); +} + +#[test] +fn two_paragraphs() { + log_start(); + let _el1 = to_vertigo( + r#"Some text. + +Line for second paragraph. +Another line of second paragraph."#, + ); + let el1_str = DomDebugFragment::from_log().to_pseudo_html(); + + log_start(); + let _el2 = dom! { +
+

"Some text."

+

"Line for second paragraph. Another line of second paragraph."

+
+ }; + let el2_str = DomDebugFragment::from_log().to_pseudo_html(); + + assert_eq!(el1_str, el2_str); +} + +#[test] +fn two_paragraphs_2() { + log_start(); + let _el1 = to_vertigo( + r#"Some text. +Some more text. + +Line for second paragraph. +Another line of second paragraph."#, + ); + let el1_str = DomDebugFragment::from_log().to_pseudo_html(); + + log_start(); + let _el2 = dom! { +
+

"Some text. Some more text."

+

"Line for second paragraph. Another line of second paragraph."

+
+ }; + let el2_str = DomDebugFragment::from_log().to_pseudo_html(); + + assert_eq!(el1_str, el2_str); +} + +#[test] +fn all_headings() { + log_start(); + let _el1 = to_vertigo( + r#" +# Heading 1 + +## Heading 2 + +### Heading 3 + +#### Heading 4 + +##### Heading 5 + +###### Heading 6 +"#, + ); + let el1_str = DomDebugFragment::from_log().to_pseudo_html(); + + log_start(); + let _el2 = dom! { +
+

"Heading 1"

+

"Heading 2"

+

"Heading 3"

+

"Heading 4"

+
"Heading 5"
+
"Heading 6"
+
+ }; + let el2_str = DomDebugFragment::from_log().to_pseudo_html(); + + assert_eq!(el1_str, el2_str); +}