├── .gitignore ├── .gitlab-ci.yml ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── bad.html ├── src ├── lib.rs ├── main.rs ├── markdown.rs ├── parse.rs └── traits.rs └── tests └── markdown.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # ---> Rust 2 | # Compiled files 3 | *.o 4 | *.so 5 | *.rlib 6 | *.dll 7 | 8 | # Executables 9 | *.exe 10 | 11 | # Generated by Cargo 12 | /target/ 13 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: rust:latest 2 | 3 | test: 4 | script: 5 | - cargo test 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | matrix: 7 | allow_failures: 8 | - rust: nightly 9 | notifications: 10 | email: false 11 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "ansi_term" 3 | version = "0.9.0" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | 6 | [[package]] 7 | name = "bitflags" 8 | version = "0.7.0" 9 | source = "registry+https://github.com/rust-lang/crates.io-index" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.0.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | 16 | [[package]] 17 | name = "cfg-if" 18 | version = "0.1.3" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | 21 | [[package]] 22 | name = "clap" 23 | version = "2.20.5" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | dependencies = [ 26 | "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 27 | "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 28 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 29 | "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 30 | "term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 31 | "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 34 | ] 35 | 36 | [[package]] 37 | name = "debug_unreachable" 38 | version = "0.1.1" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | dependencies = [ 41 | "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 42 | ] 43 | 44 | [[package]] 45 | name = "encoding" 46 | version = "0.2.33" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | dependencies = [ 49 | "encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", 50 | "encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", 51 | "encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", 52 | "encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", 53 | "encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", 54 | ] 55 | 56 | [[package]] 57 | name = "encoding-index-japanese" 58 | version = "1.20141219.5" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | dependencies = [ 61 | "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 62 | ] 63 | 64 | [[package]] 65 | name = "encoding-index-korean" 66 | version = "1.20141219.5" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | dependencies = [ 69 | "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 70 | ] 71 | 72 | [[package]] 73 | name = "encoding-index-simpchinese" 74 | version = "1.20141219.5" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | dependencies = [ 77 | "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 78 | ] 79 | 80 | [[package]] 81 | name = "encoding-index-singlebyte" 82 | version = "1.20141219.5" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | dependencies = [ 85 | "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 86 | ] 87 | 88 | [[package]] 89 | name = "encoding-index-tradchinese" 90 | version = "1.20141219.5" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | dependencies = [ 93 | "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 94 | ] 95 | 96 | [[package]] 97 | name = "encoding_index_tests" 98 | version = "0.1.4" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | 101 | [[package]] 102 | name = "fuchsia-zircon" 103 | version = "0.3.3" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | dependencies = [ 106 | "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 107 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 108 | ] 109 | 110 | [[package]] 111 | name = "fuchsia-zircon-sys" 112 | version = "0.3.3" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | 115 | [[package]] 116 | name = "futf" 117 | version = "0.1.4" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | dependencies = [ 120 | "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 121 | "new_debug_unreachable 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 122 | ] 123 | 124 | [[package]] 125 | name = "html2runes" 126 | version = "1.0.0" 127 | dependencies = [ 128 | "clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)", 129 | "html5ever 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", 130 | "html5ever-atoms 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 131 | "tendril 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 132 | ] 133 | 134 | [[package]] 135 | name = "html5ever" 136 | version = "0.13.1" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | dependencies = [ 139 | "html5ever-atoms 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 140 | "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 141 | "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 142 | "phf 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", 143 | "phf_codegen 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", 144 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 145 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 146 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 147 | "tendril 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 148 | ] 149 | 150 | [[package]] 151 | name = "html5ever-atoms" 152 | version = "0.2.2" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | dependencies = [ 155 | "string_cache 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 156 | "string_cache_codegen 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 157 | ] 158 | 159 | [[package]] 160 | name = "kernel32-sys" 161 | version = "0.2.2" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | dependencies = [ 164 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 165 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 166 | ] 167 | 168 | [[package]] 169 | name = "lazy_static" 170 | version = "0.2.11" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | 173 | [[package]] 174 | name = "libc" 175 | version = "0.2.42" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | 178 | [[package]] 179 | name = "log" 180 | version = "0.4.2" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | dependencies = [ 183 | "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 184 | ] 185 | 186 | [[package]] 187 | name = "mac" 188 | version = "0.1.1" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | 191 | [[package]] 192 | name = "matches" 193 | version = "0.1.6" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | 196 | [[package]] 197 | name = "new_debug_unreachable" 198 | version = "1.0.1" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | dependencies = [ 201 | "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 202 | ] 203 | 204 | [[package]] 205 | name = "phf" 206 | version = "0.7.22" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | dependencies = [ 209 | "phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", 210 | ] 211 | 212 | [[package]] 213 | name = "phf_codegen" 214 | version = "0.7.22" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | dependencies = [ 217 | "phf_generator 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", 218 | "phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", 219 | ] 220 | 221 | [[package]] 222 | name = "phf_generator" 223 | version = "0.7.22" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | dependencies = [ 226 | "phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", 227 | "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 228 | ] 229 | 230 | [[package]] 231 | name = "phf_shared" 232 | version = "0.7.22" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | dependencies = [ 235 | "siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 236 | ] 237 | 238 | [[package]] 239 | name = "quote" 240 | version = "0.3.15" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | 243 | [[package]] 244 | name = "rand" 245 | version = "0.4.2" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | dependencies = [ 248 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 249 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 250 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 251 | ] 252 | 253 | [[package]] 254 | name = "rustc-serialize" 255 | version = "0.3.24" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | 258 | [[package]] 259 | name = "serde" 260 | version = "0.9.15" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | 263 | [[package]] 264 | name = "siphasher" 265 | version = "0.2.2" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | 268 | [[package]] 269 | name = "string_cache" 270 | version = "0.4.0" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | dependencies = [ 273 | "debug_unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 274 | "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 275 | "phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", 276 | "serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)", 277 | "string_cache_codegen 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 278 | "string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 279 | ] 280 | 281 | [[package]] 282 | name = "string_cache_codegen" 283 | version = "0.3.1" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | dependencies = [ 286 | "phf_generator 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", 287 | "string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 288 | ] 289 | 290 | [[package]] 291 | name = "string_cache_shared" 292 | version = "0.3.0" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | 295 | [[package]] 296 | name = "strsim" 297 | version = "0.6.0" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | 300 | [[package]] 301 | name = "syn" 302 | version = "0.11.11" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | dependencies = [ 305 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 306 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 307 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 308 | ] 309 | 310 | [[package]] 311 | name = "synom" 312 | version = "0.11.3" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | dependencies = [ 315 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 316 | ] 317 | 318 | [[package]] 319 | name = "tendril" 320 | version = "0.2.4" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | dependencies = [ 323 | "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 324 | "futf 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 325 | "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 326 | "utf-8 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 327 | ] 328 | 329 | [[package]] 330 | name = "term_size" 331 | version = "0.2.3" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | dependencies = [ 334 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 335 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 336 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 337 | ] 338 | 339 | [[package]] 340 | name = "unicode-segmentation" 341 | version = "1.2.1" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | 344 | [[package]] 345 | name = "unicode-width" 346 | version = "0.1.5" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | 349 | [[package]] 350 | name = "unicode-xid" 351 | version = "0.0.4" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | 354 | [[package]] 355 | name = "unreachable" 356 | version = "0.1.1" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | dependencies = [ 359 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 360 | ] 361 | 362 | [[package]] 363 | name = "unreachable" 364 | version = "1.0.0" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | dependencies = [ 367 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 368 | ] 369 | 370 | [[package]] 371 | name = "utf-8" 372 | version = "0.6.0" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | dependencies = [ 375 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 376 | ] 377 | 378 | [[package]] 379 | name = "vec_map" 380 | version = "0.6.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | 383 | [[package]] 384 | name = "void" 385 | version = "1.0.2" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | 388 | [[package]] 389 | name = "winapi" 390 | version = "0.2.8" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | 393 | [[package]] 394 | name = "winapi" 395 | version = "0.3.5" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | dependencies = [ 398 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 399 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 400 | ] 401 | 402 | [[package]] 403 | name = "winapi-build" 404 | version = "0.1.1" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | 407 | [[package]] 408 | name = "winapi-i686-pc-windows-gnu" 409 | version = "0.4.0" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | 412 | [[package]] 413 | name = "winapi-x86_64-pc-windows-gnu" 414 | version = "0.4.0" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | 417 | [metadata] 418 | "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" 419 | "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 420 | "checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789" 421 | "checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18" 422 | "checksum clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7db281b0520e97fbd15cd615dcd8f8bcad0c26f5f7d5effe705f090f39e9a758" 423 | "checksum debug_unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9a032eac705ca39214d169f83e3d3da290af06d8d1d344d1baad2fd002dca4b3" 424 | "checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" 425 | "checksum encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" 426 | "checksum encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" 427 | "checksum encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" 428 | "checksum encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" 429 | "checksum encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" 430 | "checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" 431 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 432 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 433 | "checksum futf 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" 434 | "checksum html5ever 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d60508177ec4e5774a112efcf4d4d5f123cb00a43476fa5940b7da568371a165" 435 | "checksum html5ever-atoms 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c626dc6733babf7110d3a5078b1529e9d0eaaacf6c488ef6a7437b7d515844bb" 436 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 437 | "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" 438 | "checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1" 439 | "checksum log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6fddaa003a65722a7fb9e26b0ce95921fe4ba590542ced664d8ce2fa26f9f3ac" 440 | "checksum mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 441 | "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" 442 | "checksum new_debug_unreachable 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0cdc457076c78ab54d5e0d6fa7c47981757f1e34dc39ff92787f217dede586c4" 443 | "checksum phf 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "7d37a244c75a9748e049225155f56dbcb98fe71b192fd25fd23cb914b5ad62f2" 444 | "checksum phf_codegen 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "4e4048fe7dd7a06b8127ecd6d3803149126e9b33c7558879846da3a63f734f2b" 445 | "checksum phf_generator 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "05a079dd052e7b674d21cb31cbb6c05efd56a2cd2827db7692e2f1a507ebd998" 446 | "checksum phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "c2261d544c2bb6aa3b10022b0be371b9c7c64f762ef28c6f5d4f1ef6d97b5930" 447 | "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" 448 | "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" 449 | "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 450 | "checksum serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)" = "34b623917345a631dc9608d5194cc206b3fe6c3554cd1c75b937e55e285254af" 451 | "checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537" 452 | "checksum string_cache 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c19dbe4d2552673a8c4ec0e91523670ee2b73ba3560d935703ce5d64a40f864c" 453 | "checksum string_cache_codegen 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0c9dfe1a7c8bba1ecb90730d269fdc08afe93d23c28dd6c4aa5cabd79a05a05e" 454 | "checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" 455 | "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" 456 | "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" 457 | "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" 458 | "checksum tendril 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4ce04c250d202db8004921e3d3bc95eaa4f2126c6937a428ae39d12d0e38df62" 459 | "checksum term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07b6c1ac5b3fffd75073276bca1ceed01f67a28537097a2a9539e116e50fb21a" 460 | "checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" 461 | "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" 462 | "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" 463 | "checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" 464 | "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" 465 | "checksum utf-8 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9aee9ba280438b56d1ebc5329f2094f0ff457f811eeeff0b278d75aa99db400" 466 | "checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f" 467 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 468 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 469 | "checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd" 470 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 471 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 472 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 473 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "html2runes" 3 | version = "1.0.0" 4 | authors = ["Jonas Kalderstam "] 5 | description = "An HTML to Text converter." 6 | homepage = "https://gitlab.com/spacecowboy/html2runes" 7 | repository = "https://gitlab.com/spacecowboy/html2runes" 8 | readme = "README.md" 9 | keywords = ["html", "plaintext", "converter", "markdown"] 10 | categories = ["parsing", "text-processing"] 11 | license = "MIT" 12 | 13 | [dependencies] 14 | clap = "2.20.5" 15 | html5ever = "0.13.0" 16 | html5ever-atoms = "0.2.0" 17 | tendril = "0.2.3" 18 | 19 | [badges.travis-ci] 20 | repository = "spacecowboy/html2runes" 21 | branch = "master" 22 | 23 | [[bin]] 24 | path = "src/main.rs" 25 | name = "html2runes" 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 3 | 4 | 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: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | 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. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # html2runes 2 | 3 | [![Build Status](https://travis-ci.org/spacecowboy/html2runes.svg?branch=master)](https://travis-ci.org/spacecowboy/html2runes) 4 | 5 | A HTML to Text converter program written in Rust. Useful to convert html-only emails to plaintext for example. 6 | 7 | ## Build 8 | 9 | ```sh 10 | cargo build --release 11 | ``` 12 | 13 | ## Run 14 | 15 | `html2runes` reads html on STDIN and outputs plaintext (currently only markdown supported) on STDOUT: 16 | 17 | ``` 18 | cat bad.html | target/release/html2runes 19 | ``` 20 | 21 | Result: 22 | 23 | ``` 24 | Text without a paragraph 25 | 26 | Text inside paragraph 27 | 28 | paragraph again 29 | new line but no ending tags yet and here comes a bull shit token BLA TEXT which should be ignored, and here comes an image ![no alt text](bla.png)invalid image txt 30 | [I am the link text](http://google.com) 31 | ``` 32 | -------------------------------------------------------------------------------- /bad.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Text without a 4 | paragraph 5 |

6 | Text inside paragraph 7 | 8 |

paragraph again 9 |
10 | new line but no ending tags yet 11 | and here comes a bull shit token 12 | BLA TEXT 13 | which should be ignored, and here comes an image 14 | invalid image txt 15 |
16 | I am the link text 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate html5ever; 2 | extern crate html5ever_atoms; 3 | extern crate tendril; 4 | 5 | pub mod markdown; 6 | pub mod parse; 7 | pub mod traits; 8 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | extern crate html2runes; 3 | 4 | use clap::{App, Arg}; 5 | use html2runes::markdown; 6 | use std::str::FromStr; 7 | 8 | enum Format { 9 | Markdown, 10 | } 11 | 12 | impl FromStr for Format { 13 | type Err = (); 14 | 15 | fn from_str(s: &str) -> Result { 16 | match s { 17 | "markdown" => Ok(Format::Markdown), 18 | _ => Err(()), 19 | } 20 | } 21 | } 22 | 23 | fn main() { 24 | let args = App::new("html2textrs") 25 | .version("0.1") 26 | .about("Converts html from STDIN to plain text on STDOUT.") 27 | .arg( 28 | Arg::with_name("format") 29 | .short("f") 30 | .long("format") 31 | .help("Plain text format to use") 32 | .possible_values(&["markdown"]) 33 | .default_value("markdown"), 34 | ) 35 | .get_matches(); 36 | 37 | // Default value exists 38 | let format = args.value_of("format").unwrap(); 39 | let format = format.parse::().unwrap(); 40 | 41 | let result = match format { 42 | Format::Markdown => markdown::convert_stdin(), 43 | }; 44 | println!("{}", result) 45 | } 46 | -------------------------------------------------------------------------------- /src/markdown.rs: -------------------------------------------------------------------------------- 1 | use html5ever::rcdom::{Comment, Doctype, Document, Element, Handle, Node, Text}; 2 | use html5ever::Attribute; 3 | use html5ever_atoms::QualName; 4 | use tendril; 5 | use tendril::Tendril; 6 | 7 | use std::cell::Ref; 8 | use std::collections::LinkedList; 9 | 10 | use parse::{parse_stdin, parse_string}; 11 | use traits::HtmlConverter; 12 | 13 | pub fn convert_stdin() -> String { 14 | let dom = parse_stdin(); 15 | convert_html(dom.document) 16 | } 17 | 18 | pub fn convert_string(s: &str) -> String { 19 | let dom = parse_string(s); 20 | convert_html(dom.document) 21 | } 22 | 23 | pub fn convert_html(handle: Handle) -> String { 24 | let mut converter = MarkdownConverter::new(); 25 | converter.convert_html(handle) 26 | } 27 | 28 | pub struct MarkdownConverter<'a> { 29 | buf: String, 30 | prefix: LinkedList<&'a str>, 31 | list_markers: Vec>, 32 | } 33 | 34 | impl<'a> MarkdownConverter<'a> { 35 | pub fn new() -> MarkdownConverter<'a> { 36 | MarkdownConverter { 37 | buf: String::new(), 38 | prefix: LinkedList::new(), 39 | list_markers: Vec::new(), 40 | } 41 | } 42 | 43 | fn convert_html_into_buffer(&mut self, handle: &Handle) { 44 | let node = handle.borrow(); 45 | match node.node { 46 | Comment(_) => {} 47 | Doctype(_, _, _) => {} 48 | Text(ref text) => convert_text(text, &mut self.buf, &mut self.prefix), 49 | Element(ref name, _, ref attrs) => { 50 | self.handle_element(&name, &attrs, &node); 51 | } 52 | Document => { 53 | for child in node.children.iter() { 54 | self.convert_html_into_buffer(&child); 55 | } 56 | } 57 | } 58 | } 59 | 60 | fn handle_element(&mut self, name: &QualName, attrs: &Vec, node: &Ref) { 61 | let name: &str = &name.local.to_ascii_lowercase().to_lowercase(); 62 | 63 | match name { 64 | "head" | "style" | "script" => { 65 | // ignore these 66 | } 67 | _ => { 68 | // start element 69 | self.element_start(&name, &attrs); 70 | // do contents 71 | for child in node.children.iter() { 72 | self.convert_html_into_buffer(&child); 73 | } 74 | // end element 75 | self.element_end(&name, &attrs); 76 | } 77 | } 78 | } 79 | 80 | fn element_start(&mut self, name: &str, attrs: &Vec) { 81 | match name { 82 | "b" | "strong" => bold_start(&mut self.buf), 83 | "i" | "em" => emphasize_start(&mut self.buf), 84 | "p" | "div" => self.p_start(), 85 | "blockquote" => blockquote_start(&mut self.buf, &mut self.prefix), 86 | "br" => self.br_start(), 87 | "a" => link_start(&mut self.buf), 88 | "img" => img_start(&mut self.buf, attrs), 89 | "ul" => ul_start(&mut self.buf, &mut self.list_markers), 90 | "ol" => ol_start(&mut self.buf, &mut self.list_markers), 91 | "li" => li_start(&mut self.buf, &mut self.list_markers), 92 | _ => {} 93 | } 94 | } 95 | 96 | fn element_end(&mut self, name: &str, attrs: &Vec) { 97 | match name { 98 | "b" | "strong" => bold_end(&mut self.buf), 99 | "i" | "em" => emphasize_end(&mut self.buf), 100 | "blockquote" => blockquote_end(&mut self.buf, &mut self.prefix), 101 | "a" => link_end(&mut self.buf, attrs), 102 | "ul" | "ol" => list_end(&mut self.buf, &mut self.list_markers), 103 | "li" => { 104 | li_end(&mut self.buf, &mut self.list_markers); 105 | } 106 | _ => {} 107 | } 108 | } 109 | 110 | fn p_start(&mut self) { 111 | if let Some(prefix) = prefix(&self.list_markers) { 112 | if self.buf.ends_with(&prefix) { 113 | return; 114 | } 115 | } 116 | ensure_double_newline(&mut self.buf); 117 | prefix_with_necessary_spaces(&mut self.buf, &self.list_markers); 118 | } 119 | 120 | fn br_start(&mut self) { 121 | if let Some(prefix) = prefix(&self.list_markers) { 122 | if self.buf.ends_with(&prefix) { 123 | return; 124 | } 125 | } 126 | ensure_newline(&mut self.buf); 127 | prefix_with_necessary_spaces(&mut self.buf, &self.list_markers); 128 | } 129 | } 130 | 131 | impl<'a> HtmlConverter for MarkdownConverter<'a> { 132 | fn convert_html(&mut self, handle: Handle) -> String { 133 | self.convert_html_into_buffer(&handle); 134 | self.buf.clone() 135 | } 136 | } 137 | 138 | fn convert_text( 139 | text: &Tendril, 140 | buf: &mut String, 141 | prefix: &mut LinkedList<&str>, 142 | ) { 143 | // Start with prefixes 144 | for p in prefix.iter() { 145 | buf.push_str(p); 146 | } 147 | 148 | // Separate prefix(es) from actual text with one space 149 | if !prefix.is_empty() { 150 | buf.push_str(" "); 151 | } 152 | 153 | // True if previous is whitespace 154 | let mut prev = buf.is_empty() || buf.ends_with(" ") || buf.ends_with("\n"); 155 | for c in text.chars() { 156 | match c { 157 | // Stick to a single space 158 | ' ' | '\n' => { 159 | if !prev { 160 | prev = true; 161 | buf.push(' '); 162 | } 163 | } 164 | _ => { 165 | prev = false; 166 | buf.push(c); 167 | } 168 | } 169 | } 170 | } 171 | 172 | fn bold_start(buf: &mut String) { 173 | buf.push_str("**") 174 | } 175 | 176 | fn bold_end(buf: &mut String) { 177 | bold_start(buf) 178 | } 179 | 180 | fn emphasize_start(buf: &mut String) { 181 | buf.push_str("*") 182 | } 183 | 184 | fn emphasize_end(buf: &mut String) { 185 | emphasize_start(buf) 186 | } 187 | 188 | fn trim_ending_whitespace(buf: &mut String) { 189 | while buf.ends_with(" ") || buf.ends_with("\t") { 190 | let end = buf.len() - 1; 191 | buf.remove(end); 192 | } 193 | } 194 | 195 | fn prefix(list_markers: &Vec>) -> Option { 196 | if let Some(mark) = list_markers.last() { 197 | match *mark { 198 | Some(i) => Some(format!("{}. ", i)), 199 | None => Some("* ".to_string()), 200 | } 201 | } else { 202 | None 203 | } 204 | } 205 | 206 | fn prefix_with_necessary_spaces(buf: &mut String, list_markers: &[Option]) { 207 | let count = list_markers.iter().fold(0, |sum, mark| { 208 | match *mark { 209 | Some(_) => sum + 3, // '1. ' = three characters 210 | None => sum + 2, // '* ' = two characters 211 | } 212 | }); 213 | 214 | buf.push_str(&" ".repeat(count)); 215 | } 216 | 217 | fn ensure_double_newline(buf: &mut String) { 218 | trim_ending_whitespace(buf); 219 | if buf.ends_with("\n\n") { 220 | // Nothing to do 221 | } else if buf.ends_with("\n") { 222 | buf.push_str("\n") 223 | } else if !buf.is_empty() { 224 | buf.push_str("\n\n") 225 | } 226 | } 227 | 228 | fn ensure_newline(buf: &mut String) { 229 | trim_ending_whitespace(buf); 230 | if buf.ends_with("\n") { 231 | // Nothing to do 232 | } else if !buf.is_empty() { 233 | buf.push_str("\n") 234 | } 235 | } 236 | 237 | fn blockquote_start(buf: &mut String, prefix: &mut LinkedList<&str>) { 238 | ensure_newline(buf); 239 | prefix.push_back(">") 240 | } 241 | 242 | fn blockquote_end(buf: &mut String, prefix: &mut LinkedList<&str>) { 243 | prefix.pop_back(); 244 | ensure_newline(buf) 245 | } 246 | 247 | fn link_start(buf: &mut String) { 248 | buf.push_str("[") 249 | } 250 | 251 | fn link_end(buf: &mut String, attrs: &Vec) { 252 | let mut url = ""; 253 | 254 | for attr in attrs { 255 | let name: &str = &attr.name.local.to_ascii_lowercase().to_lowercase(); 256 | match name { 257 | "href" => { 258 | url = &attr.value; 259 | } 260 | _ => {} 261 | } 262 | } 263 | 264 | buf.push_str("]("); 265 | buf.push_str(url); 266 | buf.push_str(")") 267 | } 268 | 269 | fn img_start(buf: &mut String, attrs: &Vec) { 270 | let mut src = ""; 271 | let mut alt = "no alt text"; 272 | 273 | for attr in attrs { 274 | let name: &str = &attr.name.local.to_ascii_lowercase().to_lowercase(); 275 | match name { 276 | "src" => { 277 | src = &attr.value; 278 | } 279 | "alt" => { 280 | alt = &attr.value; 281 | } 282 | _ => {} 283 | } 284 | } 285 | 286 | buf.push_str("!["); 287 | buf.push_str(alt); 288 | buf.push_str("]("); 289 | buf.push_str(src); 290 | buf.push_str(")") 291 | } 292 | 293 | fn ul_start(buf: &mut String, list_markers: &mut Vec>) { 294 | ensure_double_newline(buf); 295 | list_markers.push(None); 296 | } 297 | 298 | fn list_end(buf: &mut String, list_markers: &mut Vec>) { 299 | ensure_double_newline(buf); 300 | list_markers.pop(); 301 | prefix_with_necessary_spaces(buf, &list_markers); 302 | } 303 | 304 | fn ol_start(buf: &mut String, list_markers: &mut Vec>) { 305 | ensure_double_newline(buf); 306 | list_markers.push(Some(1)); 307 | } 308 | 309 | fn li_start(buf: &mut String, list_markers: &Vec>) { 310 | if !list_markers.is_empty() { 311 | let last_index = list_markers.len() - 1; 312 | prefix_with_necessary_spaces(buf, list_markers.split_at(last_index).0); 313 | if let Some(prefix) = prefix(list_markers) { 314 | buf.push_str(&prefix); 315 | } 316 | } 317 | } 318 | 319 | fn li_end(buf: &mut String, list_markers: &mut Vec>) { 320 | if let Some(mark) = list_markers.pop() { 321 | ensure_newline(buf); 322 | match mark { 323 | Some(i) => list_markers.push(Some(i + 1)), 324 | None => list_markers.push(mark), 325 | } 326 | } 327 | } 328 | 329 | #[test] 330 | fn test_prefix_with_necessary_spaces() { 331 | let mut buf = String::new(); 332 | prefix_with_necessary_spaces(&mut buf, &[]); 333 | assert_eq!("", &buf); 334 | 335 | let mut buf = String::new(); 336 | prefix_with_necessary_spaces(&mut buf, &[None]); 337 | assert_eq!(" ", &buf); 338 | 339 | let mut buf = String::new(); 340 | prefix_with_necessary_spaces(&mut buf, &[Some(3)]); 341 | assert_eq!(" ", &buf); 342 | 343 | let mut buf = String::new(); 344 | prefix_with_necessary_spaces(&mut buf, &[Some(1), None, Some(2)]); 345 | assert_eq!(" ", &buf); 346 | 347 | let mut buf = String::new(); 348 | prefix_with_necessary_spaces(&mut buf, &[Some(1), None, Some(2)].split_at(2).0); 349 | assert_eq!(" ", &buf); 350 | } 351 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use std::default::Default; 2 | use std::io; 3 | 4 | use html5ever::parse_document; 5 | use html5ever::rcdom::RcDom; 6 | use tendril::TendrilSink; 7 | 8 | /// Read from stdin and parse as HTML/XML 9 | pub fn parse_stdin() -> RcDom { 10 | let stdin = io::stdin(); 11 | let dom = parse_document(RcDom::default(), Default::default()) 12 | .from_utf8() 13 | .read_from(&mut stdin.lock()) 14 | .unwrap(); 15 | dom 16 | } 17 | 18 | /// Read the provided string ad parse as HTML/XML 19 | pub fn parse_string(text: &str) -> RcDom { 20 | let dom = parse_document(RcDom::default(), Default::default()) 21 | .from_utf8() 22 | .read_from(&mut text.as_bytes()) 23 | .unwrap(); 24 | dom 25 | } 26 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | use html5ever::rcdom::Handle; 2 | 3 | pub trait HtmlConverter { 4 | fn convert_html(&mut self, handle: Handle) -> String; 5 | } 6 | -------------------------------------------------------------------------------- /tests/markdown.rs: -------------------------------------------------------------------------------- 1 | extern crate html2runes; 2 | 3 | use html2runes::markdown::*; 4 | 5 | #[test] 6 | fn plaintext() { 7 | let result = convert_string("My little car."); 8 | assert_eq!("My little car.", result); 9 | } 10 | 11 | #[test] 12 | fn newlines_are_ignored() { 13 | let result = convert_string( 14 | "My 15 | little 16 | car.", 17 | ); 18 | assert_eq!("My little car.", result); 19 | } 20 | 21 | #[test] 22 | fn lines_with_empty_spaces_are_killed() { 23 | let result = convert_string("

a b c

\n

d e f

"); 24 | assert_eq!("a b c\n\nd e f", result); 25 | } 26 | 27 | #[test] 28 | fn ending_space_is_trimmed() { 29 | let result = convert_string("a b c
\n
d e f"); 30 | assert_eq!("a b c\nd e f", result); 31 | } 32 | 33 | #[test] 34 | fn bold() { 35 | let result = convert_string("My little car."); 36 | assert_eq!("My **little** car.", result); 37 | 38 | let result = convert_string("My little car."); 39 | assert_eq!("My **little** car.", result); 40 | } 41 | 42 | #[test] 43 | fn emphasize() { 44 | let result = convert_string("My little car."); 45 | assert_eq!("My *little* car.", result); 46 | 47 | let result = convert_string("My little car."); 48 | assert_eq!("My *little* car.", result); 49 | } 50 | 51 | #[test] 52 | fn paragraph() { 53 | let result = convert_string("

A piece of text

Another piece

"); 54 | assert_eq!("A piece of text\n\nAnother piece", result); 55 | 56 | let result = convert_string( 57 | "

A piece of text

58 |

Another piece

", 59 | ); 60 | assert_eq!("A piece of text\n\nAnother piece", result); 61 | 62 | let result = convert_string("

A piece of text

Another piece"); 63 | assert_eq!("A piece of text\n\nAnother piece", result); 64 | 65 | let result = convert_string("

A piece of text

Another piece"); 66 | assert_eq!("A piece of text\n\nAnother piece", result); 67 | } 68 | 69 | #[test] 70 | fn newline() { 71 | let result = convert_string("one
two
three

four"); 72 | assert_eq!("one\ntwo\nthree\nfour", result); 73 | 74 | let result = convert_string("one


two"); 75 | assert_eq!("one\ntwo", result); 76 | 77 | let result = convert_string("
none"); 78 | assert_eq!("none", result); 79 | } 80 | 81 | #[test] 82 | fn blockquote() { 83 | let result = convert_string("

just a quote
"); 84 | assert_eq!("> just a quote\n", result); 85 | 86 | let result = convert_string( 87 | "
a nested
quote should give \ 88 | double
lines
", 89 | ); 90 | assert_eq!( 91 | "> a nested 92 | >> quote should give double 93 | > lines\n", 94 | result 95 | ); 96 | 97 | let result = convert_string( 98 | "

And he said:

Quote me
and all was \ 99 | good.", 100 | ); 101 | assert_eq!( 102 | "And he said: 103 | > Quote me 104 | and all was good.", 105 | result 106 | ); 107 | 108 | let result = convert_string( 109 | "And he said:
A long long piece of text
which you \ 110 | can find in the quote
and all was good.", 111 | ); 112 | assert_eq!( 113 | "And he said: 114 | > A long long piece of text 115 | > which you can find in the quote 116 | and all was good.", 117 | result 118 | ); 119 | } 120 | 121 | #[test] 122 | fn link() { 123 | let result = convert_string("here is a link to google"); 124 | assert_eq!("here is a [link](http://google.com) to google", result); 125 | } 126 | 127 | #[test] 128 | fn image() { 129 | let result = convert_string("here is an \"image\""); 130 | assert_eq!("here is an ![image](bla.png)", result); 131 | } 132 | 133 | #[test] 134 | fn ignoring_styles() { 135 | let result = convert_string("should ignore style tag"); 136 | assert_eq!("should ignore style tag", result); 137 | } 138 | 139 | #[test] 140 | fn ignoring_scripts() { 141 | let result = convert_string("should ignore script tag"); 142 | assert_eq!("should ignore script tag", result); 143 | } 144 | 145 | #[test] 146 | fn ignoring_head() { 147 | let result = convert_string( 148 | "I AM HEADshould ignore \ 149 | head tag", 150 | ); 151 | assert_eq!("should ignore head tag", result); 152 | } 153 | 154 | #[test] 155 | fn unordered_list() { 156 | let expected = "Here's a list: 157 | 158 | * first 159 | * second 160 | 161 | Wasn't it good?"; 162 | let result = convert_string( 163 | "Here's a list:
  • first
  • second
Wasn't it \ 164 | good?", 165 | ); 166 | assert_eq!(expected, result); 167 | 168 | let result = convert_string( 169 | "

Here's a list:

  • first
  • second
\ 170 |

Wasn't it good?

", 171 | ); 172 | assert_eq!(expected, result); 173 | } 174 | 175 | #[test] 176 | fn unordered_more_complex_list() { 177 | let expected = "Here's a list: 178 | 179 | * A paragraph 180 | with two lines. 181 | 182 | With a blank line in between. 183 | * second item 184 | with three 185 | lines 186 | * as well as 187 | 188 | * a nested 189 | list 190 | * of two 191 | 192 | and the nested list ended 193 | 194 | Wasn't it good?"; 195 | let result = convert_string( 196 | "Here's a list:
  • A paragraph
    with two lines.

    197 |

    With a blank line in between.

  • 198 |

  • second item
    with three\n

    lines
  • 199 |
  • as well as 200 |
      201 |
    • a nested
      list
    • 202 |
    • of two

    203 | and the nested list ended
  • 204 |
Wasn't it good?", 205 | ); 206 | assert_eq!(expected, result); 207 | } 208 | 209 | #[test] 210 | fn ordered_list() { 211 | let expected = "Here's a list: 212 | 213 | 1. first 214 | 2. second 215 | 216 | Wasn't it good?"; 217 | let result = convert_string( 218 | "Here's a list:
  1. first
  2. second
Wasn't it \ 219 | good?", 220 | ); 221 | assert_eq!(expected, result); 222 | 223 | let result = convert_string( 224 | "

Here's a list:

  1. first
  2. second
\ 225 |

Wasn't it good?

", 226 | ); 227 | assert_eq!(expected, result); 228 | } 229 | 230 | #[test] 231 | fn ordered_more_complex_list() { 232 | let expected = "Here's a list: 233 | 234 | 1. A paragraph 235 | with two lines. 236 | 237 | With a blank line in between. 238 | 2. second item 239 | with three 240 | lines 241 | 3. as well as 242 | 243 | 1. a nested 244 | list 245 | 2. of two 246 | 247 | and the nested list ended 248 | 249 | Wasn't it good?"; 250 | let result = convert_string( 251 | "Here's a list:
  1. A paragraph
    with two lines.

    252 |

    With a blank line in between.

  2. 253 |

  3. second item
    with three\n

    lines
  4. 254 |
  5. as well as 255 |
      256 |
    1. a nested
      list
    2. 257 |
    3. of two

    258 | and the nested list ended
  6. 259 |
Wasn't it good?", 260 | ); 261 | assert_eq!(expected, result); 262 | } 263 | 264 | #[test] 265 | fn ordered_and_unordered_mixed() { 266 | let expected = "Here's a list: 267 | 268 | 1. A paragraph 269 | with two lines. 270 | 271 | With a blank line in between. 272 | 2. as well as 273 | 274 | * a nested 275 | list 276 | 277 | 1. Even more nested list 278 | 2. Inside of that 279 | 280 | * of two 281 | 282 | and the nested list ended 283 | 3. But then a third item followed 284 | 285 | Wasn't it good?"; 286 | let result = convert_string( 287 | "Here's a list: 288 |
    289 |
  1. A paragraph
    with two lines.

    290 |

    With a blank line in between.

  2. 291 |
  3. as well as 292 |
      293 |
    • a nested
      list 294 |
        295 |
      1. Even more nested list
      2. 296 |
      3. Inside of that
      4. 297 |
      298 |
    • 299 |
    • of two

    • 300 |
    301 | and the nested list ended
  4. 302 |
  5. But then a third item followed
  6. 303 |
304 | Wasn't it good?", 305 | ); 306 | assert_eq!(expected, result); 307 | } 308 | --------------------------------------------------------------------------------